堆和二叉树--数据结构初阶(3)(C/C++)

文章目录

  • 前言
  • 理论部分
    • 堆的模拟实现:(这里举的大根堆)
    • 堆的创建
    • 二叉树的遍历
    • 二叉树的一些其他功能实现
  • 作业部分

前言

这期的话讲解的是堆和二叉树的理论部分和习题部分

理论部分

二叉树的几个性质:1.对于任意一个二叉树,度为0的节点比度为2的节点多一个

                          2.对于完全二叉树,度为1的节点要么是1,要么是03.表示二叉树的值在数组位置中父子下标关系:

parent = (child-1)/2 leftchild = parent*2+1 rightchild = parent* 2+2

前提:二叉树的根节点是下标为0,是第1个

此外,数组存储二叉树只适合完全二叉树(较满的),不然浪费的空间太多了

二叉树的第n层有2n-1个节点

节点的个数=边数+1 边数 = 节点的度相加之和

陌生的名词:

树度:也就是树的度,是树中节点度的最大值

堆是完全二叉树
小根堆:树中所有的父亲的值都小于等于孩子(最小的在根节点)
大根堆:树中所有的父亲的值都大于等于孩子(最大的在根节点)
跟二叉搜索树要区分一样(那个对左右孩子放法也有要求)

堆的模拟实现:(这里举的大根堆)

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>// 大堆
typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;void HeapInit(HP* php);
void HeapPush(HP* php, HPDataType x);
void HeapPop(HP* php);
int HeapSize(HP* php);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);
void Swap(HPDataType* p1, HPDataType* p2);void HeapInit(HP* php)
{assert(php);php->a = (HPDataType*)malloc(sizeof(HPDataType)*4);if (php->a == NULL){perror("malloc fail");return;}php->size = 0;php->capacity = 4;
}void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType x = *p1;*p1 = *p2;*p2 = x;
}
// 除了child这个位置,前面数据构成堆
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;//while (parent >= 0)while(child > 0){if (a[child] > a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * php->capacity*2);if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity *= 2;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);
}

在这里插入图片描述

注意:这种模拟实现操作的php->a[php->size]还没存数据
如果要是小堆的话,就把判断条件的的a[child]>a[parent]改成a[child]<a[parent]即可
// 左右子树都是大堆/小堆
void AdjustDown(HPDataType* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){// 选出左右孩子中大的那一个if (child + 1 < n && a[child+1] < a[child]){++child;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapPop(HP* php)
{assert(php);assert(!HeapEmpty(php));//这种一般表示为空就报错// 删除数据Swap(&php->a[0], &php->a[php->size - 1]);php->size--;AdjustDown(php->a, php->size, 0);
}int HeapSize(HP* php)
{assert(php);return php->size;
}
上面AdjustDown里面的n表示的是数组的长度
size表示当前已存长度   size-1时那个最后的下标  size是还没存数据的那个的下标

堆的创建

想给数组排升序的话,要建大堆去搞
代码实现:(这里的时间复杂度是N*logN)
void HeapSort(int*a,int n)
{
//向下调整建堆:
for(int i = (n-1-1)/2;i>=0;--i)
AdjustDown(php->a,php->size,i);int end = n-1;
while(end>0){Swap(&a[end],&a[0]);AdjustDown(a,end,0);
--end;}}

建堆方式有两种:(N为节点个数)

1.向上调整建堆 时间复杂度为N*logN

2.向下调整建堆 时间复杂度为N(一般用这个)

这里的数组是从0开始存数的
向上调整建堆代码实现:
for(int i = 1;i<n;i++)
Adjustup(a,i);向下调整建堆代码实现:
for(int i = (n-1-1)/2;i>=0;--i)
AdjustDown(php->a,php->size,i);
TOPK问题:即求数据结合中前K个最大的元素或者最小的元素(一般情况下数据量都比较大)
方法:
1.用数据集合中的前k个来建堆a.求前k个最大的元素,需要建小堆b.求前k个最小的元素,需要建大堆
2.用剩余的N-k个元素依次和堆顶元素来比较还要向下调整,满足则替换堆顶元素
这里的满足是指比小堆堆顶大,比大堆堆顶小

二叉树的遍历

二叉树的遍历:
1.前序遍历:根左右
2.中序遍历:左根右
3.后序遍历:左右根
(前中后是针对的根,左右的关系全是左在右的左边)
这里拿中序遍历来举例理解:对于每一棵树,都先去遍历他的左子树,然后再写他的根,然后再遍历其右子树
比如下面这个图:就是 1 2 4 5 6 7 8
(用代码写的话,没有的位置要写NULL来代替,比如1的两个子树是NULL)4.层序遍历:就是每层从左到右,然后一层遍历完了就去下一层

在这里插入图片描述

代码实现:
struct BTNode{TreeData data;struct BTNode *left;struct BTNode *right;};void PreOrder(BTNode* root) {if (root == NULL) {printf("NULL ");return;}printf("%d ", root->data);PreOrder(root->left);PreOrder(root->right);
}//先序遍历void InOrder(BTNode* root) {if (root == NULL) {printf("NULL ");return;}InOrder(root->left);printf("%d ", root->data);InOrder(root->right);
}//中序遍历void PostOrder(BTNode* root)
{if (root == NULL){printf("NULL ");return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->data);
}//后序遍历void LevelOrder(BTNode* root)
{Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);printf("%d ", front->data);if(front->left)QueuePush(&q, front->left);if (front->right)QueuePush(&q, front->right);}QueueDestroy(&q);
}//堆的层序遍历:就是打印一个节点之后,把他的孩子节点加进队列中去注意:一般题目是不要那个printf("NULL");的
自己这个方法遍历是会出现NULL的,要注意

