TypeScript数据结构与算法(10)树结构-二分搜索树-BinarySearchTree

二分搜索树也称二叉搜索树,它是一种树形的数据结构,如下图

它有如下性质:

1.所有的节点都比左子树中的节点大,如图中9和15

 

2.所有的节点都比右子树中的节点小,如图,

由于以上两条性质的成立,所以对一二叉树存在这样的性质:

二叉查找树的最小节点位于最顶端节点的最左边的子树行的末尾,例如图中3

 

二叉查找树的最大节点位于最顶端节点的最右边的子树行的末尾,例如图中28

好了,在上面我们使用图,解释了二叉搜索树的基本性质,接下来看看,在二叉树中添加元素是怎么样的,当我们添加元素1时:

1.从根节点15开始,由于1<15,所以根据二叉树性质,往左走

2.由于1<9,继续往左走

3.由于1<3,还是继续往左走

4.最后因为3没有左节点,所以1放在3的左子树节点上,成为3的叶子节点

在二叉树中删除某个元素时,如果该元素没有叶子节点,直接删掉就好;当删除的元素有一个叶子节点时,删除该元素后,直接将这个叶子节点,移动到删除的元素处;当删除的元素存在两个节点时,如下图删除9

那这个时候怎么删除呢?这个时候删除9之后,就要思考上面说的二叉树的性质了:由于删除的是左子树中的节点元素,为了保证二叉树的性质:二叉查找树的最小节点位于最顶端节点的最左边的子树行的末尾,二叉查找树的最大节点位于最顶端节点的最右边的子树行的末尾。所以我们需要在左子树中找到最大的元素,显而易见是4,将4移动到删除处,这样就稳定了。

至于在二叉树中搜索元素的过程也和上述类似,这里不赘述了,最后需要注意的是:如果二叉树接近形成直线,那么其搜索效率将非常差,如线性搜索,算法的时间复杂度就变成了O(n),如图

另一方面,一直保持良好平衡的二叉树称为“自平衡二叉树”,能够保持搜索效率,如图

源码实现:

import { DataStruct_Queue } from "../03-Stacks-and-Queues/DataStruct_Queue";

//自我实现定义comparable
type Comparable = {
    compareTo(that: Comparable): number;
    equals(that: Comparable): boolean;
}


//二分搜索树的内部类Node
class Node<T> {
    public e: T;
    public left: Node<T>; //左孩子
    public right: Node<T>;//右孩子
    constructor(e: T) {
        this.e = e;
        this.left = null;
        this.right = null;
    }
}

//二分搜索树特定的数据类型
export class BinarySearchTree_Data implements Comparable {
    constructor(private e: any) {
    }
    compareTo(that: BinarySearchTree_Data): number {
        return this.e - that.e;
    }
    equals(that: BinarySearchTree_Data): boolean {
        return this.e === that.e;
    }
}


/**
* Autor: Created by 李清风 on 2021-01-02.
* Desc: 二分搜索树,关键词:必须具有可比较性(TS自己实现),左孩子,右孩子,左子树,右子树 ,存在唯一的根节点,二叉树中每个节点最多有两个孩子
*/
export class DataStruct_BinarySearchTree<T> {

    private root: Node<T>;//二叉(二分搜索)树的根节点
    private size: number;//二叉树中元素数目

    public constructor() {
        this.root = null;
        this.size = 0;
    }


    public getSize(): number {
        return this.size;
    }

    public isEmpty(): boolean {
        return this.size == 0;
    }

    //二叉树前序遍历(先访问节点,再访问左右子树)
    public preOrder() {
        this._preOder(this.root);
    }
    private _preOder(node: Node<T>) {
        if (node == null) {
            return;
        }
        console.log(node.e);
        this._preOder(node.left);
        this._preOder(node.right);
    }

    //二叉树中序遍历<深度优先遍历>(访问该节点,放在了访问左子树和右子树中间,由于返回结果是排序后的,所以二叉树也叫排序树)
    public inOrder() {
        this._inOrder(this.root);
    }
    private _inOrder(node: Node<T>) {
        if (node == null) {
            return;
        }
        this._inOrder(node.left);
        console.log(node.e);
        this._inOrder(node.right);
    }


    //二叉树后序遍历(先访问该节点左子树和右子树,然后在访问该节点),应用场景:内存释放
    public postOrder() {
        this._postOrder(this.root);
    }
    private _postOrder(node: Node<T>) {
        if (node == null) {
            return;
        }
        this._postOrder(node.left);
        this._postOrder(node.right);
        console.log(node.e);
    }

