(1) 左子樹不空,則左子樹上的所有結點的值均小於根結點的值
(2) 右子樹不空,則右子樹上的所有結點的值均大於根結點的值
二叉查找樹可以爲空,二叉查找樹是遞歸定義的,也就是說其左右子樹也爲二叉查找樹。
二叉查找樹是一種動態查找表,可以進行動態地插入和刪除。前面的定義中我們假定二叉查找樹不含有相同元素。
由定義可知二叉查找樹的中序序列爲一個遞增序列
常見的二叉查找樹操作有求最小元素findMin(),求最大元素findMax(),判斷查找樹是否非空isEmpty(),判斷是否包含給定元素contains(),輸出所有元素printTree(),置空查找樹makeEmpty(),向查找樹中插入給定元素x,insert(x),在查找樹中刪除給定元素x,remove(x)。
下面先討論最重要的插入和刪除操作,接着給出完整的C風格實現。
1、基本結構定義
- class Student
- {
- public:
- int key;
- string major;
- Student(int k=int(),string s="") : key(k), major(s){}
- void operator=(const Student& rhs);
- };
- typedef Student ElementType;
- typedef int KeyType;
- typedef struct BSTNode
- {
- ElementType data;
- struct BSTNode* lchild;
- struct BSTNode* rchild;
- }BSTNode, *BST;
詳細信息請看後面完整程序
2、插入
(1) 不允許插入相同關鍵字,若二叉查找樹中存在該關鍵字,則不插入
(2) 我們可以先檢索二叉樹,看查找樹中是否含有該關鍵字,若不存在,再做一次掃描將結點插入到適當位置。使用這種方式,爲插入一個該關鍵字,做了兩次掃描。
(3) 注意到,插入的結點總是作爲某一葉子節點的子結點,我們可以在第一次掃描過程中就確定待插入的位置,即把查找是否存在該關鍵字和查找可能的插入點在一次掃描中完成,提高插入效率。
(4) 查找遞歸實現
- /*
- description:在以t爲根結點的二叉查找樹中,查找關鍵字爲key的結點
- 若查找成功,指針p指向該結點,返回true,
- 否則指向查找路徑上的最後一個結點並返回false
- 指針f指向根結點t的父節點
- */
- bool searchBST_recursion(BST t, KeyType key, BSTNode* f, BSTNode*& p)
- {
- if(t == NULL)//查找失敗
- {
- p = f;
- return false;
- }
- else if(key == t->data.key)//查找成功
- {
- p = t;
- return true;
- }
- else if(key < t->data.key)
- return searchBST_recursion(t->lchild,key,t,p);//左子樹中繼續查找
- else
- return searchBST_recursion(t->rchild,key,t,p);//右子樹中繼續查找
- }
(5) 查找非遞歸實現
- bool searchBST(BST t, KeyType key, BSTNode* f, BSTNode* &p)
- {
- while(t && key != t->data.key)
- {
- f = t;
- if(key < t->data.key)
- {
- t = t->lchild;
- }
- else
- {
- t = t->rchild;
- }
- }
- if(t)//查找成功
- {
- p = t;
- return true;
- }
- else
- {
- p = f;
- return false;
- }
- }
(6) 插入給定元素
- void insertBST(BST& t,ElementType elem)
- {
- BSTNode * p = NULL;
- if(!searchBST(t, elem.key, NULL, p))//查找失敗,不含該關鍵字,可以插入
- {
- BSTNode * s = new BSTNode;
- s->data = elem;//可能需要重載=
- s->lchild = NULL;
- s->rchild = NULL;
- if(p == NULL)//查找樹爲空
- {
- t = s;//置s爲根結點
- }
- else if(elem.key < p->data.key)
- {
- p->lchild = s; //*s爲p左結點
- }
- else
- {
- p->rchild = s; //*s爲p右結點
- }
- }
- }
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的代碼如下:
- //從二叉查找樹中刪除指針p所指向的結點 ,p非空,刪除前驅方式
- void removeNode1(BSTNode *& p)
- {
- BSTNode *q = NULL;
- if(!p->rchild)//*p的右子樹爲空
- {
- q = p;
- p = p->lchild;
- delete q;
- }
- else if(!p->lchild)//*p的左子樹爲空
- {
- q = p;
- p = p->rchild;
- delete q;
- }
- else//左右子樹均不空
- {
- BSTNode *s = NULL;
- q = p;
- s = p->lchild; //左子樹根結點
- while(s->rchild) //尋找結點*p的中序前驅結點,
- { //也就是以p->lchild爲根結點的子樹中最右的結點
- q = s; //*s指向*p的中序前驅
- s = s->rchild; //*q指向*s的父節點
- }
- p->data = s->data; //*s結點中的數據轉移到*p結點,然後刪除*s
- if(q != p) //p->lchild右子樹非空
- {
- q->rchild = s->lchild;//把*s的左子樹接到*q的右子樹上
- }
- else //p->lchild右子樹爲空 ,此時q ==p
- {
- q->lchild = s->lchild;//把*s的左子樹接到*q的左子樹上
- }
- delete s; //刪除結點*s
- }
- }
使用處理方式b的代碼如下:
- //從二叉查找樹中刪除指針p所指向的結點 ,p非空,刪除後繼方式
- void removeNode2(BSTNode *& p)
- {
- BSTNode *q = NULL;
- if(!p->rchild)//*p的右子樹爲空
- {
- q = p;
- p = p->lchild;
- delete q;
- }
- else if(!p->lchild)//*p的左子樹爲空
- {
- q = p;
- p = p->rchild;
- delete q;
- }
- else//左右子樹均不空
- {
- BSTNode *s = NULL;
- q = p;
- s = p->rchild; //右子樹根結點
- while(s->lchild) //尋找結點*p的中序後繼結點,
- { //也就是以p->rchild爲根結點的子樹中最左的結點
- q = s; //*s指向*p的中序後繼
- s = s->lchild; //*q指向*s的父節點
- }
- p->data = s->data; //*s結點中的數據轉移到*p結點,然後刪除*s
- if(q != p) //p->rchild左子樹非空
- {
- q->lchild = s->rchild;//把*s的右子樹接到*q的左子樹上
- }
- else //p->rchild左子樹爲空 ,此時q ==p
- {
- q->rchild = s->rchild;//把*s的右子樹接到*q的右子樹上
- }
- delete s; //刪除結點*s
- }
- }
使用處理方式c的代碼如下:
- //從二叉查找樹中刪除指針p所指向的結點 ,p非空,直接刪除p的方式
- void removeNode3(BSTNode *& p)
- {
- BSTNode *q = NULL;
- if(!p->rchild)//*p的右子樹爲空
- {
- q = p;
- p = p->lchild;
- delete q;
- }
- else if(!p->lchild)//*p的左子樹爲空
- {
- q = p;
- p = p->rchild;
- delete q;
- }
- else//左右子樹均不空
- {
- BSTNode *s = NULL;
- q = p;
- s = p->lchild; //左子樹根結點
- while(s->rchild) //尋找結點*p的中序前驅結點,
- { //也就是以*s爲根結點的子樹中最右的結點
- s = s->rchild;
- }
- s->rchild = p->rchild;//*p的右子樹接到*s的右子樹上
- p = p->lchild; //*p的左子樹接到父節點上
- delete q; //刪除結點*q
- }
- }
在二叉查找樹中刪除含有給定關鍵字的元素結點的遞歸函數如下:
- //刪除關鍵字爲key的元素結點 -遞歸
- void removeBST_recursion(BST& t, KeyType key)
- {
- if(t)
- {
- if(key < t->data.key)
- {
- removeBST_recursion(t->lchild,key);
- }
- else if(t->data.key < key)
- {
- removeBST_recursion(t->rchild,key);
- }
- else//找到關鍵字爲key的元素
- {
- // removeNode1(t);//刪除結點*t
- // removeNode2(t);
- removeNode3(t);
- }
- }
- }
(1)由於該函數是遞歸的,且用到了指針引用,我們在前面的刪除給定結點p的函數中,修改p就相當於修改了父節點,這是和C中不同的地方,在C中要達到這種效果,可以使用指向指針的指針變量。
(2)只給出了該函數的遞歸實現,沒有給出非遞歸實現,是爲了避免把代碼弄亂。要實現非遞歸,可以修改前面的刪除結點p的函數,添加一個指向結點p的父節點的指針引用f。同時要修改查找是否存在關鍵字爲key的非遞歸查找函數,使其在得到p的同時,可以得到其父節點。這樣我們在實現非遞歸時,只需掃描一遍,即調用查找函數,如果有該關鍵字,我們就可以得到指向該結點的指針p和指向結點p的父節點f,然後調用刪除給定結點p的函數即可。
4、完整測試程序
- #include <cstdlib>
- #include <iostream>
- #include <string>
- using namespace std;
- class Student
- {
- public:
- int key;
- string major;
- //other data
- Student(int k=int(),string s="") : key(k), major(s){}
- //重載賦值運算符
- void operator=(const Student& rhs)
- {
- if(this != &rhs)
- {
- key = rhs.key;
- major = rhs.major;
- }
- }
- };
- //重載<<,便於輸出自定義類對象
- ostream& operator<<(ostream &out, const Student& s)
- {
- out<<"("<<s.key<<","<<s.major<<")";
- }
- typedef Student ElementType;
- typedef int KeyType;
- typedef struct BSTNode
- {
- ElementType data;
- struct BSTNode* lchild;
- struct BSTNode* rchild;
- }BSTNode, *BST;
- /*
- description:在以t爲根結點的二叉查找樹中,查找關鍵字爲key的結點
- 若查找成功,指針p指向該結點,返回true,
- 否則指向查找路徑上的最後一個結點並返回false
- 指針f指向根結點t的父節點
- */
- //bool searchBST_recursion(BST t, KeyType key, BSTNode* f, BSTNode*& p)
- //{
- // if(t == NULL)//查找失敗
- // {
- // p = f;
- // return false;
- // }
- // else if(key == t->data.key)//查找成功
- // {
- // p = t;
- // return true;
- // }
- // else if(key < t->data.key)
- // return searchBST_recursion(t->lchild,key,t,p);//左子樹中繼續查找
- // else
- // return searchBST_recursion(t->rchild,key,t,p);//右子樹中繼續查找
- //}
- //非遞歸查找
- bool searchBST(BST t, KeyType key, BSTNode* f, BSTNode* &p)
- {
- while(t && key != t->data.key)
- {
- f = t;
- if(key < t->data.key)
- {
- t = t->lchild;
- }
- else
- {
- t = t->rchild;
- }
- }
- if(t)//查找成功
- {
- p = t;
- return true;
- }
- else
- {
- p = f;
- return false;
- }
- }
- //插入給定元素
- void insertBST(BST& t,ElementType elem)
- {
- BSTNode * p = NULL;
- if(!searchBST(t, elem.key, NULL, p))//查找失敗,不含該關鍵字,可以插入
- {
- BSTNode * s = new BSTNode;
- s->data = elem;//可能需要重載=
- s->lchild = NULL;
- s->rchild = NULL;
- if(p == NULL)//查找樹爲空
- {
- t = s;//置s爲根結點
- }
- else if(elem.key < p->data.key)
- {
- p->lchild = s; //*s爲p左結點
- }
- else
- {
- p->rchild = s; //*s爲p右結點
- }
- }
- }
- //從二叉查找樹中刪除指針p所指向的結點 ,p非空,刪除前驅方式
- void removeNode1(BSTNode *& p)
- {
- BSTNode *q = NULL;
- if(!p->rchild)//*p的右子樹爲空
- {
- q = p;
- p = p->lchild;
- delete q;
- }
- else if(!p->lchild)//*p的左子樹爲空
- {
- q = p;
- p = p->rchild;
- delete q;
- }
- else//左右子樹均不空
- {
- BSTNode *s = NULL;
- q = p;
- s = p->lchild; //左子樹根結點
- while(s->rchild) //尋找結點*p的中序前驅結點,
- { //也就是以p->lchild爲根結點的子樹中最右的結點
- q = s; //*s指向*p的中序前驅
- s = s->rchild; //*q指向*s的父節點
- }
- p->data = s->data; //*s結點中的數據轉移到*p結點,然後刪除*s
- if(q != p) //p->lchild右子樹非空
- {
- q->rchild = s->lchild;//把*s的左子樹接到*q的右子樹上
- }
- else //p->lchild右子樹爲空 ,此時q ==p
- {
- q->lchild = s->lchild;//把*s的左子樹接到*q的左子樹上
- }
- delete s; //刪除結點*s
- }
- }
- //從二叉查找樹中刪除指針p所指向的結點 ,p非空,刪除後繼方式
- void removeNode2(BSTNode *& p)
- {
- BSTNode *q = NULL;
- if(!p->rchild)//*p的右子樹爲空
- {
- q = p;
- p = p->lchild;
- delete q;
- }
- else if(!p->lchild)//*p的左子樹爲空
- {
- q = p;
- p = p->rchild;
- delete q;
- }
- else//左右子樹均不空
- {
- BSTNode *s = NULL;
- q = p;
- s = p->rchild; //右子樹根結點
- while(s->lchild) //尋找結點*p的中序後繼結點,
- { //也就是以p->rchild爲根結點的子樹中最左的結點
- q = s; //*s指向*p的中序後繼
- s = s->lchild; //*q指向*s的父節點
- }
- p->data = s->data; //*s結點中的數據轉移到*p結點,然後刪除*s
- if(q != p) //p->rchild左子樹非空
- {
- q->lchild = s->rchild;//把*s的右子樹接到*q的左子樹上
- }
- else //p->rchild左子樹爲空 ,此時q ==p
- {
- q->rchild = s->rchild;//把*s的右子樹接到*q的右子樹上
- }
- delete s; //刪除結點*s
- }
- }
- //從二叉查找樹中刪除指針p所指向的結點 ,p非空,直接刪除p的方式
- void removeNode3(BSTNode *& p)
- {
- BSTNode *q = NULL;
- if(!p->rchild)//*p的右子樹爲空
- {
- q = p;
- p = p->lchild;
- delete q;
- }
- else if(!p->lchild)//*p的左子樹爲空
- {
- q = p;
- p = p->rchild;
- delete q;
- }
- else//左右子樹均不空
- {
- BSTNode *s = NULL;
- q = p;
- s = p->lchild; //左子樹根結點
- while(s->rchild) //尋找結點*p的中序前驅結點,
- { //也就是以*s爲根結點的子樹中最右的結點
- s = s->rchild;
- }
- s->rchild = p->rchild;//*p的右子樹接到*s的右子樹上
- p = p->lchild; //*p的左子樹接到父節點上
- delete q; //刪除結點*q
- }
- }
- //刪除關鍵字爲key的元素結點 -遞歸
- void removeBST_recursion(BST& t, KeyType key)
- {
- if(t)
- {
- if(key < t->data.key)
- {
- removeBST_recursion(t->lchild,key);
- }
- else if(t->data.key < key)
- {
- removeBST_recursion(t->rchild,key);
- }
- else//找到關鍵字爲key的元素
- {
- // removeNode1(t);//刪除結點*t
- // removeNode2(t);
- removeNode3(t);
- }
- }
- }
- //輸出二叉查找樹,中序遞歸
- void printTree(const BST & t)
- {
- if(t)
- {
- printTree(t->lchild);
- cout<<t->data<<" ";
- printTree(t->rchild);
- }
- }
- int main(int argc, char *argv[])
- {
- const int N = 10;
- BST root = NULL;
- for(int i=1; i<=N; i++)
- {
- Student s(i,"cs");//關鍵字爲1-10
- insertBST(root,s);
- }
- cout<<"after insert: "<<endl;
- printTree(root);
- cout<<endl<<endl;
- for(int i=1;i<=N;i+=2)
- {
- removeBST_recursion(root,i);//刪除關鍵字爲1-3-5-7-9的結點
- }
- cout<<"after delete: "<<endl;
- printTree(root);
- cout<<endl<<endl;
- system("PAUSE");
- return EXIT_SUCCESS;
- }
5、二叉查找樹類的完整C++實現請看:http://blog.csdn.net/sysu_arui/article/details/7892593
[1]嚴蔚敏《數據結構(C語言版)》
[2]算法導論(第2版)