11.27二叉查找树,遍历二叉树,层序(判断是不是完全二叉树),根据遍历序列重构二叉树,递归输入建树(树的定义,结构体细节,typedef)

如果left<=right,就表明其之间还有元素,即左右指针重合,区间只有一个元素也被包含其中;

left<right,就表明递归过程中,只允许区间有两个及以上的元素,不允许区间只有一个元素,那么对应地,当递归终止时,即left=right,恰好指向同一个元素

二分

int arr[100];
int search(int key, int begin, int end) {if (begin > end) {return -1;}int left = begin, right = end, mid = (left + right) >> 1;while (left <= right) {if(arr[mid]==key){return mid;}else if (arr[mid] < key) {left = mid + 1;}else {right = mid - 1;}mid = (left + right) >> 1;}return -1;
}

二叉查找树

ACD

重构二叉查找树,只需要除了中序遍历以外的其他任意一种遍历序列即可;因为二叉查找树的中序遍历是有序序列,所以只需要对前序序列或后序序列排序,即可得到该二叉树对应的中序序列,然后就是已知前序中序,已知后序中序重构二叉树

C

需要注意,前序序列的最后一个元素不一定是最大的,但一定是最后一个根节点的孩子节点,可能是左孩子,也可能是右孩子,也就是说,可能比最后的根节点大,也可能比最后的根节点小

中序遍历结果为有序时,就说明二叉树为二叉查找树。充分必要条件

左子树所有节点的值均小于根节点的值;右子树所有节点值均大于根节点值 

查找

递归

非递归

struct node {int data;node* lchild, * rchild;
};
node* search(node* bst, int key) {if (bst == nullptr || bst->data == key) {return bst;}else if (key < bst->data) {return search(bst->lchild, key);}else {return search(bst->rchild, key);}
}
node* search1(node* bst, int key) {node* ptr = bst;while (ptr != nullptr && ptr->data != key) {if (key < ptr->data) {ptr = ptr->lchild;}else {ptr = ptr->rchild;}}return ptr;
}

插入,只在没找到时进行 

插入的步骤,首先是要让当前节点变空,根据特定的方式找到相对应的应该是空的节点;

然后就是要让当前数据作为该节点的数据值,即把空结点变为非空结点

node* insert(node* bst, int key) {if (bst == nullptr) {bst = new node(key);}else if (key < bst->data) {bst->lchild = insert(bst->lchild, key);}else {bst->rchild = insert(bst->rchild, key);}return bst;
}

非递归 

node* insert1(node* bst, int key) {node* father = nullptr;node* cur = bst;while (cur != nullptr && cur->data != key) {father = cur;if (key < cur->data) {cur = cur->lchild;}else {cur = cur->rchild;}}//找到对应的空节点if (cur == nullptr) {//,如果结束后节点不为空,就说明该数据在该二叉树上,就不进行插入cur = new node(key);if (father == nullptr) {bst = cur;}else if (key < father->data) {father->lchild = cur;}else {father->rchild = cur;}}return bst;
}

 

 

BST的结构取决于元素插入的顺序

删除

分几种情况,删除节点为叶子节点,只有一个孩子节点或只有单一方向的节点,既有左子节点又有右子节点

就是中序序列中,该节点的相邻两节点 

删除

node* remove(node* bst, int key) {node* cur = bst;node* father = nullptr;while (cur != nullptr && cur->data != key) {father = cur;if (key < cur->data) {cur = cur->lchild;}else {cur = cur->rchild;}}//先找到对应的要删除的节点if (cur == nullptr) {//没找到,就说明不在树上,就不删除return bst;}//前驱节点,father有什么用?if (cur->lchild && cur->rchild) {node* tmp = cur;father = cur;cur = cur->rchild;while (cur->lchild != nullptr) {father = cur;cur = cur->lchild;}tmp->data = cur->data;}
}

3.做后序遍历

遍历二叉树,层序遍历

struct node {int data;node* lchild, * rchild;node(int x) :data(x), lchild(nullptr), rchild(nullptr) {}
};
void pre(node* root) {if (root == nullptr) {return;}cout << root->data;pre(root->lchild);pre(root->rchild);
}
void in(node* root) {if (root == nullptr) {return;}in(root->lchild);cout << root->data;in(root->rchild);
}

