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;
}
}

浙公网安备 33010602011771号