数据结构入门-二叉搜索树

二叉搜索树 - javascript

简介

特点:每个结点最多只能有两棵子树,且有左右之分。左比节点小右比节点大;
在这里插入图片描述
1. 数组:(顺序存储)线性数据结构,内存连续;除了在数组最后push一个新数据的复杂度为O(1)外,无论你在哪个位置插入新数据的复杂度都为o(n) ***(注)** 在C或者Java等其他语言中,数组大小是固定的且内存地址是连续的,不会进行动态扩容。而在JavaScript中,数组就是对象,可以理解为一个键值映射:var arr = [‘a’, ‘b’, ‘c’];基本上等价于var arr = {1: ‘a’, 2: ‘b’, 3: ‘c’};所以此数组非彼数组
插入数据时,待插入位置的的元素和它后面的所有元素都需要向后移
删除数据时,待删除位置后面的所有元素都需要向前移

  • 优点:可以通过下标值访问,效率高;
  • 缺点:查找数据时需要先对数据进行排序,生成有序数组,才能提高查找效率;并且在插入和删除元素时,需要大量的位移操作;

2.链表:(链式存储,线性表)元素不之间连续,通过指针将一组零散的内存块串联起来;如果知道某一元素的前驱和后驱,操作指针即可删除该元素或者插入新元素,时间复杂度O(1);链表中最常见的三种结构分别是单链表,双链表,循环链表;***(注)** 数据结构的存储方式只有两种:数组(顺序存储)和链表(链式存储)

  • 优点:数据的插入和删除操作效率都很高;
  • 缺点:查找效率低,需要从头开始依次查找,直到找到目标数据为止;当需要在链表中间位置插入或删除数据时,插入或删除的效率都不高。

3.哈希表:哈希表又叫散列表;哈希表的插入、查询、删除效率都是非常高的,拉链法。简单来说,哈希表就是通过一个映射函数f(key)将一组数据散列存储在数组中的一种数据结构 ;当选择了一个哈希函数之后,有可能不同的数据会计算出相同的key;会有一些解决冲突的方法;

  • 优点:树结构综合了上述三种结构的优点,同时也弥补了它们存在的缺点(虽然效率不一定都比它们高),比如树结构中数据都是有序的,查找效率高;空间利用率高;并且可以快速获取最大值和最小值等。
  • 缺点:当数据是是顺序的话会造成深度过高,这个时候就需要平衡二叉树;

实现

节点函数

1
2
3
4
5
function Node(key) {
this.key = key;
this.left = null;
this.right = null;
}

二叉搜索树函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

function BinarySearchTree() {
//根
this.root = null;
//添加
this.insert = function (key) {
let newNode = new Node(key);
if (this.root == null) {
this.root = newNode
} else {
//已有根加点,寻找合适的位置插入
this.insertNode(this.root, newNode);
}
}
//insertNode方法:用于比较节点从左边插入还是右边插入
this.insertNode = function (node, newNode) {
if (newNode.key < node.key) {
if (node.left == null) {
node.left = newNode;
} else {
this.insertNode(node.left, newNode)
}
} else {
if (node.right == null) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode)
}
}
}
}
}

遍历数据

常见的三种二叉树遍历方式为:

  • 先序遍历;
  • 中序遍历;
  • 后序遍历;

还有层序遍历,使用较少。

先序遍历
根->左树->右树
在这里插入图片描述
上图为:11->7->15->16

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

this.preOrderTraversal = function () {
let result = [];
this.preOrderTraversalNode1(this.root, function (res) {
result.push(res);
})
return result
}

this.preOrderTraversalNode = function (node, handler) {
if (node != null) {
handler(node.key)
this.preOrderTraversalNode1(node.left, handler)
this.preOrderTraversalNode1(node.right, handler)
}
}

中序遍历
在这里插入图片描述
上图为:7->11->15->16
和先序遍历没什么不同,不同的只是递归操作的时候的handler操作顺序变了
先遍历左子树; 然后遍历根(父)节点; 最后遍历其右子树;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

this.inOrderTraverse = function () {
let result = [];
this.preOrderTraversalNode1(this.root, function (res) {
result.push(res);
})
return result
}