层序遍历

void levelorder(node* root) {if (root == nullptr) {return;}queue<node*>q;q.push(root);while (!q.empty()) {node* cur = q.front();q.pop();cout << cur->data << " ";if (cur->lchild) {q.push(cur->lchild);}if (cur->rchild) {q.push(cur->rchild);}}
}
struct node {int data;node* lchild, * rchild;node(int x) :data(x), lchild(nullptr), rchild(nullptr) {}
};void level(node* root) {if (root == nullptr) {return;}queue<node*>q;q.push(root);while (!q.empty()) {node* cur = q.front();q.pop();cout << cur->data << " ";if (cur->lchild) {q.push(cur->lchild);}if (cur->rchild) {q.push(cur->rchild);}}
}
注意事项

入队元素不是二叉树结点的数据域,是整个二叉树节点

那么在改变队列元素类型的时候:

要求队列元素为二叉树节点类型的指针,来接收节点地址 

层序遍历应用,检测是不是完全二叉树

思路就是层序遍历树,一旦遇到空结点,那么就退出;

如果是完全二叉树的话,那么这个空结点就是末尾,即已经遍历完了整棵树,

如果不是完全二叉树,就说明还有空缺,还有没遍历到的节点,也就是说队列里还有数据

bool complete(node* root) {if (root == nullptr) {return true;}queue<node*>q;q.push(root);while (!q.empty()) {node* cur = q.front();q.pop();if (cur == nullptr) {break;}//此时判断是不是完全二叉树,就需要左右子树都加入队列当中//如果是完全二叉树,就不会先遇到空结点,遇到就是最后的节点q.push(cur->lchild);q.push(cur->rchild);}if (q.empty()) {return true;}else {return false;}
}

根据遍历序列重构二叉树

前序+中序

先根据前序遍历找到根节点,在中序序列中找到根节点的位置,然后就可以在中序序列中区分出左子树与右子树。然后就得到左子树的节点数量,即中序序列中根节点下标减去此时中序序列的起点。那么对应右子树的节点数量就是根节点的下一个下标一直到中序序列结尾。

在说构建左子树时,前序遍历里左子树里的第一个就是左子树的根节点,而由于上层递归划分出了左子树的区域,即该区域里都是左子树里的节点,那么也就相当于是在左子树的区域里构建一个树,实际上也就是重复建树这一过程,即将建树的问题划分为了了两个相同的子问题

构建要返回,返回的就是树的根节点。每层递归,只构建一个节点,就是前序的根节点

怎么导入树的遍历序列?

用char*,有一个好处是可以直接截取相应的数组,只需要对数组名做修改就可以,就类似于sort函数里的。


int nfind(char* inorder, int size, char v) {for (int i = 0; i < size; i++) {if (inorder[i] == v) {return i;}}//在中序序列中找到树根的位置,找的值就是当时截取的前序序列里的第一个元素
}
node* build(char* preorder, char* inorder, int size) {//前两个参数都是该子树的区域的起点,只是起点,数组名就代表是开始的起点,第一个元素if (!size) {//然后size就代表是这个子树里的节点个数,前序与中序里的数量是一定一致的return nullptr;}char rd = preorder[0];//由于传入的是数组名,所以第一个元素就代表的是截取的数组里的第一个元素//即传入的数组和原来的大数组已经不是同一个数组了,是截取后的数组,所以可以直接取第一个元素,就是树根int leftsize = nfind(inorder, size, rd);node* root = new node(rd);//中序为左根右,先序为根左右,根已经构建了,所以中序依然不变(指数组头元素,即左子树里第一个元素依然在中序序列的头元素里),root->lchild = build(preorder + 1, inorder, leftsize);//变的是大小,size->leftsize,以及先序序列的头元素,由于是跟左右,所以往后一个就是左子树的序列,而且第一个元素就是左子树里的根节点root->rchild = build(preorder + 1 + leftsize, inorder + 1 + leftsize, size - 1 - leftsize);//先序序列里要越过左子树区域与根节点,中序序列里要越过左子树与根节点,所以加的数据都是一样的return root;
}//需要注意,右子树大小为size-1-leftsize,因为size包含了根节点,即由根节点,左子树区域,右子树区域构成,根节点已经构建了,所以也要被刨掉,所以多个-1,而不是区间长度的问题

后序+中序

后序是左右根,先根据后序序列找到根节点,以此找到根节点在中序序列中位置,以此在中序序列中划分出左右子树,在左右子树中,后序序列的最后一个元素都是其子树的根节点,能构建一层,就能递归向下构建出全部

int nfind(char* inorder, int size, char v) {for (int i = 0; i < size; i++) {//i就是要从0开始,因为是char*,传入的时候是被截取的数组,不是原来的长数组if (inorder[i] == v) {return i;}}return -1;
}
node* build(char* pastorder, char* inorder, int size) {if (!size) {return nullptr;}char rd = pastorder[size - 1];node* root =new node(rd);int leftsize = nfind(inorder, size, rd);//后序是左右根,所以在构建左子树时,左孩子的数组开头是不用动的,在右子树时,后序只需要越过左子树区域,即leftsize即可,而不需要根节点//在中序中是左根右,所以还需要越过1个根节点.右子树大小和之前一样,依然要减去左子树大小以及一个根节点大小root->lchild = build(pastorder, inorder, leftsize);root->rchild = build(pastorder + leftsize, inorder + 1 + leftsize, size - leftsize - 1);//后序是左右根return root;
}

前序+后序

通过先序中的第二个元素可以找到左子树的根节点,然后在后序中找这个节点,可以确定出左子树的大小,进而可以划分出左子树与右子树

先序+后序可以重建真二叉树的原因:
通过先序序列中的第二个元素可以找到左子树的根结点并且可以求出左子树的长度从而可以找到先序序列和后序序列中的左子树序列,然后递归调用,用整体长度减去左子树长度就可以求出右子树长度从而可以找到先序序列和后序序列中的右子树序列,然后递归调用,直到退化为长度为1的情况,并将其赋值,左右子树设置为NULL,返回根结点,重建二叉树完成

但是只能构建出真二叉树,即不能有度为1的节点

当作左子树处理就是说d是b的孩子,但是是左孩子还是右孩子不能确定

int nfind(char* order, int size, char v) {for (int i = 0; i < size; i++) {if (order[i] == v) {return i;}}return -1;
}
node* build(char* preorder, char* postorder, int size) {if (!size) {return nullptr;}char rd = preorder[0];node* root = new node(rd);int leftsize = nfind(postorder, size, preorder[1]);//先序序列的第二个元素就是此时左子树的根节点,在后序序列中找到,就可以确定其左子树大小root->lchild = build(preorder + 1, postorder, leftsize);root->rchild = build(preorder + 1 + leftsize, postorder + leftsize, size - 1 - leftsize);return root;
}

层序遍历

void level(node*& t) {queue<node*>q;int x;cin >> x;if (x != 0) {node* t = new node(x);q.push(t);}while (!q.empty()) {node* cur = q.front();q.pop();cin >> x;if (x != 0) {cur->lchild = new node(x);q.push(cur->lchild);}cin >> x;if (x != 0) {cur->rchild = new node(x);q.push(cur->rchild);}}
}

char*数组怎么输入并传入

 建树指针细节

这里传入的是一个节点指针的引用,所以在其中做修改后,可以切实修改唯一的树

这里之所以传入的是指针的引用是因为,这个树指针的节点是在上面,之前已经建立好的,这里是在对已经建好的头结点做修改

而之前没有传入,上面的话,是直接在函数里建立的新的节点,无所谓幅不副本,因为最后传出来的就是一个已经建立好的树,而之前的话,是要对原树,已经建立好的树节点做修改,所以是要传入指针的引用 

所以这个传入引用的函数,是不需要返回值的,void型即可;而不需要传入引用的函数,则需要返回值为节点指针型

new后返回的就是对应数据的指针类型,new BITNODE,就会返回节点的指针 

 

这里必须要写tree=new binode,即必须要建立一个新节点,在这里,即使最开始的树根是在外面定义好的(题目里的要求,固定格式),也必须要再定义一遍,因为在建树过程中会多次调用creat,一开始的树根有分配空间,但是它的左右孩子都没有明确分配空间,也就是都是空指针,没有明确指向某一区域,所以就需要在每个creat的过程里对传入的根节点进行new一下,分配一个空间,才能保证左右孩子指针确实是指向某一区域,而不是纯粹的空指针

结构体定义,typedef

有了typedef才能给结构体struct起别名 

如果不写typedef就在后面起别名,比如*tree之类的就会报错,但如果一旦写了,就可以自由起别名,在结构体的最后,加*就是指针,不加*就是同一结构体的别名

先序输入,构建树

*只是表明数据类型,实际名字上并不加*,加*就表明在定义时定义其为指针

比如node*lchild,node*rchild,在调用时,就是root->lchild,而不是root->*lchild;

这之中,*BiTree就代表说是节点BItnODE的指针,*BITREE就是BITNODE的指针。

那么再调用的时候,只用说明BITREE即可

实际上是在定义的时候,就是在名称前加的,node*lchild就表明lchild是Node的*,指针,而不是*lchild

void creat(tree& root) {char t;cin >> t;if (t == '#') {root = nullptr;}else {root = new node;root->data = t;creat(root->lchild);creat(root->rchild);}
}
void pre(tree root) {if (!root) { return; }cout << root->data;pre(root->lchild);pre(root->rchild);
}
int main() {tree root = new node;creat(root);pre(root);return 0;
}

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

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

相关文章

万户协同办公平台ezoffice SendFileCheckTemplateEdit.jsp接口存在SQL注入漏洞 附POC

@[toc] 万户协同办公平台ezoffice SendFileCheckTemplateEdit.jsp接口存在SQL注入漏洞 附POC 免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文…

如何拥有免费的docker镜像仓库

shigen日更文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 hello&#xff0c;伙伴们&#xff0c;最近在研究devops的事情&#xff0c;发现了很有意思的东西。 就是我们所有…

qgis添加arcgis的mapserver

左侧浏览器-ArcGIS地图服务器-右键-新建连接 Folder: / 展开-双击图层即可

西南科技大学模拟电子技术实验二(二极管特性测试及其应用电路)预习报告

目录 一、计算/设计过程 二、画出并填写实验指导书上的预表 三、画出并填写实验指导书上的虚表 四、粘贴原理仿真、工程仿真截图 一、计算/设计过程 说明:本实验是验证性实验,计算预测验证结果。是设计性实验一定要从系统指标计算出元件参数过程,越详细越好。用公式输入…

LLM、ChatGPT与多模态必读论文150篇

为了写本 ChatGPT 笔记&#xff0c;我和10来位博士、业界大佬&#xff0c;在过去半年翻了大量中英文资料/paper&#xff0c;读完 ChatGPT 相关技术的150篇论文&#xff0c;当然还在不断深入。 由此而感慨&#xff1a; 读的论文越多&#xff0c;你会发现大部分人对ChatGPT的技…

正则表达式 通配符 awk文本处理工具

目录 什么是正则表达式 概念 正则表达式的结构 正则表达式的组成 元字符 元字符点&#xff08;.&#xff09; 代表字符. 点值表示点需要转义 \ r..t 代表r到t之间任意两个字符 过滤出小写 过滤出非小写 space空格 [[:space:]] 表示次数 位置锚定 例&#xff1a…

【开源】基于Vue.js的海南旅游景点推荐系统的设计和实现

项目编号&#xff1a; S 023 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S023&#xff0c;文末获取源码。} 项目编号&#xff1a;S023&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四…

webpack如何设置兼容浏览器的范围​browserslist

Browserslist 是前端工程化不可或缺的工具&#xff0c;无论是处理 js 的 babel 还是处理 css 的 postcss&#xff0c;他们背后都有Browserslist 的身影。 一、如何查看查看所有浏览器和它的市场占有率 我们如何知道现在的浏览器那些被废弃、那些市场占有率高&#xff0c;可以…

基于若依的ruoyi-nbcio流程管理系统增加流程节点配置(二)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 上一节把数据库与相关基础数据字典准备好&#xff0c;下面就来实现相应的功能&#xff0c;目前先针对自定义…

数据结构-交换排序(冒泡、快速)

冒泡排序 基本思想 先将第一个记录与第二个记录比较&#xff0c;将较大的记录放到第二个位置上&#xff0c;之后再将第二个记录与第三 个记录比较&#xff0c;将较大的记录放到第三个位置上&#xff0c;如此类推&#xff0c;知道比较完最后一个位置&#xff0c;此时注意到 …

『 Linux 』进程优先级

文章目录 什么是优先级Linux下的进程优先级PRI与NI使用top查看进程以及对进程的优先级的修改 进程优先级的其他概念竞争性与独立性并发与并行 什么是优先级 优先级,顾名思义,即在同一环境下不同单位对同一个资源的享有顺序; 一般优先级高的单位将优先占有该资源; 在进程当中进…

万宾科技第四代可燃气体监测仪的作用

燃气作为一种重要的能源已在居民生活、工业生产和商业活动等领域得到了广泛的应用。但是与之而来的便是各种各样的燃气管网的安全问题&#xff0c;其中燃气管网泄漏成为了城市生命线建设中亟待解决的安全隐患。因此采取切实有效的措施来保障燃气管网的安全运行&#xff0c;应用…

Matlab 点云曲率计算(之二)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 之前已经讨论过许多关于计算曲率的问题,这里使用一个通过拟合三次曲面方程的方式来计算曲率,计算过程如下图所示: 二、实现代码 %********

centos7.9 + gitlab12.3.0安装

本文在centos7.9操作系统上安装gitlab 12.3.0&#xff0c;gitlab官方最新的版本已经是16.6.0了&#xff0c;这里仍然安装12.3.0版本的原因是汉化包的最新版本是12.3.0&#xff0c;如果汉化包的版本和gitlab的版本不对应&#xff0c;会出现汉化他无法启动的现象。 1、安装依赖 …

4/150:寻找两个正序数组的中位数⭐

题目&#xff1a;寻找两个正序数组的中位数 给定两个大小分别为 m 和 n 的正序&#xff08;从小到大&#xff09;数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 算法的时间复杂度应该为 O(log (mn)) 。 题解1&#xff1a;暴力 暴力思路简介&#xff0c;…

服务器bash进程占用cpu过多疑似中挖矿病毒记录

发现过程 因为我有使用conky的习惯&#xff0c;也就是在桌面上会显示cpu和内存的占用情况&#xff0c;由于服务器不止我一个人使用&#xff0c;最近发现好几次我同学的账户下的bash进程占用特别多&#xff0c;问了他之后&#xff0c;他也说他几次都是没有使用过bash相关服务&a…

029 - STM32学习笔记 - ADC(三) 独立模式单通道DMA采集

029 - STM32学习笔记 - 单通道DMA采集&#xff08;三&#xff09; 单通道ADC采集在上节中学习完了&#xff0c;这节在上节的内容基础上&#xff0c;学习单通道DMA采集。程序代码以上节的为基础&#xff0c;需要删除NVIC配置函数、中段服务子程序、R_ADC_Mode_Config()函数中使能…

JSP forEach 标签遍历map集合

之前我们说了 普通list 单纯按数量循环 bean类型list的遍历方式 那么 我们forEach标签 也能循环map语法非常简单&#xff0c;和循环list基本是一样的 我们直接上jsp代码 <% page import"java.util.Map" %> <% page import"java.util.HashMap" %…

2023网络安全产业图谱

1. 前言 2023年7月10日&#xff0c;嘶吼安全产业研究院联合国家网络安全产业园区&#xff08;通州园&#xff09;正式发布《嘶吼2023网络安全产业图谱》。 嘶吼安全产业研究院根据当前网络安全发展规划与趋势发布《嘶吼2023网络安全产业图谱》调研&#xff0c;旨在进一步了解…