    //二叉树层序遍历<广度优先>
    public levelOrder() {
        let q: DataStruct_Queue<Node<T>> = new DataStruct_Queue<Node<T>>(10);
        q.enqueue(this.root);
        while (!q.isEmpty()) {
            let cur: Node<T> = q.dequeue();
            console.log(cur.e);
            if (cur.left != null) {
                q.enqueue(cur.left); //存在左孩子,入队
            }
            if (cur.right != null) {
                q.enqueue(cur.right); //存在右孩子,入队
            }
        }
    }

    //删除二叉树中任意元素
    public remove(e: T) {
        this._remove(this.root, e);
    }

    //移除任意元素实现
    private _remove(node: Node<T>, e: T | any): any {
        if (node == null) {
            return;//根节点为空,代表是个空二叉树
        }
        if (e.compareTo(node.e) < 0) {
            node.left = this._remove(node.left, e); //与左节点相比较
            return node;
        } else if (e.compareTo(node.e) > 0) {//与右节点相比较
            node.right = this._remove(node.right, e);
            return node;
        } else {
            //成功找到待删除元素e
            if (node.left == null) {
                let right = node.right;
                node.right = null;
                this.size--;
                return right;
            }
            if (node.right == null) {
                let left = node.left;
                node.left = null;
                this.size--;
                return left;
            }

            //待删除节点左右子树都不为空的情况
            //找到比待删除节点大的最小节点,即待删除节点右子树的最小节点
            //用这个节点顶替待删除的节点位置
            let successor = this._searchMinElement(node.right);
            successor.right = this._removeMin(node.right);
            this.size++
            successor.left = node.left;
            node.left = node.right = null;
            this.size--;
            return successor;
        }
    }

    //删除二叉树中最小元素,并返回
    public removeMin() {
        let ret: T = this.searchMinElement(); //找到最小元素,返回
        this.root = this._removeMin(this.root);
        return ret;
    }

    //返回删除节点后新的二分搜索树的根
    _removeMin(node: Node<T | any>) {
        if (node.left == null) {
            let rightNode = node.right;
            node.right = null;
            this.size--;
            return rightNode;
        }

        node.left = this._removeMin(node.left);
        return node;
    }

    //删除二叉树中最大元素,并返回
    public removeMax() {
        let ret: T = this.searchMaxElement();
        this.root = this._removeMax(this.root);
        return ret;
    }

    //返回删除节点后新的二分搜索树的根
    _removeMax(node: Node<T | any>) {
        if (node.right == null) {
            let leftNode = node.left;
            node.left = null;
            this.size--;
            return leftNode;
        }

        node.right = this._removeMax(node.right);
        return node;
    }

    //查找二叉树中最小元素(最左)
    public searchMinElement(): T {
        if (this.size == 0) {
            throw new Error("BST is Empty!");
        }
        return this._searchMinElement(this.root).e;
    }

    //最小的叶子
    private _searchMinElement(node: Node<T>): Node<T> {
        if (node.left == null) {
            return node;
        }
        return this._searchMinElement(node.left);
    }

    //查找二叉树中最大元素(最右)
    public searchMaxElement(): T {
        if (this.size == 0) {
            throw new Error("BST is Empty!");
        }
        return this._searchMaxElement(this.root).e;
    }

    //最大的叶子
    private _searchMaxElement(node: Node<T>): Node<T> {
        if (node.right == null) {
            return node;
        }
        return this._searchMaxElement(node.right);
    }

    //查找二叉树中是否包含某个元素
    public contains(e: T): boolean {
        return this._contains(this.root, e);
    }

    //查找的递归实现
    private _contains(node: Node<T>, e: T | any) {
        if (node == null) {
            return false;
        }
        if (e.compareTo(node.e) == 0) {
            return true; //找到了
        } else if (e.compareTo(node.e) < 0) {
            return this._contains(node.left, e); //从左子树找
        } else {
            return this._contains(node.right, e);//从右子树递归找
        }
    }

    //向二分搜索树添加元素,注意:我实现的二分搜索树不包括重复元素
    public add(e: T) {
        this.root = this._add(this.root, e);
    }

    //返回插入新节点后的二分搜索树的根
    private _add(node: Node<T>, e: T | any): Node<T> {
        if (node == null) {
            this.size++; //如果根节点为null,就创建根节点
            return new Node(e);
        }
        if (e.compareTo(node.e) < 0) {
            node.left = this._add(node.left, e);//node.left为囧,就创建node.left节点
        } else if (e.compareTo(node.e) > 0) {
            node.right = this._add(node.right, e);//同理
        }
        return node;
    }
}

 

posted @ 2021-01-21 17:26  CYNLINQ  阅读(456)  评论(0)    收藏  举报