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

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