二叉查找樹插入刪除

二叉查找樹是如下定義的:

(1)  左子樹不空,則左子樹上的所有結點的值均小於根結點的值

(2)  右子樹不空,則右子樹上的所有結點的值均大於根結點的值

二叉查找樹可以爲空,二叉查找樹是遞歸定義的,也就是說其左右子樹也爲二叉查找樹。

二叉查找樹是一種動態查找表,可以進行動態地插入和刪除。前面的定義中我們假定二叉查找樹不含有相同元素。

由定義可知二叉查找樹的中序序列爲一個遞增序列

常見的二叉查找樹操作有求最小元素findMin(),求最大元素findMax(),判斷查找樹是否非空isEmpty(),判斷是否包含給定元素contains(),輸出所有元素printTree(),置空查找樹makeEmpty(),向查找樹中插入給定元素x,insert(x),在查找樹中刪除給定元素x,remove(x)。

下面先討論最重要的插入和刪除操作,接着給出完整的C風格實現

1、基本結構定義

[cpp] view plain copy
  1. class Student  
  2. {  
  3.     public:  
  4.         int key;  
  5.         string major;  
  6.         Student(int k=int(),string s="") : key(k), major(s){}  
  7.         void operator=(const Student& rhs);  
  8. };  
  9. typedef Student ElementType;  
  10. typedef int KeyType;  
  11.   
  12. typedef struct BSTNode  
  13. {  
  14.     ElementType data;  
  15.     struct BSTNode* lchild;  
  16.     struct BSTNode* rchild;  
  17. }BSTNode, *BST;  

詳細信息請看後面完整程序

2、插入

(1)  不允許插入相同關鍵字,若二叉查找樹中存在該關鍵字,則不插入

(2)  我們可以先檢索二叉樹,看查找樹中是否含有該關鍵字,若不存在,再做一次掃描將結點插入到適當位置。使用這種方式,爲插入一個該關鍵字,做了兩次掃描。

(3)  注意到,插入的結點總是作爲某一葉子節點的子結點,我們可以在第一次掃描過程中就確定待插入的位置,即把查找是否存在該關鍵字查找可能的插入點在一次掃描中完成,提高插入效率。

(4)  查找遞歸實現

[cpp] view plain copy
  1. /* 
  2.   description:在以t爲根結點的二叉查找樹中,查找關鍵字爲key的結點 
  3.   若查找成功,指針p指向該結點,返回true, 
  4.   否則指向查找路徑上的最後一個結點並返回false 
  5.   指針f指向根結點t的父節點  
  6. */  
  7. bool searchBST_recursion(BST t, KeyType key, BSTNode* f, BSTNode*& p)  
  8. {  
  9.     if(t == NULL)//查找失敗   
  10.     {  
  11.         p = f;  
  12.         return false;  
  13.     }  
  14.     else if(key == t->data.key)//查找成功   
  15.     {  
  16.         p = t;  
  17.         return true;  
  18.     }  
  19.     else if(key < t->data.key)  
  20.         return searchBST_recursion(t->lchild,key,t,p);//左子樹中繼續查找   
  21.     else   
  22.         return searchBST_recursion(t->rchild,key,t,p);//右子樹中繼續查找   
  23. }  

(5)  查找非遞歸實現

[cpp] view plain copy
  1. bool searchBST(BST t, KeyType key, BSTNode* f, BSTNode* &p)  
  2. {  
  3.     while(t && key != t->data.key)  
  4.     {  
  5.         f = t;  
  6.         if(key < t->data.key)  
  7.         {  
  8.             t = t->lchild;  
  9.         }  
  10.         else  
  11.         {  
  12.             t = t->rchild;  
  13.         }             
  14.     }  
  15.     if(t)//查找成功   
  16.     {  
  17.         p = t;  
  18.         return true;  
  19.     }  
  20.     else  
  21.     {  
  22.         p = f;  
  23.         return false;  
  24.     }  
  25. }  

(6)  插入給定元素

[cpp] view plain copy
  1. void insertBST(BST& t,ElementType elem)  
  2. {  
  3.     BSTNode * p = NULL;  
  4.     if(!searchBST(t, elem.key, NULL, p))//查找失敗,不含該關鍵字,可以插入   
  5.     {  
  6.         BSTNode * s = new BSTNode;  
  7.         s->data = elem;//可能需要重載=   
  8.         s->lchild = NULL;  
  9.         s->rchild = NULL;  
  10.           
  11.         if(p == NULL)//查找樹爲空   
  12.         {  
  13.             t = s;//置s爲根結點   
  14.         }  
  15.         else if(elem.key < p->data.key)  
  16.         {  
  17.             p->lchild = s;   //*s爲p左結點   
  18.         }  
  19.         else  
  20.         {  
  21.             p->rchild = s;   //*s爲p右結點   
  22.         }  
  23.     }  
  24. }  

