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.mzph.cn/news/180700.shtml

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

相关文章

AB|如何正确从罗克韦尔官网下载资料?

哈喽呀&#xff0c;大家好&#xff0c;我是雷工&#xff01; 作为工控行业的从业者&#xff0c;可能要和各个厂家的中控系统、PLC、触摸屏、变频器、等软硬件产品打交道。 虽然从业十余年&#xff0c;但也不可能接触使用过所有的工控产品。还有海量的产品是没有接触过的。 但很…

Programming Abstractions in C阅读笔记:p202-p234

《Programming Abstractions in C》学习第65天&#xff0c;p202-p234总结。 一、技术总结 完成第五章学习&#xff0c;第五章介绍递归在实际问题中的进一步应用&#xff0c;例如汉诺塔问题&#xff0c;数学中的排列问题&#xff0c;更有难度。使用递归解决问题时有时候需要借…

TOD和PPS精确时间同步技术

介绍 PPS和TOD PPS和TOD是两种用于精确时间同步的技术&#xff0c;它们在许多领域都有广泛的应用&#xff0c;总的来说&#xff0c;PPS和TOD被广泛应用于各种需要高度精确时间同步的领域&#xff0c;包括通信、测量、测试、系统集成和计算机网络等。 一、PPS PPS&#xff08…

“华为杯”研究生数学建模竞赛2019年-【华为杯】C题:视觉情报信息分析(附Python代码实现)

目录 摘 要: 一、问题重述 1.1 背景描述 1.2 要解决的问题 二、符号说明

【专题】支持向量机(Support Vector Machines,SVM)

​​​​​ ​​ 支持向量机&#xff08;Support Vector Machines&#xff0c;SVM&#xff09;是一种强大的监督学习模型&#xff0c;常用于分类、回归和异常值检测任务。它的核心思想是通过构建一个最大间隔超平面来有效地分隔不同类别的数据点。 在SVM中&#xff0c;数据点…

Mysql8.1.0 安装问题-缺少visual studio 2019x64组件

缺少visual studio x64组件的问题 使用Mysql8以上的安装包mysql-8.1.0-winx64.msi进行安装&#xff0c; 提示缺少visual studio 2019 x64可再发行组件 在微软官网下载vc可再发行程序包 Microsoft Visual C 可再发行程序包最新支持的下载 在Visual Studio 2015、2017、2019 和…

使字符串的单词倒序输出表示

题目 任务描述 本关任务&#xff1a;请实现函数 revWordoder&#xff0c;能够将 pa 指向的单词表字符串中的所有单词&#xff0c;按相反顺序放入 pb&#xff0c;同时去除多余的空格&#xff0c;单词之间只留一个空格. 例如 pa 中为 red blue, 则调用函数后&#xff0c;pb 中为b…

MIT线性代数笔记-第17讲-正交矩阵,Schmidt正交化

目录 17.正交矩阵&#xff0c; S c h m i d t Schmidt Schmidt正交化打赏 17.正交矩阵&#xff0c; S c h m i d t Schmidt Schmidt正交化 “标准”经常表示单位长度 标准正交基&#xff1a;由两两正交的单位向量组成的基 将标准正交基中的元素记作 q ⃗ 1 , q ⃗ 2 , ⋯ , q …

基于FactoryBean、实例工厂、静态工厂创建Spring中的复杂对象

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…

公司网站遇到HTTPS攻击,有什么办法解决

随着互联网的不断发展和进步&#xff0c;Web应用程序的重要性不断提升。Web应用程序往往需要承载大量的用户数据&#xff0c;在传输过程中&#xff0c;为了保护数据安全&#xff0c;往往使用HTTPS协议进行数据加密。但是&#xff0c;HTTPS协议也面临着各种攻击的威胁&#xff0…

会声会影2024旗舰版系统配置要求及格式支持

会声会影2024旗舰版是一款广受欢迎的视频编辑软件&#xff0c;它的最新版本&#xff0c;会声会影2023&#xff0c;已经发布。在这篇文章中&#xff0c;我们将探讨会声会影2024旗舰版系统配置要求及格式支持 会声会影2024是一款专业的视频剪辑软件&#xff0c;能够帮助用户制作高…

如何在h5和小程序中适配iphoneX及更高版本全面屏底部的安全区

关键词 IOS 前言 在项目开发的过程中&#xff0c;需要IOS全面屏底部安全区适配 步骤 h5需要设置页面属性&#xff1a; <meta name"viewport"content"widthdevice-width, initial-scale1, maximum-scale1, user-scalableno,viewport-fitcover">2.…

geemap学习笔记016:获取图像的基本属性和描述性信息

前言 遥感数据中通常包含众多信息&#xff0c;例如图像获取的时间、云覆盖量、以及每个波段的最大值最小值等等。 1 导入库并显示地图 import ee import geemapMap geemap.Map() Map2 添加图像数据 centroid ee.Geometry.Point([-122.4439, 37.7538]) #创建一个点坐标lan…

Spark SQL,DF,RDD cache常用方式

RDD中的cache 调用cache方法 val testRDD sc.parallelize(Seq(elementA, elementB, elementC)).map(x > (x._1, x._2)).setName("testRDD")testRDD.cache() dataframe中的cache 利用catalog以表的形式对数据进行缓存 import org.apache.spark.SparkConf impo…

腾讯面试真题(C语言)

一.题目 求123...n&#xff0c;要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句&#xff08;A?B:C&#xff09;。 二.题目剖析 首先题目要求不能用乘除&#xff0c;那么&#xff08;首相末项&#xff09;*项数/2就不能用&#xff0c;其次不…

springcloud==openfeign

单独使用 创建一个服务端 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.Path…

浅析函数防抖节流

防抖和节流都是前端开发中常用的优化性能的技术。 一、定义 防抖&#xff1a; 防抖指的是在事件触发后&#xff0c;在规定的时间内若再次触发&#xff0c;则重新计时&#xff0c;直到规定时间内没有再次触发事件&#xff0c;才执行事件处理。这样可以避免在短时间内频繁地触发…

Redis实战命令

实战命令 单值缓存 set key value get key 对象缓存 &#xff08;1&#xff09;set user:1 value(json格式) &#xff08;2&#xff09;mset user:1:name junfeng user:1:age 18 mget user:1:name user:1:age 分布式锁 分布式锁解决了什么问题&#xff1f; 分布式锁解…

2 线、3 线和 4 线 RTD 配置之间有什么区别?

电阻温度检测器 (RTD) 是温度传感器的一种&#xff0c;由于其准确性、可重复性和稳定性而广泛应用于各种工业应用。这些设备通过感测材料温度变化时电阻的变化来测量温度。 RTD 探头有多种配置&#xff0c;包括 2 线、3 线和 4 线型号。这些类型之间存在显着差异&#xff0c;在…

【干货】接口公共方法(字典表查询)

公共方法 import * as api from /api/commonAjax.js;//获取字典表 const getAjaxListCommonfunction(mm,backfun) {let ajaxFun""if(mm1){//字典表1ajaxFun"getAjax1"}else if(mm2){//字典表2ajaxFun"getAjax12"}api[ajaxFun]({}).then((res)&g…