数据结构——【万字文章+配图+代码】带你深入理解二叉树

1. 二叉树的概念

二叉树是一种有限集合,由根和左右子树构成,每个结点最多有两棵子树,且这两棵子树具有顺序关系
请添加图片描述

2. 二叉树的特殊情况:

2.1 满二叉树:

一个二叉树,如果每次的结点都达到最大值,那么这个数就是满二叉树。如果一个二叉树有k层,且结点的总个数为2^k^-1,则它就是一个满二叉树

请添加图片描述

2.2 完全二叉树:

如果设二叉树的深度为h,那么除了第h层外,其它各层 (即1~h-1层) 的结点数都达到最大个数;同时,第h层所有的结点都连续集中在最左边。满二叉树是一种特殊的完全二叉树。

请添加图片描述

3. 二叉树的性质:

  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2(i-1)个结点。(第一层1个,第二层2个,第三层4个,第四层8个……)请添加图片描述

  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2h-1请添加图片描述

  3. 对任何一颗二叉树,如果度为0其叶结点个数为n0,度为2的分支结点个数为n2,则有n0=n2+1

  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度为h=log2(n+1)

  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,对于序号为i的结点有:

    1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点
    2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左子树
    3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右子树

4. 二叉树的存储结构

4.1 顺序结构

二叉树顺序存储在物理上是一个数组,在逻辑上是一棵二叉树

请添加图片描述

4.2 链式结构

二叉树的链式存储结构是指,用链接来表示一颗二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给处该结点的左孩子和右孩子所在链结点的存储地址。

请添加图片描述

5. 二叉树的实现

5.1 二叉树的创建

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{//如果数组已经遍历完或者当前节点的是空姐发,则返回空指针if (*pi >= n || a[*pi] == '#'){//当前节点为空,返回NULL(*pi)++;return nullptr;}//创建新节点并赋初值BTNode* root = new BTNode;root->_data = a[*pi];root->_left = nullptr;root->_right = nullptr;(*pi)++;root->_left = BinaryTreeCreate(a, n, pi);root->_right = BinaryTreeCreate(a, n, pi);return root;
}

5.2 二叉树的销毁

