最近看《算法導論》,發現裏面短短几頁篇幅卻包含着很多內容。要完全理解它可以試試不看書自己總結出來。如果可以不看書寫出來纔算真的"吃進去"了,否則都不算掌握了。大家可以檢測下!借這篇文章來稍微總結下,便於以後查閱。
下面說說二叉查找樹( Binary search tree)。
1、二叉查找樹和二叉堆的區別(前者簡稱 bstree ,後者簡稱 heap)
區別一:性質不同。heap(此處默認說的是最小堆)的左右孩子節點都比父親大,而bstree 的左孩子中的所有節點比父親小,而右孩子中的所有節點比父親大。
區別二:排序方式不同。heap 的根節點永遠是最小節點,所以只需要依次彈出root節點,由此組成的序列即是;而 bstree 的中序遍歷纔得到的是排序結果。
區別三:堆有特殊性質,一般可用數組實現,index[parent] 等於 index[child]/2 。
2、二叉樹操作中的難點
查找二叉樹的前驅、後繼,以及插入、刪除操作!
------------------------------------------------------------------------------------------------------------------------------------
下面依次列出各種操作的僞碼:
遍歷
1、遞歸實現中序遍歷
遍歷整棵樹可以調用 PRINT-BSTREE(root[T])。
if x ≠ NIL then //只能這麼表示"不等於"了,"NIL"相當於"NULL指針"
PRINT-BSTREE(left[x])
print key[x]
PRINT-BSTREE(right[x])
2、非遞歸中序遍歷(需要藉助Stack來實現)
S <---- {NIL} //無法輸入書中那種集合空,暫用"{NIL}"來替代
while TRUE do
if left[x] ≠ NIL then
x <--- left[x]
PUSH(S, x) //將該節點壓入棧
else
print key[x] // 如果沒有左孩子了,則輸出該節點
if right[x] ≠ NIL then
x <---- right[x]
else
if S == {NIL} then
break // 循環退出條件:到達葉子節點,且棧爲空,即已經輸出最後那個節點
else x <---- POP(S) //到達葉子節點了,該節點沒有孩子
查找
1、遞歸查找某個元素
如果要從根節點開始查找,則調用 FIND-RECURSION(root[T], k)。
if x ≠ NIL and key[x] ≠ k then
if k < key[x] then
return FIND-RECURSION(left[x], k)
else
return FIND-RECURSION(right[x], k)
return x //返回指向這個節點的指針,或者沒找到,返回空
2、非遞歸查找某個元素
while x ≠ NIL and key[x] ≠ k do
if k < key[x] then
x <--- left[x]
else
x <--- right[x]
return x
3、查找樹的最小值(在查找前驅和後繼的時候有用)
y <--- NIL
while left[x] ≠ NIL do
y <---- x
x <---- left[x]
return y
4、查找樹的最大值
y <--- NIL
while right[x] ≠ NIL do
y <---- x
x <---- right[x]
return y
5、查找某個元素的前驅
如果知道元素的值,比如 k,可以先用 p = FIND(x,k) 得到指向該節點的指針,然後再調用下面的函數 FIND-PREV(p)
if left[x] ≠ NIL then
return FIND-MAX(left[x]) //如果該節點有左孩子,則左子樹中最大的節點就是它的前驅
else
y <--- p[x] //p[x]表示x節點的父節點
while y ≠ NIL and right[y] ≠ x do
x <--- y
y <--- p[y]
return y //如果x沒有左孩子,則它的前驅肯定是它的某個父親,滿足這個條件:某個父親P 的右子樹包含了x(或者說x所在的子樹) ,則P 就是x的前驅。
6、查找某個元素的後繼(和前驅類似)
if right[x] ≠ NIL then
return FIND-MIN(right[x]) //如果該節點有右孩子,則右子樹中最小的節點就是它的後繼
else
y <--- p[x] //p[x]表示x節點的父節點
while y ≠ NIL and left[y] ≠ x do
x <--- y
y <--- p[y]
return y //如果x沒有右孩子,則它的前驅肯定是它的某個父親,滿足這個條件:某個父親P 的左子樹包含了x(或者說x所在的子樹) ,則P 就是x的後繼。
插入
向二叉樹中插入節點,先需要生成節點m,然後調用 INSERT(T, m)。
x <---- root[T]
y <--- NIL //用來記錄要插入節點位置的父親
while x ≠ NIL do //尋找要插入的位置
y <--- x
if key[m] < key[x] then
x <--- left[x]
else
x <--- right[x]
p[m] <--- y //對該節點的parent指針賦值
if y == NIL then
root[T] <--- m
else if key[m] < key[y] then
left[y] <--- m
else right[y] <--- m
刪除
刪除操作是最複雜的,該節點分三種情況:沒有孩子,有一個孩子,有兩個孩子。對於兩個孩子的情況,我們採用"邏輯刪除",也就是刪除掉它的"後繼"節點,把這個節點的值賦給要刪除的節點!
先要通過 m = FIND(root[T],k) 來找到這個要刪除節點的指針,而後調用 DELETE(T, m)來刪除
x <--- NIL
y <--- NIL
if left[m]==NIL or right[m]==NIL then
x <--- m //如果至多隻有一個孩子,則x指向該節點本身
else
x <--- FIND-NEXT(m) // 如果有兩個孩子,則x指向m的後繼節點,由於後繼節點沒有左孩子,所以可以將刪除後繼的操作和上面結合起來
if left[x] ≠ NIL then //x指向的是y節點的孩子節點(y最多有一個孩子),如果沒有則y爲NULL
y <--- left[x]
else
y <--- right[x]
if y ≠ NIL then //修改孩子節點的父節點
p[y] <--- p[x]
if p[y] == NIL then
root[T] <---- y //刪除的剛好是根節點
else if left[p[x]] == x then
left[p[x]] <--- y
else
right[p[x]] <--- y
if x ≠ m then
key[m] <----- key[x] //把x節點的內容copy給m節點,以實現邏輯上刪除m節點
delete x
C語言的源代碼如下,僅做參考:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//二叉樹節點
typedef struct node{
int data;
struct node *left,*right,*parent;
}bsnode;
//遍歷二叉樹(中序)
void print_bstree(bsnode *tree){
if(tree!=NULL){
print_bstree(tree->left);//遞歸
printf("%d,",tree->data);
print_bstree(tree->right);
}
}
//查找元素(非遞歸)
bsnode * find(bsnode *tree,int k){
while(tree!=NULL && tree->data !=k){
if(k < tree->data){
tree = tree->left;
}else{
tree = tree->right;
}
}
return tree;//返回指向data域等於k的節點的指針!
}
//查找元素(遞歸)
bsnode * re_find(bsnode *tree,int k){
if(tree==NULL || tree->data ==k){
return tree;
}else if(k < tree->data){
return re_find(tree->left,k);
}else{
return re_find(tree->right,k);
}
}
//求最小值(給定一個二叉查找樹的根snode,求這顆樹的最小節點)
bsnode * find_min(bsnode *snode){
bsnode *p=NULL;
while(snode!=NULL){
p=snode;
snode=snode->left;
}
return p;//樹的最左子樹葉節點
}
//求最大值
bsnode * find_max(bsnode *snode){
bsnode *p=NULL;
while(snode!=NULL){
p=snode;
snode=snode->right;
}
return p;
}
//查找前驅
bsnode * find_prev(bsnode *m){
assert(m!=NULL);
if(m->left != NULL){
return find_max(m->left);
}else{
//尋找m的祖先中滿足這樣關係的點:righ[p[m]] == m
bsnode *p = m->parent;
bsnode *child = m;
while(p!=NULL && p->right != child){
child=p;
p=child->parent;
}
return p;
}
}
//查找後繼
bsnode * find_next(bsnode *m){
assert(m==NULL);
if(m->right !=NULL){
return find_min(m->right);
}else{
bsnode *p=m->parent;
bsnode *child=m;
while(p!=NULL && p->left != child){
child=p;
p=p->parent;
}
return p;
}
}
//插入元素(遞歸)
bsnode *re_insert(bsnode *tree,bsnode *newer){
if(tree==NULL){
tree=newer;
}else{
if(newer->data < tree->data){
tree->left=re_insert(tree->left,newer);
}else{
tree->right=re_insert(tree->right,newer);
}
}
return tree;
}
//插入元素(非遞歸)
bsnode *insert(bsnode *tree,bsnode *newer){
bsnode *root=tree;
bsnode *p= NULL;
while(root!=NULL){
p=root;
if(newer->data < root->data){//往左子樹
root=root->left;
}else{
root=root->right;
}
}//p指針就是要插入節點的父親
newer->parent = p;//修改parent指針,讓其指向父親
if(p==NULL){//只有一個節點
tree = newer;
}else{
if(newer->data < p->data){
p->left=newer;
}else{
p->right=newer;
}
}
return tree;
}
//刪除元素
bsnode * delete(bsnode *tree,int k){
bsnode *delnode = find(tree,k);
if(delnode ==NULL){
printf("要刪除的節點不存在!請檢查\n");
}else{
bsnode *xnode = NULL;
bsnode *ynode = NULL;
if(delnode->left == NULL || delnode->right == NULL){
xnode = delnode;//delnode有一個孩子或者沒有,則xnode表示它本身
}else{
xnode = find_next(delnode);//delnode有兩個孩子,xnode則表示它的後繼節點
}
//得到xnode的孩子節點
if(xnode->left != NULL){
ynode = xnode->left;
}else{
ynode = xnode->right;
}
if(ynode != NULL){
ynode->parent = xnode->parent;
}
if(xnode->parent == NULL){
tree = ynode;
}else{
if(xnode->parent->left == xnode){
xnode->parent->left = ynode;
}else{
xnode->parent->right = ynode;
}
}
if(xnode != delnode){//兩個孩子的情況,得將後繼節點的值賦給delnode,以實現邏輯刪除
delnode->data = xnode->data;
}
//刪除掉節點
free(xnode);
}
return tree;
}
int main(){
bsnode *tree=NULL;
int data[]={10,2,11,3,8,6,1,7};
int i;
for(i=0;i<sizeof(data)/sizeof(int);i++){
bsnode *newer = (bsnode *)malloc(sizeof(bsnode));
newer->data=data[i];
newer->left=newer->right=newer->parent=NULL;
tree = insert(tree,newer);//插入
}
print_bstree(tree);//中序遍歷
printf("\n");
tree = delete(tree,2);//刪除節點
print_bstree(tree);
printf("\n");
return 0;
}
如果轉載,請標明出處 blog.csdn.net/whuslei
(全文完)