文章目录

  • 1 二叉搜索树简介
  • 2 二叉搜索树的算法实现
    • 2.1 节点结构体的定义
    • 2.2 二叉搜索树插入节点
    • 2.3 二叉搜索树删除结点
    • 2.4 二叉搜索树搜索
    • 2.5 二叉搜索树的遍历

1 二叉搜索树简介

当我们要在一组数中要找到 26?你该怎么找?

答案: 从左至右 或 从右至左遍历一次,找到这个数字。

当我们把数据进行排序(按照从小到大的顺序排列)后,再查找相应的这条记录?还是用上面的方法吗?

答案:最快的方式,是采用折半法(俗称二分查找)。

思考: 当我们有新的数据加进来,或者删除其中的一条记录,为了保障查找的效率,我们仍然要保障数组有序,但是,会碰到我们讲顺序表时的问题,涉及到大量数据的移动!在插入和删除操作上,就需要耗费大量的时间(需进行元素的移位),能否有一种既可以使得插入和删除效率不错,又可高效查找的数据结构和算法呢?

抛砖: 首先解决一个问题,插入时不移动元素,我们可以想到链表,但是要保证其有序的话,首先得遍历链表寻找合适的位置,那么又如何高效的查找合适的位置呢,能否可以像二分一样,通过一次比较排除一部分元素?

引玉: 那么我们可以用二叉树的形式,以数据集第一个元素为根节点,之后将比根节点小的元素放在左子树中,将比根节点大的元素放在右子树中,在左右子树中同样采取此规则。那么在查找 x 时,若 x 比根节点小可以排除右子树所有元素,去左子树中查找(类似二分查找),这样查找的效率非常好,而且插入的时间复杂度为 O(h),h 为树的高度,较 O(n)来说效率提高不少。故二叉搜索树用作一些查找和插入使用频率比较高的场景。


二叉树一般采用链式存储方式:每个结点包含两个指针域,指向两个孩子结点,还包含一个数据域,存储结点信息。


2 二叉搜索树的算法实现

2.1 节点结构体的定义

#define MAX_NODE 1024#define isLess(a, b) (a<b)
#define isEqual(a, b) (a==b)typedef int ElemType;typedef struct _Bnode{ElemType data; //元素类型struct _Bnode *lchild, *rchild;//指向左右孩子节点
}Bnode, *Btree;

2.2 二叉搜索树插入节点

将要插入的结点 e,与节点 root 节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以上操作直到找到一个空位置用于放置该新节点。