//二叉树销毁
void BinaryTreeDestory(BTNode** root)
{if (*root == nullptr){//如果当前节点为空直接返回return;}//递归销毁左子树和右子树BinaryTreeDestory(&((*root)->_left));BinaryTreeDestory(&((*root)->_right));//释放当前节点的内容free(*root);*root = nullptr;
}

5.3 二叉树节点个数

//二叉树节点个数
int BinaryTreeSize(BTNode* root)
{if (root == nullptr){//如果当前节点为空返回0return 0;}//返回左子树节点个数+右子树节点个数+1return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}

5.4 二叉树叶子节点个数

//二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{if (root == nullptr){//如果当前节点为空,返回0return 0;}if (root->_left == nullptr && root->_right == nullptr){//如果当前节点是叶子节点,返回1return 1;}//返回左子树叶子节点个数+右子树叶子节点个数return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}

5.5 二叉树第k层节点个数

int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == nullptr){//如果当前节点为空,返回0return 0;}if (k == 1){//如果当前节点是第k层节点,返回1return 1;}//返回左子树第k-1层节点个数+右子树第k-1层节点个数int leftk = BinaryTreeLevelKSize(root->_left, k - 1);int	rightk = BinaryTreeLevelKSize(root->_right, k - 1);return leftk + rightk;
}

5.6 二叉树查找值为x的节点

//二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == nullptr){//如果当前节点为空,返回nullptr}if (root->_data == x){//如果当前节点的值等于x,返回指向当前节点的指针return root;}//在左子树中查找xBTNode* lret = BinaryTreeFind(root->_left, x);//如果在左子树中找到了x,返回指向左子树中x的节点的指针if (lret) return lret;//在右子树中查找xBTNode* rret = BinaryTreeFind(root->_right, x);//如果在右子树中找到了x,返回指向右子树中x的节点的指针if (rret)return rret;//否则返回nullptrreturn nullptr;
}

5.7 二叉树的前序遍历

//二叉树的前序遍历 根节点->左子树->右子树
void BinaryTreePrevOrder(BTNode* root)
{if (root == nullptr){//如果当前节点为空,直接返回return;}//打印当前节点的值,并递归打印左子树和右子树cout << root->_data << " ";BinaryTreePrevOrder(root->_left);BinaryTreePrevOrder(root->_right);
}

5.8 二叉树的中序遍历

//二叉树的中序遍历 左子树->根节点->右子树
void BinaryTreeInOrder(BTNode* root)
{if (root == nullptr){//如果当前节点为空,直接返回return;}//递归打印左子树,打印当前节点的值,再递归打印右子树BinaryTreeInOrder(root->_left);cout << root->_data << " ";BinaryTreeInOrder(root->_right);
}

5.9 二叉树的后序遍历

//二叉树的后序遍历,左子树->右子树->根节点
void BinaryTreePostOrder(BTNode* root)
{if (root == nullptr){//如果当前节点为空,直接返回return;}//递归打印左子树和右子树,再打印当前节点的值BinaryTreePostOrder(root->_left);BinaryTreePostOrder(root->_right);cout << root->_data << " ";
}

5.10 二叉树的层序遍历

//二叉树的层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{if (root == nullptr)//如果根节点为空,直接返回{return;}queue<BTNode*> q;//创建一个队列,用于存储待访问的节点q.push(root);//将根节点入队while (!q.empty())//当队列不为空时执行循环{BTNode* node = q.front();//取出队列前面的节点q.pop();//将该节点出队cout << node->_data <<" ";//输出节点数据if (node->_left != nullptr)//如果左子节点不为空,将其入队{q.push(node->_left);}if (node->_right != nullptr)//如果右子节点不为空,将其入队{q.push(node->_right);}}
}

5.11 判断二叉树是否是完全二叉树

//判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{if (root == nullptr) //如果根节点为空,则空树是完全二叉树{return 1;}BTNode* queue[1000];//定义一个队列用于存储待访问的节点int front = 0, rear = 0;//初始化队列的前后指针queue[rear++] = root;//将根节点入队int flag = 0;//标记是否存在空树while (front < rear)//当前队列不为空时,继续循环{BTNode* node = queue[front++];//取出队列前面的节点if (node->_left != nullptr)//如果左子树不为空{//如果之前出现过空子树,则该树不是完全二叉树if (flag)//如果之前出现过空子树,则该数不是完全二叉树{return 0;}queue[rear++] = node->_left;//将左子节点入栈}else//如果左子树为空{flag = 1;//标记存在空子树}if (node->_right != nullptr)//如果右子节点不为空{//如果之前出现过空子树,则该树不是完全二叉树if (flag){return 0;}queue[rear++] = node->_right;//将右子节点入队}else//如果右子节点为空{flag = 1;//标记存在空子树}}//如果队列中所有节点均无左右孩子,则该树是完全二叉树return 1;
}

5.12 二叉树完整代码

#include<stdlib.h>
#include<iostream>
#include<queue>
using namespace std;
typedef int BTDataType;
//创建一个结构体表示节点
typedef struct BinaryTreeNode
{BTDataType _data;//数据域struct BinaryTreeNode* _left;//左指针域struct BinaryTreeNode* _right;//右指针域
}BTNode;// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{//如果数组已经遍历完或者当前节点的是空姐发,则返回空指针if (*pi >= n || a[*pi] == '#'){//当前节点为空,返回NULL(*pi)++;return nullptr;}//创建新节点并赋初值BTNode* root = new BTNode;root->_data = a[*pi];root->_left = nullptr;root->_right = nullptr;(*pi)++;root->_left = BinaryTreeCreate(a, n, pi);root->_right = BinaryTreeCreate(a, n, pi);return root;
}//二叉树销毁
void BinaryTreeDestory(BTNode** root)
{if (*root == nullptr){//如果当前节点为空直接返回return;}//递归销毁左子树和右子树BinaryTreeDestory(&((*root)->_left));BinaryTreeDestory(&((*root)->_right));//释放当前节点的内容free(*root);*root = nullptr;
}//二叉树节点个数
int BinaryTreeSize(BTNode* root)
{if (root == nullptr){//如果当前节点为空返回0return 0;}//返回左子树节点个数+右子树节点个数+1return BinaryTreeSize(root->_left) + BinaryTreeSize(root->_right) + 1;
}//二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{if (root == nullptr){//如果当前节点为空,返回0return 0;}if (root->_left == nullptr && root->_right == nullptr){//如果当前节点是叶子节点,返回1return 1;}//返回左子树叶子节点个数+右子树叶子节点个数return BinaryTreeLeafSize(root->_left) + BinaryTreeLeafSize(root->_right);
}//二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == nullptr){//如果当前节点为空,返回0return 0;}if (k == 1){//如果当前节点是第k层节点,返回1return 1;}//返回左子树第k-1层节点个数+右子树第k-1层节点个数int leftk = BinaryTreeLevelKSize(root->_left, k - 1);int	rightk = BinaryTreeLevelKSize(root->_right, k - 1);return leftk + rightk;
}//二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == nullptr){//如果当前节点为空,返回nullptr}if (root->_data == x){//如果当前节点的值等于x,返回指向当前节点的指针return root;}//在左子树中查找xBTNode* lret = BinaryTreeFind(root->_left, x);//如果在左子树中找到了x,返回指向左子树中x的节点的指针if (lret) return lret;//在右子树中查找xBTNode* rret = BinaryTreeFind(root->_right, x);//如果在右子树中找到了x,返回指向右子树中x的节点的指针if (rret)return rret;//否则返回nullptrreturn nullptr;
}//二叉树的前序遍历 根节点->左子树->右子树
void BinaryTreePrevOrder(BTNode* root)
{if (root == nullptr){//如果当前节点为空,直接返回return;}//打印当前节点的值,并递归打印左子树和右子树cout << root->_data << " ";BinaryTreePrevOrder(root->_left);BinaryTreePrevOrder(root->_right);
}//二叉树的中序遍历 左子树->根节点->右子树
void BinaryTreeInOrder(BTNode* root)
{if (root == nullptr){//如果当前节点为空,直接返回return;}//递归打印左子树,打印当前节点的值,再递归打印右子树BinaryTreeInOrder(root->_left);cout << root->_data << " ";BinaryTreeInOrder(root->_right);
}//二叉树的后序遍历,左子树->右子树->根节点
void BinaryTreePostOrder(BTNode* root)
{if (root == nullptr){//如果当前节点为空,直接返回return;}//递归打印左子树和右子树,再打印当前节点的值BinaryTreePostOrder(root->_left);BinaryTreePostOrder(root->_right);cout << root->_data << " ";
}//二叉树的层序遍历
void BinaryTreeLevelOrder(BTNode* root)
{if (root == nullptr)//如果根节点为空,直接返回{return;}queue<BTNode*> q;//创建一个队列,用于存储待访问的节点q.push(root);//将根节点入队while (!q.empty())//当队列不为空时执行循环{BTNode* node = q.front();//取出队列前面的节点q.pop();//将该节点出队cout << node->_data <<" ";//输出节点数据if (node->_left != nullptr)//如果左子节点不为空,将其入队{q.push(node->_left);}if (node->_right != nullptr)//如果右子节点不为空,将其入队{q.push(node->_right);}}
}//判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root)
{if (root == nullptr) //如果根节点为空,则空树是完全二叉树{return 1;}BTNode* queue[1000];//定义一个队列用于存储待访问的节点int front = 0, rear = 0;//初始化队列的前后指针queue[rear++] = root;//将根节点入队int flag = 0;//标记是否存在空树while (front < rear)//当前队列不为空时,继续循环{BTNode* node = queue[front++];//取出队列前面的节点if (node->_left != nullptr)//如果左子树不为空{//如果之前出现过空子树,则该树不是完全二叉树if (flag)//如果之前出现过空子树,则该数不是完全二叉树{return 0;}queue[rear++] = node->_left;//将左子节点入栈}else//如果左子树为空{flag = 1;//标记存在空子树}if (node->_right != nullptr)//如果右子节点不为空{//如果之前出现过空子树,则该树不是完全二叉树if (flag){return 0;}queue[rear++] = node->_right;//将右子节点入队}else//如果右子节点为空{flag = 1;//标记存在空子树}}//如果队列中所有节点均无左右孩子,则该树是完全二叉树return 1;
}int main()
{BTDataType a[] = { 'A', 'B', 'D', '#', '#', 'E', '#', 'H', '#', '#', 'C', 'F', '#', '#', 'G', '#', '#' };int n = sizeof(a) / sizeof(a[0]);int i = 0;BTNode* root = BinaryTreeCreate(a, n, &i);cout<<"二叉树节点个数:" << BinaryTreeSize(root) << endl;cout << "二叉树叶子节点的个数:" << BinaryTreeLeafSize(root) << endl;cout << "二叉树第1层节点个数:"<<BinaryTreeLevelKSize(root,1)<<endl;cout << "二叉树第2层节点个数:" << BinaryTreeLevelKSize(root, 2) << endl;cout << "二叉树第3层节点个数:" << BinaryTreeLevelKSize(root, 3) << endl;cout << "二叉树查找值为x的节点:"<<BinaryTreeFind(root,'A') << endl;cout << "二叉树的前序遍历:";BinaryTreePrevOrder(root);cout << endl;cout << "二叉树的中序遍历:";BinaryTreeInOrder(root);cout << endl;cout << "二叉树的后序遍历:";BinaryTreePostOrder(root);cout << endl;cout << "二叉树的层序遍历:";BinaryTreeLevelOrder(root);cout << endl;if (BinaryTreeComplete(root) == 1){cout << "完全二叉是树" << endl;}else{cout << "不是完全二叉树" << endl;}return 0;
}

6. 二叉搜索树

6.1 二叉搜索树概念

二叉搜索搜又称二叉排序树,它或者是一颗空树

  1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

  2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

  3. 它的左右子树也分别为二叉搜索树

6.2 二叉搜索树的查找

  1. 从根开始比较,查找,比根大的往右边走查找,比根小则往左走查找
  2. 最多查找高度次,走到空,还没找到,这个值不存在

6.3 二叉搜索树的插入

  1. 树为空,则直接新增节点,赋值给root指针

  2. 树不空,按二叉搜索树性质查找插入位置,插入新节点

6.4 二叉搜索树的删除

首先查找元素是否在二叉树中,如果不存在,则返回,否则要删除的结点可能分下面四种情况

  1. 要删除的结点无孩子结点
  2. 要删除的结点只有左孩子结点
  3. 要删除的结点只有右孩子结点
  4. 要删除的结点有左、右孩子结点

看起来有待删除结点有4种情况,实际情况1可以与情况2或者3合起来,因此真正的删除过程如下:

情况2:删除该结点且使被删除结点的双亲结点指向被删除节点的左孩子结点——直接删除

情况3:删除该结点且使被删除结点的双亲结点指向被删除结点的右孩子结点——直接删除

情况4:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除结点中,再来处理该结点的删除问题——替换法删除

6.5 二叉搜索树的实现

#include <iostream>
using namespace std;struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};class BST {
public:TreeNode* insert(TreeNode* root, int val) {if (root == NULL) {return new TreeNode(val);}if (val < root->val) {root->left = insert(root->left, val);}else if (val > root->val) {root->right = insert(root->right, val);}return root;}void inorderTraversal(TreeNode* root) {if (root == NULL) {return;}inorderTraversal(root->left);cout << root->val << " ";inorderTraversal(root->right);}
};int main() {BST bst;TreeNode* root = NULL;root = bst.insert(root, 5);root = bst.insert(root, 3);root = bst.insert(root, 7);root = bst.insert(root, 2);root = bst.insert(root, 4);root = bst.insert(root, 6);root = bst.insert(root, 8);cout << "中序遍历二叉搜索树:";bst.inorderTraversal(root);cout << endl;return 0;
}

6.6 二叉搜索树的应用

  1. K模型:K模型即只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值

  2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key,Value>的键值对,比如:英汉词典、统计单词次数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/284920.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

谈谈你知道的设计模式?请手动实现单例模式 , Spring 等框架中使用了哪些模式?

文章目录 谈谈你知道的设计模式请手动实现单例模式Spring等框架中使用哪些设计模式&#xff1f;设计模式分类 谈谈你知道的设计模式 我们知道 InputStream 是一个抽象类&#xff0c;标准类库中提供了 FileInputStream、ByteArrayInputStream 等各种不同的子类&#xff0c;分别…

全球移动通信(2G/3G/4G/5G)频谱分布情况

一、概述 随着通信技术的不断发展&#xff0c;全球各国都在积极推进2G、3G、4G、5G网络的建设和应用。根据FCC统计&#xff0c;目前全球移动通信频谱分布如下&#xff1a; 二、分布 &#xff08;一&#xff09;俄罗斯 2G&#xff1a;主要使用900MHz和1800MHz两个频段。其中&…

【MYSQL】-库的操作

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

6.s081操作系统Lab4: trap

文章目录 chapter 4概览4.1 CPU trap流程使用寄存器如果cpu想处理1个trap 4.2 用户态引发的trap4.2.1 uservec4.2.2 usertrap4.2.3 usertrapret和userretusertrapretuserret Lab4Backtrace (moderate)Alarm (hard) chapter 4 概览 trap的场景&#xff1a;系统调用&#xff0c…

Vue3-22-组件-插槽的使用详解

插槽是干啥的 插槽 就是 组件中的一个 占位符&#xff0c; 这个占位符 可以接收 父组件 传递过来的 html 的模板值&#xff0c;然后进行填充渲染。 就这么简单&#xff0c;插槽就是干这个的。要说它的优点吧&#xff0c;基本上就是可以使子组件的内容可以被父组件控制&#xf…

【️接口和抽象类的区别,如何选择?】

✅接口和抽象类的区别&#xff0c;如何选择&#xff1f; ✅ 接口和抽象类的区别✅方法定义✅修饰符✅构造器✅继承和实现✅单继承 、 多实现✅职责不同 ✅什么是模板方法模式&#xff0c;有哪些应用呢&#xff1f;✅典型理解✅示例&#x1f4a1;思考 ✅你在工作中是如何使用设计…

PythonStudio:国人开发的python窗体IDE,学生管理系统

国人开发的python窗体设计IDE&#xff0c;详情请看&#xff1a;PythonStudio&#xff1a;一款国人写的python及窗口开发编辑IDE&#xff0c;可以替代pyqt designer等设计器了-CSDN博客 这个软件作者还录制了入门的教程&#xff0c;跟着视频做&#xff0c;是个不错的python视频…

mysql使用全文索引+ngram全文解析器进行全文检索

表结构&#xff1a;表名 gamedb 主键 id 问题类型 type 问题 issue 答案 answer 需求 现在有个游戏资料库储存在mysql中&#xff0c;客户端进行搜索&#xff0c;需要对三个字段进行匹配&#xff0c;得到三个字段的相关性&#xff0c;选出三个字段中相关性最大的值进…

Python---进程

1. 进程的介绍 在Python程序中&#xff0c;想要实现多任务可以使用进程来完成&#xff0c;进程是实现多任务的一种方式。 2. 进程的概念 一个正在运行的程序或者软件就是一个进程&#xff0c;它是操作系统进行资源分配的基本单位&#xff0c;也就是说每启动一个进程&#xf…

第三十五周:文献阅读+Self-attention

目录 摘要 Abstract 文献阅读&#xff1a;基于LSTM和注意机制的水质预测 现有问题 提出方法 前提点 1. LSTM 2. 注意力机制 研究模型&#xff08;AT-LSTM&#xff09;结构 模型验证 总结AT-LSTM优于LSTM的方面 Self-attention&#xff08;自注意力机制&#xff09;…

机器学习——支持向量机

目录 一、基于最大间隔分隔数据 二、寻找最大间隔 1. 最大间隔 2. 拉格朗日乘子法 3. 对偶问题 三、SMO高效优化算法 四、软间隔 五、SMO算法实现 1. 简化版SMO算法 2. 完整版SMO算法 3. 可视化决策结果 六、核函数 1. 线性不可分——高维可分 2. 核函数 …

100GPTS计划-AI翻译TransLingoPro

地址 https://poe.com/TransLingoPro https://chat.openai.com/g/g-CfT8Otig6-translingo-pro 测试 输入: 我想吃中国菜。 预期翻译: I want to eat Chinese food. 输入: 请告诉我最近的医院在哪里。 预期翻译: Please tell me where the nearest hospital is. 输入: 明天…