this.inOrderTraverseNode = function (node, handler) {
if (node != null) {
this.inOrderTraverseNode(node.left, handler)
handler(node.key)
this.inOrderTraverseNode(node.right, handler)
}
}

后序遍历
在这里插入图片描述
上图为:7->16->15->11
和先序遍历也没什么不同,不同的也只是递归操作的时候的handler操作顺序变了
先遍历其左子树; 然后遍历其右子树; 最后遍历根(父)节点;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

this.postorderTraversal= function () {
let result = [];
this.preOrderTraversalNode1(this.root, function (res) {
result.push(res);
})
return result
}

this.postorderTraversalNode= function (node, handler) {
if (node != null) {
this.inOrderTraverseNode(node.left, handler)
handler(node.key)
this.inOrderTraverseNode(node.right, handler)
}
}

查找

最大值与最小值
二叉搜索树中的最大值在树的最右边;最小值在最左边;只需要一直查找就可以获得

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

this.max = function () {
let node = this.root;
while (node.right) {
node = node.right;
}
return node.key;
}

this.min = function () {
let node = this.root;
while (node.left) {
node = node.left;
}
return node.key;
}

查找特定值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

//循环实现
this.search = function (key) {
let node = this.root;
while (node) {
if (key > node.key) {
node = node.right;
} else if (key > node.key) {
node = node.left;
} else {
return key
}
}
return null
}

//递归实现
this.search = function (key, node) {
if (node) {
if (node.key > key) {
this.search(key, node.right)
} else if (node.key < key) {
this.search(key, node.left)
} else {
return node.key
}
}
return null
}

删除节点
删除节点相对来说比较复杂,需要考虑到多种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//删除操作比较复杂,有三种情况考虑
// 1.无子节点:左右均为空,又称为叶子节点;最简单
// 2.删除只有一个节点的节点
// 3.删除有两个子节点的节点
this.remove = function (key) {
//搜寻节点
//保存要删除的节点
let current = this.root;
let parent = null;
let isLeftChild = true;
while (current.key != key) {
parent = current; //11
if (key < current.key) {
isLeftChild = true; //true
current = current.left;

} else {
isLeftChild = false;
current = current.right;
}
if (current == null) return false;
}
//根据情况删除节点
// 1.删除叶子节点
if (current.right == null && current.left == null) {
// 如果是根
if (current === this.root) {
this.root = null;
} else if (isLeftChild) {
parent.left = null;
} else {
parent.right = null;
}
}
// 2.删除只有一个节点的节点
// 如果右边节点为空
else if (current.right == null) {
if (current == this.root) {
this.root = current.left
} else if (isLeftChild) {
parent.left = current.left
} else {
parent.right = current.left
}
} else if (current.left == null) {
if (current == this.root) {
this.root = current.right
} else if (isLeftChild) {
parent.left = current.right
} else {
parent.right = current.right
}
}
// 3.第三种情况
// 删除的节点有两个或者以上的子节点;这个时候应该找删除节点的前驱或者后继;
else {
// 1.获取后继节点
let successor = this.getSuccess(current);
// 2.判断是否根节点;
if (this.root == current) {
this.root = successor
} else if (isLeftChild) {
parent.left = successor
} else {

parent.right = successor
}
// getSuccess的时候已经做了successor.right的处理
successor.left = current.left;
}
return true;
},

// 获得前驱后继
this.getSuccess = function (delNode) {
//现在只考虑后继
let successor = delNode;
let current = delNode.right
let successorParent = delNode;

while (current != null) {

successorParent = successor;
successor = current;
current = current.left
}
// 因为后继找到的都是最小的左节点,所以上替换后上一个节点是不存在左节点的;不用考虑这种情况
//https://img-blog.csdnimg.cn/20210821111147928.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3NzE1NjM5,size_16,color_FFFFFF,t_70
//如果后继节点不是删除节点的右节点
if (successor != delNode.right) {
successorParent.left = successor.right;
successor.right = delNode.right;
}
return successor;
},

二叉搜索树是有缺陷的:当插入的数据是有序的数据,就会造成二叉搜索树的深度过大;这个时候就需要对树进行处理达到树得平衡;常见的有AVL和红黑树


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!