二叉树的一些其他功能实现

二叉树的销毁
void TreeDestory(BTNode* root)
{if (root == NULL)return;TreeDestory(root->left);TreeDestory(root->right);free(root);
}求二叉树节点的个数
int TreeSize(BTNode* root)
{return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;//可以这样写,不用续行符
}求二叉树的深度
int TreeHeight(BTNode* root)
{if (root == NULL)return 0;int leftHeight = TreeHeight(root->left);int rightHeight = TreeHeight(root->right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}//递归如果不好想的话要画图去理解特别注意,不要写成这样:
//int TreeHeight(BTNode* root)
//{//if (root == NULL)//	return 0;//return TreeHeight(root->left) > TreeHeight(root->right)//	? TreeHeight(root->left) + 1 : TreeHeight(root->right) + 1;
//}
这样写的话会求很多次eg:TreeHeight(root->left)求二叉树第k层有多少个节点
int TreeKLevel(BTNode* root, int k)//刚开始传进来的是根节点
{assert(k > 0);if (root == NULL)return 0;if (k == 1)return 1;return TreeKLevel(root->left, k - 1)+ TreeKLevel(root->right, k - 1);
}二叉树查找首个值为x的结点(先序遍历的话)
BTNode* TreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->data == x)return root;BTNode* lret = TreeFind(root->left, x);if (lret)return lret;//这个办法好,解决了递归深层的想return出去的东西出不去的问题BTNode* rret = TreeFind(root->right, x);if (rret)return rret;return NULL;
}判断二叉树是否是完全二叉树
bool TreeComplete(BTNode* root)
{
//把二叉树放入队列Queue q;QueueInit(&q);if (root)QueuePush(&q, root);while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);if (front == NULL){break;//这里不是continue,遇到第一个NULL就可以停了}else{QueuePush(&q, front->left);QueuePush(&q, front->right);}}//判断是不是完全二叉树while (!QueueEmpty(&q)){BTNode* front = QueueFront(&q);QueuePop(&q);// 后面有非空,说明非空节点不是完全连续if (front){QueueDestroy(&q);return false;}}QueueDestroy(&q);//free空指针也没事return true;
}

全局变量定义在函数声明后面,函数定义前面的话,可以直接在函数里面用

局部变量的话不行,必须传参过去(只用在函数调用之前生成就行了)

工程中很少用全局变量和静态变量(如果用了,要特别注意),一般用指针去代替

关于续行符:一般只有字符串需要使用续行符

作业部分

在用树表示的目录结构中,从根目录到任何数据文件,有(A)通道
A.唯一一条
B.二条
C.三条
D.不一定
下列关键字序列中,序列(D )是堆。
A.{16,72,31,23,94,53}
B.{94,23,31,72,16,53}
C.{16,53,23,94,31,72}
D.{16,23,53,31,94,72}
这种题的话是把这些数据从左到右来按堆从上到下去排

力扣 单值二叉树

力扣 单值二叉树
做法:遍历二叉树,然后让左右节点和自己的根节点比较(注意考虑节点为NULL的情况!)代码实现:
bool isUnivalTree(struct TreeNode* root) {if(root == NULL){return true;}if(root->left){if(root->val!=root->left->val)return false;}if(root->right){if(root->val!=root->right->val)return false;}return isUnivalTree(root->left)&&isUnivalTree(root->right);}

力扣 相同的树