3、刪除

在二叉查找樹中刪除一個給定的結點p有三種情況

(1)  結點p無左右子樹,則直接刪除該結點,修改父節點相應指針

(2)  結點p有左子樹(右子樹),則把p的左子樹(右子樹)接到p的父節點上

(3)  左右子樹同時存在,則有三種處理方式

a.      找到結點p的中序直接前驅結點s,把結點s的數據轉移到結點p,然後刪除結點s,由於結點s爲p的左子樹中最右的結點,因而s無右子樹,刪除結點s可以歸結到情況(2)。嚴蔚敏數據結構P230-231就是該處理方式。

b.     找到結點p的中序直接後繼結點s,把結點s的數據轉移到結點p,然後刪除結點s,由於結點s爲p的右子樹總最左的結點,因而s無左子樹,刪除結點s可以歸結到情況(2)。算法導論第2版P156-157該是該處理方式。

c.      找到p的中序直接前驅s,將p的左子樹接到父節點上,將p的右子樹接到s的右子樹上,然後刪除結點p。

使用處理方式a的代碼如下:

[cpp] view plain copy
  1. //從二叉查找樹中刪除指針p所指向的結點 ,p非空,刪除前驅方式   
  2. void removeNode1(BSTNode *& p)  
  3. {  
  4.     BSTNode *q = NULL;  
  5.     if(!p->rchild)//*p的右子樹爲空   
  6.     {  
  7.         q = p;  
  8.         p = p->lchild;  
  9.         delete q;  
  10.     }  
  11.     else if(!p->lchild)//*p的左子樹爲空   
  12.     {  
  13.         q = p;  
  14.         p = p->rchild;  
  15.         delete q;  
  16.     }  
  17.     else//左右子樹均不空   
  18.     {  
  19.         BSTNode *s = NULL;  
  20.         q = p;  
  21.         s = p->lchild;       //左子樹根結點  
  22.         while(s->rchild) //尋找結點*p的中序前驅結點,                      
  23.         {                   //也就是以p->lchild爲根結點的子樹中最右的結點   
  24.             q = s;          //*s指向*p的中序前驅   
  25.             s = s->rchild;   //*q指向*s的父節點   
  26.         }  
  27.         p->data = s->data;    //*s結點中的數據轉移到*p結點,然後刪除*s  
  28.         if(q != p)          //p->lchild右子樹非空   
  29.         {  
  30.             q->rchild = s->lchild;//把*s的左子樹接到*q的右子樹上   
  31.         }  
  32.         else                //p->lchild右子樹爲空 ,此時q ==p   
  33.         {  
  34.             q->lchild = s->lchild;//把*s的左子樹接到*q的左子樹上   
  35.         }  
  36.         delete s;           //刪除結點*s   
  37.     }  
  38. }  

使用處理方式b的代碼如下:

[cpp] view plain copy
  1. //從二叉查找樹中刪除指針p所指向的結點 ,p非空,刪除後繼方式   
  2. void removeNode2(BSTNode *& p)  
  3. {  
  4.     BSTNode *q = NULL;  
  5.     if(!p->rchild)//*p的右子樹爲空   
  6.     {  
  7.         q = p;  
  8.         p = p->lchild;  
  9.         delete q;  
  10.     }  
  11.     else if(!p->lchild)//*p的左子樹爲空   
  12.     {  
  13.         q = p;  
  14.         p = p->rchild;  
  15.         delete q;  
  16.     }  
  17.     else//左右子樹均不空   
  18.     {  
  19.         BSTNode *s = NULL;  
  20.         q = p;  
  21.         s = p->rchild;       //右子樹根結點  
  22.         while(s->lchild) //尋找結點*p的中序後繼結點,                      
  23.         {                   //也就是以p->rchild爲根結點的子樹中最左的結點   
  24.             q = s;          //*s指向*p的中序後繼   
  25.             s = s->lchild;   //*q指向*s的父節點   
  26.         }  
  27.         p->data = s->data;    //*s結點中的數據轉移到*p結點,然後刪除*s  
  28.         if(q != p)          //p->rchild左子樹非空   
  29.         {  
  30.             q->lchild = s->rchild;//把*s的右子樹接到*q的左子樹上   
  31.         }  
  32.         else                //p->rchild左子樹爲空 ,此時q ==p   
  33.         {  
  34.             q->rchild = s->rchild;//把*s的右子樹接到*q的右子樹上   
  35.         }  
  36.         delete s;           //刪除結點*s   
  37.     }  
  38. }  

