当前位置: 首页 > news >正文

c++_二叉树的介绍

内存模型

一.内存中有代码区;栈区;数据段 堆区 

1、栈区存放了函数所有局部变量和形参;

它的局限在于:局部变量和形参的生存期;即函数返回后对象就会被回收

解决方案是:1)使用全局变量 (存放在在数据段)2) 使用堆空间

2.&的作用是什么?

==>在定义语句中应用表示定义了一个引用;在非定义语句中表示取地址;

int main(){
    int i=10;
    int *p;//p是一个指针变量,打算指向一个int变量
    p=&i;//取出i的地址 放入p中
    ++* p;//根据p找到指向的数据,再对数据做一个自增
}

3.指针变量是一个存储地址的变量;

注意:不要返回局部变量的地址;因为可能指针最终指向的内存空间已被释放而不存在;

故我们需要使用堆空间:使用new delete 来进行申请空间和释放空间.他们的语法如下:

注意:我们不推荐使用new的动态数组,而推荐优先使用局部变量/全局变量/vector

new type;//int,float/double/自定义
new type[length];// 申请长度为length的动态数组(不推荐使用)

我们来举个例子介绍再函数中使用new,delete;然后返回指针的用法:

int* func(){
int* p=new int;
*p=10;
return p;
}
​
int main(){
int* p;
p1=func();
++* p1;
delete p1;
return 0;
}

代码分析:以上的代码的示意图如下,由于我们的整数是在堆区中开辟的一段内存,func()和main()传递的有只有指针,所以尽管func()的声明周期结束了;堆区的这块区域也不会消失;且main()中的p1指针仍然在指着它。

二叉树(层序建树)

树在内存的存储有两种形式:

顺序存储:只适用于一些特殊的树,例如,完全二叉树

链式存储:每一个节点有数据域和指针域(left,right,parent等)

struct可以自定义类型

往树种插入节点的方式不唯一,以下的例子我们使用层序建树。

先分析以下,若给定序列abc##de#g##f###(空域表示为#),我们怎么进行建树呢?

==》层序建树需要使用一个队列维持记录插入的信息;

1.我们会有一个proot指针指向根节点,我们这里的根节点是a。

所以在一开始,我们要先创建树节点a,作为根节点插入;即

TreeNode a;
proot=&a;

2、因为a的两个指针现在还没有连上其他的节点,但它将来有机会去连其他的节点;故当a节点插入时,我们就将其left和right的位置入队再来新的数据(若不是#),创建树节点,获取队首,出队,以让新节点插入到这个位置。再让这个新节点的左右指针入队;

3.若数据是一个#,则直接让队首出队;(以表示其连的是一个空域)

#define  _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include<string>
#include<cstdio>
#include<vector>
#include<set>
#include<algorithm>
#include<queue>
using namespace std;
​
struct TreeNode {
    int data;
    TreeNode* left;//这里必须使用指针,指针的长度我们是知道的
    TreeNode* right;
};
​
​
//队列成员
struct  QueueNode {
    TreeNode* parent;
    //用来表示这个节点保存的位置的左孩子是否为空
    bool isLeft;
};
​
​
​
void BuildTree(TreeNode*& proot, queue< QueueNode*>& pos, char data) {
    //判断data是否为就#
    if (data != '#') {
        //申请一个树节点
        TreeNode* pNew = new TreeNode;
        //修改数据域
        //(*pNew).data = data;//.的优先级高于*
        pNew->data = data;//->和(*).是等价
        //申请一个队列节点
        QueueNode* pQueueNode = new QueueNode;
        pQueueNode->parent = pNew;//在队列节点中,保存刚创建的新节点的位置
        pos.push(pQueueNode);
        //插入
        //若为根节点
        if (proot == NULL) {
            proot = pNew;
        }
        else {
            //若不为根节点;则从队列中找到这个节点要插入的位置
            QueueNode* pCur = pos.front();
            if (pCur->isLeft == false) {
                pCur->parent->left = pNew;
                pCur->isLeft = true;
            }
            else {
                pCur->parent->right = pNew;
                pos.pop();
                delete pCur;
            }
        }
    }
    else {
        //若当前根节点为空,直接无需理会;若不为空,则得到队列中弹出一个保存的地址,令其左域或右域指向为空。
        if (proot != NULL) {
            QueueNode* pCur = pos.front();
            if (pCur->isLeft == false) {
                pCur->parent->left = NULL;
                pCur->isLeft = true;
            }
            else {
                pCur->parent->right = NULL;
                pos.pop();
                delete pCur;
            }
        }
    }
}
​
​
int main() {
    TreeNode* proot = NULL;// 有一个指向根节点的指针
    //若proot为NULL,说明这是一个空树
    char data;
    while (1) {
        scanf("%c", &data);
        if (data == '\n') {
            break;
        }
        queue<QueueNode*> pos;
        BuildTree(proot, pos, data);
    }
    return 0;
}

代码分析:

1.上述使用QueueNode表示队列节点,是可以被插入队列queue中的,为什么queue中要保存的是QueueNode的指针,而不直接是QueueNode节点?

1)避免拷贝开销:指针的大小是固定的(通常为 4 或 8 字节),拷贝指针的开销远小于拷贝整个 QueueNode 对象的开销。

