首页 > 编程语言 >数据结构和算法——二叉排序树

数据结构和算法——二叉排序树

时间:2023-06-14 18:38:24浏览次数:45  
标签:左子 node right 二叉 排序 数据结构 root 节点


一、二叉排序树

对于无序的序列“62,58,88,47,73,99,35,51,93,29,37,49,56,36,48,50”,是否存在一种高效的查找方案,使得能够快速判断在序列中是否存在指定的数值?二叉排序树是一种简单,高效的数据结构。

二叉排序树,又称为二叉查找树。二叉排序树或者是一棵空树,或者是具有以下性质的二叉树:若其左子树不为空,则左子树上的所有节点的值均小于它的根结点的值;若其右子树不为空,则右子树上的所有节点的值均大于它的根结点的值;左右子树又分别是二叉排序树。

对于以上的序列,我们构建如下的二叉排序树,其左子树小于根结点的值,右子树大于根结点的值:

数据结构和算法——二叉排序树_子树

二、二叉排序树的基本操作

二叉排序树的结构为:

typedef struct tree_node{
        double value;
        struct tree_node *left;
        struct tree_node *right;
}*node, binode;

在二叉排序树中,其最主要的特点是其左子树小于其根结点的值,右子树大于根结点的值。

1、查找

二叉排序树的查找是指在二叉排序树中查找到对应的值,如在上述的二叉排序树中查找“49”,其具体过程为:

  • 与根结点的值相比:49<52,查找其左子树
  • 49<58,查找其左子树
  • 49>47,查找其右子树
  • 49<51,查找其左子树
  • 49=49,查找成功

其具体过程如下图所示:

数据结构和算法——二叉排序树_结点_02

其具体实现过程为:

int search_value(node root, double a, node *p){
        if (NULL == root) {
                return 1; // 查找不成功
        }
        else if (a == root->value) {
                return 0; // 树中已经存在该节点
        }else if (a < root->value) {
                *p = root; // 记录父节点
                return search_value(root->left, a, p); //查找左子树
        }else {
                *p = root;
                return search_value(root->right, a, p); // 查找右子树
        }
}

2、插入

对于插入操作,主要分为两种情况:

  • 若二叉排序树中存在该值,则不做任何操作
  • 若二叉排序树中不存在该值,则插入

插入的具体操作是判断与二叉排序树中节点的值,若小于当前节点的值,则选择左子树插入,若大于当前节点的值,则选择右子树插入;对于左右子树,进行同样的操作。

其实现过程为:

int insert_tree(node *root, double a){
        node p = *root;
        node q = NULL;
        printf("insert: %lf\n", a);

        if (search_value(*root, a, &p) == 1) { // 查找树中是否存在a
                q = (binode *)malloc(sizeof(binode)); // 不存在a,申请空间
                q->value = a;
                q->left = q->right = NULL;

                if (*root == NULL){ // 树为空
                        *root = q;
                }else {
                        if (a < p->value){
                                p->left = q; // 插入左子树
                        }else{
                                p->right = q; // 插入右子树
                        }
                }
                return 0;
        }
        return 1;
}

3、删除

二叉排序树中节点的删除操作相比其他的操作就比较复杂一点,首先,通过查找二叉排序树中是否存在节点,若存在,主要分为如下的三种情况:

  • 节点既无左子树,又无右子树

删除的方法:设置父节点指向该节点的指针为空,直接删除该节点。如删除值为“50”的节点:

数据结构和算法——二叉排序树_二叉排序树_03

  • 若删除的节点只包含左子树或者只包含右子树

删除的方法:删除该节点,以其左子树或者右子树代替该节点。如删除值为“58”的节点:

数据结构和算法——二叉排序树_结点_04

  • 若删除的节点既包含左子树,又包含右子树

删除的方法:

  • 找到待删除的节点,选择其左子树中的最大的节点或者其右子树中最小的节点,这里选择左子树中最大的节点,以删除值为“47”的节点为例:

数据结构和算法——二叉排序树_二叉排序树_05

  • 在左子树中找到最大的节点,根据二叉排序树的特点,值最大的节点要么是根结点(无右子树),要么是最右的节点,在这里,其左子树中最大的节点为“37”,以该节点替换需要删除的节点,即以“37”替换节点“47”,再将该节点的左子树作为其父节点的右子树,即将“36”作为“35”的右子树:

数据结构和算法——二叉排序树_结点_06

其具体的过程为:

int delete_node(node *root, double a){
        // 判断树中书否存在a
        node p = *root;
        if (search_value(*root, a, &p) == 0){// 存在该节点
                // 开始删除,p指向的删除节点的父节点
                node q = NULL;// q指向要删除的节点
                if (a < p->value){
                        q = p->left;
                }else{
                        q = p->right;
                }

                if (q->left == NULL && q->right == NULL){ //叶子节点,直接删除
                        if (a < p->value) p->left = NULL;
                        else p->right = NULL;
                }else if ((q->left == NULL && q->right != NULL) || (q->left != NULL && q->right == NULL)){
                        // 只有左子树或者只有右子树
                        node r = (q->left == NULL ? q->right : q->left); // 指向q不为空的子树
                        if (a < p->value) p->left = r;
                        else p->right = r;
                }else{ // 既有左子树又有右子树
                        // 这里选择左子树最大的节点作为父节点
                        node r = q->left;
                        // 判断r的右子树是否为空
                        if (r->right == NULL){ // 右子树为空
                                p->left = r;
                                r->right = q->right;
                        }else{ // 右子树不为空
                                node r1 = r;
                                node r1_father = r;
                                // 寻找最右子树
                                while (r1->right != NULL){
                                        r1_father = r1;
                                        r1 = r1->right;
                                }
                                // 此时r1不可能存在右子树
                                r1_father->right = r1->left;
                                r1->left = r;
                                r1->right = q->right;
                                p->left = r1;
                        }
                }
                free(q);
                q = NULL;
                return 0;

        }
        return 1;
}

