詳解二叉排序樹
二叉排序樹的插入、查詢、刪除
二叉排序樹的定義
二叉排序樹右稱二叉查詢樹。或者為空樹,或者是具有以下性質:
(1)若它的左子樹不為空,則左子樹所有節點的值小於根結點,
(2)若它的右子樹不為空,則根結點的值小於所有右子樹結點的值
(3)它的左右子樹葉分別為二叉排序樹
總結起來就是根據結點的值有:左子樹<根結點<右子樹
如下圖就是一棵二叉排序樹
它的中序遍歷:12、19、23、28、34、36、38、42、53、65、90,剛好是排好序的。
二叉排序樹的查詢分析
二叉排序樹有一些基本的應用,可以用於查詢,查詢的長度與樹的深度有關。其平均查詢長度為logN(以2位底的對數,N為結點個數)。
一個序列的排序二叉樹有多種構造情況。下面考慮兩種極端情況:深度最小和深度最大的二叉排序樹。
關鍵字序列(45,24,53,12,37,93),假設6個記錄的查詢概率相等都為1/6
容易看出這是一棵完全二叉樹,該數的深度(最大層次)為3,它的平均查詢長度為:
ASL = 1/6(1+2+2+3+3+3)= 14/6
再來看該關鍵字序列構成深度最大的二叉排序樹
這是一棵單支樹,其深度為6,它的平均查詢長度為:
ASL = 1/6(1+2+3+4+5+6)= 21/6
在隨機情況下,該數的平均查詢長度總是介於這兩種情況之間,即14/6 < log6 < 21/6。二叉排序樹的平均查詢長度和
二叉排序樹的構造過程
以上述完全叉樹序列為例,構造該二叉排序數的過程如下圖:
容易看出,每次插入新的結點都是二叉排序樹上的葉子結點,則在插入時,不必移動其他結點,只需改動某個結點的指標,由空變為非空即可。
結點的插入操作與二叉排序樹的定義緊密相關,即左<根<右,新插入一個關鍵字時,從根結點開始比較,直到找到合適的插入位置為止。還有一種情況就是一個序列中可能有兩個相同的關鍵字,對於這種情況,向樹中插入關鍵字時遇到相同關鍵字時,什麼都不做,不進行重複插入操作。
二叉排序樹的刪除過程
刪除二叉排序樹中任意一個結點,需要分成以下三種情況討論:
(1)刪除的是葉子結點
這種情況最簡單,由於刪除葉子結點不會破壞整棵樹的結構,只需要修改其雙親結點的指標即可。
(2)刪除的結點只有左子樹或只有右子樹。
這種情況只需要將其左子樹或右子樹往上推,替代要刪除結點的位置,顯然,作此修改也不會破壞二叉排序樹的結構。
(3)刪除的結點左右子樹都不為空
這種情況稍微複雜一點,為了不保持二叉排序樹的結構,有兩種思路,一種就是從要刪除結點的左子樹中選取最大的結點進行替代之,另一種就是從要刪除的結點的右子樹中選取最小的結點替代之。
下面考慮第三種情況
f、p、q、s分別為指向結點F、P、Q、S的指標,假如現要刪除結點P,它的左右子樹都不為空。
其一就是尋找P的左子樹中的最大結點,代替之。
易知左子樹中最大結點為S,將P結點用S結點代替,還需處理S結點的左孩子K,由於S只有左子樹K,在刪除結點S之後,只要令K為S的雙親Q的右子樹即可。
其二就是尋找P結點的右子樹中最小的結點,代替之。
找到P後,第一步就是往右走一步到R,若R的左子樹不為空,則最小的結點在R的左子樹中,只需一直往左走即p=p->Left,指定p->Left為空(右子樹最小結點,其左子樹定為空
),則找到最小結點,將其代替要刪除的結點P,並將最小結點的右子樹代替該最小結點即可。如下圖,Z為要刪除結點中右子樹中最小結點,將P用Z代替後,Z的位置再由Y代替
若R的左子樹為空,那麼最小結點就是R,只需將R往上推代替P
具體實現:
/*BinarySearchTree.cpp*/
#include<stdio.h>
#include<stdlib.h>
typedef int ElementType;
typedef struct TreeNode{
ElementType Element;
struct TreeNode *Left;
struct TreeNode *Right;
}TreeNode,*SearchTree;
SearchTree Insert(SearchTree T,ElementType e)
{
//二叉排序樹的插入過程
if(!T)
{
//未找到值為e的結點,就新生成一個結點
T = (TreeNode*)malloc(sizeof(TreeNode));
T->Element = e;
T->Left = NULL;
T->Right = NULL;
}
else if(e<T->Element) //在左子樹中插入
T->Left = Insert(T->Left,e);
else if(e>T->Element) //在右子樹中插入
T->Right = Insert(T->Right,e);
else //e == T->Element do nothing
{
}
return T;
}
int Delete(SearchTree &p)
{
//從二叉排序樹中刪除結點p,並重接它的左子樹或右子樹
TreeNode *q,*s;
if(!p->Right) //右子樹為空,重接左子樹
{
q = p;
p = p->Left;
free(q);
}
else if(!p->Left) //左子樹為空,重接右子樹
{
q = p;
p = p->Right;
free(q);
}
else //左右子樹都不為空
{
q = p;
s = p->Left;
while(s->Right)
{
q = s; //q是s的前驅結點
s = s->Right; //往右走到盡頭
}
p->Element = s->Element;
if(q != p)
q->Right = s->Left;
else //q = p;
q->Left = s->Left;
free(s);
}
return 1;
}
int DeleteBST(SearchTree &T,ElementType x)//考慮這裡為什麼用引用?
{
//在排序二叉樹T中刪除關鍵字等於x的結點
if(!T) //不存在關鍵字等於x的資料元素
return -1;
else
{
if(x == T->Element) //查詢成功
return Delete(T);
else if(x < T->Element)
return DeleteBST(T->Left,x); //在左子樹中繼續查詢
else
return DeleteBST(T->Right,x); //在右子樹中繼續查詢
}
return 1;
}
void InOderTravesal(SearchTree T)
{
//中序遍歷
if(T)
{
InOderTravesal(T->Left);
printf("%d ",T->Element);
InOderTravesal(T->Right);
}
}
void removeTree(SearchTree T)
{
//銷燬二叉樹
if(T)
{
removeTree(T->Left);
removeTree(T->Right);
free(T);
T=NULL;
}
}
int main()
{
SearchTree T=NULL;
int a[7] = {45,24,53,45,12,24,90},i;
for(i=0; i<7; ++i)
T=Insert(T,a[i]);
InOderTravesal(T);
printf("\n");
DeleteBST(T,24);
InOderTravesal(T);
removeTree(T);
return 0;
}