2)动态内存管理:通过指针,可以动态地创建和销毁 QueueNode 对象,而不需要依赖 queue 的生命周期。

3)共享数据:指针可以指向同一个 QueueNode 对象,从而在多个地方共享数据,而不需要复制数据。

因此,存储 QueueNode* 指针是一种更高效、更灵活的方式。

2.QueueNode中的parent保存的是节点的地址;以至于我们可以使用

pCur->parent->left 来表示其左指针域指向

pCur->parent->right 来表示其右指针域指向

请品味这里的parent保存节点的地址的好处?

==>因为我们要实现的是让队列中的节点能够使得节点a的左指针域进而右指针域在某一时刻被赋值,故队列节点保存节点a的地址是最好的。因为这样我们就可以使用指针来达到我们的效果。

3.在输入不是#时,我们要从队列中弹出一个位置,以让新节点插入这个位置的左指针域或右指针域,这时需要判断队列是否为空。即代码应该补上!pos.empty()

  if (proot != NULL && !pos.empty()) {

树的遍历

树的遍历分为2种,即广度优先(层序遍历,需要辅助队列)和深度优先(更常见,需要借助递归机制实现)

深度优先中根据遍历顺序分为:先序,中序和后序

层序遍历

void levelOrder(TreeNode* proot){
//简历一个队列
queue<TreeNode*>pos;
//先将根节点的指针入队
pos.push(proot);
//循环条件:队列不为空
while(pos.empty()==false){
    //获取当前队首元素
    TreeNode* pCur=pos.front();
    //弹出队首元素,并打印输出,并将其左右孩子的指针入队
    pos.pop();
    printf("%c",pCur->data);
    
    if(pCur->left!=NULL){
    pos.push(pCur->left);
    }
    if(pCur->right!=NULL){
    pos.push(pCur->right);
    }
}
  printf("\n");  
    
}

代码分析:

上述想要层序遍历树;使用了辅助队列;存储的依然是指针;即存储的是 TreeNode* 指针,而不是 TreeNode 对象本身。(想象一下,一个个的节点是实体,但是都由一个个的指针指着,而我们操作的就是这一个个的指针的指向,而不动实体,实在是一种很轻巧的操作)

这样做的好处我们上面已经分析过了。

先序遍历

参数:根节点指针

返回值:无

处理:递归进行树的遍历

1)出口:当前指向为空域

2)工作:打印当前节点,再递归遍历其左子树;再递归遍历其右子树

void preOrder(TreeNode* proot){
if(proot==NULL){
    return;
}else{
    printf("%c",proot->data);
    preOrder(proot->left);
    preOrder(proot->right);
}
}

效果:输入abc##de#g##f###

可有:abcdgfe

中序遍历

void InOrder(TreeNode* proot){
if(proot==NULL){
    return;
}else{
    preOrder(proot->left);
    printf("%c",proot->data);
    preOrder(proot->right);
}
}

后序遍历

void PostOrder(TreeNode* proot){
if(proot==NULL){
    return;
}else{
    preOrder(proot->left);
    preOrder(proot->right);
    printf("%c",proot->data);
}
}

相关文章:

  • idea技巧
  • C++中,内存管理和内存泄漏总结
  • AIP-162 资源修订
  • 如何解决python安装scipy时报错
  • Unity 通用UI界面逻辑总结
  • 导轨式ARM工业控制器:组态软件平台的“神经中枢”
  • Windows本地部署OpenManus并接入Mistral模型的实践记录
  • Etcd的安装与使用
  • vulkanscenegraph显示倾斜模型(5.2)-交换链
  • ViT-Small与Vit-Base区别
  • CI/CD—GitLab部署
  • 【redis】数据类型之geo
  • 【GPT入门】第12课 FunctionCall 生成数据库sql代码
  • 虚拟机的xml格式
  • 市场动态变化中的策略运用
  • Scala(Array,List,Set,Map,Tuple,字符串 使用的简单介绍)
  • 【redis】五种数据类型和编码方式
  • HTML 标签语义化指南:让网页更易读
  • 【认知学习篇】【深度拆解DeepSeek:从技术内核到江湖地位(万字暴力拆机报告)】
  • Linux系统之nethogs工具的基本使用
  • flash网站建设价格/郑州seo使用教程
  • 打开山东城市建设职业学院网站/优化网站关键词的技巧
  • 互联网客户做网站/百度竞价排名软件
  • 网站没有做301定向/西安百度快速排名提升
  • 各个做网站的有什么区别/招代理最好的推广方式
  • 留言网站建设的报告/网站seo技术能不能赚钱