【二叉树】数据结构——BST二叉树基本概念及算法设计(插入、删除、遍历操作)

简介: 【二叉树】数据结构——BST二叉树基本概念及算法设计(插入、删除、遍历操作)

一、二叉树基本概念

二叉树的其中一个重要应用,是提供一种快速查找数据的方法,即:将数据节点按照某种规律形成一棵二叉树,然后利用二叉树特殊的逻辑结构减少搜索数据的次数,提高查找的效率。

这种按照某种规律构建,用来提高搜索性能的二叉树,被称为搜索二叉树(Binary Search Tree),即BST。

具体而言,二叉树提高搜索效率的秘诀在于:按照“小-中-大”(当然“大-中-小”也是一样的)的规律来存储数据,即对于任意一个节点,都可以明确找到其值大于或等于其左孩子节点,且小于或等于其右孩子节点。如下图所示:

比如需要用二叉树储存整个年级的数学成绩

由于树中所有的节点均满足“小-中-大”的规律,因此当从根开始查找某个节点时速度比顺序查找要快得多,比如要找节点38,当发现38大于根节点13后,就可以确定13的左子树一定没有38,这就去掉了半边子树的节点。

因此,二叉搜索树又被称为二叉排序树、二叉查找树。

实际上,对于一棵二叉树而言,其搜索节点的时间复杂度,最糟糕的情形是其退化为链表,最乐观的情形是完美或完全二叉树,那么其搜索时间复杂度就是介于:

最差:T(n)=O(n)

最优:T(n)=O(log2n)

二、二叉树的算法设计

1、构建二叉树节点

struct node
{
//数据域
datatype data;

//指针域
struct node * Lchild;//指向左孩子指针
struct node * Rchild;//指向右孩子指针
};

2、插入节点

对于BST而言,插入一个节点主要是要保持其“小-中-大”的逻辑不变,因此插入的节点的逻辑可以从根节点开始,一步步寻找新节点的“最终归宿”,比如在如下BST中,要插入新节点16,最终应该插入到节点17的左孩子处。

在实现插入算法的时候,由于树状结构本身是递归的,因此可以使用递归函数更优雅地实现插入算法。如下:

情况①:

第一次插入节点给这个二叉树,二叉树是空的,则直接把Root根指针指向新节点

struct node *Root=NULL;
Root = bstInsert(Root,25);

对应插入代码为:

if(root == NULL)
  return new;

情况②:

非第一次插入节点

递进深入二叉树

递进的条件:

只要节点的Lchild或Rchild不为NULL 则以下一个节点作为根 继续深入

回归的条件:

到了 尾巴为NULL 同时满足大小关系条件 则返回当前节点地址

// 将新数据data(以整型为例),插入到二叉搜索树root中
// 插入节点后,返回新的BST的根
node *bstInsert(node *root, int data)
{
    // 准备好新节点,并将数据填入其中
    node *new = (node *)malloc(sizeof(node));
    new->data = data;
    
     new->lchild = NULL;
    new->rchild = NULL;

    // 若此时BST为空,则new称为二叉树的根节点
    if(root == NULL)
        return new;//只要满足这个条件就开始回归

    // 若新节点比根节点小,那么新节点应该插入左子树中
    // 至于插入到左子树的具体什么位置就不用管了,直接递归即可
    if(data < root->data)
        root->lchild = bstInsert(root->lchild, data);//左递进

    // 若新节点比根节点大,那么新节点应该插入右子树中
    // 至于插入到右子树的具体什么位置就不用管了,直接递归即可
    else if(data > root->data)
        root->rchild = bstInsert(root->rchild, data);//右

    // 若已存在,则不处理
    else
    {
        printf("%d已存在\n", data);
    }
    free(new);
    return root;
}

3、删除节点

(1)删除一个BST的节点要比插入困难一点,但同样是要遵循一个原则,即:删除节点后仍然要保持“小-中-大”的逻辑关系

(2)假设要删除的节点是x,大体思路如下:

  • 若要删除的节点小于根节点,则递归地在左子树中删除x
  • 若要删除的节点大于根节点,则递归地在右子树中删除x

若要删除的节点恰好就是根节点,则分如下几种情况:

  • 根节点若有左子树,则用左子树中最大的节点max替换根节点,并在左子树中递归删除max
  • 否则,若有右子树,则用右子树中最小的节点min替换根节点,并在右子树中递归删除min
  • 否则,直接删除根节点

(3)举个例子

以下图为例,假设在一棵二叉树中要删除节点15,在找到节点之后,判断其有左子树,那么就沿着其左子树找到最右下角(最大)的节点19,替换要删除的节点15,然后再将多余的节点19删掉:

(4)示例代码

// 将数据(以整型为例)data从二叉树中删除
// 并返回删除之后的二叉树的根
node *bstRemove(node *root, int data)
{
    if(root == NULL)
        return NULL;

    // 若data小于根节点,则递归地在左子树中删除它
    if(data < root->data)
        root->lchild = bstRemove(root->lchild, data);

    // 若data大于根节点,则递归地在右子树中删除它
    else if(data > root->data)
        root->rchild = bstRemove(root->rchild, data);

    // 若data恰好就是根节点,则分如下几种情况:  
    else
    {
        // a. 根节点若有左子树,则用左子树中最大的节点max替换根节点
        //    并在左子树中递归删除max  
        if(root->lchild != NULL)
        {
            node *max;
            for(max=root->lchild; max->rchild!=NULL;
                max=max->rchild);

            root->data = max->data;
            root->lchild = bstRemove(root->lchild, max->data);
        }

        // b. 否则,若有右子树,则用右子树中最小的节点min替换根节点
        //    并在右子树中递归删除min  
        else if(root->rchild != NULL)
        {
node *tmp;
            for(tmp=root->rchild; tmp->lchild!=NULL;
                tmp=tmp->lchild);

            root->data = tmp->data;
            root->rchild = bstRemove(root->rchild, tmp->data);
        }

        // c. 否则,直接删除根节点
        else
        {
            free(root);
            return NULL;
        }
    }

    return root;
}

