二叉查找樹

最近看《算法導論》,發現裏面短短几頁篇幅卻包含着很多內容。要完全理解它可以試試不看書自己總結出來。如果可以不看書寫出來纔算真的"吃進去"了,否則都不算掌握了。大家可以檢測下!借這篇文章來稍微總結下,便於以後查閱。

下面說說二叉查找樹( Binary search tree)。

1、二叉查找樹和二叉堆的區別(前者簡稱 bstree ,後者簡稱 heap)

區別一:性質不同。heap(此處默認說的是最小堆)的左右孩子節點都比父親大,而bstree 的左孩子中的所有節點比父親小,而右孩子中的所有節點比父親大。

區別二:排序方式不同。heap 的根節點永遠是最小節點,所以只需要依次彈出root節點,由此組成的序列即是;而 bstree 的中序遍歷纔得到的是排序結果。

區別三:堆有特殊性質,一般可用數組實現,index[parent] 等於 index[child]/2 。

2、二叉樹操作中的難點

查找二叉樹的前驅、後繼,以及插入、刪除操作!

------------------------------------------------------------------------------------------------------------------------------------

下面依次列出各種操作的僞碼:

遍歷

1、遞歸實現中序遍歷

遍歷整棵樹可以調用 PRINT-BSTREE(root[T])。

PRINT-BSTREE(x)
    if x ≠ NIL  then    //只能這麼表示"不等於"了,"NIL"相當於"NULL指針"
       PRINT-BSTREE(left[x])
       print key[x]
       PRINT-BSTREE(right[x])

2、非遞歸中序遍歷(需要藉助Stack來實現)

PRINT-BSTREE(x)
    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)。

FIND-RECURSION(x,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、非遞歸查找某個元素

FIND(x,k)
    while x ≠ NIL and key[x] ≠ k do
       if k < key[x] then
          x <--- left[x]
       else
         x <--- right[x]
     return x

3、查找樹的最小值(在查找前驅和後繼的時候有用)

FIND-MIN(x)
    y <--- NIL
    while left[x] ≠ NIL do
        y <---- x
        x <---- left[x]  
    return y

4、查找樹的最大值

FIND-MAX(x)
    y <--- NIL
    while right[x] ≠ NIL do
        y <---- x
        x <---- right[x]  
    return y

5、查找某個元素的前驅

如果知道元素的值,比如 k,可以先用 p = FIND(x,k) 得到指向該節點的指針,然後再調用下面的函數 FIND-PREV(p)

FIND-PREV(x)   //暫且用prev這個詞來代表 "前驅"
    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、查找某個元素的後繼(和前驅類似)

FIND-NEXT(x)   //暫且用next這個詞來代表 "後繼"
    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)。

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)來刪除

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

(全文完)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章