4、中序遍历

对于二叉排序树,其中序遍历的输出结果正好是排好序的序列,其中序

void in_order(node root){
        node p = root;
        if (p != NULL){
                in_order(p->left);
                fprintf(stderr, "%lf\n", p->value);
                in_order(p->right);
        }
}

对于上述的序列“62,58,88,47,73,99,35,51,93,29,37,49,56,36,48,50”其中序遍历的结果为:

数据结构和算法——二叉排序树_子树_07

删除“47”后的中序遍历的结果为:

数据结构和算法——二叉排序树_二叉排序树_08

参考文献

  • 《大话数据结构》
  • 《数据结构》(C语言版)


标签:左子,node,right,二叉,排序,数据结构,root,节点
From: https://blog.51cto.com/u_16161414/6479821

相关文章

  • C/C++——排序
    在C/C++中的排序,使用到的函数主要有:sort()qsort()下面详细分析sort()函数和qsort()函数。1、sort()函数sort()是STL中提供的算法,头文件为:#include<algorithm>usingnamespacestd;函数原型如下:template<classRandomAccessIterator>voidsort(RandomAccessIteratorfirst,Ran......
  • 挑战数据结构和算法面试题——最大差值
    题目来自伯乐在线,欢迎有不同答案的同学来一起讨论。分析:基本方法是遍历数组,找到当前值前面所有数组元素的最小值。方法:intget_max_distance(int*a,constintn){intmax_distance=0;//纪录最大距离if(n==0)returnmax_distance;intmin=a[0];//纪录最小的......
  • 【数据结构与算法面试题】求和
    题目来源“数据结构与算法面试题80道”。问题分析:可以使用类的构造方法,在类的每次实例化对象时都会调用构造方法,那么只需要实例化n个对象,就会调用n次构造方法,这就模拟了循环的过程,此时,只需要有一个全局变量记录累加的值即可。方法:#include<stdio.h>classcalnum{ public: cal......
  • JS排序:插入排序 冒泡排序 选择排序
    1.插入排序 1letarr=[30,5,7,60,22,18,29]2letfn=arr=>{3for(letj=1;j<arr.length;j++){4letcurrent=arr[j]5letpreIdx=j-16while(preIdx>=0&&arr[preIdx]>......
  • 微软亚洲工程院面试题:寻找两个二叉树节点的最近祖先
    给定一颗二叉树,并指定二叉树中任意两个节点,要求找出这两个节点在二叉树中的最近祖先,假定二叉树每个节点都有一个指向其父节点的指针,图中没有画出来,要求算法的空间复杂度必须是O(1),时间复杂度为O(h)。例如给定下面二叉树:如果指定的两个节点是401和257,那么他们的最近祖先就是节点1......
  • 快速排序以及 TopN 问题
    快速排序快速排序的划分函数1.firstelement划分2.medianofthreeelement划分快速排序的稳定性TopN问题Referencehttps://baobaobear.github.io/post/20191007-qsort-talk-1/......
  • C# 获取数组排序后的下标
    usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;namespaceConsoleApp9{classProgram{staticvoidMain(string[]args){int[]src=newint[]{2,1......
  • 算法面试:根据前序遍历结果序列和中序遍历结果序列重构二叉树
    对不同结构的二叉树进行前序,后序或中序遍历时,得到的结果可以是相同的,例如:上面三种结构的二叉树在进行前序遍历时,得到的序列都是:{3,4,5},但如果给定两种遍历序列,例如再加上中序遍历序列:{4,3,5},那么二叉树的结构就可以唯一确定了,满足这两种遍历序列的二叉树只能是中间那颗二叉树。于......
  • 算法面试题:逆时针打印二叉树外围边缘
    给定一颗二叉树如下:要求把二叉树的外边缘按照逆时针的方式打印出来,也就是你需要打印出以下节点:314,6,271,28,0,17,641,257,29,278,7整个二叉树的外边缘分三部分,第一部分是最左边缘,也就是节点314,6,271,28。第二部分是底边节点,他们分别是0,17,641,257,29。第三部分是右边缘,他们分......
  • 算法面试:单向链表节点的奇偶排序。
    给定一个单项链表,要求实现一个算法,把链表分成两部分,前一部分全是下标为偶数的节点,后一部分全是下标为奇数的节点,例如给定链表为下图的第一个队列,要求编写一个算法,将链表转换为第二个队列:要求算法不能分配多余的内存,同时,在操作链表是,不能更改节点内部的内容,只能更改节点的next指针......