算法打基础——二叉查找树Ⅰ
二叉查找树是一种基本的数据结构。 它的优势在于其高效的查询,排序过程,且它也支持多种操作,如插
入、删除、前趋,后接等。二叉查找树常被用于其他抽象结构的一个基础,比如字典、优先队列、集合、多
集等。总之,就是用处多多。
二叉查找树基本上各种操作的效率跟树的高度都是直接成比例的。所以查找树的结构就非常重要了,平衡的
结构可使得效率更高。这就带来了随机化版本的二叉树。 具体各种操作的分析在这一节中讲,随机化版本的
分析涉及数学很多,放在下一讲中,下一讲实际上也写好了,就放在候选区了~
这一讲的主要知识点有:1.二叉树基本结构 2.查找操作 3.插入删除操作
1.二叉树基本结构
二叉树的每个节点(node)就是一个对象,每个node里主要有key、卫星数据(就是主要数据)、left(存储左儿
子), right(存储右儿子),parent(存储父节点)。如果相应节点不存在,则为NULL
二叉查找树的组织方式很简单,当一个节点进来后,与树中节点比较,如果大于当前节点key,就进入它的右
子树,否则进入左子树。知道找到NULL的位置。下面是两个BST的例子
二叉树的一个基本操作是树的遍历。树的遍历也主要有三种:前序、中序、后序。其主要区别看下面中序
的伪代码就可以很好理解了。
INORDER-TREE-WALK(x)
1 if x != NIL
2 INORDER-TREE-WALK(x.left)
3 print x.key
4 INORDER-TREE-WALK(x.right)
中序就是打印的命令放在了左、右递归的中间。 而实际上,根据二叉查找树的性质,中序遍历打印出来的结
构就是所有键值按从小到大顺序排序后的结果!
树的遍历作为一种遍历,当然其时间复杂度就是Θ(n)
2.查找操作
二叉查找树作为一种查找树,当然常见操作就是查找了。 查询除了常用的查找某个关键字之外,还有找最
大、最小、后续、前趋关键字。对高度为h的的树,这些操作的时间复杂度都是Θ(h)
查找关键字
TREE-SEARCH(x; k)
1 if x == NIL or k == x.key
2 return x
3 if k < x.key
4 return TREE-SEARCH(x.left; k)
5 else return TREE-SEARCH(x.right; k)
其过程是沿着树的根节点比较着往下找,大则右,小则左。所以时间复杂度是O(h)
最大关键字与最小关键字
TREE-MINIMUM(x)
1 while x.left != NULL
2 x = x.left
3 return x
TREE-MAXIMUM(x)
1 while x.right != NULL
2 x = x.right
3 return x
最大与最小是分别沿着右子树或左子树一直下降,时间复杂度O(h)
前趋和后续
TREE-SUCCESSOR(x)
1 if x.right != NULL
2 return TREE-MINIMUM(x.right)
3 y = x.p
4 while y != NIL and x == y.right
5 x = y
6 y = y.p
7 return y
后继操作的过程需要分为两种情况:1 存在右子树,则是右子树当中的最小值(比它大的元素里面的最小值,合
理!);2 只有左子树,则顺流而上,找到第一次做左子树的地方。图例见下:case1-15 case2-13
前趋的过程与后继是对称的,伪代码就不写了,后面附的代码里面会有。
3.插入删除操作
插入与删除操作因为需要改变一些东西,且还要维持数据结构的性质,稍微会复杂一些
TREE-INSERT(T; z)
1 y = NULL
2 x = T.root
3 while x != NULL
4 y = x
5 if z.key < x.key
6 x = x.left
7 else x = x.right
8 z.p = y
9 if y == NULL
10 T.root = z // tree T was empty
11 elseif z.key < y.key
12 y.left = z
13 else y.right = z
在伪代码里面,y始终指向x的父节点。实现主要注意当是空树的时候,需要将当前节点作为根。否则就是一路下
去直到找到空位置
删除操作比较复杂,需要分为三种情况,所以这里先做分析。 三种情况对应不同的操作方法,1是既没有左子树
也没有右子树 2只有一个子树 3同时有两个子树。对于1直接操作即可; 对于2删除当前节点时,将其子树接到
节点的父节点上去;对于3,需要找到待删除节点的后继节点,然后用这个后继节点替代当前节点并删除那个后继
节点之前的位置。 注意:找到的后继节点是没有左子树的否则它的前趋就不可能是那个待删除节点了。下面用
图示例这三种情况:
下面给出伪代码:
TREE-DELETE(T, z)
1 if left[z] = NIL or right[z] = NIL
2 then y ← z
3 else y ← TREE-SUCCESSOR(z)
4 if left[y] = NIL
5 then x ← left[y]
6 else x ← right[y]
7 if x = NIL
8 then p[x] ← p[y]
9 if p[y] = NIL
10 then root[T ] ← x
11 else if y = left[p[y]]
12 then left[p[y]] ← x
13 else right[p[y]] ← x
14 if y = z
15 then key[z]← key[y]
16 copy y’s satellite data into z
17 return y
其过程与上面的三种情况顺序是不同的。 首先y是记录需要删除的节点的,即y必然有一个子树是空的;x是记录
y的一个子节点的,若有就是相应子节点,没有则默认是右边的(Row 6); Row9-13是执行将删除点父节点的更
新操作。Row14-16 是如果需要是替换的情况作一个替换。
下面附上C++实现的BST:
1 #include<iostream> 2 #include<cstdlib> 3 #include<ctime> 4 using namespace std; 5 6 #define random(x)(rand()%x) 7 8 class BSTree; 9 10 class Node{ 11 public: 12 int key; 13 Node* lchild; 14 Node* rchild; 15 Node* parent; 16 Node(int value, Node* lc, Node* rc,Node* pa):key(value), 17 lchild(lc),rchild(rc),parent(pa){}; 18 friend class BSTree; 19 }; 20 21 class BSTree 22 { 23 public: 24 Node* root; 25 BSTree(Node* r){root=r;} 26 void inorderwalk(Node* t) const; 27 Node* search(Node* t,int ele)const; 28 Node* findmax(Node* t) const; 29 Node* findmin(Node* t) const; 30 Node* predecessor(Node* t) const; 31 Node* successor(Node* t) const; 32 void insert(int k, Node* &t); 33 void dele(int k, Node* &t); 34 }; 35 36 void BSTree::inorderwalk(Node* t) const 37 { 38 if(t!=NULL) 39 { 40 inorderwalk(t->lchild); 41 cout<<t->key<<" "; 42 inorderwalk(t->rchild); 43 } 44 } 45 Node* BSTree::search(Node* t, int ele) const 46 { 47 if(t==NULL) 48 { 49 cout<<"Not Found"<<endl; 50 return t; 51 } 52 if(t->key==ele) 53 { 54 return t; 55 } 56 if(ele< (t->key)) 57 return search(t->lchild,ele); 58 else return search(t->rchild,ele); 59 } 60 61 Node* BSTree::findmax(Node *t) const 62 { 63 while(t->rchild!=NULL) 64 { 65 t=t->rchild; 66 } 67 return t; 68 } 69 Node* BSTree::findmin(Node *t) const 70 { 71 while(t->lchild!=NULL) 72 { 73 t=t->lchild; 74 } 75 return t; 76 } 77 Node* BSTree::predecessor(Node *t) const 78 { 79 if(t->lchild!=NULL) 80 { 81 return findmax(t->lchild); 82 } 83 while(t->parent->lchild==t) 84 { 85 t=t->parent; 86 } 87 return t->parent; 88 } 89 Node* BSTree::successor(Node* t) const 90 { 91 if(t->rchild!=NULL) 92 { 93 return findmin(t->rchild); 94 } 95 while(t->parent->rchild==t) 96 { 97 t=t->parent; 98 } 99 return t->parent; 100 } 101 // Node* &rt 是传进来的root的一个引用,即它跟root是一个指针,只是一个别名 102 void BSTree::insert(int k, Node * &rt) 103 { 104 //空树的情况 105 if(rt==NULL) 106 { 107 // 这个new返回是什么? 应该就是一个指针,因为必须赋值给指针 108 Node* nd = new Node(k,NULL,NULL,NULL); 109 rt=nd; 110 return; 111 } 112 if(rt->key>k) 113 { 114 insert(k,rt->lchild); 115 } 116 else 117 { 118 insert(k,rt->rchild); 119 } 120 121 } 122 //注意所有调用dele函数时,参数都必须是传进来的参数t的某个直接相关元素 123 //否则无法操作 124 // 实际上 insert函数也有这个性质 125 void BSTree::dele(int k, Node *&t) 126 { 127 if(t==NULL) 128 { 129 cout<<"Can't find"<<endl; 130 return; 131 } 132 if(t->key==k) 133 { 134 if((t->lchild!=NULL)&&(t->rchild!=NULL)) 135 { 136 Node* nd = successor(t); 137 t->key = nd->key; 138 dele(nd->key,t->rchild); 139 //dele(nd->key,nd); 这样写是错误的! 140 } 141 else{ 142 t= (t->lchild!=NULL)?t->lchild:t->rchild; 143 144 } 145 } 146 else if(t->key>k) 147 { 148 dele(k,t->lchild); 149 } 150 else dele(k,t->rchild); 151 } 152 153 154 155 int main() 156 { 157 srand(time(0)); 158 int keys[10],i; 159 for(i=0;i<10;i++) 160 keys[i]=random(500); 161 BSTree* tree = new BSTree(NULL); 162 for(i=0;i<8;i++) 163 { 164 tree->insert(keys[i],tree->root); 165 } 166 tree->inorderwalk(tree->root); 167 cout<<endl; 168 Node* n = tree->findmax(tree->root); 169 cout<<n->key<<endl; 170 n = tree->findmin(tree->root); 171 Node* n2=tree->successor(n); 172 cout<<n2->key<<endl; 173 tree->dele(keys[4],tree->root); 174 tree->inorderwalk(tree->root); 175 cout<<endl; 176 177 178 179 180 return 0; 181 }