力扣 相同的树(这个好)
注意:不能对NULL进行解引用和->
做法:要单独检验是否为空  然后递归return的是左子树相等&&右子树相等
代码实现:
bool isSameTree(struct TreeNode*p,struct TreeNode*q)
{
//两个都为空if(p == NULL&&q == NULL)   return true;
//一个为空if(p == NULL ||q == NULL)   return false;
//都不为空
if(p->val!=q->val)   return false;return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);}
力扣 对称二叉树
做法;把除根外的二叉树分成两部分,然后把isSameTree的最后一句改成
return isSameTree(p->left,q->right)&&isSameTree(p->right,q->left);即可

力扣 前序遍历

力扣 前序遍历
注意:a[(*pi)++]的这个括号不能省略 那个pi要是指针才对
下面这个只是代码的一部分
要注意的是,题目要求的返回值要是int*类型的,那就要返回数组名了

在这里插入图片描述

延伸出的一些问题:1.指针在定义和初始化一起搞的时候,eg:int*pi,要给他赋值地址才行,并且指针要是不初始化的话是不行的,要么就malloc一下,要么就给他一个量(不能是字面常量)的地址

          2.void类型的自定义函数的返回只能返回return ;不能return 0;
代码实现:
int TreeSize(struct TreeNode* root)
{return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;//可以这样写,不用续行符
}
void preorder(struct TreeNode*root,int*a,int*pi)
{if(root == NULL)  return ;a[(*pi)++] = root->val;preorder(root->left,a,pi);preorder(root->right,a,pi);return ;}int* preorderTraversal(struct TreeNode* root, int* returnSize) {* returnSize = TreeSize(root);int *a = malloc(*returnSize * sizeof(int));int*pi = malloc(sizeof(int*));*pi = 0;preorder(root,a,pi);
return a;
}

力扣 另一棵树的子树

力扣 另一棵树的子树
做法:把左边树的每一个结点对应的树跟目标树比较
(这里的isSameTree是自己实现的比较两个树相等的)
代码实现:
bool isSameTree(struct TreeNode*p,struct TreeNode*q)
{
//两个都为空if(p == NULL&&q == NULL)   return true;
//一个为空if(p == NULL ||q == NULL)   return false;
//都不为空
if(p->val!=q->val)   return false;return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);}bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {if(root == NULL) return false;if(isSameTree(root,subRoot)) return true;
return  isSubtree(root->left,subRoot)||isSubtree(root->right,subRoot);
}
错误写法:
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot) {if(root == NULL) return false;isSubtree(root->left,subRoot);isSubtree(root->right,subRoot);//中间这两步让程序经过了正确的,但没有返回值产生反馈return  isSameTree(root,subRoot);
}

牛客网 KY11二叉树遍历

牛客网 KY11二叉树遍历
注意:这题要求打印中序遍历,但是给的是前序遍历的数组,要注意
易忘把树连接起来:
root->left = PreOrder();   // 构建左子树
root->right = PreOrder();  // 构建右子树
//转换成二叉树的代码:
BTNode* PreOrder() {if (i >= s1.size()) return NULL;char ch = s1[i++];if (ch == '#') return NULL;//还是要转换成NULLBTNode* node = (BTNode*)malloc(sizeof(BTNode));node->data = ch;node->left = PreOrder();   // 构建左子树node->right = PreOrder();  // 构建右子树return node;
}
代码实现:
#include<bits/stdc++.h>
using namespace std;
string s1;
int i;
struct BTNode{char data;struct BTNode *left;struct BTNode *right;};
BTNode* PreOrder() {if (i >= s1.size()) return NULL;char ch = s1[i++];if (ch == '#') return NULL;BTNode* node = (BTNode*)malloc(sizeof(BTNode));node->data = ch;node->left = PreOrder();   // 构建左子树node->right = PreOrder();  // 构建右子树return node;
}void InOrder(BTNode* root) {if (root == NULL) {return;}InOrder(root->left);printf("%c ", root->data);InOrder(root->right);
}int main()
{cin>>s1;BTNode*root =  PreOrder();InOrder(root);return 0;
}

引伸:开辟的空间不会因为函数的结束而自动销毁,只有程序结束才会自动销毁

