/*
8.6 二叉排序樹:在創建樹的時候就構建一個有序的樹
特點:
1.若它的左子樹不空,則左子樹上所有結點的值均小於它的根結構的值;
2.若它的右子樹不空,則右子樹上所有結點的值均大於它的根節點的值;
3.它的左、右子樹也分別為二叉排序樹
構建一顆二叉排序樹的目的,其實並不是為了排序,而是為了提高查找和插入刪除關鍵字的速度。不管怎么說,在一個有序數據集上的查找,
速度總是要快於無序的數據集的,而二叉排序樹這種非線性的結構,也有利於插入和刪除的實現。
*/
/*
8.6.1 二叉排序樹查找操作
首先我們提供一個二叉樹結構
*/
typedef struct BiTNode
{
//結點數據
int data;
//左右孩子指針
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
//然后我們來看看二叉排序樹的查找是如何實現的
/*
遞歸查找二叉排序樹T中是否存在key
指針f指向T的雙親,其初始調用值為NULL
若查找成功,則指針p指向該數據元素結點,並返回TRUE
否則指針p指向查找路徑上訪問的最后一個結點並返回FALSE
*/
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p)
{
//查找不成功
if(!T)
{
*p = f;
return FALSE;
}
//查找成功
else if(key == T->data)
{
*p = T;
return TRUE;
}
else if (key < T->data)
return SearchBST(T->lchild, key, T, p); //在左子樹繼續查找
else
return SearchBST(T->rchild, key, T, p); //在右子樹繼續查找
}
/*8.6.2 二叉排序樹插入操作
有了二叉排序樹的查找函數,那么所謂的二叉排序樹的插入,其實也就是將關鍵字放到樹中合適位置而已。
當二叉排序樹T中不存在關鍵字等於key的數據元素時,插入key並返回TRUE,否則返回FALSE
*/
Status InsertBST(BiTree *T, int key)
{
BiTree p,s;
//查找不成功
if(!SearchBST(*T, key, NULL, &p))
{
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if(!p)
//插入s為新的根節點
*T = s;
else if(key < p->data)
//插入s為左孩子
p->lchild = s;
else
//插入孩子為右孩子
p->rchild = s;
return TRUE;
}
else
//樹中已有關鍵字相同的結點,不再插入
return FALSE;
}
/*
8.6.3 二叉樹刪除操作
俗話說“請神容易送神難”,我們已經介紹了二叉排序樹的查找與插入算法,但是對於二叉排序樹的刪除,就不那么容易,
我們不能因為刪除了結點,而讓這棵樹變得不滿足二叉樹排序的特性,所以刪除需要考慮多種情況。
*/
/*
根據我們對刪除結點三種情況的分析:
1.葉子結點;
2.僅有左或右子樹的結點;
3.左右子樹都有結點,我們來看代碼,下面這個算法是遞歸方式對二叉排序樹T查找key,查找到時刪除。
*/
/*
若二叉排序樹T中存在關鍵字等於key的數據元素時,則刪除該數據元素結點,並返回TRUE;否則返回FALSE
*/
Status DeleteBST(BiTree *T, int key)
{
//不存在關鍵字等於key的數據元素
if(!*T)
return FALSE;
else
{
//找到關鍵字等於key的數據元素
if (key == (*T)->data)
return Delete(T);
else if (key < (*T)->data)
return DeleteBST(&(*T)->lchild, key);
else
return DeleteBST(&(*T)->rchild, key);
}
}
/*
上邊這段代碼和前面的二叉排序樹查找幾乎完全相同,唯一的區別就在於第8行,此時執行的時Delete方法,
對當前結點進行三處操作。我們來看Delete的代碼。
*/
//從二叉排序樹中刪除結點p,並重接它的左或右子樹。
Status Delete(BiTree *p)
{
BiTree q,s;
//右子樹空則只需重接它的左子樹
if ((*p)->rchild == NULL)
{
q = *p;
*p = (*p)->lchild;
free(q);
}
//左子樹為空則只需重接它的右子樹
if((*p)->lchild == NULL)
{
q = *p;
*p = (*p)->rchild;
free(q);
}
//左右子樹均不空
else
{
q = *p; s = (*p)->lchild;
//轉左,然后向右到盡頭(找待刪結點的前驅)
while (s->rchild)
{
q = s; s = s->rchild;
}
//s指向被刪除結點的直接前驅
(*p)->data = s->data;
if (q != *p)
//重接q的右子樹
q->rchild = s->lchild;
else
//重接q的左子樹
q->lchild = s->lchild;
free(s);
}
return TRUE;
}
/*
8.6.4 二叉排序樹總結
總之,二叉排序樹是以鏈接的方式存儲,保持了鏈接存儲結構在執行插入或刪除操作時不用移動元素的優點,
只要找到合適的插入和刪除位置后,僅需修改鏈接指針即可。插入刪除的時間性能比較好。而對於二叉排序樹
的查找,走的就是從根節點到要查找的結點的路徑,其比較次數等於給定值的結點在二叉排序樹的層數。極端
情況,最少為1次,即根節點就是要找的結點,最多也不會超過樹的深度。也就是說,二叉排序樹的查找性能
取決於二叉樹的形狀。可問題就在於,二叉排序樹的形狀是不確定的,有可能時極端的左傾或者右傾。
也就是說,我們希望二叉排序樹是平衡的,即其深度與完全二叉樹相同,均為,那么查找的時間復雜度
也就是O(logn),近似於折半查找,事實上左圖也不夠平衡(p580 圖8-6-18),明顯的左重右輕。
不平衡的最壞情況就是像p580 圖8-6-18 右圖的斜樹,查找時間復雜度為O(n),這等同於順序查找。
因此,如果我們希望對一個集合按二叉排序樹查找,做好是把它們構建成一顆平衡的二叉排序樹。這樣我們就
引申出另一個問題,如何讓二叉排序樹平衡的問題。
*/