bool InsertBtree(Btree **root, Bnode *node){Bnode *tmp = NULL;Bnode *parent = NULL;if(!node){return false;}else {//清空新节点的左右子树node->lchild = NULL;node->rchild = NULL;}if(*root){//存在根节点tmp= *root;}else{ //不存在根节点*root = node;return true;}while(tmp != NULL){parent = tmp;//保存父节点printf("父节点: %d\n", parent->data);if(isLess(node->data,tmp->data)){tmp = tmp->lchild;}else{tmp = tmp->rchild;}}//若该树为空树,则直接将 node 放置在根节点上if(isLess(node->data, parent->data)){//找到空位置后,进行插入parent->lchild = node;}else{parent->rchild = node;}return true;
}

2.3 二叉搜索树删除结点

将要删除的节点的值,与节点 root 节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以上操作直到找到一个节点的值等于删除的值,则将此节点删除。删除时有 4 中情况须分别处理:

  1. 删除节点不存在左右子节点,即为叶子节点,直接删除。
  2. 删除节点存在左子节点,不存在右子节点,直接把左子节点替代删除节点。
  3. 删除节点存在右子节点,不存在左子节点,直接把右子节点替代删除节点。
  4. 删除节点存在左右子节点,则取左子树上的最大节点或右子树上的最小 节点替换删除节点。

代码如下:

/************************
* 采用二叉搜索树上最大的结点
*************************/
int findMax(Btree* root)
{assert(root!=NULL);//方式一 采用递归/*if(root->rchild==NULL){return root->data;}return findMax(root->rchild);*///方式二 采用循环while(root->rchild){root = root->rchild;}return root->data;
}/************************
* 采用递归方式删除结点
*************************/```c
Btree* DeleteNode(Btree* root, int key) {if(root==NULL)return NULL;//没有找到删除节点if(root->data > key){root->lchild = DeleteNode(root->lchild, key);return root;}if(root->data < key){root->rchild = DeleteNode(root->rchild, key);return root;}//删除节点不存在左右子节点,即为叶子节点,直接删除if(root->lchild==NULL && root->rchild==NULL)return NULL;//删除节点只存在右子节点,直接用右子节点取代删除节点if(root->lchild==NULL && root->rchild!=NULL)return root->rchild;//删除节点只存在左子节点,直接用左子节点取代删除节点if(root->lchild!=NULL && root->rchild==NULL)return root->lchild;//删除节点存在左右子节点,直接用左子节点最大值取代删除节点int val = findMax(root->lchild);root->data=val;root->lchild = DeleteNode(root->lchild,val);return root;
}

2.4 二叉搜索树搜索

/************************
* 采用递归方式查找结点
*************************/
Bnode* queryByRec(Btree *root, ElemType e){if (root == NULL || isEqual(root->data, e)){return root;} else if(isLess(e, root->data)) {return queryByRec(root->lchild, e);} else {return queryByRec(root->rchild, e);}
}/**
* 使用非递归方式查找结点
*/
Bnode* queryByLoop(Bnode *root, int e){while(root != NULL && !isEqual(root->data, e)){if(isLess(e, root->data)){root = root->lchild;}else{root = root->rchild;}}return root;
}

2.5 二叉搜索树的遍历

二叉树的遍历是指从根结点出发,按照某种次序依次访问所有结点,使得每个结点仅被访问一次。共分为四种方式:

  • 前序遍历。
  • 中序遍历。
  • 后序遍历。
  • 层次遍历。

前序遍历 : 先访问根节点,然后前序遍历左子树,再前序遍历右子树。


代码如下:

/************************
* 采用递归方式实现前序遍历
*************************/
void PreOrderRec(Btree *root)
{if (root == NULL){return;}printf("- %d -", root->data);preOrderRec(root->lchild);preOrderRec(root->rchild);
}/*
前序遍历 - 非递归方式实现
具体过程:
首先申请一个新的栈,记为 stack;
将头结点 head 压入 stack 中;
每次从 stack 中弹出栈顶节点,记为 cur,然后打印 cur值,如果 cur 右孩子不为空,则将右孩子压入栈中;如果 cur 的左孩子不为空,将其压入 stack 中;
重复步骤 3,直到 stack 为空.
*/
void PreOrder(Btree *root)
{Bnode cur ;if (root == NULL){return;}SqStack stack;InitStack(stack);PushStack(stack, *root); //头节点先入栈while (!(IsEmpty(stack))) //栈为空,所有节点均已处理{PopStack(stack, cur); //要遍历的节点printf("- %d -", cur.data);if (cur.rchild != NULL){PushStack(stack, *(cur.rchild)); //右子节点先入栈,后处理}if (cur.lchild != NULL){PushStack(stack, *(cur.lchild)); //左子节点后入栈,接下来先处理}}DestroyStack(stack);
}

另一种版本的实现:

void BinarySortTreePrintFrontRecursive(BinarySortNode* root)
{if (!root) return;cout << root->e << endl;BinarySortTreePrintFrontRecursive(root->lChild);BinarySortTreePrintFrontRecursive(root->rChild);
}void BinarySortTreePrintFront(BinarySortTree* tree)
{if (!tree) return;BinarySortNode* node = tree;stack<BinarySortNode*> nodeStack;while ((node != NULL) || (nodeStack.size() > 0)){if (node != NULL){cout << node->e << endl;nodeStack.push(node);node = node->lChild;}else{node = nodeStack.top();nodeStack.pop();node = node->rChild;}}
}

中序遍历: 先访问根节点的左子树,然后访问根节点,最后遍历右子树。

void BinarySortTreePrintMiddleRecursive(BinarySortNode* root)
{if (!root) return;BinarySortTreePrintMiddleRecursive(root->lChild);cout << root->e << endl;BinarySortTreePrintMiddleRecursive(root->rChild);
}void BinarySortTreePrintMiddle(BinarySortNode* tree)
{if (!tree) return;BinarySortNode* node = tree;stack<BinarySortNode*> nodeStack;while ((node != NULL) || (nodeStack.size() > 0)){if (node != NULL){nodeStack.push(node);node = node->lChild;}else{node = nodeStack.top();nodeStack.pop();cout << node->e << endl;node = node->rChild;}}
}

后序遍历: 从左到右,先叶子后节点的方式遍历访问左右子树,最后访问根节点。

void BinarySortTreePrintTailRecursive(BinarySortNode* root)
{if (!root) return;BinarySortTreePrintTailRecursive(root->lChild);BinarySortTreePrintTailRecursive(root->rChild);cout << root->e << endl;
}void BinarySortTreePrintTail(BinarySortNode* tree)
{if (!tree) return;BinarySortNode* node = tree;stack<BinarySortNode*> nodeStack;stack<BinarySortNode*> output;while ((node != NULL) || (nodeStack.size() > 0)){if (node != NULL){nodeStack.push(node);output.push(node);node = node->rChild;}else{node = nodeStack.top();nodeStack.pop();node = node->lChild;}}while (!output.empty()){cout << output.top()->e << endl;output.pop();}
}

层序遍历: 从根节点从上往下逐层遍历,在同一层,按从左到右的顺序对节点逐个访问。

void BinarySortTreePrintLevel(BinarySortNode* tree)
{if (!tree) return;BinarySortNode* node = tree;queue<BinarySortNode*> nodeQueue;nodeQueue.push(node);while (!nodeQueue.empty()){node = nodeQueue.front();nodeQueue.pop();cout << node->e << endl;if (node->lChild != NULL){nodeQueue.push(node->lChild);}if (node->rChild != NULL){nodeQueue.push(node->rChild);}}
}

参考资料:

  1. C/C++从入门到精通-高级程序员之路【奇牛学院】

二叉搜索树的算法实现相关推荐

  1. C语言Catalan number卡特兰数(使用n个键可以搜索多少个二叉搜索树)的算法(附完整源码)

    C语言使用n个键可以搜索多少个二叉搜索树的算法 C语言使用n个键可以搜索多少个二叉搜索树的算法完整源码(定义,实现,main函数测试) C语言使用n个键可以搜索多少个二叉搜索树的算法完整源码(定义,实 ...

  2. 23王道数据结构二叉搜索树(BST)算法题(6-11题)总结(伪代码)

    6.判断给定的二叉树是否是二叉排序树 算法思想:中序遍历,一棵树为二叉排序树即左右子树为二叉排序树,且当前根节点和左右子树呈递增序列,对左右子树也是如此判断,显然是个递归过程              ...

  3. 二叉搜索树——插入、查找、删除

    二叉搜索树 二叉搜索树的特点 根节点的值大于左结点的值,小于右结点的值 根节点的左.右子树也是一个二叉搜索树 没有重复值 中序遍历得到的序列是从小到大排列的 二叉树的存储结构 typedef stru ...

  4. 【二叉搜索树专题】—— 相识到相知

    一.二叉搜索树的定义与基本操作: 什么是二叉搜索树? 二叉搜索树又叫二叉排序树和二叉查找树,对于二叉树中的结点的属性值是按照一定规律排列的 二叉搜索树的查找操作: 二叉搜索树的插入操作: 二叉搜索树的 ...

  5. 算法基础知识科普:8大搜索算法之二叉搜索树(下)

    由于微信发代码以及数学符号很吃力,所以我们做知识科普只能利用图片来做,本算法代码较多,所以分为三个部分来介绍.本篇把剩余的部分补齐.当然二叉搜索树也有自己的缺陷,即构造的二叉树跟数据的初始状态以及删除 ...

  6. 【每日一算法】二叉搜索树结点最小距离

    微信改版,加星标不迷路! 每日一算法-二叉搜索树节点最小距离 作者:阿广 阅读目录 1 题目 2 解析 1 题目 给定一个二叉搜索树的根结点 root, 返回树中任意两节点的差的最小值. 示例: 输入 ...

  7. 数据结构与算法 整理笔记---二叉搜索树

    二叉搜索树 查找问题是计算机中非常重要的基础问题 二分查找法 对于有序数组,才能使用二分查找法(排序作用) public class BinarySearch {public static int b ...

  8. 算法篇 - 二叉搜索树

    前言 在前端的工作当中,二叉搜索树不怎么常见,虽然没有快排.冒泡.去重.二分.希尔等算法常见,但是它的作用,在某些特定的场景下,是非常重要的. 目前es6的使用场景比较多,所以我准备能用es6的地方就 ...

  9. leetcode算法题--不同的二叉搜索树

    原题链接:https://leetcode-cn.com/problems/unique-binary-search-trees/ 相关题目:leetcode算法题--不同的二叉搜索树 II 1.递归 ...

最新文章

  1. plot参数详解python_30行Python代码实现3D数据可视化
  2. [转] Web前端优化之 内容篇
  3. 华为emui10是鸿蒙,昨日,华为EMUI10,鸿蒙系统正式发布!
  4. allegro16.3 中的orcad capture CIS 无法打开何解决?
  5. HR面 - 十大经典提问
  6. 接口测试基础——第5篇xlrd模块
  7. docker安装JDK
  8. python爬虫大众点评_Python爬虫(三)爬取大众点评网
  9. 生成对抗网络,从DCGAN到StyleGAN、pixel2pixel,人脸生成和图像翻译。
  10. 微信程序开发之小程序入门
  11. 悉尼大学计算机专业新生,2020年悉尼大学计算机科学专业课程设置难不难
  12. 80psi等于多少kpa_kpa与psi的换算(kpa与psi对照表)
  13. 编写MTK6737平台的GPIO驱动例程(二)
  14. 19年1月底得一些装机心得(一)
  15. 电子元器件手册中assert和deassert的含义
  16. 数据分析之excel和finebi报表可视化对比
  17. windows启动tomcat乱码问题
  18. Eclipse安装PMD插件
  19. [BZOJ]1064 [NOI2008] 假面舞会 dfs判环
  20. 让HR主动邀请你面试的简历,该如何写?

热门文章

  1. 改mysql修改界定符_dbvisualizer参数设置
  2. linux gcc 静态编译,GCC 程序编译的静态链接和动态链接
  3. makefile中变量有哪些?
  4. java各种包的用途
  5. [python] import后的模块是否能del移除掉
  6. 那些很骚很酷很有内涵的话
  7. 【LDPC/STBC】基于LDPC/STBC编译码的图像传输系统的MATLAB仿真
  8. 码code | 巧用2种方法,打破20条云开发数据库限制
  9. 组件生命周期管理和通信方案
  10. AI+云 华为开启智能时代新纪元