二叉排序樹簡介
二叉排序樹(Binary Sort Tree,簡稱BST),又稱二叉查找樹,是紅黑樹、AVL樹等的基礎。它或是一棵空樹,或者是具有下列性質的一棵二叉樹:
1、若它的左子樹不空,則左子樹上所有節點的值均小於它的根節點的值;
2、若它的右子樹不空,則右子樹上所有節點的值均大於它的根節點的值;
3、它的左右子樹也分別爲二叉排序樹。
下面的一棵樹即爲二叉排序樹:
很明顯,對二叉排序樹進行中序遍歷,便可得到一個有序序列,該有序序列中的各元素按照從小到大的順序排列,因此一個無序序列可以通過構造一棵二叉排序樹而變成一個有序序列。
二叉排序樹相關操作
二叉排序樹通常有查找、插入、刪除等操作。查找操作很簡單,無非就是遞歸查找,有點類似二叉樹遍歷的過程。插入操作也不難,一般是先在二叉排序樹pTree中查找,看是否存在有等於給定值的元素,如果查找不到,則將給定值插入到該二叉排序樹中,但是要保證插入後的樹依然是二叉排序樹。這樣,新節點插入的位置便是唯一的,而且新插入的節點一定是一個新添加的葉子節點,並且是查找不成功時查找路徑上訪問的最後一個節點的左孩子或右孩子。正是由於其在查找過程中插入節點的特性,二叉排序樹是一種動態樹。
在給出各操作實現的具體代碼前,要詳細看下二叉排序樹的刪除操作,刪除操作相比於二叉排序樹的其他操作要難些,但也只是相對於其本身的其他操作而已,真正理解了也就很容易了。閒話少說,下面就來具體分析二叉排序樹的刪除操作。
我們假設在二叉排序樹中要被刪除的節點爲p(即p指向的節點,下同),其父節點爲f,當然節點p可能是節點f的的左孩子或右孩子,但在下面各種情況的分析中,你會發現,無論是左孩子還是右孩子,都不影響刪除操作的通用性。很明顯,刪除操作要分爲如下3種情況:
1、若待刪節點p爲葉子節點,則刪除p後,並不會破壞整棵樹的結構,因此只需令p=NULL即可。
2、若待刪節點p只有左子樹或右子樹,則只需將左子樹或右子樹重接到p的父節點上即可,即執行如下操作:p=p->lchild或p=p->rchild。
3、若待刪節點p既有左子樹又有右子樹,顯然就不如上面兩種情況那麼簡單了。我們要使節點p被刪除後,二叉排序樹的結構不變,就需要對它的子樹做一些操作,而且只需操作一個子樹即可,操作左子樹和操作右子樹的思路相似,我們這裏以操作左子樹爲例來實現節點p的刪除操作,並結合下圖做具體分析(圖中三角形代表節點的左子樹或右子樹)。
我們這裏將圖a展開爲更詳細的圖b進行分析,則在刪除節點p前,中序遍歷該二叉排序樹的結果爲:...CL C...QL Q SL S P PR F ...,刪除節點p後,我們要保持其他元素在該序列中的先後順序不變,觀察圖b,我們可以採取如下兩種做法:
1)將節點p的左子樹直接接到其父節點f上,作爲f的左子樹,將節點p的右子樹接到節點s上,作爲s的右子樹(這裏s爲p的前驅節點,即p在有序序列中緊接在s的前面),而後刪除節點p。採用這種方法刪除節點p後,得到的二叉排序樹的形狀如下圖中的圖c所示:
採取該方法刪除節點的實現代碼如下:
- /*
- 採用第一種算法從二叉排序樹中刪除指針p所指向的節點,
- 並在保持二叉排序樹有序的情況下,將其左右子樹重接到該二叉排序樹中.
- 該函數主要用來被後面的刪除函數調用
- */
- void delete_Node1(BSTree &p)
- {
- BSTree q,s;
- if(!p->lchild)
- { //如果左子樹爲空,則只需重接其右子樹
- //這裏包含了左右子樹均爲空的情況
- q = p;
- p = p->rchild ;
- free(q);
- }
- else if(!p->rchild)
- { //如果右子樹爲空,則只需重接其左子樹
- q = p;
- p = p->lchild;
- free(q);
- }
- else
- { //如果左右子樹都不爲空,我們採取第一種方法來重接左右子樹,
- //我們這裏採取修改左子樹的方法,也可以修改右子樹,方法類似
- s = p->lchild; //取待刪節點的左節點
- //一直向右,最終s爲待刪節點的前驅節點
- //如果將各節點元素按從小到大順序排列成一個序列,
- //則某節點的前驅節點即爲序列中該節點的前面一個節點
- while(s->rchild)
- s = s->rchild;
- s->rchild = p->rchild; //將p的右子樹接爲s的右子樹
- q = p;
- p = p->lchild; //將p的左子樹直接接到其父節點的左子樹上
- free(q);
- }
- }
- /*
- 採用第二種算法從二叉排序樹中刪除指針p所指向的節點,
- 並在保持二叉排序樹有序的情況下,將其左右子樹重接到該二叉排序樹中.
- 該函數主要用來被後面的刪除函數調用
- */
- void delete_Node2(BSTree &p)
- {
- BSTree q,s;
- if(!p->lchild)
- { //如果左子樹爲空,則只需重接其右子樹
- //這裏包含了左右子樹均爲空的情況
- q = p;
- p = p->rchild ;
- free(q);
- }
- else if(!p->rchild)
- { //如果右子樹爲空,則只需重接其左子樹
- q = p;
- p = p->lchild;
- free(q);
- }
- else
- { //如果左右子樹都不爲空,我們採取第二種方法來重接左右子樹,
- //我們這裏採取修改左子樹的方法,也可以修改右子樹,方法類似
- q = p;
- s = p->lchild; //取待刪節點的左節點
- while(s->rchild)
- { //一直向右,最終s爲待刪節點的前驅節點。
- //如果將各節點元素按從小到大順序排列成一個序列,
- //則某節點的前驅節點即爲序列中該節點的前面一個節點
- q = s;
- s = s->rchild;
- }
- //用s來替換待刪節點p
- p->data = s->data;
- //根據情況,將s的左子樹重接到q上
- if(p != q)
- q->rchild = s->lchild;
- else
- q->lchild =s->lchild;
- free(s);
- }
- }
完整源碼
上面重點分析了刪除節點的思路和過程,下面給出二叉排序樹各種操作實現的完整C代碼(含測試代碼並加有詳細註釋):
- /*********************************
- 二叉排序樹的相關操作實現
- Author:蘭亭風雨 Date:2014-02-23
- Email:[email protected]
- **********************************/
- #include<stdio.h>
- #include<stdlib.h>
- typedef struct Node
- {
- int data;
- struct Node *lchild;
- struct Node *rchild;
- }NODE,*BSTree;
- /*
- 在指針pTree所指的二叉排序樹中遞歸查找關鍵字爲key的元素,
- 若查找成功,則返回指向該元素節點的指針,否則返回NULL
- */
- BSTree search(BSTree pTree,int key)
- {
- if(!pTree || pTree->data == key) //查找到時返回的pTree爲該元素節點,沒查找到時爲NULL
- return pTree;
- else if(key < pTree->data) //如果key小於當前節點的值,則在其左子樹中遞歸查找
- return search(pTree->lchild,key);
- else //如果key大於當前節點的值,則在其右子樹中遞歸查找
- return search(pTree->rchild,key);
- }
- /*
- 在指針pTree所指的二叉排序樹中遞歸查找關鍵字爲key的元素,
- 若查找成功,則返回ture,並查找到的數據對應的節點指針保存在p中,
- 否則返回false,並將查找路徑上訪問的最後一個節點指針保存在p中。
- 這裏的參數parent指向每次遞歸遍歷的子樹的根節點的父節點,即始終是參數pTree的父節點,
- 它的初始值爲NULL,其目的是跟蹤查找路徑上訪問的當前節點的父節點(即上一個訪問節點)
- 該函數用來被後面的插入函數調用。
- */
- bool search_BSTree(BSTree pTree,int key,BSTree parent,BSTree &p)
- {
- if(!pTree) //如果pTree爲NULL,則查找不成功
- { //這裏包含了樹空,即pTree爲NULL的情況
- p = parent;
- return false;
- }
- else //否則,繼續查找
- {
- if(key == pTree->data) //如果相等,則查找成功
- {
- p = pTree;
- return true;
- }
- else if(key < pTree->data) //在左子樹中遞歸查找
- return search_BSTree(pTree->lchild,key,pTree,p);
- else //在右子樹中遞歸查找
- return search_BSTree(pTree->rchild,key,pTree,p);
- }
- }
- /*
- 當在pTree所指向的二叉排序樹中查找不到關鍵字爲key的數據元素時,
- 將其插入該二叉排序樹,並返回ture,否則返回false。
- 樹空時插入會改變根節點的值,因此要傳入引用。
- */
- bool insert(BSTree &pTree,int key)
- {
- BSTree p;
- if(!search_BSTree(pTree,key,NULL,p)) //如果查找失敗,則執行插入操作
- {
- //爲新節點分配空間,並對各域賦值
- BSTree pNew = (BSTree)malloc(sizeof(NODE));
- pNew->data = key;
- pNew->lchild = pNew->rchild = NULL;
- if(!p) //如果樹空,則直接置pNew爲根節點
- pTree = pNew;
- else if(key < p->data) //作爲左孩子插入p的左邊
- p->lchild = pNew; //作爲右孩子插入p的右邊
- else
- p->rchild = pNew;
- }
- else
- return false;
- }
- /*
- 採用第一種算法從二叉排序樹中刪除指針p所指向的節點,
- 並在保持二叉排序樹有序的情況下,將其左右子樹重接到該二叉排序樹中.
- 該函數主要用來被後面的刪除函數調用
- */
- void delete_Node1(BSTree &p)
- {
- BSTree q,s;
- if(!p->lchild)
- { //如果左子樹爲空,則只需重接其右子樹
- //這裏包含了左右子樹均爲空的情況
- q = p;
- p = p->rchild ;
- free(q);
- }
- else if(!p->rchild)
- { //如果右子樹爲空,則只需重接其左子樹
- q = p;
- p = p->lchild;
- free(q);
- }
- else
- { //如果左右子樹都不爲空,我們採取第一種方法來重接左右子樹,
- //我們這裏採取修改左子樹的方法,也可以修改右子樹,方法類似
- s = p->lchild; //取待刪節點的左節點
- //一直向右,最終s爲待刪節點的前驅節點
- //如果將各節點元素按從小到大順序排列成一個序列,
- //則某節點的前驅節點即爲序列中該節點的前面一個節點
- while(s->rchild)
- s = s->rchild;
- s->rchild = p->rchild; //將p的右子樹接爲s的右子樹
- q = p;
- p = p->lchild; //將p的左子樹直接接到其父節點的左子樹上
- free(q);
- }
- }
- /*
- 採用第二種算法從二叉排序樹中刪除指針p所指向的節點,
- 並在保持二叉排序樹有序的情況下,將其左右子樹重接到該二叉排序樹中.
- 該函數主要用來被後面的刪除函數調用
- */
- void delete_Node2(BSTree &p)
- {
- BSTree q,s;
- if(!p->lchild)
- { //如果左子樹爲空,則只需重接其右子樹
- //這裏包含了左右子樹均爲空的情況
- q = p;
- p = p->rchild ;
- free(q);
- }
- else if(!p->rchild)
- { //如果右子樹爲空,則只需重接其左子樹
- q = p;
- p = p->lchild;
- free(q);
- }
- else
- { //如果左右子樹都不爲空,我們採取第二種方法來重接左右子樹,
- //我們這裏採取修改左子樹的方法,也可以修改右子樹,方法類似
- q = p;
- s = p->lchild; //取待刪節點的左節點
- while(s->rchild)
- { //一直向右,最終s爲待刪節點的前驅節點。
- //如果將各節點元素按從小到大順序排列成一個序列,
- //則某節點的前驅節點即爲序列中該節點的前面一個節點
- q = s;
- s = s->rchild;
- }
- //用s來替換待刪節點p
- p->data = s->data;
- //根據情況,將s的左子樹重接到q上
- if(p != q)
- q->rchild = s->lchild;
- else
- q->lchild =s->lchild;
- free(s);
- }
- }
- /*
- 若pTree所指向的二叉排序樹中查找到關鍵字爲key的數據元素,
- 則刪除該元素對應的節點,並返回true,否則返回false
- 如果要刪除的恰好是根節點,則會改變根節點的值,因此要傳入引用
- */
- bool delete_BSTree(BSTree &pTree,int key)
- {
- //不存在關鍵字爲key的節點
- if(!pTree)
- return false;
- else
- {
- if(key == pTree->data) //查找到關鍵字爲key的節點
- {
- delete_Node1(pTree);
- // delete_Node2(pTree);
- return true;
- }
- else if(key < pTree->data) //繼續查找左子樹
- return delete_BSTree(pTree->lchild,key);
- else //繼續查找右子樹
- return delete_BSTree(pTree->rchild,key);
- }
- }
- /*
- 根據所給的長爲len的arr數組,按數組中元素的順序構建一棵二叉排序樹
- */
- BSTree create_BSTree(int *arr,int len)
- {
- BSTree pTree = NULL;
- int i;
- //按順序逐個節點插入到二叉排序樹中
- for(i=0;i<len;i++)
- insert(pTree,arr[i]);
- return pTree;
- }
- /*
- 遞歸中序遍歷二叉排序樹,得到元素從小到大有序排列的序列
- */
- void in_traverse(BSTree pTree)
- {
- if(pTree)
- {
- if(pTree->lchild)
- in_traverse(pTree->lchild);
- printf("%d ",pTree->data);
- if(pTree->rchild)
- in_traverse(pTree->rchild);
- }
- }
- int main()
- {
- int i;
- int num;
- printf("請輸入節點個數:");
- scanf("%d",&num);
- //輸入num個整數
- int *arr = (int *)malloc(num*sizeof(int));
- printf("請依次輸入這%d個整數(必須互不相等):",num);
- for(i=0;i<num;i++)
- scanf("%d",arr+i);
- //中序遍歷該二叉排序樹,使數據按照從小到大的順序輸出
- BSTree pTree = create_BSTree(arr,num);
- printf("中序遍歷該二叉排序樹的結果:");
- in_traverse(pTree);
- printf("\n");
- //查找給定的整數
- int key;
- printf("請輸入要查找的整數:");
- scanf("%d",&key);
- if(search(pTree,key))
- printf("查找成功\n");
- else
- printf("查找不到該整數\n");
- //插入給定的整數
- printf("請輸入要插入的整數:");
- scanf("%d",&key);
- if(insert(pTree,key))
- {
- printf("插入成功,插入後的中序遍歷結果:");
- in_traverse(pTree);
- printf("\n");
- }
- else
- printf("插入失敗,該二叉排序樹中已經存在整數%d\n",key);
- //刪除給定的整數
- printf("請輸入要刪除的整數:");
- scanf("%d",&key);
- if(delete_BSTree(pTree,key))
- {
- printf("刪除成功,插入後的中序遍歷結果:");
- in_traverse(pTree);
- printf("\n");
- }
- else
- printf("刪除失敗,該二叉排序樹中不存在整數%d\n",key);
- return 0;
- }
轉載:http://blog.csdn.net/ns_code/article/details/19823463