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

文章目录

前言

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

理论部分

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

复制代码
                          2.对于完全二叉树,度为1的节点要么是1,要么是0

                          3.表示二叉树的值在数组位置中父子下标关系:

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

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

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

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

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

陌生的名词:

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

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

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

c++ 复制代码
#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);
}
c++ 复制代码
注意:这种模拟实现操作的php->a[php->size]还没存数据
如果要是小堆的话,就把判断条件的的a[child]>a[parent]改成a[child]<a[parent]即可
c++ 复制代码
// 左右子树都是大堆/小堆
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;
}
c++ 复制代码
上面AdjustDown里面的n表示的是数组的长度
size表示当前已存长度   size-1时那个最后的下标  size是还没存数据的那个的下标

堆的创建

c++ 复制代码
想给数组排升序的话,要建大堆去搞
代码实现:(这里的时间复杂度是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(一般用这个)

c++ 复制代码
这里的数组是从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);
c++ 复制代码
TOPK问题:即求数据结合中前K个最大的元素或者最小的元素(一般情况下数据量都比较大)
方法:
1.用数据集合中的前k个来建堆
   a.求前k个最大的元素,需要建小堆
   b.求前k个最小的元素,需要建大堆
2.用剩余的N-k个元素依次和堆顶元素来比较还要向下调整,满足则替换堆顶元素
这里的满足是指比小堆堆顶大,比大堆堆顶小

二叉树的遍历

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

4.层序遍历:就是每层从左到右,然后一层遍历完了就去下一层
c++ 复制代码
代码实现:
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的,要注意

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

c++ 复制代码
二叉树的销毁
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;
}

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

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

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

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

作业部分

c++ 复制代码
在用树表示的目录结构中,从根目录到任何数据文件,有(A)通道
A.唯一一条
B.二条
C.三条
D.不一定
c++ 复制代码
下列关键字序列中,序列(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}
这种题的话是把这些数据从左到右来按堆从上到下去排

力扣 单值二叉树

c++ 复制代码
力扣 单值二叉树
做法:遍历二叉树,然后让左右节点和自己的根节点比较(注意考虑节点为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);

}

力扣 相同的树

c++ 复制代码
力扣 相同的树(这个好)
注意:不能对NULL进行解引用和->
做法:要单独检验是否为空  然后递归return的是左子树相等&&右子树相等
c++ 复制代码
代码实现:
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);
 }
c++ 复制代码
力扣 对称二叉树
做法;把除根外的二叉树分成两部分,然后把isSameTree的最后一句改成
return isSameTree(p->left,q->right)&&isSameTree(p->right,q->left);即可

力扣 前序遍历

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

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

复制代码
          2.void类型的自定义函数的返回只能返回return ;不能return 0;
c++ 复制代码
代码实现:
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;
}

力扣 另一棵树的子树

c++ 复制代码
力扣 另一棵树的子树
做法:把左边树的每一个结点对应的树跟目标树比较
(这里的isSameTree是自己实现的比较两个树相等的)
c++ 复制代码
代码实现:
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);
}
c++ 复制代码
错误写法:
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二叉树遍历

c++ 复制代码
牛客网 KY11二叉树遍历
注意:这题要求打印中序遍历,但是给的是前序遍历的数组,要注意
易忘把树连接起来:
root->left = PreOrder();   // 构建左子树
root->right = PreOrder();  // 构建右子树
//转换成二叉树的代码:
BTNode* PreOrder() {
        if (i >= s1.size()) return NULL;

    char ch = s1[i++];
    if (ch == '#') return NULL;//还是要转换成NULL
    BTNode* node = (BTNode*)malloc(sizeof(BTNode));
  
	node->data = ch;
       node->left = PreOrder();   // 构建左子树
    node->right = PreOrder();  // 构建右子树
    return node;
}
c++ 复制代码
代码实现:
#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;
}

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

c++ 复制代码
如果一颗二叉树的前序遍历的结果是ABCD,则满足条件的不同的二叉树有( B)种
A.13
B.14
C.15
D.16
技巧:可以先判断出二叉树的层数
假设有n个节点(不包含NULL),可以算出层数范围
c++ 复制代码
一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足( C)
A.所有的结点均无左孩子
B.所有的结点均无右孩子//是错的,因为在子树只有一个孩子时,取左和取右是都是成立的
C.只有一个叶子结点
D.至多只有一个结点
c++ 复制代码
已知某二叉树的中序遍历序列为JGDHKBAELIMCF,后序遍历序列为JGKHDBLMIEFCA,
则其前序遍历序列为(B)
A.ABDGHJKCEFILM
B.ABDGJHKCEILMF
C.ABDHKGJCEILMF
D.ABDGJHKCEIMLF

通用做法:像这种题的话,一般来说,都是中序遍历用来确定根的左右区间有啥
                            先或者后序遍历用来确定每个"子树"的根是啥
eg:
A的左子树:JGDHKB       A的右子树:ELIMCF

A的左子树的根:B        ............

B的左子树:JGDHK  B的右子树:空    (因为JGDHKB 在B右边的为空)  ........................
B的左子树的根为:D ............
相关推荐
写个博客3 分钟前
代码随想录算法训练营第二十六天
算法
努力写代码的熊大6 分钟前
c语言中文件操作详解
c语言·开发语言
QUST-Learn3D13 分钟前
高精度并行2D圆弧拟合(C++)
开发语言·c++
明月醉窗台35 分钟前
Qt 入门 6 之布局管理
c语言·开发语言·c++·qt
海底火旺39 分钟前
寻找缺失的最小正整数:从暴力到最优的算法演进
javascript·算法·面试
优美的赫蒂42 分钟前
理解欧拉公式
线性代数·算法·数学建模
云小逸1 小时前
【C++】继承
开发语言·c++
Pasregret1 小时前
策略模式:动态切换算法的设计智慧
算法·bash·策略模式
努力学习的小廉1 小时前
【C++】 —— 笔试刷题day_21
开发语言·c++·算法
YuforiaCode1 小时前
第十四届蓝桥杯 2023 C/C++组 冶炼金属
c语言·c++·蓝桥杯