二叉排序樹又稱“二叉查找樹”、“二叉搜索樹”。
二叉排序樹:或者是一棵空樹,或者是具有下列性質的二叉樹:
1. 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
2. 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
3. 它的左、右子樹也分別為二叉排序樹。
二叉排序樹通常采用二叉鏈表作為存儲結構。中序遍歷二叉排序樹可得到一個依據關鍵字的有序序列,一個無序序列可以通過構造一棵二叉排序樹變成一個有序序列,構造樹的過程即是對無序序列進行排序的過程。每次插入的新的結點都是二叉排序樹上新的葉子結點,在進行插入操作時,不必移動其它結點,只需改動某個結點的指針,由空變為非空即可。搜索、插入、刪除的時間復雜度等於樹高,期望O(logn),最壞O(n)(數列有序,樹退化成線性表,如右斜樹)。
/* 二叉樹的二叉鏈表結點結構定義 */ typedef struct BiTNode /* 結點結構 */ { int data; /* 結點數據 */ struct BiTNode *lchild, *rchild; /* 左右孩子指針 */ } BiTNode, *BiTree;
雖然二叉排序樹的最壞效率是O(n),但它支持動態查找,且有很多改進版的二叉排序樹可以使樹高為O(logn),如AVL、紅黑樹等。
二元排序樹的查找算法
在二元排序樹b中查找x的過程為:
1.若b是空樹,則搜索失敗,否則:
2.若x等於b的根節點的數據域之值,則查找成功;否則:
3.若x小於b的根節點的數據域之值,則搜索左子樹;否則:
4.查找右子樹。
算法實現:
/* 遞歸查找二叉排序樹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); /* 在右子樹中繼續查找 */ }
二叉排序樹的插入算法
利用查找函數,將關鍵字放到樹中的合適位置。
/* 當二叉排序樹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) *T = s; /* 插入s為新的根結點 */ else if (key<p->data) p->lchild = s; /* 插入s為左孩子 */ else p->rchild = s; /* 插入s為右孩子 */ return TRUE; } else return FALSE; /* 樹中已有關鍵字相同的結點,不再插入 */ }
二叉排序樹的刪除算法
在二叉排序樹中刪去一個結點,分三種情況討論:
1.若*p結點為葉子結點,即PL(左子樹)和PR(右子樹)均為空樹。由於刪去葉子結點不破壞整棵樹的結構,則只需修改其雙親結點的指針即可。
2.若*p結點只有左子樹PL或右子樹PR,此時只要令PL或PR直接成為其雙親結點*f的左子樹(當*p是左子樹)或右子樹(當*p是右子樹)即可,作此修改也不破壞二叉排序樹的特性。
3.若*p結點的左子樹和右子樹均不空。在刪去*p之后,為保持其它元素之間的相對位置不變,可按中序遍歷保持有序進行調整。比較好的做法是,找到*p的直接前驅(或直接后繼)*s,用*s來替換結點*p,然后再刪除結點*s。
/* 若二叉排序樹T中存在關鍵字等於key的數據元素時,則刪除該數據元素結點, */ /* 並返回TRUE;否則返回FALSE。 */ Status DeleteBST(BiTree *T,int key) { if(!*T) /* 不存在關鍵字等於key的數據元素 */ return FALSE; else { if (key==(*T)->data) /* 找到關鍵字等於key的數據元素 */ return Delete(T); else if (key<(*T)->data) return DeleteBST(&(*T)->lchild,key); else return DeleteBST(&(*T)->rchild,key); } } /* 從二叉排序樹中刪除結點p,並重接它的左或右子樹。 */ Status Delete(BiTree *p) { BiTree q,s; if((*p)->rchild==NULL) /* 右子樹空則只需重接它的左子樹(待刪結點是葉子也走此分支) */ { q=*p; *p=(*p)->lchild; free(q); } else 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; } (*p)->data=s->data; /* s指向被刪結點的直接前驅(將被刪結點前驅的值取代被刪結點的值) */ if(q!=*p) q->rchild=s->lchild; /* 重接q的右子樹 */ else q->lchild=s->lchild; /* 重接q的左子樹 */ free(s); } return TRUE; }
二叉排序樹性能分析
每個結點的Ci為該結點的層次數。最好的情況是二叉排序樹的形態和折半查找的判定樹相同,其平均查找長度和logn成正比(O(log2(n)))。最壞情況下,當先后插入的關鍵字有序時,構成的二叉排序樹為一棵斜樹,樹的深度為n,其平均查找長度為(n + 1) / 2。也就是時間復雜度為O(n),等同於順序查找。因此,如果希望對一個集合按二叉排序樹查找,最好是把它構建成一棵平衡的二叉排序樹(平衡二叉樹)。
Reference:
[1] 《大話數據結構》
[2] wikipedia: 二叉查找樹