/* 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),這等同於順序查找。 因此,如果我們希望對一個集合按二叉排序樹查找,做好是把它們構建成一顆平衡的二叉排序樹。這樣我們就 引申出另一個問題,如何讓二叉排序樹平衡的問題。 */