二叉查詢樹的插入和刪除詳解
(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;
}
參考資料:
[1]嚴蔚敏《資料結構(C語言版)》
[2]演算法導論(第2版)