1、二叉排序樹簡介:二叉排序樹(Binary Sort Tree),又稱二叉查找樹(Binary Search Tree),也稱二叉搜索樹。
樹中的任意節點上的左子樹都比父節點小,右子樹都比父節點大的,且所有值都唯一的樹叫二叉排序樹。
2、二叉排序樹的查找:
如果當前節點的鍵值等於待查詢的數值,則找到;
若前節點的鍵值小於待查詢的數值,由二叉樹的定義可知,大的數值在右子樹,則遞歸查找該節點的右子樹。
若前節點的鍵值大於待查詢的數值,小的數值在該節點的左子樹,則遞歸查找該節點的左子樹。
時間複雜度:O(Log2n)到O(n)之間
3、二叉查找樹的聲名與創建:
我先將待插入數值存在數組中,再將數組中的數值依次添加到二叉排序樹中,按照排序樹的性質創建樹
typedef struct tree //二叉排序樹與二叉樹聲名相似
{
int nval;
struct tree *pleft;
struct tree *pright;
}bst;
bst *createbst(int arr[],int length);
void addnode(bst **ptree,int num);
bst *createbst(int arr[],int length)
{
int i;
if(arr==NULL||length<=0) return NULL;
bst *ptree = NULL;
for(i=0;i<length;i++)
{
addnode(&ptree,arr[i]);
}
return ptree;
}
void addnode(bst **ptree,int num)
{
bst *ptmp = (bst*)malloc(sizeof(bst));
ptmp->nval = num;
ptmp->pleft = NULL;
ptmp->pright = NULL;
if(*ptree == NULL)
{
*ptree = ptmp;
return ;
}
bst *pnode = *ptree;
while(pnode)
{
if((pnode->nval) > num) //當查詢數值小於節點鍵值
{
if((pnode->pleft) == NULL)
{
pnode->pleft = ptmp; //加入左子樹
return ;
}
pnode = pnode->pleft;
}
else if((pnode->nval) < num) //當查詢數值大於節點鍵值
{
if(pnode->pright == NULL)
{
pnode->pright = ptmp; //加入右子樹
return ;
}
pnode = pnode->pright;
}
else if(pnode->nval == num) //如果該鍵值已出現過,刪除
{
printf("%d這個數字已經有了\n",num);
free(ptmp);
ptmp = NULL;
return ;
}
}
}
4、二叉排序樹的刪除:
如果感覺建樹添加節點容易的話,那刪除就稍顯麻煩了。要想在樹中刪除節點,我們就要想到該節點的父親和兒子同不同意。。。所以考慮以下三種情況:
(1)刪除節點爲葉子節點即終端節點,沒有兒子的情況下,可以就直接刪除了,也不會破壞整個樹結構;
(2)刪除節點只有一個兒子(左子或右子),那麼刪除後就要考慮到兒子的死活(因爲這關係到整個樹的死活。。。),由於只有一個兒子,所以該節點的刪除不影響 它父親與它兒子的大小關係。所以直接將刪除節點的兒子繼承到刪除節點的父親下;這是就要知道刪除節點是父親的左兒子還是右兒子,然後直接將指針指向兒子即可。
簡而言之:刪除結點的左右孩子有一個爲空,那麼將不空的那個孩子代替要刪的結點即可。
(3)刪除節點有左右雙子,由於有左右雙子二個節點,所以直接接到刪除節點的父親上不可行。所以要找到一個適合的值替換,也就是和刪除節點值最相近的節點。根據性質,我們知道刪除節點的左邊都是比他小的,而在這些衆多比他小的節點中,最右端的節點一定是這些節點中最大的,而這個最大的值又不大於刪除節點的右兒子,所以這是最佳替換的節點。以此規則類推,我們也可以將右子樹的最左端節點賦給刪除節點(所有比刪除節點大的節點中最小的那個),使得新節點的左子樹都小於它,右子樹都大於它。最後,別忘了刪除那個最小或最大節點。
簡而言之:刪除結點的左右孩子都不爲空,那麼找到這個結點的右(左)子樹中的最小結點也就是最左(右)節點(此結點的左/右孩子一定爲空),將這個最小結點的值賦給要刪除的結點,然後刪除這個最小結點。(找直接前驅)
void findnode(bst *ptree,bst **pdel,bst **pFather,int nNum);
void deletenode(bst **ptree,int nNum);
void findnode(bst *ptree,bst **pdel,bst **pFather,int nNum)
{
//找到刪除節點和刪除節點的父親
while(ptree)
{
if(ptree->nval == nNum)
{
*pdel = ptree;
return ;
}
else if(ptree->nval > nNum)
{
*pFather = ptree;
ptree = ptree->pleft;
}
else
{
*pFather = ptree;
ptree = ptree->pright;
}
}
*pFather = NULL;
}
void deletenode(bst **ptree,int nNum) //刪除節點函數
{
bst *pdel = NULL;
bst *pFather = NULL;
findnode(*ptree,&pdel,&pFather,nNum);
if(pdel == NULL) return; //未找到
bst *pMark = NULL;
if(pdel->pleft != NULL && pdel->pright != NULL) //雙子
{
pMark = pdel;
pFather =pdel;
pdel = pdel->pleft; //找左子樹
while(pdel->pright != NULL) //找左子樹最右節點
{
pFather = pdel;
pdel = pdel->pright;
}
pMark->nval = pdel->nval; //替換節點
}
if(pFather == NULL)
{
*ptree = pdel->pleft?pdel->pleft:pdel->pright;
free(pdel);
return ;
}
if(pdel == pFather->pleft) //單子
{
pFather->pleft = pdel->pleft?pdel->pleft:pdel->pright;
}
else
{
pFather->pright = pdel->pleft?pdel->pleft:pdel->pright;
}
free(pdel); //無子直接刪除
pdel = NULL;
}
測試:
int main()
{
int arr[] = {16,2,88,56,23,9,49,100,45};
bst *ptree = NULL;
ptree = createbst(arr,sizeof(arr)/sizeof(arr[0]));
printf("二叉排序樹爲:");
midtravel(ptree);
printf("\n");
deletenode(&ptree,88);
printf("刪除節點後的二叉排序樹爲:");
midtravel(ptree);
printf("\n");
return 0;
}