使用處理方式c的代碼如下:

[cpp] view plain copy
  1. //從二叉查找樹中刪除指針p所指向的結點 ,p非空,直接刪除p的方式   
  2. void removeNode3(BSTNode *& p)  
  3. {  
  4.     BSTNode *q = NULL;  
  5.     if(!p->rchild)//*p的右子樹爲空   
  6.     {  
  7.         q = p;  
  8.         p = p->lchild;  
  9.         delete q;  
  10.     }  
  11.     else if(!p->lchild)//*p的左子樹爲空   
  12.     {  
  13.         q = p;  
  14.         p = p->rchild;  
  15.         delete q;  
  16.     }  
  17.     else//左右子樹均不空   
  18.     {  
  19.         BSTNode *s = NULL;  
  20.         q = p;  
  21.         s = p->lchild;       //左子樹根結點  
  22.         while(s->rchild) //尋找結點*p的中序前驅結點,                      
  23.         {                   //也就是以*s爲根結點的子樹中最右的結點   
  24.             s = s->rchild;      
  25.         }  
  26.         s->rchild = p->rchild;//*p的右子樹接到*s的右子樹上   
  27.         p = p->lchild;           //*p的左子樹接到父節點上   
  28.         delete q;           //刪除結點*q   
  29.     }  
  30. }  

在二叉查找樹中刪除含有給定關鍵字的元素結點的遞歸函數如下:

[cpp] view plain copy
  1. //刪除關鍵字爲key的元素結點 -遞歸   
  2. void removeBST_recursion(BST& t, KeyType key)  
  3. {  
  4.     if(t)  
  5.     {  
  6.         if(key < t->data.key)  
  7.         {  
  8.             removeBST_recursion(t->lchild,key);  
  9.         }  
  10.         else if(t->data.key < key)  
  11.         {  
  12.             removeBST_recursion(t->rchild,key);  
  13.         }  
  14.         else//找到關鍵字爲key的元素   
  15.         {  
  16. //          removeNode1(t);//刪除結點*t   
  17. //          removeNode2(t);  
  18.             removeNode3(t);  
  19.         }  
  20.     }  
  21. }  
注:

(1)由於該函數是遞歸的,且用到了指針引用,我們在前面的刪除給定結點p的函數中,修改p就相當於修改了父節點,這是和C中不同的地方,在C中要達到這種效果,可以使用指向指針的指針變量。

(2)只給出了該函數的遞歸實現,沒有給出非遞歸實現,是爲了避免把代碼弄亂。要實現非遞歸,可以修改前面的刪除結點p的函數,添加一個指向結點p的父節點的指針引用f。同時要修改查找是否存在關鍵字爲key的非遞歸查找函數,使其在得到p的同時,可以得到其父節點。這樣我們在實現非遞歸時,只需掃描一遍,即調用查找函數,如果有該關鍵字,我們就可以得到指向該結點的指針p和指向結點p的父節點f,然後調用刪除給定結點p的函數即可。

4、完整測試程序

