二叉搜索树

概念

二叉搜索树:其实本质上还是二叉树,只不过其在二叉树的基础之上增加了一些新的约束,从而变成了了一颗有规律的二叉树。

要想弄明白二叉搜索树,我们就必须牢牢把握住这三点规则:

  1. 每个节点都有一个作为搜索依据的关键码,每个关键码都不相同,树中没有关键码相同的两个节点;
  2. 左子树上所有的节点的关键码都小于根节点的关键码;
  3. 右子树上的所有节点的关键码都大于根节点的关键码。

二叉搜索树的左右子树也都是二叉搜索树**!!!**

二叉搜索树节点的结构

typedef int KeyType;
typedef struct BstNode
{
    KeyType key;//关键码
    BstNode* parent; // 双亲指针
    BstNode* leftchild; //左孩子指针
    BstNode* rightchild;  //右孩子指针
}BstNode,*BSTree;

每个节点都包含四个域,除了关键码域,其余三个域串联起了整个树,二叉搜索树按照中序遍历的方法,遍历出来的应该是一个从小到大的数组

注意:根节点没有双亲,这一点在后面有很重要的应用

//申请节点
BstNode* BuyNode()
{
	BstNode* s = (BstNode*)malloc(sizeof(BstNode));
	if(s == nullptr) return nullptr;
	memset(s , 0 , sizeof(BstNode));
	return s;
}

构建二叉搜索树

在构建二叉树搜索树的时候,要把握好左子树大于根节点大于右子树的规则。

在插入节点的时候,要考虑几种情况:

  1. 如果根节点为空:根节点为空说明这是一个空树,插入的节点应该作为根节点;
  2. 如果插入的值小于根节点的关键码,依次用根节点的左子树与之比较;
  3. 如果插入的值大于根节点得关键码,依次用根节点的右子树与之比较;
  4. 如果插入的值在二叉树中找到了相同的关键码,插入失败,因为二叉树终不能有相同的关键码;
//插入函数
//使用引用是因为我们在局部函数中申请了空间,如果是单纯的指针,在局部函数结束的时候,申请的空间将释放,所以需要用引用,想要通过形参改变实参只能使用引用
bool Insert(BstNode*& root,KeyType val)
{
    BstNode* pa = nullptr;
    BstNode* p = root;
    while(p != nullptr && p->key != val)
    {
        pa = p;
        p = val < p->key ? p->leftchild : p->rightchild;
    }
    if(p != nullptr && p->key == val) return false;
     
    p = BuyNode();
    p->key = val;
    p->parent = pa;
    //此时如果pa 还是空,说明上面的操作都没进行,既树是一颗空树,直接让申请的节点作为根节点
    if(pa == nullptr) 
    {
        root = p;
    }
    else
    {
        if(pa->key<p->key)
        {
            pa->rightchild = p;
        }
        else
        {
            p->leftchild = p;
        }
    }
    retrun true;
}

//创建二叉搜索树
BSTree CreateBSTree(KeyType* ar)
{
    if(ar == nullptr) return nullptr;//如果数组为空,就无法构建二叉树,返回空指针
    int n = sizeof(ar)/sizefo(ar[0]);
    
    BstNode* ptr = nullptr;
    
    for(int i = 0;i < n; ++i)
    {
        Insert(ptr,ar[i]);
    }
    
    return ptr;
}

相关的API函数

既然是二叉树那么二叉树对应的前中后序遍历等函数都能够被应用,这里就不过多赘述了,因为二叉搜索树的特殊性质,可以归纳出一下几个函数,

  1. 找到中序遍历的最头的节点:BstNode* Fast();
  2. 找到树在中序遍历之后每个节点的后继节点:BstNode* Next();
  3. 找到中序遍历的最尾的节点:BstNode* Last();
  4. 找到树在中序遍历之后每个节点的前驱节点:BstNode* Prev();