如果一颗二叉树的前序遍历的结果是ABCD,则满足条件的不同的二叉树有( B)种
A.13
B.14
C.15
D.16
技巧:可以先判断出二叉树的层数
假设有n个节点(不包含NULL),可以算出层数范围
一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足( C)
A.所有的结点均无左孩子
B.所有的结点均无右孩子//是错的,因为在子树只有一个孩子时,取左和取右是都是成立的
C.只有一个叶子结点
D.至多只有一个结点
已知某二叉树的中序遍历序列为JGDHKBAELIMCF,后序遍历序列为JGKHDBLMIEFCA,
则其前序遍历序列为(B)
A.ABDGHJKCEFILM
B.ABDGJHKCEILMF
C.ABDHKGJCEILMF
D.ABDGJHKCEIMLF通用做法:像这种题的话,一般来说,都是中序遍历用来确定根的左右区间有啥先或者后序遍历用来确定每个"子树"的根是啥
eg:
A的左子树:JGDHKB       A的右子树:ELIMCFA的左子树的根:B        …………B的左子树:JGDHK  B的右子树:空    (因为JGDHKB 在B右边的为空)  ……………………
B的左子树的根为:D …………

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

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

相关文章

Dockerfile讲解与示例汇总

容器化技术已经成为应用开发和部署的标准方式,而Docker作为其中的佼佼者,以其轻量、高效、可移植的特性,深受开发者和运维人员的喜爱。本文将从实用角度出发,分享各类常用服务的Docker部署脚本与最佳实践,希望能帮助各位在容器化之路上少走弯路。 无论你是刚接触Docker的…

在QGraphicsView中精确地以鼠标为锚缩放图片

在pyqt中以鼠标所在位置为锚点缩放图片-CSDN博客中的第一个示例中&#xff0c;通过简单设置&#xff1a; self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) 使得QGraphicsView具有了以鼠标为锚进行缩放的功能。但是&#xff0c;其内部应当是利用了滚动条的移动来…

制造工厂如何借助电子看板实现高效生产管控

在当今高度竞争的制造业环境中&#xff0c;许多企业正面临着严峻的管理和生产挑战。首先&#xff0c;管理流程落后&#xff0c;大量工作仍依赖"人治"方式&#xff0c;高层管理者理论知识薄弱且不愿听取专业意见。其次&#xff0c;生产过程控制能力不足&#xff0c;导…

在 C# .NET 中驾驭 JSON:使用 Newtonsoft.Json 进行解析与 POST 请求实战

JSON (JavaScript Object Notation) 已经成为现代 Web 应用和服务之间数据交换的通用语言。无论你是开发后端 API、与第三方服务集成&#xff0c;还是处理配置文件&#xff0c;都绕不开 JSON 的解析与生成。在 C# .NET 世界里&#xff0c;处理 JSON 有多种选择&#xff0c;其中…

Debian10系统安装,磁盘分区和扩容

1、说明 过程记录信息有些不全&#xff0c;仅作为参考。如有其它疑问&#xff0c;欢迎留言。 2、ISO下载 地址&#xff1a;debian-10.13.0镜像地址 3、开始安装 3.1、选择图形界面 3.2、选择中文语言 3.3、选择中国区域 3.4、按照提示继续 3.5、选择一个网口 3.6、创建管…

1.10软考系统架构设计师:优秀架构设计师 - 练习题附答案及超详细解析

优秀架构设计师综合知识单选题 每道题均附有答案解析&#xff1a; 题目1 衡量优秀系统架构设计师的核心标准不包括以下哪项&#xff1f; A. 技术全面性与底层系统原理理解 B. 能够独立完成模块开发与调试 C. 与利益相关者的高效沟通与协调能力 D. 对业务需求和技术趋势的战略…

MPI Code for Ghost Data Exchange in 3D Domain Decomposition with Multi-GPUs

MPI Code for Ghost Data Exchange in 3D Domain Decomposition with Multi-GPUs Here’s a comprehensive MPI code that demonstrates ghost data exchange for a 3D domain decomposition across multiple GPUs. This implementation assumes you’re using CUDA-aware MPI…

计算机考研精炼 计网

第 19 章 计算机网络体系结构 19.1 基本概念 19.1.1 计算机网络概述 1.计算机网络的定义、组成与功能 计算机网络是一个将分散的、具有独立功能的计算机系统&#xff0c;通过通信设备与线路连接起来&#xff0c;由功能完善的软件实现资源共享和信息传递的系统。 …

KUKA机器人自动备份设置

在机器人的使用过程中&#xff0c;对机器人做备份不仅能方便查看机器人的项目配置与程序&#xff0c;还能防止机器人项目和程序丢失时进行及时的还原&#xff0c;因此对机器人做备份是很有必要的。 对于KUKA机器人来说&#xff0c;做备份可以通过U盘来操作。也可以在示教器上设…

【wpf】 WPF中实现动态加载图片浏览器(边滚动边加载)