[cpp] view plain copy
  1. #include <cstdlib>  
  2. #include <iostream>  
  3. #include <string>  
  4.   
  5. using namespace std;  
  6.   
  7. class Student  
  8. {  
  9.     public:  
  10.         int key;  
  11.         string major;  
  12.         //other data  
  13.         Student(int k=int(),string s="") : key(k), major(s){}  
  14.         //重載賦值運算符   
  15.         void operator=(const Student& rhs)  
  16.         {  
  17.             if(this != &rhs)  
  18.             {  
  19.                 key = rhs.key;  
  20.                 major = rhs.major;  
  21.             }  
  22.         }     
  23. };  
  24. //重載<<,便於輸出自定義類對象   
  25. ostream& operator<<(ostream &out, const Student& s)  
  26. {  
  27.     out<<"("<<s.key<<","<<s.major<<")";  
  28. }  
  29.   
  30. typedef Student ElementType;  
  31. typedef int KeyType;  
  32.   
  33. typedef struct BSTNode  
  34. {  
  35.     ElementType data;  
  36.     struct BSTNode* lchild;  
  37.     struct BSTNode* rchild;  
  38. }BSTNode, *BST;  
  39.   
  40.       
  41. /* 
  42.   description:在以t爲根結點的二叉查找樹中,查找關鍵字爲key的結點 
  43.   若查找成功,指針p指向該結點,返回true, 
  44.   否則指向查找路徑上的最後一個結點並返回false 
  45.   指針f指向根結點t的父節點  
  46. */  
  47. //bool searchBST_recursion(BST t, KeyType key, BSTNode* f, BSTNode*& p)  
  48. //{  
  49. //  if(t == NULL)//查找失敗   
  50. //  {  
  51. //      p = f;  
  52. //      return false;  
  53. //  }  
  54. //  else if(key == t->data.key)//查找成功   
  55. //  {  
  56. //      p = t;  
  57. //      return true;  
  58. //  }  
  59. //  else if(key < t->data.key)  
  60. //      return searchBST_recursion(t->lchild,key,t,p);//左子樹中繼續查找   
  61. //  else   
  62. //      return searchBST_recursion(t->rchild,key,t,p);//右子樹中繼續查找   
  63. //}  
  64.   
  65. //非遞歸查找   
  66. bool searchBST(BST t, KeyType key, BSTNode* f, BSTNode* &p)  
  67. {  
  68.     while(t && key != t->data.key)  
  69.     {  
  70.         f = t;  
  71.         if(key < t->data.key)  
  72.         {  
  73.             t = t->lchild;  
  74.         }  
  75.         else  
  76.         {  
  77.             t = t->rchild;  
  78.         }             
  79.     }  
  80.     if(t)//查找成功   
  81.     {  
  82.         p = t;  
  83.         return true;  
  84.     }  
  85.     else  
  86.     {  
  87.         p = f;  
  88.         return false;  
  89.     }  
  90. }  
  91.   
  92. //插入給定元素   
  93. void insertBST(BST& t,ElementType elem)  
  94. {  
  95.     BSTNode * p = NULL;  
  96.   
  97.     if(!searchBST(t, elem.key, NULL, p))//查找失敗,不含該關鍵字,可以插入   
  98.     {  
  99.         BSTNode * s = new BSTNode;  
  100.         s->data = elem;//可能需要重載=   
  101.         s->lchild = NULL;  
  102.         s->rchild = NULL;  
  103.           
  104.         if(p == NULL)//查找樹爲空   
  105.         {  
  106.             t = s;//置s爲根結點   
  107.         }  
  108.         else if(elem.key < p->data.key)  
  109.         {  
  110.             p->lchild = s;   //*s爲p左結點   
  111.         }  
  112.         else  
  113.         {  
  114.             p->rchild = s;   //*s爲p右結點   
  115.         }  
  116.     }  
  117. }  
  118.   
  119. //從二叉查找樹中刪除指針p所指向的結點 ,p非空,刪除前驅方式   
  120. void removeNode1(BSTNode *& p)  
  121. {  
  122.     BSTNode *q = NULL;  
  123.     if(!p->rchild)//*p的右子樹爲空   
  124.     {  
  125.         q = p;  
  126.         p = p->lchild;  
  127.         delete q;  
  128.     }  
  129.     else if(!p->lchild)//*p的左子樹爲空   
  130.     {  
  131.         q = p;  
  132.         p = p->rchild;  
  133.         delete q;  
  134.     }  
  135.     else//左右子樹均不空   
  136.     {  
  137.         BSTNode *s = NULL;  
  138.         q = p;  
  139.         s = p->lchild;       //左子樹根結點  
  140.         while(s->rchild) //尋找結點*p的中序前驅結點,                      
  141.         {                   //也就是以p->lchild爲根結點的子樹中最右的結點   
  142.             q = s;          //*s指向*p的中序前驅   
  143.             s = s->rchild;   //*q指向*s的父節點   
  144.         }  
  145.         p->data = s->data;    //*s結點中的數據轉移到*p結點,然後刪除*s  
  146.         if(q != p)          //p->lchild右子樹非空   
  147.         {  
  148.             q->rchild = s->lchild;//把*s的左子樹接到*q的右子樹上   
  149.         }  
  150.         else                //p->lchild右子樹爲空 ,此時q ==p   
  151.         {  
  152.             q->lchild = s->lchild;//把*s的左子樹接到*q的左子樹上   
  153.         }  
  154.         delete s;           //刪除結點*s   
  155.     }  
  156. }  
  157.   
  158. //從二叉查找樹中刪除指針p所指向的結點 ,p非空,刪除後繼方式   
  159. void removeNode2(BSTNode *& p)  
  160. {  
  161.     BSTNode *q = NULL;  
  162.     if(!p->rchild)//*p的右子樹爲空   
  163.     {  
  164.         q = p;  
  165.         p = p->lchild;  
  166.         delete q;  
  167.     }  
  168.     else if(!p->lchild)//*p的左子樹爲空   
  169.     {  
  170.         q = p;  
  171.         p = p->rchild;  
  172.         delete q;  
  173.     }  
  174.     else//左右子樹均不空   
  175.     {  
  176.         BSTNode *s = NULL;  
  177.         q = p;  
  178.         s = p->rchild;       //右子樹根結點  
  179.         while(s->lchild) //尋找結點*p的中序後繼結點,                      
  180.         {                   //也就是以p->rchild爲根結點的子樹中最左的結點   
  181.             q = s;          //*s指向*p的中序後繼   
  182.             s = s->lchild;   //*q指向*s的父節點   
  183.         }  
  184.         p->data = s->data;    //*s結點中的數據轉移到*p結點,然後刪除*s  
  185.         if(q != p)          //p->rchild左子樹非空   
  186.         {  
  187.             q->lchild = s->rchild;//把*s的右子樹接到*q的左子樹上   
  188.         }  
  189.         else                //p->rchild左子樹爲空 ,此時q ==p   
  190.         {  
  191.             q->rchild = s->rchild;//把*s的右子樹接到*q的右子樹上   
  192.         }  
  193.         delete s;           //刪除結點*s   
  194.     }  
  195. }  
  196.   
  197. //從二叉查找樹中刪除指針p所指向的結點 ,p非空,直接刪除p的方式   
  198. void removeNode3(BSTNode *& p)  
  199. {  
  200.     BSTNode *q = NULL;  
  201.     if(!p->rchild)//*p的右子樹爲空   
  202.     {  
  203.         q = p;  
  204.         p = p->lchild;  
  205.         delete q;  
  206.     }  
  207.     else if(!p->lchild)//*p的左子樹爲空   
  208.     {  
  209.         q = p;  
  210.         p = p->rchild;  
  211.         delete q;  
  212.     }  
  213.     else//左右子樹均不空   
  214.     {  
  215.         BSTNode *s = NULL;  
  216.         q = p;  
  217.         s = p->lchild;       //左子樹根結點  
  218.         while(s->rchild) //尋找結點*p的中序前驅結點,                      
  219.         {                   //也就是以*s爲根結點的子樹中最右的結點   
  220.             s = s->rchild;      
  221.         }  
  222.         s->rchild = p->rchild;//*p的右子樹接到*s的右子樹上   
  223.         p = p->lchild;           //*p的左子樹接到父節點上   
  224.         delete q;           //刪除結點*q   
  225.     }  
  226. }  
  227.   
  228. //刪除關鍵字爲key的元素結點 -遞歸   
  229. void removeBST_recursion(BST& t, KeyType key)  
  230. {  
  231.     if(t)  
  232.     {  
  233.         if(key < t->data.key)  
  234.         {  
  235.             removeBST_recursion(t->lchild,key);  
  236.         }  
  237.         else if(t->data.key < key)  
  238.         {  
  239.             removeBST_recursion(t->rchild,key);  
  240.         }  
  241.         else//找到關鍵字爲key的元素   
  242.         {  
  243. //          removeNode1(t);//刪除結點*t   
  244. //          removeNode2(t);  
  245.             removeNode3(t);  
  246.         }  
  247.     }  
  248. }  
  249.   
  250.   
  251. //輸出二叉查找樹,中序遞歸   
  252. void printTree(const BST & t)  
  253. {  
  254.     if(t)  
  255.     {  
  256.         printTree(t->lchild);  
  257.         cout<<t->data<<" ";  
  258.         printTree(t->rchild);  
  259.     }  
  260. }  
  261.   
  262. int main(int argc, char *argv[])  
  263. {  
  264.     const int N = 10;  
  265.     BST root = NULL;  
  266.       
  267.     for(int i=1; i<=N; i++)  
  268.     {  
  269.         Student s(i,"cs");//關鍵字爲1-10   
  270.         insertBST(root,s);  
  271.     }  
  272.     cout<<"after insert: "<<endl;  
  273.     printTree(root);  
  274.     cout<<endl<<endl;  
  275.       
  276.     for(int i=1;i<=N;i+=2)  
  277.     {  
  278.         removeBST_recursion(root,i);//刪除關鍵字爲1-3-5-7-9的結點   
  279.     }  
  280.     cout<<"after delete: "<<endl;  
  281.     printTree(root);  
  282.     cout<<endl<<endl;  
  283.       
  284.     system("PAUSE");  
  285.     return EXIT_SUCCESS;  
  286. }  

5、二叉查找樹類的完整C++實現請看http://blog.csdn.net/sysu_arui/article/details/7892593


參考資料:

[1]嚴蔚敏《數據結構(C語言版)》

[2]算法導論(第2版)


發佈了164 篇原創文章 · 獲贊 26 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章