四、樹與二叉樹
1. 二叉樹的順序存儲結構
二叉樹的順序存儲就是用數組存儲二叉樹。二叉樹的每個結點在順序存儲中都有自己的固定位置。
這里要注意一下根結點是取 [0] 和 [1] 的區別。i 層的滿二叉樹,其節點總數為 2^i - 1 。二叉樹每增加一層,結點數就要加倍,所以二叉樹的順序存儲結構特別適合完全二叉樹。如果二叉樹的形態和完全二叉樹的差別很大,采用順序存儲結構就很浪費存儲空間,這種情況可以采用鏈式存儲結構。
2. 二叉樹的鏈式存儲結構
二叉樹的二叉鏈表存儲結構刪除和插入結點或子樹都很靈活。結點動態生成,可充分利用空間。
實現:
//二叉樹鏈表結點結構體
template<typename T>struct BiTNode
{
T data;
BiTNode<T> *lchild, *rchild;
};
//二叉鏈表結構的二叉樹類
enum Tags {Left, Right}; //枚舉類型標志位
enum style {Pre, In, Post}; //枚舉類型遍歷方式
template<typename T>struct StackElem
{//帶模板的棧元素類
BiTNode<T> *p;
Tags flag;
}
template<typename T>class SOSTree //聲明帶模板的靜態樹表類
template<typename T>class BiTree;
{//帶模板的二叉鏈表結構的二叉樹類
friend SOSTree<struct S>; //設置靜態樹表的實例為友類
protected:
BiTNode<t> *root;
private:
void DestroyBiTree(BiTNode<T>* &t)
{//銷毀 t 所指二叉樹或子樹
if (t != NULL)
{
DestroyBiTree(t->lchild); //遞歸銷毀左子樹
DestroyBiTree(t->rchild); //遞歸銷毀右子樹
delete t; //釋放結點
t = NULL; //指針 t 賦空
}
}
public:
BiTree()
{//構造函數,構造一顆空的二叉樹
root = NULL;
}
~BiTree()
{//析構函數,銷毀二叉樹
DestroyBiTree(root);
}
void CreateBiTreeFromFile(ifstream &f)
{//按先序次序由數據文件輸入二叉樹中結點的值,構造二叉樹
T e;
InputFromFile(f, e); //由文件輸入結點的值
if (e == NULL)
return;
root = new BiTNode<T>;
assert(root != NULL);
root->data = e;
BiTree<T> left, right; //子樹對象
left.CreateBiTreeFromFile(f); //遞歸創建左子樹
right.CreateBiTreeFromFile(f); //遞歸創建右子樹
root->lchild = left.root; //根結點的左孩子接左子樹的根結點
left.root = NULL; //左子樹為空樹
root->rchild = right.root; //根結點的右孩子接右子樹的根結點
right.root = NULL; //右子樹為空樹
}
bool BiTreeEmpty()const
{//若為空二叉樹,則返回 true ;否則返回 false
return root == NULL;
}
int BiTreeDepth(BiTNode<T>* t)const
{//返回 t 所指的二叉樹或子樹的深度
int i, j;
if (t == NULL)
return 0;
else
{
i = BiTreeDepth(t->lchild);
j = BiTreeDepth(t->rchild);
return i > j ? i+1 : j+1; //t 的深度為其左右子樹的深度大者 +1
}
}
void PreOrderTraverse(void(*visit) (BiTNode<T>*))const
{//前序非遞歸(利用棧)遍歷二叉樹,對每個結點調用函數 visit 一次且僅一次
stack<BiTNode<T>*> s; //棧元素的類型為結點指針類型
BiTNode<T> *t = root;
s.push(NULL); //壓入空指針,作為標記
while(t != NULL)
{
visit(t);
if (t->rchild != NULL)
s.push(t->rchild); //先處理左子樹
if (t->lchild != NULL)
t = t->lchild;
else
{
t = s.top();
s.pop();
}
}
}
void InOrderTranverse(void(*visit) (BiTNode<T>*))const
{//中序非遞歸(利用棧)遍歷二叉樹,對每個結點調用 visit 一次且僅一次
stack<BiTNode<T>*> s;
BiTNode<T> *t = root;
while(t != NULL || !s.empty())
{
if (t)
{//根指針進棧,遍歷左子樹
s.push(t);
t = t->lchild;
}
else
{
t = s.top();
s.pop();
visit(t);
t = t->rchild;
}
}
cout << endl;
}
void PostOrderTraverse(void(*visit) (BiTNode<T>*))const
{//后序非遞歸(利用棧)遍歷二叉樹,對每個結點調用函數 visit 一次且僅一次
StackElem<T> se;
stack<StackElem<T> > s; //注意:兩個 > 中間要有空格,否則與右移運算符相混
BiTNode<T> *t = root;
if (t == NULL)
return;
while(!s.empty() || t != NULL)
{
while(t != NULL)
{//入棧根結點,繼續向左,找到最左結點的孩子(空指針)
se.p = t;
se.flag = Left; //表示其出棧時已訪問過左子樹
s.push(se); //入棧 se
t = t->lchild;
}
se = s.top;
s.pop();
t = se.p;
if (se.flag == NULL) //已訪問過左子樹
{
se.flag = Right; //表示其出棧時已訪問過右子樹
s.push(se);
t = t->rchild;
}
else //已訪問過右子樹
{
visit(t);
t = NULL; //置空,以繼續出棧
}
}
}
void LevelOrderTraverse(void(*visit) (BiTNode<T>*))const
{//層序遍歷二叉樹,對每個結點調用函數 visit 一次且僅一次
queue<BiTNode<T>*> q;
BiTNode<T> *a, *t = root;
if (t != NULL)
{
q.push(t);
while(!q.empty())
{
a = q.front();
q.pop();
visit(a);
if (a->lchild != NULL)
q.push(a->lchild);
if (a->rchild != NULL)
q.push(a->rchild);
}
}
cout << endl;
}
void OrderTraverse(BiTNode<T>* t, style mode, void(*visit) (BiTNode<T>*))
{//根據 mode 的值,先序、中序或后序遞歸遍歷 t 所指的二叉樹或子樹
if (mode == Pre) //先序遞歸遍歷
visit(t);
OrderTraverse(t->lchild, mode, visit);
if (mode == In) //中序遞歸遍歷
visit(t);
OrderTraverse(t->rchild, mode, visit);
if (mode == Post)
visit(t);
}
BiTNode<T>* Root()
{//返回根結點的地址
return root;
}
bool InsertChild(BiTNode<T>* &p, bool LR, BiTree<T> &c)
{//初始條件:p 指向二叉樹中某個結點,非空二叉樹 c 的右子樹為空
//操作結果:根據 LR 為 false 或 true ,插入 c 為二叉樹中 p 所指結點的左或右子樹
// p 所指結點的原有左或右子樹則成為 c 的右子樹,c 為空樹
BiTNode<T> *q = c.Root();
c.root = NULL;
if (p != NULL)
{
if (!LR) //把二叉樹 c 插為 p 所指結點的左子樹
{
q->rchild = p->lchild; //p 所指結點的原有左子樹成為 c 的右子樹
p->lchild = q; //二叉樹 c 成為 p 的左子樹
}
else //把二叉樹 c 插為 p 所指結點的右子樹
{
q->rchild = p->rchild; //p 所指結點的原有右子樹成為 c 的右子樹
p->rchild = q; //二叉樹 c 成為 p 的右子樹
}
return true;
}
return false; //p 空
}
bool DeleteChild(BiTNode<T>* &p, bool LR)
{//初始條件:p 指向二叉樹中某個結點
//操作結果:根據 LR 為 false 或 true ,刪除二叉樹中 p 所指結點的左或右子樹
if (p != NULL)
{
if (!LR) //刪除左子樹
DestroyBiTree(p->lchild);
else
DestroyBiTree(p->rchild);
return true;
}
else
return false;
}
T Value(BiTNode<T>* p)const
{//返回二叉樹中 p 所指結點的值
return p->data;
}
void Assign(BiTNode<T>* p, T Value)const
{//給二叉樹中 p 所指結點賦值為 value
p->data = value;
}
BiTNode<T>* Parent(BiTNode<T>* p)const
{//初始條件:p 指向二叉樹的某個結點
//操作結果:若 p 是二叉樹的非根結點,則返回它雙親的指針,否則返回 NULL
queue<BiTNode<T>*> q;
BiTNode<T> a;
if (root != NULL)
{
q.push(root);
while(!q.empty())
{
a = q.front();
q.pop();
if (a->lchild && a->lchild == p || a->rchild && a->rchild == p)
return a;
else //未找到 p ,則入隊其左右孩子指針(若非空)
{
if (a->lchild != NULL)
q.push(a->lchild);
if (a->rchild != NULL)
q.push(a->rchild);
}
}
}
return NULL;
}
void Child(BiTNode<T>* p, BiTNode<T>* &left, BiTNode<T>* &right)const
{//初始條件:p 指向二叉樹的某個結點
//操作結果:分別用 left 和 right 返回 p 的左右孩子指針
left = p->lchild;
right = p->rchild;
}
bool Sibling(BiTNode<T>* p, BiTNode<T>* &sib, bool &LR)const
{//初始條件:p 指向二叉樹中某個結點
//操作結果:用 sib 返回 p 的兄弟指針,LR 指示其為左(false)或右(true)兄弟
BiTNode<T> *q = Parent(p);
if (q == NULL)
return false;
if (q->lchild == p)
{
sib = q->rchild;
LR = true;
}
else
{
sib = q->lchild;
LR = false;
}
if (sib != NULL)
return true;
else
return false;
}
};
在上面的實現中,CreateBiTreeFromFile() 是由文件創建二叉樹,文件按先序次序輸入結點的值,而且還要把葉子結點的左右孩子空指針和度為 1 的結點的空指針輸入(以 # 表示)。其原因是只根據結點的先序次序還不能唯一確定二叉樹的形狀。
由于 BiTNode 結點類型只有左右孩子指針,這使得由 BiTNode 結點類型構造的二叉樹查找雙親結點和左右兄弟結點的時間復雜度較高。PBiTNode 結點類型在 BiTNode 的基礎上增加了指向雙親的指針。
//三叉鏈表結點類型結構體
template<typename T>struct PBiTNode
{
T data;
PBiTNode<T> *lchild, *rchild, *parent;
};
由于增加了指向雙親的指針,大大降低了查找雙親結點和左右兄弟結點的時間復雜度,但在創建二叉樹和向二叉樹插入子樹時要處理插入結點的雙親指針。
實現:
//三叉鏈表結構的二叉樹類
template<typename>class PBiTree: public BiTree<T>
{//帶模板并繼承二叉鏈表結構的二叉樹
public:
void CreateBiTreeFromFile(ifstream &f)
{//按先序次序由數據文件輸入二叉樹中節點的值,構造二叉樹
T e;
InputFromFile(f, e);
if (e == NULL)
return;
root = new PBiTNode<T>;
assert(root != NULL);
root->data = e;
root->parent = NULL;
PBiTree<T> left, right;
left.CreateBiTreeFromFile(f);
right.CreateBiTreeFromFile(f);
root->lchild = left.root;
if (left.root != NULL)
{
left.root->parent = root;
left.root = NULL;
}
root->rchild = right.root;
if (right.root != NULL)
{
right.root->parent = root;
right.root = NULL;
}
}
bool InsertChild(PBiTNode<T>* &p, bool LR, PBiTree<T> &c)
{//初始條件:p 指向二叉樹中某個結點,非空二叉樹 c 的右子樹為空
//操作結果:根據 LR 為 false 或 true ,插入 c 為二叉樹中 p 所指結點的左或右子樹,
// p 所指結點的原有左或右子樹則成為 c 的右子樹,c 成為空樹
PBiTNode<T> *q = c.Root();
c.root = NULL;
if (p != NULL)
{
if (!LR) //把二叉樹 c 插為 p 所指節點的左子樹
{
q->rchild = p->lchild;
p->lchild->parent = q;
p->lchild = q;
}
else //把二叉樹 c 插為 p 所指節點的右子樹
{
q->rchild = p->rchild;
p->rchild->parent = q;
p->rchild = q;
}
q->parent = p;
return true;
}
return false;
}
BiTNode<T>* Parent(BiTNode<T>* p)const
{//初始條件:p 指向二叉樹中某個結點
//操作結果:若 p 是二叉樹的非根結點,則返回它雙親的指針,否則返回 NULL
return p->parent;
}
bool Sibling(BiTNode<T>* p, BiTNode<T>* &sib, bool &LR)const
{//初始條件:p 指向二叉樹中某個結點
//操作結果:用 sib 返回 p 的兄弟指針,LR 指示其為左(false)或右(true)兄弟
if (p->parent == NULL)
return false;
if (p->parent->lchild == p)
{
sib = p->parent->rchild;
LR = true;
}
else
{
sib = p->parent->lchild;
LR = false;
}
return sib != NULL;
}
};
3. 二叉樹的遍歷
遍歷二叉樹就是按某種規則,對二叉樹的每個結點都訪問一次,而且僅訪問一次。這實際上就是將非線性的二叉樹結構線性化。遍歷二叉樹的方法有先序、中序、后序和層序,訪問順序各不相同。先序、中序和后序遍歷利用遞歸很簡單,層序遍歷可以利用隊列,先序、中序和后序非遞歸遍歷可以利用棧。
4. 線索二叉樹
為了更方便、更快捷地遍歷二叉樹,最好在二叉樹的結點上增加兩個指針,他們分別指向對二叉樹進行某種遍歷時該結點的前驅和后繼結點。這樣,從二叉樹的任一結點都可以方便地找到其遍歷的前驅和后繼結點。但這樣做大大降低了結點的存儲密度。另外,根據二叉樹的性質,有 n0 = n2 + 1 (葉子結點數 = 度為2的結點數 + 1)。空鏈域 = 2 * n0 + n1 (葉子結點有兩個空鏈域,度為 1 的結點有一個空鏈域)= n0 + n1 + n2 + 1 = n + 1 。也就是說,再由 n 個結點組成的二叉樹中,有 n+1 個指針是空指針。如果能利用這 n+1 個空指針,使它們指向結點的前驅(當左孩子指針空)或后繼(當右孩子指針空),則既可不降低結點的存儲密度,又可方便快捷地遍歷二叉樹。不過,這樣就無法區分左右孩子指針所指的到底是結點的左右孩子,還是結點的前驅后繼。為了有所區別,另增兩個域 LTag 和 RTag 。當所指的是孩子,其值為 0 (Link);當所指的是前驅后繼,其值為 1 (Thread)。這樣做,結點的存儲密度也有所降低,但不大。因為 LTag 和 RTag 分別只占兩個比特位即可。
//線索二叉鏈表結點類型結構體
enum PointerTag {Link, Thread}; //Link (0):指針,Thread (1):線索
template<typename T>struct BiThrNode
{
T data;
BiThrNode<T> *lchild, *rchild;
PointerTag LTag:2, RTag:2; //各占 2 bit
};
構造線索二叉樹和構造二叉鏈表結構的二叉樹方法相似,但除了結點的結構不同之外,它們的區別有兩點:
- 線索二叉樹比二叉鏈表結構的二叉樹多了一個頭結點,其 lchild 域指向根結點;
- 構造線索二叉樹時,若有左右孩子結點,還要給左右標志賦值 0 (Link)。
構造一棵可以線索化的二叉樹后還需要完成線索化。對于一棵給定的二叉樹,其先序、中序、后序和層序的順序是不同的。顯然其線索化的操作和遍歷的操作也是不同的。
實現:
//線索而茶鏈表結構的二叉樹類
template<typename T>class BiThrTree
{//帶模板的線索二叉鏈表結構的二叉樹
private:
BiThrNode<T> *Thrt, *pre; //Thrt 指向頭結點,pre在遍歷時始終指向剛剛訪問過的結點
void CreateBiThrTreeFromFile(ifstream &f, BiThrNode<T>* &t)
{//按先序次序由文件流 f 輸入二叉樹結點的值,構造由 t 所指的二叉樹
T e;
InputFromFile(f, e);
if (e == NULL)
t = NULL;
else
{
t = new BiThrNode<T>;
assert(t != NULL);
t->data = e;
CreateBiThrTreeFromFile(f, t->lchild);
if (t->lchild != NULL)
t->LTag = Link;
else
t->LTag = Thread;
CreateBiThrTreeFromFile(f, t->rchild);
if (t->rchild != NULL)
t->RTag = Link;
else
t->RTag = Thread;
}
}
void DestroyBiTree(BiThrNode<T>* &t)
{//~BiThrTree() 調用的遞歸函數,銷毀 t 所指二叉樹或子樹
if (t != NULL)
{
if (t->LTag == Link)
DestroyBiTree(t->lchild);
if (t->RTag == Link)
DestroyBiTree(t->rchild);
delete t;
t = NULL;
}
}
void InOrderThreading(BiThrNode<T>* p)
{//通過中序遞歸遍歷進行中序線索化,線索化后 pre 指向最后一個結點
if (p != NULL)
{
if (p->LTag == Link)
InOrderThreading(p->lchild);
else
p->lchild = pre;
if (pre->RTag == Thread)
pre->rchild = p;
pre = p;
if (p->Rtag == Link)
InOrderThreading(p->rchild);
}
}
void PreOrderThreading(BiThrNode<T>* p)
{//通過先序遞歸進行先序線索化
if (p != NULL)
{
if (pre->RTag == Thread)
pre->rchild = p;
if (p->LTag == Thread)
p->lchild = pre;
pre = p;
if (p->LTag == Link)
PreOrderThreading(p->lchild);
if (p->RTag == Link)
PreOrderThreading(p->rchild);
}
}
void PostOrderThreading(BiThrNode<T>* p)
{//通過后序遞歸遍歷進行后序線索化
if (p != NULL)
{
if (p->LTag == Link)
PostOrderThreading(p->lchild);
if (p->RTag == Link)
PostOrderThreading(p->rchild);
if (p->LTag == Thread)
p->lchild = pre;
if (pre->Rtag == Thread)
pre->rchild = p;
pre = p;
}
}
public:
BiThrTree()
{//構造函數,構造空的線索二叉樹
Thrt = new BiThrNode<T>;
assert(Thrt != NULL);
Thrt->LTag = Link; //左標志為指針
Thrt->Rtag = Thread; //右標志為線索
Thrt->rchild = Thrt->lchild = Thrt; //左右孩子指針回指
}
~BiThrTree()
{//析構函數,銷毀線索二叉樹
if (Thrt != NULL)
{
if (Thrt->lchild)
DestroyBiTree(Thrt->lchild);
delete Thrt;
}
}
void CreateBiThrTreeFromFile(char* FileName)
{//按先序次序由數據文件輸入線索二叉樹結點的值,構造線索二叉樹
ifstream fin(FileName);
CreateBiThrTreeFromFile(fin, Thrt->lchild);
fin.close();
}
void InOrderThreading()
{//中序遍歷線索二叉樹,并將其中序線索化
if (Thrt->lchild != Thrt)
{
pre = Thrt;
InOrderThreading(Thrt->lchild);
pre->rchild = Thrt;
Thrt->rchild = pre;
}
}
void InOrderTranverse(void(*visit) (BiThrNode<T>*))const
{//中序遍歷線索二叉樹
BiThrNode<T> *p = Thrt->lchild;
while(p != Thrt)
{
while(p->LTag == Link)
p = p->lchild;
visit(p);
while(p->Rtag == Thread && p->rchild != Thrt)
{//p->rchild 是線索(后繼),且不是遍歷的最后一個結點
p = p->rchild;
visit(p);
}
p = p->rchild; //若 p->rchild 不是線索(是右孩子),p 指向右孩子,返回循環,
//找到這顆子樹中序遍歷的第一個結點
}
}
void PreOrderThreading()
{//先序線索化二叉樹,頭結點的右孩子指針指向先序遍歷的最后一個結點
if (Thrt->lchild != Thrt)
{
pre = Thrt;
PreOrderThreading(Thrt->lchild);
pre->rchild = Thrt;
Thrt->rchild = pre;
}
}
void PreOrderTraverse(void(*visit) (BiThrNode<T>*))const
{//先序遍歷線索二叉樹
BiThrNode<T> *p = Thrt->lchild;
while(p != Thrt)
{
visit(p);
if (p->LTag == Link)
p = p->lchild;
else
p = p->rchild;
}
}
void PostOrderThreading()
{//后序遞歸線索化二叉樹
if (Thrt->lchild != Thrt)
{
Thrt->rchild = Thrt->lchild;
pre = Thrt;
PostOrderThreading(Thrt->lchild);
if (pre->Rtag != Link) //最后一個結點沒有右孩子
pre->rchild = Thrt; //最后一個結點的后繼指向頭結點
}
}
};
線索二叉樹和二叉鏈表結構的二叉樹相比,多了一個頭結點。其左孩子指針指向根結點,右孩子指針(線索)指向遍歷所訪問的最后一個結點;另外,它每個結點的左右孩子指針都不是空指針。在沒有孩子的情況下,分別指向該結點遍歷的前驅和后繼。
InOrderThreading() 的算法是:令數據成員 pre 總是指向遍歷的前驅結點, p 指向當前結點;在遍歷過程總,如果 p 所指結點沒有左孩子,則結點的左孩子指針指向 pre 所指結點,結點的 LTag 域的值為 Thread ;如果 p 所指結點沒有右孩子,則結點的右孩子指針指向 p ,結點的 Rtag 域的值為 Thread 。
對于中序線索二叉樹,我們能不能在找到遍歷的第一個結點后,順著右孩子指針一直找到遍歷的最后一個結點呢?這是不一定的。因為結點的右孩子指針并不一定指向后繼結點,它也可能指向右孩子,而右孩子并不一定恰好是后繼結點。
InOrderTranverse() 的算法是:當樹不空時,由樹根向左找,一直找到沒有左孩子的結點(最左結點)。這就是中序遍歷的第一個結點。若該結點沒有右孩子,則右孩子指針指向其后繼結點;否則,以其右孩子為子樹的根向左找,一直找到沒有左孩子的結點。這就是后繼結點。當結點的右孩子指針指向頭結點,遍歷結束。
先序線索化的遞歸函數 PreOrderThreading() 和后序線索化的遞歸函數 PostOrderThreading() 與中序線索化的遞歸函數 InOrderThreading() 很相像,都是利用遞歸進行線索化,只不過順序不同。
PreOrderTraverse() 的算法是:根結點是遍歷的第一個結點,如果結點有左孩子,則左孩子是其后繼;若結點沒有左孩子,則右孩子指針所指的結點是其后繼(無論該結點有沒有右孩子)。
PostOrderTraverse() 的算法是:頭結點的左右孩子指針都指向根結點,后序遍歷的第一個結點必定是葉子結點,它的左孩子指針指向頭結點,對于后序線索化二叉樹,因為根結點實在最后遍歷,所以后序遍歷的算法及時線索化也需要棧或者遞歸,故后序線索化不具有實際應用價值。
5. 二叉排序樹
實現:
//定義數據元素類型和關鍵字類型
typedef int KeyType;
struct T
{
KeyType key; //關鍵字
int others; //其他數據
};
//對兩個數值型關鍵字的比較約定如下的宏定義
#define EQ(a, b) ((a) == (b))
#define LT(a, b) ((a) < (b))
#define LQ(a, b) ((a) <= (b))
#define GT(a, b) ((a) > (b))
//二叉排序樹的刪除類
template<typename T>class BSDTree: public BiTree<T>
{//帶模板并繼承 BiTree 的二叉排序樹的刪除類
protected:
virtual void Delete(BiTNode<T>* &p)=0; //虛函數
bool DeleteBST(BiTNode<T>* &p, KeyType key)
{//若二叉排序樹 p 中存在關鍵字等于 key 的數據元素時,則刪除該數據元素結點,
//并返回 true ,否則返回 false
if (p == NULL)
return false;
else
{
if EQ(key, p->data.key)
{
Delete(p);
return true;
}
else if LT(key, p->data.key) //關鍵字小于 p 所指結點的關鍵字
return DeleteBST(p->lchild, key); //在 p 的左孩子中遞歸查找
else //關鍵字大于 p 所指結點的關鍵字
return DeleteBST(p->rchild, key); //在 p 的右孩子中遞歸查找
}
}
public:
bool Delete(KeyType key)
{//若二叉排序樹 p 中存在關鍵字等于 key 的數據元素時,則刪除該數據元素結點,
//并返回 true ,否則返回 false
return DeleteBST(root, key);
}
};
//二叉排序樹的類
template<typename T>class BSTree: public BSDTree<T>
{//帶模板并繼承 BSDTree 的二叉排序樹
private:
void Delete(BiTNode<T>* &p)
{//從二叉排序樹中刪除 p 所指結點,并重接它的左或右子樹
BiTNode<T> *s, *q = p;
if (p->rchild == NULL)
{//p 的右子樹空則只須重接它的左子樹(待刪結點是葉子也走此分支)
p = p->lchild;
delete q;
}
else if (p->lchild = NULL)
{//p 的左子樹空,只須重接它的右子樹
p = p->rchild;
delete q;
}
else //p 的左右子樹均不空
{
s = p->lchild;
while(s->rchild != NULL)
{
q = s;
s = s->rchild;
}//s 向右走到盡頭(s 指向待刪結點的前驅結點,q 指向 s 的雙親結點)
p->data = s->data;
if (q != p) //情況一:待刪結點的左孩子有右子樹
q->rchild = s->lchild;
else //情況二:待刪結點的左孩子沒有右子樹
q->lchild = s->lchild;
delete s;
}
}
protected:
bool SearchBST(BiTNode<T>* &p, KeyType key, BiTNode<T>* f, BiTNode<T>* &q)
{//在二叉排序樹 p 中遞歸查找其關鍵字等于 key 的數據元素,若查找成功,
//則指針 q 指向該數據元素結點,并返回 true ,否則指針 q 指向查找路徑上訪問的最后
//一個結點(以便插入),并返回 false ,指針 f 指向 p 的雙親,其初始調用值為 NULL
if (p == NULL)
{
q = f;
return false;
}
else if EQ(key, p->data.key)
{
q = p;
return true;
}
else if LT(key, p->data.key) //小于
return SearchBST(p->lchild, key, p, q); //遞歸查找左子樹
else //大于
return SearchBST(p->rchild, key, p, q); //遞歸查找右子樹
}
public:
BiTNode<T>* SearchBST(BiTNode<T>* p, KeyType key)const
{//在指針 p 所指二叉排序樹或平衡二叉樹中遞歸查找關鍵字等于 key 的數據元素,
//成功返回結點指針,失敗返回空指針
if (p == NULL || EQ(key, p->data.key))
return p;
else if LT(key, p->data.key) //小于
return SearchBST(p->lchild, key);
else //大于
return SearchBST(p->rchild, key);
}
bool Insert(T e)
{//若二叉排序樹中沒有關鍵字等于 e.key 的元素,插入 e 并返回 true ;否則返回 false
BiTNode<T> *p, *s;
if (!SearchBST(root, e.key, NULL, p))
{//查找不成功,p 指向查找路徑上訪問的最后一個葉子結點
s = new BiTNode<T>;
s->data = e;
s->lchild = s->rchild = NULL;
if (p == NULL)
root = s;
else if LT(e.key, p->data.key) //小于
p->lchild = s;
else
p->rchild = s;
return true;
}
else //查找成功
return false;
}
void CreateBiTreeFromFile(char* FileName)
{//覆蓋基類函數,避免構造出不符合二叉排序順序的二叉樹
}
void InsertChild(BiTNode<T>* &p, bool LR, BiTree<T> &c)
{//覆蓋基類函數
return false;
}
void Assign(BiTNode<T>* p, T value)
{//覆蓋基類函數
}
};
二叉排序樹中任何一個結點,其左子樹上所有結點的關鍵字值均小于該結點的關鍵字值;其右子樹所有結點的關鍵字值均大于該結點的關鍵字值。中序遍歷二叉排序樹可得到按關鍵字有序的序列。通過中序和先序(或中序和后序)遍歷二叉排序樹,就可以確定二叉排序樹的形態。
在二叉排序樹中插入一個結點,這個結點總是葉子結點。刪除一個結點從算法上分有待刪除結點最多一棵子樹和待刪除結點有兩棵子樹兩種情況。
- 如果待刪除結點最多有一棵子樹,則待刪結點與它的雙親結點最后存在的唯一的孩子結點形成單鏈表結構。只要將其雙親結點原來指向其的指針指向其唯一的孩子結點(若待刪結點是葉子結點,則指空),就從二叉排序樹中將其刪除了,同時仍然保持二叉排序樹的有序性。
- 如果待刪結點的兩棵子樹都存存在,刪除其需重接兩棵子樹,是比較麻煩的。故采取變通的方法:查找待刪結點的前驅結點,這個結點是待刪結點左子樹的最右結點,它沒有右子樹。把這個結點的值賦給待刪結點,相當于刪除了待刪結點,但卻在同一位置增加了一個前驅結點。這樣就有了兩個相鄰的、值與前驅結點相同的結點。再把原來的前驅結點刪除即可。原來的前驅結點最多有一棵子樹,刪除它是很容易的。這樣做仍保持了二叉排序樹的有序性。當然,對稱的,也可以利用待刪結點的后繼結點(它是待刪結點右子樹的最左結點)來處理這個問題。
二叉排序樹的形態與數據元素插入的順序有關。如果數據輸入的順序不當,二叉排序樹的深度可能很深,導致平均查找長度很長,失去了二叉排序樹存在的意義。平衡二叉樹(也是排序樹)克服了二叉排序樹的這個缺點,它通過當左右子樹的深度差大于 1 時調換根結點,使樹的深度盡量淺,同時仍保持排序特性。
6. 平衡二叉樹
實現:
//平衡二叉樹的結點類型結構體
template<typename T>struct AVLTNode
{
T data;
int bf; //結點的平衡因子
AVLTNode<T> *lchild, *rchild;
};
//設置平衡因子的值(左子樹的深度 - 右子樹的深度)
const int LH = 1; //左高
const int EH = 0; //等高
const int RH = -1; //右高
//二叉排序樹轉換操作的類
template<typename T>class ChangeTree: public BSTree<T>
{//帶模板并繼承 BSTree 的二叉排序樹轉換操作的類
protected:
void LL_Rotate(BiTNode<T>* &p)
{//對 *p 為根的平衡二叉樹的 LL 型失衡作處理平衡,但不修改平衡因子
BiTNode<T> *lc = p->lchild; //lc 指向中值結點
p->lchild = lc->rchild; //大值結點的新左孩子為中值結點的右孩子
lc->rchild = p; //中值結點的新右孩子為大值結點
p = lc; //p 指向中值結點(新的根結點)
}
void RR_Rotate(BiTNode<T>* &p)
{//對 *p 為根的平衡二叉樹的 RR 型失衡作處理平衡,但不修改平衡因子
BiTNode<T> *rc = p->rchild; //rc 指向中值結點
p->rchild = rc->lchild; //小值結點的新右孩子為中值結點的左孩子
rc->lchild = p; //中值結點的新左孩子為小值結點
p = rc; //p 指向中值結點(新的根結點)
}
void LR_Rotate(BiTNode<T>* &p)
{//對 *p 為根的平衡二叉樹的 LR 型失衡作處理平衡,但不修改平衡因子
BiTNode<T> *lc = p->lchild; //lc 指向小值結點
p->lchild = lc->rchild->rchild; //中值結點的右子樹成為大值結點的左子樹
lc->rchild->rchild = p; //大值結點成為中值結點的右子樹
p = lc->rchild; //根結點指向中值結點
lc->rchild = p->lchild; //中值結點的左子樹成為小值結點的右子樹
p->lchild = lc; //小值結點成為中值結點的左子樹
}
void RL_Rotate(BiTNode<T>* &p)
{//對 *p 為根的平衡二叉樹的 RL 型失衡作處理平衡,但不修改平衡因子
BiTNode<T> *rc = p->rchild; //rc 指向大值結點
p->rchild = rc->lchild->lchild; //中值結點的左子樹成為小值結點的右子樹
rc->lchild->lchild = p; //小值結點成為中值結點的左子樹
p = rc->lchild; //根指針指向中值結點
rc->lchild = p->rchild; //中值結點的右子樹成為大值結點的左子樹
p->rchild = rc; //大值結點成為中值結點的右子樹
}
BiTNode<T>* Parent(BiTNode<T>* p)const
{//覆蓋基類函數
if (root == p)
return NULL;
BiTNode<T> *q = root;
while(q != NULL)
{
if (p->data.key > p->data.key) //p 在左子樹中
if (q->lchild == p)
return q;
else
q = q->lchild;
else //p 在右子樹
if (q->rchild == p)
return q;
else
q = q->rchild;
}
return NULL;
}
BiTNode<T>* Grandfather(BiTNode<T>* p)const
{//返回 p 的祖父結點,否則返回 NULL
BiTNode<T> *a = Parent(p);
if (a != NULL)
a = Parent(a);
return a;
}
};
//平衡二叉樹的類
template<typename T>class AVLTree: public ChangeTree<T>
{//帶模板并繼承 ChangeTree 的平衡二叉樹類
private:
void LeftBalance(AVLTNode<T>* &p)
{//初始條件:原本 p 的左子樹比右子樹高(LH),又在左子樹中插入了結點,導致失衡
//對不平衡的樹 p 做平衡處理,p 的返回值指向新的平衡二叉樹根結點
AVLTNode<T> *lc, *rd;
lc = p->lchild;
switch(lc->bf) //檢查 *p 左子樹的平衡度,并作相應平衡處理
{
case LH: //LL 型不平衡
p->bf = lc->bf = EH; //旋轉后,大值結點的平衡因子都為 EH
LL_Rotate(p);
break;
case RH: //LR 型不平衡
rd = lc_>rchild;
switch(rd->bf) //檢查*p左孩子的右子樹的平衡度,修改 *p 及其左孩子的平衡因子
{
case LH: //新結點插入在 *p 左孩子的右子樹的左子樹上
p->bf = RH; //旋轉后,大值結點的平衡因子為右高
lc->bf = EH; //旋轉后,小值結點的平衡因子為等高
break;
case EH: //新結點插入在 *p 左孩子的右孩子(葉子)上
p->bf = lc->bf = EH; //旋轉后,原根和左孩子結點的平衡因子都為等高
break;
case RH: //新結點插入在 *p 左孩子的右子樹的右子樹上
p->bf = EH; //旋轉后,大值結點的平衡因子為等高
lc->bf = LH; //旋轉后,小值結點的平衡因子為左高
}
rd->bf = EH; //旋轉后的新根結點(中值結點)的平衡因子為等高
LR_Rotate(p);
}
}
void RightBalance(AVLTNode<T>* &p)
{//初始條件:原本 p 的右子樹比左子樹高(RH),又在右子樹中插入了結點,導致失衡
//對不平衡的樹 p 做平衡處理,p 的返回值指向新的平衡二叉樹根結點
AVLTNode<T> *rc, *ld;
rc = p->rchild;
switch(rc->bf) //檢查 *p 的右子樹的平衡度,并作相應平衡處理
{
case RH: //RR 型不平衡
p->bf = rc->bf = EH; //旋轉后,小值結點和中值結點的平衡因子都是 EH
RR_Rotate(p);
break;
case LH: //RL 型不平衡
ld = rc->lch;
switch(ld->bf) //檢查*p右孩子的左子樹的平衡度,修改 *p 及其右孩子的平衡因子
{
case RH: //新結點插入在 *p 右孩子的左子樹的右子樹上
p->bf = LH; //旋轉后,小值結點的平衡因子為左高
rc->bf = EH; //旋轉后,大值結點的平衡因子為等高
break;
case EH: //新結點插入為 *p 右孩子的左孩子(葉子)
p->bf = rc->bf = EH; //旋轉后,原根和右孩子結點的平衡因子都為等高
break;
case LH: //新結點插入在 *p 右孩子的左子樹的左子樹上
p->bf = EH; //旋轉后,小值結點的平衡因子為等高
rc->bf = RH; //旋轉后,大值結點的平衡因子為右高
}
ld->bf = EH; //旋轉后的新根結點(中值結點)的平衡因子為等高
RL_Rotate(p);
}
}
bool InsertAVL(AVLTNode<T>* &p, T e, bool &taller)
{//若在平衡二叉樹 p 中不存在和 e 有相同關鍵字的結點,則插入一個數據元素為 e 的新
//結點,并返回 true ,否則返回 false 。若因插入使平衡二叉樹 p 失衡,則做平衡旋轉
//處理,taller 反映調用 InsertAVL() 前后 p 是否長高
if (p == NULL) //樹空
{//插入新結點,樹 “長高” ,置 taller 為 true
p = new AVLTNode<T>;
p->data = e;
p->lchild = p->rchild = NULL;
p->bf = EH;
taller = true;
}
else //樹非空
{
if EQ(e.key, p->data.key)
return false;
else if LT(e.key, p->data.key) //小于
{
if (!InsertAVL(p->lchild, e, taller))
return false; //沒插入
if (taller) //插入且左子樹 “長高”
switch(p->bf) //檢查 *p 的平衡度,并作適當處理
{
case LH: //原本 p 的左子樹比右子樹高,現在左子樹又 “長高” 了
LeftBalance(p);
taller = false;
break;
case EH: //原本左右子樹等高,現在左子樹 “長高” 了
p->bf = LH;
taller = true;
break;
case RH: //原本 p 的右子樹比左子樹高,現在左右子樹等高
p->bf = EH;
taller = false;
}
}
else //大于
{
if (!InsertAVL(p->rchild, e, taller))
return false; //未插入
if (taller) //已插入
switch(p->bf) //檢查 p 的平衡度
{
case LH: //原本 p 的左子樹比右子樹高,現在左右子樹等高
p->bf = EH;
taller = false;
break;
case EH: //原本 p 的左右子樹等高,現在右子樹 “長高” 了
p->bf = RH;
taller = true;
break;
case RH: //原本右子樹比左子樹高,現在右子樹又 “長高” 了
RightBalance(p);
taller = false;
}
}
}
return true;
}
bool DeleteAVL(AVLTNode<T>* &p, T &e, bool &lower)
{//若在 AVL 樹 p 中存在和 e 相同關鍵字的結點,則刪除該結點,并返回 true,
//e 返回刪除的結點,否則返回 false 。若因刪除而使 AVL 樹 p 失衡,則做平衡旋轉
//處理,lower 反映在調用 DeleteAVL() 前后 p 是否降低
AVLTNode<T> *rc, *lc;
T e1;
if (p == NULL)
return false;
else
{
if EQ(e.key, p->data.key) //相等
{
e = p->data;
rc = p;
if (p->lchild != NULL && p->rchild != NULL)
{//p 所指結點的度為 2 (左右孩子均有),找前驅或后繼結點代替刪除
if (p->bf == RH) //右子樹更高,找后繼
{
lc = p->rchild;
while(lc->lchild)
lc = lc->lchild;
}
else //左子樹更高,找前驅
{
lc = p->lchild;
while(lc->rchild)
lc = lc->rchild;
}
e1 = lc->data;
DeleteAVL(p, e1, lower);
rc->data = e1;
}
else //p 所指結點的度為 1 或 0
{
if (p->rchild == NULL)
p = p->lchild;
else
p = p->rchild;
delete rc;
lower = true;
}
}
else if LT(e.key, p->data.key) //大于
{
if (!DeleteAVL(p->lchild, e, lower))
return false; //沒刪除
if (lower) //刪除導致子樹降低
{
switch(p->bf) //對 p 所指結點平衡因子分類分析
{
case EH: //原來等高,現在左子樹有了刪除
p->bf = RH;
lower = false;
break;
case LH: //原來左高,現在左子樹有了刪除
p->bf = EH;
lower = true;
break;
case RH: //原來右高,現在左子樹有了刪除,失衡
D_LeftBalance(p, lower);
}
}
}
else //小于
{
if (!DeleteAVL(p->rchild, e, lower))
return false; //未刪除
if (lower) //刪除導致子樹降低
{
switch(p->bf) //對 p 所指結點的平衡因子分類分析
{
case EH: //原來等高,現在右子樹有了刪除
p->bf = LH;
lower = false;
break;
case RH: //原來右高,現在右子樹有了刪除
p->bf = EH;
lower = true;
break;
case LH: //原來左高,現在右子樹有了刪除,失衡
D_RightBalance(p, lower);
}
}
}
return true;
}
}
void D_LeftBalance(AVLTNode<T>* &p, bool &lower)
{//刪除結點調用的左失衡處理函數
AVLTNode<T> *ld, *rc = p->rchild;
switch(rc->bf)
{
case EH: //rc 左右子樹等高
rc->bf = LH;
p->bf = RH;
RR_Rotate(p);
lower = false;
break;
case RH: //rc 的右子樹高于左子樹
p->bf = rc->bf = EH;
RR_Rotate(p);
lower = true;
break;
case LH: //rc 的左子樹高于右子樹
ld = rc->lchild;
switch(ld->bf)
{
case EH: //ld 左右子樹等高
p->bf = rc->bf = EH;
break;
case LH: //ld 的左子樹高
p->bf = EH;
rc->bf = RH;
break;
case RH: //ld 的右子樹高
p->bf = LH;
rc->bf = EH;
}
ld->bf = EH;
RL_Rotate(p);
lower = true;
}
}
void D_RightBalance(AVLTNode<T>* &p)
{//刪除結點調用的右失衡處理函數
AVLTNode<T> *rd, *lc = p->lchild;
switch(lc->bf)
{
case EH: //左右子樹
lc->bf = RH;
p->bf = LH;
LL_Rotate(p);
lower = false;
break;
case LH: //左子樹高于右子樹
p->bf = lc->bf = EH;
LL_Rotate(p);
lower = true;
break;
case RH: //右子樹高于左子樹
rd = lc->rchild;
switch(rd->bf)
{
case EH: //rd 的左右子樹等高
p->bf = lc->bf = EH;
break;
case RH: //rd 的右子樹高
p->bf = EH;
lc->bf = LH;
break;
case LH: //rd 的左子樹高
p->bf = RH;
lc->bf = EH;
}
rd->bf = EH;
LR_Rotate(p);
lower = true;
}
}
public:
bool Insert(T e)
{//若在平衡二叉排序樹中不存在和 e 有相同關鍵字的結點,則插入一個數據元素為 e 的
//新結點,并返回 true ;否則返回 false 。若因插入失衡,則作平衡旋轉處理
bool taller;
return InsertAVL(root, e, taller);
}
bool Delete(T &e)
{//在平衡二叉排序樹中刪除結點,成功返回 true ,否則返回 false 。若因刪除造成失衡
//則作平衡旋轉處理
bool lower;
return DeleteAVL(root, e, lower);
}
bool DeleteChild(AVLTNode<T>* &p, int LR)
{//覆蓋基類函數,避免由于刪除子樹導致失衡
return false;
}
};
插入結點導致平衡二叉樹失衡只有 4 中情況:
- 在左子樹的左孩子分支上插入結點導致失衡,稱為 LL 型
- 在左子樹的右孩子分支上插入結點導致失衡,稱為 LR 型
- 在右子樹的左孩子分支上插入結點導致失衡,稱為 RR 型
- 在右子樹的右孩子分支上插入結點導致失衡,稱為 RL 型
與插入結點不同的是,刪除結點有可能要作多次平衡旋轉處理。
7. 紅黑樹
紅黑樹也是一種平衡二叉樹,紅黑樹中任何一個結點的左右子樹的 “黑色深度” 是相同的,且不允許出現父子都是紅色結點的情況。。所以紅黑樹左右子樹的高度差之比不會大于 2 。它的平衡性不如 AVL 樹,但它的插入、刪除算法比 AVL 樹易于實現。因此是一種較好的排序二叉樹結構。AVL 樹和紅黑樹的高度都為 O(logn),其中 n 是樹的結點數。
實現:
//紅黑樹的結點類型結構體
enum Color {Red, Black};
template<typename T>struct RBTNode
{
T data;
Color RB; //結點顏色
RBTNode<T> *lchild, *rchild;
};
//紅黑樹的類
template<typename T>class RBTree: public ChangeTree<T>
{//帶模板并繼承 ChangeTree 的紅黑樹類
private:
RBTNode<T>* Uncle(RBTNode<T>* p)const
{//返回 p 的叔叔結點,否則返回 NULL
RBTNode<T> *g, *l, *r, *a = Parent(p);
if (p != NULL)
{
g = Parent(a);
Child(g, l, r); //l 和 r 分別指向 g 的左右孩子
if (l == a)
return r;
else
return l;
}
return NULL;
}
void AdjustDoubleRed(RBTNode<T>* &s, RBTNode<T>* &p)
{//對 *s 和 *s 的雙親結點 *p 作遞歸雙紅調整
RBTNode<T> *u, *g, *gp;
int flag;
u = Uncle(s);
g = Grandfather(s);
if (g == root)
flag = 0;
else
{
gp = Parent(g);
if (g->data.key < gp->data.key)
flag = 1; //g 是 gp 的左孩子
else
flag = 2; //g 是 gp 的右孩子
}
if (u == NULL || u->RB == Black)
{
if (g->data.key > p->data.key)
if (p->data.key > s->data.key)
LL_Rotate(g);
else
LR_Rotate(g);
else
if (p->data.key < s->data.key)
RR_Rotate(g);
else
RL_Rotate(g);
g->RB = Black;
g->lchild->RB = g->rchild->RB = Red;
switch(flag)
{
case 0: root = g; break; //更新根結點
case 1: gp->lchild = g; break; //重接 *g 子樹
case 2: gp->rchild = g; //重接 *g 子樹
}
}
else
{
p->RB = u->RB = Black; //設置雙親結點和叔叔結點為黑色
if (flag > 0) //祖父結點不是根結點
{
g->RB = Red; //設置祖父結點為紅色
u = Parent(g); //u 為祖父結點的雙親結點
if (u->RB == Red) //出現雙紅色問題
AdjustDoubleRed(g, u); //向上遞歸作雙紅調整
}
}
}
void AdjustDoubleBlack(RBTNode<T>* &pa, bool lr)
{//由于 *pa 的左(lr = true)或右(lr = false)子樹的黑色深度減 1 ,作雙黑調整
RBTNode<T> *gp;
int flag;
if (pa == root)
flag = 0;
else
{
gp = Parent(pa);
if (pa->data.key < gp->data.key)
flag = 1;
else
flag = 2;
}
if (lr) //*pa 的左孩子被刪除
{
if (pa->lchild != NULL && pa->lchild->RB == Red) //*pa的新左孩子存在且紅
pa->lchild->RB = Black; //設置 *pa 的新左孩子為黑色
else //*pa 的新左孩子不存在或是黑色
{
if (pa->rchild != NULL && pa->rchild->RB == Black) //*pa的右孩子是黑色
{
if (pa->rchild->rchild != NULL && pa->rchild->rchild->RB == Red)
{//*pa 的右孩子的右孩子是紅色
pa->rchild->RB = pa->RB; //*pa 的右孩子顏色同 *pa 的(新根)
pa->RB = pa->rchild->rchild->RB = Black;
RR_Rotate(pa);
switch(flag)
{
case 0: root = pa; break;
case 1: gp->lchild = pa; break;
case 2: gp->rchild = pa;
}
}
else if (pa->rchild->lchild != NULL && pa->rchild->lchild->RB==Red)
{
pa->rchild->lchild->RB = pa->RB;
pa->RB = pa->rchild->RB = Black;
RL_Rotate(pa);
switch(flag)
{
case 0: root = pa; break;
case 1: gp->lchild = pa; break;
case 2: gp->rchild = pa;
}
}
else //*pa 的右孩子的左右孩子都是黑色或空
{
pa->rchild->RB = Red;
if (pa->RB == Red)
pa->RB = Black;
else
{
switch(flag)
{
case 1: AdjustDoubleBlack(gp, true); break;
case 2: AdjustDoubleBlack(gp, false);
}
}
}
}
else if (pa->rchild != NULL && pa->rchild->RB == Red)
{
pa->RB = Red;
pa->rchild->RB = Black;
RR_Rotate(pa);
switch(flag)
{
case 0: root = pa; break;
case 1: gp->lchild = pa; break;
case 2: gp->rchild = pa;
}
AdjustDoubleBlack(pa->lchild, true);
}
}
}
else //*pa 的右孩子被刪除
{
if (pa->rchild != NULL && pa->rchild->Rb == Red)
pa->rchild->RB = Black;
else
{
if (pa->lchild->lchild != NULL && pa->lchild->lchild->RB == Red)
{
pa->lchild->RB = pa->RB;
pa->RB = pa->lchild->lchild->RB = Black;
LL_Rotate(pa);
switch(flag)
{
case 0: root = pa; break;
case 1: gp->lchild = pa; break;
case 2: gp->rchild = pa;
}
}
else if (pa->lchild->rchild != NULL && pa->lchild->rchild->RB == Red)
{
pa->lchild->rchild->RB = pa->RB;
pa->RB = pa->lchild->RB = Black;
LR_Rotate(pa);
switch(flag)
{
case 0: root = pa;
case 1: gp->lchild = pa; break;
case 2: gp->rchild = pa;
}
}
else //*pa 的左孩子的左右孩子都是黑色或空
{
pa->lchild->RB = Red;
if (pa->RB == Red)
pa->Rb = Black;
else
switch(flag)
{
case 1: AdjustDoubleBlack(gp, true); break;
case 2: AdjustDoubleBlack(gp, false);
}
}
}
else if (pa->lchild != NULL && pa->lchild->RB == Red)
{
pa->RB = Red;
pa->lchild->RB = Black;
LL_Rotate(pa);
switch(flag)
{
case 0: root = pa; break;
case 1: gp->lchild = pa; break;
case 2: gp->rchild = pa;
}
AdjustDoubleBlack(pa->rchild, false);
}
}
}
void Delete(RBTNode<T>* &p)
{//從紅黑樹中刪除 p 所指結點,并重接它的左或右子樹
RBTNode<T> *pa, *s, *q = p;
bool lr, deleflag = false; //調用雙黑調整函數的標志,初始為 false (不調用)
bool rootflag = (p == root);
if (p->rchild == NULL || p->lchild == NULL)
{
if (p->RB == Black && p != root)
{
deleflag = true;
pa = Parent(p);
if (p->data.key < pa->data.key)
lr = true;
else
lr = false;
}
if (p->rchild == NULL)
p = p->lchild;
else
p = p->rchild;
if (rootflag && p != NULL)
p->RB = Black;
delete q;
}
else
{
s = p->lchild;
while(s->rchild != NULL)
{
q = s;
s = s->rchild;
}
if (s->RB == Black)
{
deleflag = true;
pa = Parent(s);
if (s->data.key < pa.data.key)
lr = true;
else
lr = false;
}
p->data = s->data;
if (q != p)
q->rchild = s->lchild;
else
q->lchild = s->lchild;
delete s;
}
if (deleflag)
AdjustDoubleBlack(pa, lr);
}
public:
bool Insert(T e)
{//若紅黑樹中沒有關鍵字等于 e.key 的元素,輸入 e 并返回 true ;否則返回 false
RBTNode<T> *p, *s;
if (!SearchBST(root, e.key, NULL, p))
{
s = new RBTNode<T>;
s->data = e;
s->lchild = s->rchild = NULL;
if (p == NULL)
{
root = s;
s->RB = Black;
}
else
{
if LT(e.key, p->data.key) //小于
p->lchild = s;
else
p->rchild = s;
s->RB = Red;
if (p->RB == Red)
AdjustDoubleRed(s, p);
}
return true;
}
else //查找成功
return false;
}
};
8. 伸展樹
伸展樹也是一種排序二叉樹。它并不限制樹的左右子樹高度差在較低的范圍。事實上,有時它還會形成單支樹。它通過伸展操作把剛剛插入或查找的結點旋轉到根結點的位置。如果查找失敗,伸展查找終止處的葉子結點。刪除結點后,伸展實際被刪結點的父結點。伸展樹把訪問最頻繁的結點聚集在距離樹根較近的位置,從而使得平均查找次數較低。所以,伸展樹使用與結點的查找頻率差別較大的情況。
實現:
//伸展樹的類
template<typename T>class SqTree: public ChangeTree<T>
{//帶模板并繼承 ChangeTree 的伸展樹類
private:
void Zig(BiTNode<T>* p)
{//將結點 p 伸展為根結點
if (p->data.key < root->data.key)
{
root->lchild = p->rchild;
p->rchild = root;
}
else
{
root->rchild = p->lchild;
p->lchild = root;
}
root = p;
}
void ZigZig(BiTNode<T>* p, BiTNode<T>* pp, BiTNode<T>* pg)
{//將結點 p 做一次同向或異向偏轉,使其深度減少 2 層,pp 是 p 的雙親,pg是p的祖父
BiTNode<T> *r = Parent(pg);
if (pp->data.key < pg->data.key)
{
if (p->data.key < pp->data.key)
{//同向偏轉,p 在雙親和祖父的左邊
pp->lchild = p->rchild;
pg->lchild = pp->rchild;
pp->rchild = pg;
p->rchild = pp;
}
else
{//異向偏轉
pp->rchild = p->lchild;
pg->lchild = p->rchild;
p->lchild = pp;
p->rchild = pg;
}
}
else
{
if (p->data.key > pp->data.key)
{//同向偏轉,p 在雙親和祖父的右邊
pp->rchild = p->lchild;
pg->rchild = pp->lchild;
pp->lchild = pg;
p->lchild = pp;
}
else
{//異向偏轉
pp->lchild = p->rchild;
pg->rchild = p->lchild;
p->rchild = pp;
p->lchild = pg;
}
}
if (r == NULL)
root = p;
else if (r->data.key > p->data.key)
r->lchild = p;
else
r->rchild = p;
}
void Splay(BiTNode<T>* p)
{//伸展 p 所指結點
BiTNode<T> *pg, *pp;
if (p != root && p != NULL)
{
pp = Parent(p);
pg = Grandfather(p);
if (pg == NULL)
Zig(p);
else
{
ZigZig(p, pp, pg);
Splay(p);
}
}
}
void Delete(BiTNode<T>* &p)
{//從二叉樹中刪除 p 所指結點,并重接它的左或右子樹
BiTNode<T> *s = Parent(p), *q = p;
if (p->rchild == NULL)
{
p = p->lchild;
delete q;
}
else if (p->lchild == NULL)
{
p = p->rchild;
delete q;
}
else
{
s = p->lchild;
while(s->rchild != NULL)
{
q = s;
s = s->rchild;
}
p->data = s->data;
if (q != p) //待刪結點的左孩子有右子樹
q->rchild = s->lchild;
else //待刪結點的左孩子沒有右子樹
q->lchild = s->lchild;
delete s;
s = q;
}
Splay(s);
}
public:
bool Insert(T e)
{//若伸展樹中沒有關鍵字等于 e.key 的元素,就插入并返回 true ;否則返回 false
BiTNode<T> *p, *s;
if (!BSTree::SearchBST(root, e.key, NULL, p))
{
s = new BiTNode<T>;
s->data = e;
s->lchild = s->rchild = NULL;
if (p == NULL)
root = s;
else if LT(e.key, p->data.key)
{
p->lchild = s;
Splay(p->lchild);
}
else
{
p->rchild = s;
Splay(p->rchild);
}
return true;
}
else //查找成功
return false;
}
bool Search(KeyType key)
{//在伸展樹中查找關鍵字等于 key 的數據元素,若查找成功,則將該數據元素結點伸展為
//根結點,并返回 true ,否則將查找路徑上訪問的最后一個結點伸展為根結點并返回false
BiTNode<T> *p;
bool f = BSTree<T>::SearchBST(root, key, NULL, p);
Splay(p);
return f;
}
};
9. 樹的存儲結構
二叉樹是最簡單的樹,還有多于二叉的樹,多于二叉的樹統稱為樹。一棵樹無論有多少叉,它最多有一個長子和一個排序恰好在其下的兄弟。孩子-兄弟二叉鏈表結構的樹就是根據這樣的定義,把每個結點的結構都統一到了二叉鏈表結構上。這樣有利于對結點進行操作。
在數據結構中,多于一棵樹的情況稱為 “森林” 。孩子-兄弟二叉鏈表結構也可以存儲森林。根結點為第一棵樹的根,其他樹的根結點作為第一棵樹根結點的兄弟。
實現:
//孩子-兄弟二叉鏈表結點類型結構體
template<typename T>struct CSNode
{
T data;
CSNode<T> *firstchild, *nextsibling; //長子,下一個兄弟的指針
};
//孩子-兄弟二叉鏈表結構的樹或森林類
template<typename T>class CSTree
{//帶模板的孩子-兄弟二叉鏈表結構的樹或森林類
friend DFST<struct V, struct A>; //設置深度優先生成樹類的實例為友類
private:
CSNode<T> *root;
void ClearTree(CSNode<T>* &t)
{//清空 t 所指樹或森林
if (t != NULL)
{
ClearTree(t->firstchild);
ClearTree(t->nextsibling);
delete t;
t = NULL;
}
}
int TreeDepth(CSNode<T> *t)const
{//返回樹 t 的深度
CSNode<T> *p;
int depth, max = 0;
if (t == NULL)
return 0;
for(p = t->firstchild; p != NULL; p = p->nextsibling)
{//對于樹 t 根結點的所有孩子結點,求子樹深度最大值
depth = TreeDepth(p);
if (depth > max)
max = depth;
}
return max+1;
}
void PreOrderTraverse(CSNode<T>* t, void(*visit) (CSNode<T>*))const
{//先序遞歸遍歷
if (t != NULL)
{
visit(t);
PreOrderTraverse(t->firstchild, visit);
PreOrderTraverse(t->nextsibling, visit);
}
}
void PostOrderTraverse(CSNode<T>* t, void(*visit) (CSNode<T>*))const
{//后序遞歸遍歷
if (t != NULL)
{
PostOrderTraverse(t->firstchild, visit);
visit(t);
PostOrderTraverse(t->nextsibling, visit);
}
}
public:
CSTree()
{//構造函數,構造空樹或森林
root = NULL;
}
~CSTree()
{//析構函數,清空樹或森林
ClearTree();
}
void ClearTree()
{//清空樹或森林
ClearTree(root); //遞歸清空
}
void CreateTreeFromFile(char *FileName)
{//根據文件安層序構造樹或森林
ifstream fin(FileName);
CSNode<T> *p;
queue<CSNode<T>*> q;
int i, m;
fin >> m; //樹的棵數
root = new CSNode<T>;
fin >> root->data;
q.push(root);
p = root;
for(i = 1; i < m; i++) //對除第一棵樹之外的其他樹
{
p->nextsibling = new CSNode<T>;
p = p->nextsibling;
fin >> p->data;
q.push(p);
}
p->nextsibling = NULL;
while(!q.empty())
{
p = q.front();
q.pop();
fin >> m; //輸入 p 所指結點的孩子數
if (m > 0) //p 所指結點有孩子
{
p = p->firstchild = new CSNode<T>;
fin >> p->data;
q.push(p);
for(i = 1; i < m; i++)
{
p->nextsibling = new CSNode<T>;
p = p->nextsibling;
fin >> p->data;
q.push(p);
}
p->nextsibling = NULL;
}
else //沒孩子
p->firstchild = NULL;
}
fin.close();
}
bool TreeEmpty()const
{//判空
if (root == NULL)
return true;
else
return false;
}
int TreeDepth()const
{//返回樹或森林的深度(森林中所有樹中深度的最大值)
CSNode<T> *p;
p = root;
int dep, max = TreeDepth(p);
if (p != NULL)
p = p->nextsibling;
while(p != NULL)
{
dep = TreeDepth(p);
if (dep > max)
max = dep;
p = p->nextsibling;
}
return max;
}
CSNode<T>* Point(T s)const
{//返回樹或森林中指向元素值為 s 的結點的指針
CSNode<T> *p;
queue<CSNode<T>*> q;
if (root)
{
q.push(root);
while(!q.empty())
{
p = q.front();
q.pop();
if (p->data == s)
return p;
if (p->firstchild)
q.push(p->firstchild);
if (p->nextsibling)
q.push(p->nextsibling);
}
}
return NULL;
}
T Value(CSNode<T> *p)const
{//返回 p 所指結點的值
if (p != NULL)
return p->data;
else
return Nil; //返回空結點
}
void PreOrderTraverse(void(*visit) (CSNode<T>*))const
{//先序遍歷
PreOrderTraverse(root, visit);
}
void PostOrderTraverse(void(*visit) (CSNode<T>*))const
{//后序遍歷
PostOrderTraverse(root, visit);
}
void LevelOrderTraverse(void(*visit) (CSNode<T>*))const
{//層序遍歷
CSNode<T> *p = root;
queue<CSNode<T>*> q;
if (p != NULL)
{
while(p != NULL)
{
visit(p);
q.push(p);
p = p->nextsibling;
}
while(!q.empty())
{
p = q.front();
q.pop();
if (p->firstchild)
{
p = p->firstchild;
visit(p);
q.push(p);
while(p->nextsibling)
{
p = p_>nextsibling;
visit(p);
q.push(p);
}
}
}
}
}
CSNode<T>* Root()const
{//返回根結點指針
return root;
}
void Assign(CSNode<T>* p, T value)const
{//給 p 所指結點賦值為 value
if (p != NULL)
p->data = value;
}
T Parent(T e)const
{//返回 e 的雙親的值,否則 Nil
CSNode<T> *t, *p = root;
queue<CSNode<T>*> q;
if (p != NULL)
{
while(p != NULL)
if (Value(p) == e) //e 為樹的根結點
return Nil;
else
{
q.push(p);
p = p->nextsibling;
}
while(!q.empty())
{
p = q.front();
q.pop();
if (p->firstchild)
{
if (p->firstchild->data == e)
return p->data;
t = p;
p = p->firstchild;
q.push(p);
while(p->nextsibling)
{
p = p->nextsibling;
if (p->data == e)
return t->data;
q.push(p);
}
}
}
}
return Nil;
}
T LeftChild(T e)const
{//返回值為 e 的結點的左孩子的值
CSNode<T> *f = Point(e);
if (f && f->firstchild)
return f->firstchild->data;
else
return Nil;
}
T RightSibling(T e)const
{//返回值為 e 的結點的右兄弟的值
CSNode<T> *f = Point(e);
if (f && f->nextsibling)
return f->nextsibling->data;
else
return Nil;
}
bool InsertChild(CSNode<T> *p, int i, CSTree<T> &c)const
{//插入非空樹 c 為樹中 p 結點的第 i 棵子樹
int j;
CSNode<T> *q, *s = c.Root();
c.root = NULL;
if (root != NULL)
{
if (i == 1) //插入 s 為 p 的長子
{
s->nextsibling = p->firstchild;
p->firstchild = s;
}
else //s 不作為 p 的長子
{
q = p->firstchild;
j = 2;
while(q && j < i)
{
q = q->nextsibling;
j++;
}
if (j == i) //找到插入位置
{
s->nextsibling = q->nextsibling;
q->nextsibling = s;
}
else
return false;
}
return true;
}
else //樹空
return false;
}
bool DeleteChild(CSNode<T> *p, int i)
{//刪除樹中 p 所指結點的第 i 棵子樹
CSNode<T> *b, *q;
int j;
if (i == 1) //刪除長子
{
b = p->firstchild;
p->firstchild = b->nextsibling;
b->nextsibling = NULL;
ClearTree(b);
}
else //刪除非長子
{
q = p->firstchild;
j = 2;
while(q && j < i)
{
q = q->nextsibling;
j++;
}
if (j == i) //找到第 i 棵子樹
{
b = q->nextsibling;
q->nextsibling = b->nextsibling;
b->nextsibling = NULL;
ClearTree(b);
}
else //p 沒有孩子
return false;
}
return true;
}
};
10. 赫夫曼樹和赫夫曼編碼
赫夫曼樹又稱為最優二叉樹。它是帶權路徑長度最短的二叉樹。根據結點的個數、權值的不同,最優二叉樹的形狀也各不相同。特點是:帶權值的結點都是葉子結點;權值越小的結點,其到根結點的路徑越長。
構造最優二叉樹的方法如下:
- 將每個帶有權值的結點作為一棵僅有根結點的二叉樹,樹的權值為結點的權值;
- 將其中兩顆權值最小的樹組成一棵新的二叉樹,新樹的權值為兩棵樹的權值之和;
- 重復第二步,直到所有結點都在一棵二叉樹上,這棵二叉樹就是最優二叉樹。
最優二叉樹的左右子樹是可以互換的,因為這不影響樹的帶權值路徑長度。當結點的權值差別大到一定程度,最優二叉樹就形成了 “一邊倒” 的形狀;而當權值差別很小的時候,最優二叉樹就形成了接近滿二叉樹的形狀,葉子結點的路徑長度近似相等。
最優二叉樹除了葉子結點就是度為 2 的結點,沒有度為 1 的結點,也稱為 “真二叉樹” 。這樣才使得樹的帶權路徑長度最短。
//赫夫曼樹結點類型結構體
template<typename T>struct HTNode
{
T weight; //權值
int parent, lchild, rchild; //結點的雙親,左右孩子靜態指針
};
這次將使用靜態三叉鏈表二叉樹結構建立赫夫曼樹,因為它特別適合建立赫夫曼樹。赫夫曼樹是由最初多棵單結點二叉樹(森林)組合而成的一棵二叉樹。這種二叉樹結構既適合表示樹,也適合表示森林。赫夫曼樹結點包括權值、雙親及左右孩子靜態指針。設置根結點的雙親值和葉子結點的左右孩子值均為 -1 。這種二叉樹結構是動態生成的順序結構。當葉子結點樹確定,赫夫曼樹的結點樹也就確定了。可以按需動態生成數組,因此不需要建立備用鏈表。
實現:
//赫夫曼樹類
template<typename T>class HuffmanTree
{//帶模板的赫夫曼樹類
private:
HTNode<T> *HT; //基址
T Sum; //權值總和
int N; //葉子結點數
int min(int i)const
{//返回赫夫曼樹的前 i 個結點中權值最小的樹的根結點的序號,并給選中的根結點的雙親
//賦非負值
int j, m;
T k = Sum;
for(j = 0; j < i; j++)
if (HT[j].weight < k && HT[j].parent < 0) //HT[j]的權值小于k,又是根結點
{
k = HT[j].weight;
m = j;
}
HT[m].parent = i;
return m;
}
void CreateHT(char *FileName)
{//根據文件建立赫夫曼樹
int m, i, s1, s2;
ifstream fin(FileName);
fin >> N;
if (N <= 1)
return;
m = 2 * N - 1;
HT = new HTNode<T>[m];
assert(HT != NULL);
Sum = 0; //初值
for(i = 0; i < N; ++i) //給葉子結點賦值
{
fin >> HT[i].weight;
Sum += HT.weight;
HT[i].parent = -1; //-1 表示空
HT[i].lchild = -1;
HT[i].rchild = -1;
}
fin.close();
for(i = N; i < m; ++i) //非葉子結點
HT[i].parent = -1;
for(i = N; i < m; ++i) //建立建立赫夫曼樹
{//在 HT[0 ~ i-1] 中選擇 parent 為 -1 且 weight 最小的兩個結點
s1 = min(i);
s2 = min(i);
HT[i].lchild = s1;
HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
}
void HuffmanCodingLeaf()const
{//從葉子到根逆向求赫夫曼編碼 HC
int i, p, c;
string *HC = new string[N];
assert(HC != NULL);
cout << "從葉子到根逆向求得的赫夫曼編碼為:" << endl;
for(i = 0; i < N; i++)
{
for(c = i, p = HT[i].parent; p >= 0; c = p, p = HT[p].parent)
if (c == HT[p].lchild)
HC[i] = '0' + HC[i]; //將 '0' 加在串前
else
HC[i] = '1' + HC[i]; //將 '1' 加在串前
cout << HC[i] << endl;
}
delete[] HC;
}
void HuffmanCodingRoot()const
{//無棧非遞歸從根到葉子求赫夫曼編碼 HC
string str = "", *HC = new string[N];
assert(HC != NULL);
int c = 2 * N - 2;
for(int i = 0; i <= c; ++i)
HT[i].weight = 0; //權值域改作結點狀態標志,0 表示其左右孩子都不曾被訪問
while(c >= 0)
{
if (HT[c].weight == 0)
{//向左
HT[c].weight = 1;
if (HT[c].lchild != -1) //有左孩子
{
c = HT[c].lchild;
str = str + '0'; //左分支編碼為 0
}
else //c 為葉子結點
{
HC[c] = str;
c = HT[c].parent;
str = str.substr(0, str.length() - 1); //退到父結點,編碼長度減 1
}
}
else if (HT[c].weight == 1) //左孩子被訪問過,右孩子不曾被訪問
{//向右
HT[c].weight = 2;
c = HT[c].rchild;
str = str + '1'; //右分支編碼為 1
}
else //左右孩子均被訪問過,向根結點退一步
{
c = HT[c].parent;
str = str.substr(0, str.length() - 1); //退到父結點,編碼長度減 1
}
}
for(i = 0; i < N; i++)
cout << HC[i] << endl; //依次輸出赫夫曼編碼
delete[] HC;
}
public:
HuffmanTree()
{//構造函數
HT = NULL; //樹空
}
void HuffmanCoding(char *FileName)
{//建立赫夫曼樹并用兩種方法求赫夫曼編碼 HC ,然后銷毀赫夫曼樹
CreateHT(FileName);
HuffmanCodingLeaf(); //從葉子到根逆向求赫夫曼編碼 HC
HuffmanCodingRoot(); //無棧非遞歸從根到葉子求赫夫曼編碼 HC
delete[] HT;
}
};