WPF中实现动态加载图片浏览器&#xff08;边滚动边加载&#xff09; 在做图片浏览器程序时&#xff0c;遇到图片数量巨大的情况&#xff08;如几百张、上千张&#xff09;&#xff0c;一次性加载所有图片会导致界面卡顿甚至程序崩溃。 本文介绍一种 WPF Prism 实现动态分页加…

Kubernetes》》k8s》》Taint 污点、Toleration容忍度

污点 》》 节点上 容忍度 》》 Pod上 在K8S中&#xff0c;如果Pod能容忍某个节点上的污点&#xff0c;那么Pod就可以调度到该节点。如果不能容忍&#xff0c;那就无法调度到该节点。 污点和容忍度的概念 》》污点等级——>node 》》容忍度 —>pod Equal——>一种是等…

SEO长尾关键词优化核心策略

内容概要 在搜索引擎优化领域&#xff0c;长尾关键词因其精准的流量捕获能力与较低的竞争强度&#xff0c;已成为提升网站自然流量的核心突破口。本文围绕长尾关键词优化的全链路逻辑&#xff0c;系统拆解从需求洞察到落地执行的五大策略模块&#xff0c;涵盖用户搜索意图解析…

AWS中国区ICP备案全攻略:流程、注意事项与最佳实践

导语 在中国大陆地区开展互联网业务时,所有通过域名提供服务的网站和应用必须完成ICP备案(互联网内容提供商备案)。对于选择使用AWS中国区(北京/宁夏区域)资源的用户,备案流程因云服务商的特殊运营模式而有所不同。本文将详细解析AWS中国区备案的核心规则、操作步骤及避坑…

计算机视觉——通过 OWL-ViT 实现开放词汇对象检测

介绍 传统的对象检测模型大多是封闭词汇类型&#xff0c;只能识别有限的固定类别。增加新的类别需要大量的注释数据。然而&#xff0c;现实世界中的物体类别几乎无穷无尽&#xff0c;这就需要能够检测未知类别的开放式词汇类型。对比学习&#xff08;Contrastive Learning&…

大语言模型的“模型量化”详解 - 04:KTransformers MoE推理优化技术

基本介绍 随着大语言模型&#xff08;LLM&#xff09;的规模不断扩大&#xff0c;模型的推理效率和计算资源的需求也在迅速增加。DeepSeek-V2作为当前热门的LLM之一&#xff0c;通过创新的架构设计与优化策略&#xff0c;在资源受限环境下实现了高效推理。 本文将详细介绍Dee…

排序算法详解笔记

评价维度 运行效率就地性稳定性 自适应性&#xff1a;自适应排序能够利用输入数据已有的顺序信息来减少计算量&#xff0c;达到更优的时间效率。自适应排序算法的最佳时间复杂度通常优于平均时间复杂度。 是否基于比较&#xff1a;基于比较的排序依赖比较运算符&#xff08;…

【“星瑞” O6 评测】 — llm CPU部署对比高通骁龙CPU

前言 随着大模型应用场景的不断拓展&#xff0c;arm cpu 凭借其独特优势在大模型推理领域的重要性日益凸显。它在性能、功耗、架构适配等多方面发挥关键作用&#xff0c;推动大模型在不同场景落地 1. CPU对比 星睿 O6 CPU 采用 Armv9 架构&#xff0c;集成了 Armv9 CPU 核心…

Ocelot的应用案例

搭建3个项目&#xff0c;分别是OcelotDemo、ServerApi1和ServerApi2这3个项目。访问都是通过OcelotDemo进行轮训转发。 代码案例链接&#xff1a;https://download.csdn.net/download/ly1h1/90715035 1.架构图 2.解决方案结构 3.步骤一&#xff0c;添加Nuget包 4.步骤二&…

DeepSeek+Dify之五工作流引用API案例

DeepSeekDify之四Agent引用知识库案例 文章目录 背景整体流程测试数据用到的节点开始HTTP请求LLM参数提取器代码执行结束 实现步骤1、新建工作流2、开始节点3、Http请求节点4、LLM节点&#xff08;大模型检索&#xff09;5、参数提取器节点&#xff08;提取大模型检索后数据&am…

《从分遗产说起:JS 原型与继承详解》

“天天开心就好” 先来讲讲概念&#xff1a; 原型&#xff08;Prototype&#xff09; 什么是原型&#xff1f; 原型是 JavaScript 中实现对象间共享属性和方法的机制。每个 JavaScript 对象&#xff08;除了 null&#xff09;都有一个内部链接指向另一个对象&#xff0c;这…