C語言 數據結構 二叉排序樹(BST)

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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章