//二叉搜索树 的最左侧节点肯定是最小的
BstNode* Fast(BstNode* root)
{
    if(root == nullptr) return nullptr;
    while(root != nullptr && root->leftchild != nullptr)
    {
        root = root->leftchild;
    }
    return root;
}
BstNode* Next(BstNode* ptr)
{
    if(ptr == nullptr) return ptr;
    //根据二叉搜索树的特点,左孩子都比根节点大,右孩子都比根节点小,那么当右孩子不为空的时候,根节点的中序遍历的直接后继应该就是右子树中的最小的节点
    if(ptr->rightchild != nullptr)
    {
        return Fast(ptr->rightchild);
    }
    else
    {
    	BstNode* pa = ptr->parent;
        while(pa != nullptr && pa->leftchild != ptr)  // pa->rightchild == ptr
        {
            ptr = pa ;
            pa = ptr->parent;
        }
        return pa;
    }
}
BstNode* Last(BstNode* root)
{
    if(root == nullptr) return nullptr;
    while(root!=nullptr && root->rightchild != nullptr)
    {
        root= root->rightchild;
    }
    return child;
}

BstNode* Prev(BStNode* ptr)
{
    if(ptr == nullptr) return ptr;
    
    if(ptr->leftchild != nullptr)
    {
        return Last(ptr->leftchild);
    }
    else
    {
        BstNode* pa = ptr->parent;
        while(pa != nullptr && pa->leftchild != nullptr)
        {
            ptr = pa;
            pa = ptr->parent;
        }
        return pa;
    }
    
}

如下图所示:
在这里插入图片描述

  1. 当我们Fast(53)的时候,可以看到53为根节点,这棵树的中序遍历之后最小的节点的就是最左子树的节点9;
  2. 当我们Next(53)的时候,找53的后继,因为他有右子树,根据二叉搜索树的规则,53的右子树的最小节点就是53的后继节点。
  3. 当我们Next(88)的时候,88没有右子树,那么他的父节点应该改就是他的后继节点。

中序遍历

通常来说,我们对二叉树继续拧中序遍历,最常用的应该是递归的方式:

void InOrder(BstNode* root)
{
	if(root != nullptr)
	{
		InOrder(root->leftchild);
		cout<<root->key;
		InOrder(root->rightchild);
	}
}

但是有时要求我们使用非递归的方式进行二叉树的中序遍历,此时之前铺垫的Fast和Next函数就有作用了:

void NiceInOrder(BstNode* root)
{
    if(root == nullptr) return;
    while(BstNode* ptr = Fast(root);ptr!=nullptr;ptr = Next(ptr))
    {
        cout << ptr->key << " ";
    }
}

删除节点 : bool Remove(BstNode* root ,KeyType val)

我们要删除节点,首先要判断节点的类型:

  1. 空节点:不删除,删除失败
  2. 没找到相应的节点:删除失败
  3. 要删除的是叶子节点:直接删除,释放节点,并指向空
  4. 要删除的节点是单分支节点:交换
  5. 要删除双分支节点:变成单分支
bool Remove(BstNode*& root ,KeyType val) 
{
    if(root == nullptr) return false;
    BstNode* ptr = root;
    while(ptr != nullptr && ptr->key != val)
    {
        ptr = val < ptr->key ? ptr->leftchild : ptr->rightchild;
    }
    if(ptr == nullptr)
    {
        return nullptr;
    }
    else
    {
        //如果是双分支节点,找到有分支的最小节点与要删除节点调换,释放右子树最小节点
        if(ptr->leftchild != nullptr && ptr->rightchild != nullptr)
        {
            BstNode* last = Fast(ptr->rightchild);
            ptr->key = last->key;
            free(last);
            last = nullptr;
        }
        //如果不是双分支,
        BstNode* pa = ptr->parent;
        BstNode* child = pa->rightchild == nullptr ? pa->leftchid:pa->rightchild;
        if(pa == nullptr) root = child;
        else
        {
            if (pa->leftchild == p)
            {
                pa->leftchild = child;
            }
            else
            {
                pa->rightchild = child;
            }
        }
        free(p);
        return true;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值