(5)总结

  • 先递进的找到待删除节点
  • 根据找到待删除节点分析其三种情况 有左子树—优先找到左子树最大数的节点 只有右子树,从右子树中找到最小的那个数的节点
    待删除节点时叶子–直接删除—开始回归
  • 如果上面的红色部分情况 找到了左子树中最大 或找了右子树最小的 拿这个数替换掉待删除节点的树
  • 把找到的这个替换的节点作为新的待删除节点,重复上面步骤直到满足回归条件

4、遍历二叉树

// 前序遍历
void preOrder(node *root)
{
//1 空树  2 遇到一个度为0/1的节点  ---- 回归条件
    if(root == NULL)
        return;

    // 先访问根节点
    printf("%d", root->data);

    // 再依次使用前序算法,遍历其左、右子树 --- 递进
    preOrder(root->lchild);
    
    preOrder(root->rchild);
}

// 中序遍历
void inOrder(node *root)
{
    if(root == NULL)
        return;

    // 先访问左子树
    inOrder(root->lchild);

    // 再访问根节点
    printf("%d", root->data);

    // 再访问右子树
    inOrder(root->rchild);
}

// 后序遍历
void postOrder(node *root)
{
    if(root == NULL)
        return;

    // 先依次使用后序算法,遍历其左、右子树
    postOrder(root->lchild);
    postOrder(root->rchild);

    // 再访问根节点
    printf("%d", root->data);
}

5、层次遍历

对于按层遍历,则需要借助队列来达到这一目的。具体做法是:

  • 创建一个队列,并将根节点指针入队
  • 判断队列是否为空:
    a. 是则退出程序
  • b. 否则让队头元素出队,并将队头的左右孩子依次入队
    c. 循环此步骤

示例代码:

void levelOrder(node *root)
{
    if(root == NULL)
        return;

    // 将根节点入队
    linkQueue *q = initQueue();
    enQueue(q, root);

    node *tmp;
    while(!isEmpty(q))
    {
        // 出队并访问队头
        outQueue(q, &tmp);
        printf("%d\t", tmp->data);

        // 依次将其左右孩子(若有)入队
        if(tmp->lchild != NULL)
            enQueue(q, tmp->lchild);

        if(tmp->rchild != NULL)
            enQueue(q, tmp->rchild);
    }
    printf("\n");
}
相关文章
|
20天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
30 1
|
21天前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
21天前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
29天前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
94 23
|
29天前
|
算法
数据结构之蜜蜂算法
蜜蜂算法是一种受蜜蜂觅食行为启发的优化算法,通过模拟蜜蜂的群体智能来解决优化问题。本文介绍了蜜蜂算法的基本原理、数据结构设计、核心代码实现及算法优缺点。算法通过迭代更新蜜蜂位置,逐步优化适应度,最终找到问题的最优解。代码实现了单链表结构,用于管理蜜蜂节点,并通过适应度计算、节点移动等操作实现算法的核心功能。蜜蜂算法具有全局寻优能力强、参数设置简单等优点,但也存在对初始化参数敏感、计算复杂度高等缺点。
59 20
|
29天前
|
机器学习/深度学习 存储 算法
数据结构实验之二叉树实验基础
本实验旨在掌握二叉树的基本特性和遍历算法,包括先序、中序、后序的递归与非递归遍历方法。通过编程实践,加深对二叉树结构的理解,学习如何计算二叉树的深度、叶子节点数等属性。实验内容涉及创建二叉树、实现各种遍历算法及求解特定节点数量。
80 4
|
29天前
|
机器学习/深度学习 算法 C++
数据结构之鲸鱼算法
鲸鱼算法(Whale Optimization Algorithm,WOA)是由伊朗研究员Seyedali Mirjalili于2016年提出的一种基于群体智能的全局优化算法,灵感源自鲸鱼捕食时的群体协作行为。该算法通过模拟鲸鱼的围捕猎物和喷出气泡网的行为,结合全局搜索和局部搜索策略,有效解决了复杂问题的优化需求。其应用广泛,涵盖函数优化、机器学习、图像处理等领域。鲸鱼算法以其简单直观的特点,成为初学者友好型的优化工具,但同时也存在参数敏感、可能陷入局部最优等问题。提供的C++代码示例展示了算法的基本实现和运行过程。
49 0
|
1月前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
1月前
|
存储 缓存 算法
如何提高二叉树遍历算法的效率?
选择合适的遍历算法,如按层次遍历树时使用广度优先搜索(BFS),中序遍历二叉搜索树以获得有序序列。优化数据结构,如使用线索二叉树减少空指针判断,自定义节点类增加辅助信息。利用递归与非递归的特点,避免栈溢出问题。多线程并行遍历提高速度,注意线程安全。缓存中间结果,避免重复计算。预先计算并存储信息,提高遍历效率。综合运用这些方法,提高二叉树遍历算法的效率。
58 5
|
1月前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
128 8