二叉树和堆(三)

举报
跳动的bit 发表于 2022/05/24 07:12:56 2022/05/24
【摘要】 一、二叉树链式结构及实现 💦 前置说明普通二叉树的增删查改复杂且没有意义,所以我们并不打算学习它的增删查改,主要是学习它的结构在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在对二叉树结构掌握还不够深入,为了降低学习成本,此处手动快速创建一棵简单的二叉树,以此快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。⚠...

一、二叉树链式结构及实现

💦 前置说明

普通二叉树的增删查改复杂且没有意义,所以我们并不打算学习它的增删查改,主要是学习它的结构
在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。
由于现在对二叉树结构掌握还不够深入,为了降低学习成本,此处手动快速创建一棵简单的二叉树,以此快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

⚠ 以下代码并不是创建二叉树的方式,真正创建二叉树方式后序详解重点讲解

❗ 回顾二叉树 ❕

  1️⃣ 空树

  2️⃣ 非空:根节点,根节点的左子树、根节点的右子树组成
在这里插入图片描述
💨 从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right; 
}BTNode; 

BTNode* CreatBinaryTree()
{
	BTNode* node1 = BuyNode('A');
	BTNode* node2 = BuyNode('B');
	BTNode* node3 = BuyNode('C');
	BTNode* node4 = BuyNode('D');
	BTNode* node5 = BuyNode('E');
	BTNode* node6 = BuyNode('F');

	node1->_left = node2;
	node1->_right = node3;
	node2->_left = node4;
	node3->_left = node5;
	node3->_right = node6;
	
	return node1; 
 }
 

💦 二叉树的遍历

学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历 (Traversal) 是按照某种特定的规则,依次对二叉树中的节点进行相应的操作,并且每个节点只操作一次。
访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

其实这种就是递归的思想,且在现实生活中也经常使用到 —— 比如 1 位校长要统计学校的人数,他不可能亲自去挨个数,一般是通过院长、系主任、辅导员、班长、舍长的层层反馈才得到结果的
在这里插入图片描述

1️⃣ 前序遍厉: 根   左子树  右子树
在这里插入图片描述
2️⃣ 中序遍厉: 左子树   根  右子树
在这里插入图片描述

3️⃣ 后序遍厉: 左子树  右子树  根
在这里插入图片描述
4️⃣ 层序遍厉: 一层一层的遍
在这里插入图片描述
在之前就提到过深度和广度,其实指的就是这个:

1️⃣ 深度优先遍厉:前序遍厉、中序遍厉、后序遍厉,注意有些说法只认同前序遍厉

2️⃣ 广度优先遍厉:层序遍厉

1、前序、中序以及后序遍历

1️⃣ 前序遍历(Preorder Traversal 亦称先序遍历) —— 访问根结点的操作发生在遍历其左右子树之前。

2️⃣ 中序遍历(Inorder Traversal) —— 访问根结点的操作发生在遍历其左右子树之中(间)。

3️⃣ 后序遍历(Postorder Traversal) —— 访问根结点的操作发生在遍历其左右子树之后。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。
// 二叉树前序遍历
void PreOrder(BTNode* root);
// 二叉树中序遍历
void InOrder(BTNode* root);
// 二叉树后序遍历
void PostOrder(BTNode* root);

❗ 实现 ❕

#include<stdio.h>
#include<stdlib.h>

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right; 
}BTNode; 

BTNode* BuyNode(BTDataType x)
{
	BTNode* node = malloc(sizeof(BTNode));
	node->_data = x;
	node->_left = NULL;
	node->_right = NULL;
	
	return node;
}

BTNode* CreatBinaryTree()
{
	BTNode* node1 = BuyNode('A');
	BTNode* node2 = BuyNode('B');
	BTNode* node3 = BuyNode('C');
	BTNode* node4 = BuyNode('D');
	BTNode* node5 = BuyNode('E');
	BTNode* node6 = BuyNode('F');

	node1->_left = node2;
	node1->_right = node3;
	node2->_left = node4;
	node3->_left = node5;
	node3->_right = node6;
	
	return node1; 
 }
//二叉树前序遍历
void PreOrder(BTNode* root)
{
	if(root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->_data);
	PreOrder(root->_left);
	PreOrder(root->_right);
}
//二叉树中序遍历
void InOrder(BTNode* root)
{
	if(root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->_left);
	printf("%c ", root->_data);
	InOrder(root->_right);
}
//二叉树后序遍历
void PostOrder(BTNode* root)
{
	if(root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->_left);
	PostOrder(root->_right);
	printf("%c ", root->_data);
}
//构建二叉树
void TestTree()
{
	BTNode* root = CreatBinaryTree();
	PreOrder(root);
}

int main()
{
	TestTree();
	return 0;
}

📝 分析:

以上真正的核心代码是 PreOrder、InOrder、PostOrder 函数的递归调用短短几行代码却执行了如此复杂的计算,这里豌豆太懒了就只画一个展开图:

❗ PreOrder ❕

在这里插入图片描述

2、层序遍历
除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。
设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,
以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
// 层序遍历 - 在下面实现
void LevelOrder(BTNode* root);
3、概念选择题 (如何利用已知限有条件构建二叉树)

1.某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为( )

A. ABDHECFG

B. ABCDEFGH

C. HDBEAFCG

D. HDEBFGCA

📝 分析

在这里插入图片描述
所以选择 A 选项


2.二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK; 中序遍历:HFIEJKG; 则二叉树根结点为( )

A. E

B. F

C. G

D. H

📝 分析

根据这棵树的先序遍厉、中序遍厉并不能重建 (其实可以重建的,详情看下题) 这棵树。但是这里并不需要重建,因为先序遍厉是从根开始的。

所以选择 A 选项

3.设—课二叉树的中序遍历序列: badce,后序遍历序列: bdeca, 则二叉树前序遍历序列为( )

A. adbce

B. decab

C. debac

D. abcde

📝 分析

这里在没有 # 的情况下,满足以下条件即可构建出二叉树
在这里插入图片描述

在这里插入图片描述
所以选择 D 选项


4.某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF,则按层次输出 (同一层从左到右) 的序列为( )

A. FEDCBA

B. CBAFED

C. DEFCBA

D. ABCDEF

📝 分析

显然这道题作为选择题来说一眼就能知道答案:根据它的后序遍厉知道根是 F

在这里插入图片描述

所以选择 A 选项


❓ 如果知道了前中后序遍历序列,不包括 #(空) ,能否构建一棵二叉树 ❔

     不能构建,但满足以下的条件即可构建:
在这里插入图片描述

💦 节点个数以及高度等

❓ 实现以下接口 ❔

// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
//二叉树深度/高度
int BinaryTreeDepth(BTNode* root)
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

❗ 实现代码 ❕

  ❤ BinaryTreeSize ❤

    🔑 核心思想1 :使用前序 || 中序 || 后序遍历,全局变量记录

    ❌ 但是以下代码有 Bug :如果采用前序重复遍历 2 次

    ✔ 经查,问题出在全局变量上,这里只需要在第 2 次遍历时置 0 即可

    ⚠ 在以后的知识面更大后,其实你会发现这种操作还有线程安全的问题

    🔑 核心思想 2:函数使用带返回值的方式,其内部的递归本质上是一个后序遍厉

  ❤ BinaryTreeLeafSize ❤

    🔑 核心思想 :以 left 和 right 为标志,如果都为 NULL,则累加

  ❤ BinaryTreeLevelKSize ❤

    🔑 核心思想 :求当前树的第 k 层 = 左子树的第 k - 1 层 + 右子树的第 k - 1 层 (当 k = 1 时,说明此层就是目标层)

  ❤ BinaryTreeDepth ❤

    🔑 核心思想 :当前树的深度 = max (左子树的深度,右子树的深度) + 1

  ❤ BinaryTreeFind ❤

    🔑 核心思想 :

      1、先判断是不是当前节点,是就返回;

      2、先去左树找,找到就返回

      3、左树没找到,再找右树

#include<stdio.h>
#include<stdlib.h>

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

BTNode* BuyNode(BTDataType x)
{
	BTNode* node = malloc(sizeof(BTNode));
	node->data = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}
// 二叉树节点个数
	//1、前序递归遍历
int sz1 = 0;
void BinaryTreeSize1(BTNode* root)
{
	if (root == NULL)
		return;
	else
		sz1++;
	BinaryTreeSize1(root->left);
	BinaryTreeSize1(root->right);
}
//2、中序递归遍历
int sz2 = 0;
void BinaryTreeSize2(BTNode* root)
{
	if (root == NULL)
		return;
	BinaryTreeSize2(root->left);
	if (root != NULL)
		sz2++;
	BinaryTreeSize2(root->right);
}
//3、后序递归遍历
int sz3 = 0;
void BinaryTreeSize3(BTNode* root)
{
	if (root == NULL)
		return;
	BinaryTreeSize3(root->left);
	BinaryTreeSize3(root->right);
	if (root != NULL)
		sz3++;
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	return root == NULL ? 0 : 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}
//二叉树叶子节点个数
int BinaryTreeLeaSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	else if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	else
	{
		return BinaryTreeLeaSize(root->left) + BinaryTreeLeaSize(root->right);
	}
}
//二叉树第k层节点个数
int BinaryTreeLeveLKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}

	return BinaryTreeLeveLKSize(root->left, k - 1) + BinaryTreeLeveLKSize(root->right, k - 1);
}
//二叉树的深度/高度 
int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftDepth = BinaryTreeDepth(root->left);
	int rightDepth = BinaryTreeDepth(root->right);

	return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* retleft = BinaryTreeFind(root->left, x);
	if (retleft)
	{
		return retleft;
	}
	BTNode* retright = BinaryTreeFind(root->right, x);
	if (retright)
	{
		return retright;
	}
	return NULL;
}
BTNode* CreatBinaryTree()
{
	BTNode* node1 = BuyNode('A');
	BTNode* node2 = BuyNode('B');
	BTNode* node3 = BuyNode('C');
	BTNode* node4 = BuyNode('D');
	BTNode* node5 = BuyNode('E');
	BTNode* node6 = BuyNode('F');

	node1->left = node2;
	node1->right = node3;
	node2->left = node4;
	node3->left = node5;
	node3->right = node6;

	return node1;
}

void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}
int main()
{
	BTNode* root = CreatBinaryTree();
	//遍厉前中后序输出二叉树节点的个数
	BinaryTreeSize1(root);
	BinaryTreeSize2(root);
	BinaryTreeSize3(root);
	printf("BinaryTreeSize:%d\n", sz1);
	printf("BinaryTreeSize:%d\n", sz2);
	printf("BinaryTreeSize:%d\n", sz3);
	printf("-----------------cur-----------------\n");
	//优化二叉树节点的个数
	printf("BinaryTreeSize:%d\n", BinaryTreeSize(root));
	printf("-----------------cur-----------------\n");
	//二叉树叶子节点个数
	printf("BinaryTreeLeaSize:%d\n", BinaryTreeLeaSize(root));
	printf("-----------------cur-----------------\n");
	//二叉树第k层节点个数
	printf("BinaryTreeLeveLKSize:%d\n", BinaryTreeLeveLKSize(root, 3));
	printf("-----------------cur-----------------\n");
	//二叉树的深度/高度 
	printf("BinaryTreeDepth:%d\n", BinaryTreeDepth(root));
	printf("-----------------cur-----------------\n");
	// 二叉树查找值为x的节点
	BTNode* ret = BinaryTreeFind(root, 'D');
	if(ret)
	{
		printf("找到了\n");
	}
	else
	{
		printf("没找到\n");
	}

	PreOrder(root);
	return 0;
}

💦 二叉树的创建和销毁

//二叉树创建
BTNode* BinaryCreatBinaryTree();
// 二叉树销毁
void BinaryTreeDestroy(BTNode* root);

❗ BinaryCreatBinaryTree && BinaryTreeDestroy ❕

    注意对于 BinaryTreeDestroy 使用后序的方式销毁

#include<stdio.h>
#include<stdlib.h>

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;

BTNode* BuyNode(BTDataType x)
{
	BTNode* node = malloc(sizeof(BTNode));
	node->_data = x;
	node->_left = NULL;
	node->_right = NULL;

	return node;
}
//创建二叉树
BTNode* BinaryCreatBinaryTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);

	node1->_left = node2;
	node1->_right = node3;
	node2->_left = node4;
	node3->_left = node5;
	node3->_right = node6;

	return node1;
}
//后序销毁二叉树
void BinaryTreeDestroy(BTNode* root)
{
	if (root == NULL)
		return;
	BinaryTreeDestroy(root->_left);
	BinaryTreeDestroy(root->_right);
	free(root);
}
int main()
{
	BTNode* root = BinaryCreatBinaryTree();
	BinaryTreeDestroy(root);
	root = NULL;
	return 0;
}

💦 二叉树的层序遍厉

//层序遍厉
void BinaryTreeLeveOrder(BTNode* root);
//判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root);

BinaryTreeLeveOrder:

🔑 核心思想 🔑

    使用队列的方式:先入第一层,出上一层,再入下一层 … …

    需要使用之前实现的队列

BinaryTreeComplete:

🔑 核心思想 🔑

    想必完全二叉树在此处出现一定跟层序有关系

    层序遍厉,把空也入队列

    完全二叉树,非空是连续的,空也是连续的

    非完全二叉树,非空就不是连续的,空也是不连续的


❗ 这里需要三个文件 ❕

    1️⃣ Queue_LeveOrder.h,用于函数的声明

#pragma once

//头
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>

//结构体
typedef struct BinaryTreeNode*  QDataType;
typedef struct QueueNode
{
	struct QueueNode* next; //指向下一个节点
	QDataType data; //存储整型数据
}QueueNode;

typedef struct Queue
{
	QueueNode* phead;//头指针
	QueueNode* ptail;//尾指针
}Queue;

//函数
void QueueInit(Queue* pq); 
void QueuePush(Queue* pq, QDataType x);	
bool QueueEmpty(Queue* pq);
void QueuePop(Queue* pq);
QDataType QueueSize(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
void QueueDestory(Queue* pq);

    2️⃣ Queue_LeveOrder.c,用于函数的实现

#include"Queue_LeveOrder.h"

void QueueInit(Queue* pq)
{
	assert(pq);
	//把2个指针置空
	pq->phead = pq->ptail = NULL;
}
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//malloc空间,如果需要频繁的开辟空间建议再实现一个BuyQueueNode用于malloc
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	//第一次插入
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	//非第一次插入
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
}
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	//空链表返回true,非空链表返回false
	return pq->phead == NULL;
}
void QueuePop(Queue* pq)
{
	assert(pq);
	//链表为空时不能删除
	assert(!QueueEmpty(pq));
	//只有一个节点的情况
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	//多个节点的情况
	else
	{
		QueueNode* next = pq->phead->next;
		free(pq->phead)	;
		pq->phead = next;
	}
}
QDataType QueueSize(Queue* pq)
{
	assert(pq);
	//如果需要频繁的调用QueueSize这个接口,可以在Queue这个结构体中增加一个成员用于记录长度
	int sz = 0;
	QueueNode* cur = pq->phead;
	while (cur)
	{
		sz++;
		cur = cur->next;
	}
	return sz;
}
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	//链表为空时不能取头
	assert(!QueueEmpty(pq));
	
	return pq->phead->data;
}
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	//链表为空时不能取尾
	assert(!QueueEmpty(pq));

	return pq->ptail->data;
}
void QueueDestory(Queue* pq)
{
	assert(pq);
	
	QueueNode* cur = pq->phead;
	//遍历链表
	while (cur)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->phead = pq->ptail = NULL;
}

    3️⃣ Test.c,用于函数的测试

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType _data;
	struct BinaryTreeNode* _left;
	struct BinaryTreeNode* _right;
}BTNode;

//从此处包含Queue.h文件
#include"Queue_LeveOrder.h"

BTNode* BuyNode(BTDataType x)
{
	BTNode* node = malloc(sizeof(BTNode));
	node->_data = x;
	node->_left = NULL;
	node->_right = NULL;

	return node;
}
//创建二叉树
BTNode* BinaryCreatBinaryTree()
{
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);

	node1->_left = node2;
	node1->_right = node3;
	node2->_left = node4;
	node3->_left = node5;
	node3->_right = node6;

	return node1;
}
//层序遍厉
void BinaryTreeLeveOrder(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);
		}
	}
	printf("\n");
	QueueDestory(&q);
}
//判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		//入队列
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		//取队头的数据
		BTNode* front = QueueFront(&q);
		//出队
		QueuePop(&q);
		//遇到空就break出去再判断
		if(front == NULL)
		{
			break;
		}
		//入左子树
		QueuePush(&q, front->_left);
		//入右子树
		QueuePush(&q, front->_right);
	}
	//队列中全是空,就是完全二叉树;还有非空,就不是完全二叉树
	while(!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		//非空
		if(front)
		{
			QueueDestory(&q);
			return false;
		}
	}
	//全是空
	QueueDestory(&q);
	return true;
}
//后序销毁二叉树
void BinaryTreeDestroy(BTNode* root)
{
	if (root == NULL)
		return;
	BinaryTreeDestroy(root->_left);
	BinaryTreeDestroy(root->_right);
	free(root);
}
int main()
{
	BTNode* root = BinaryCreatBinaryTree();
	BinaryTreeLeveOrder(root);
	printf("BinaryTreeComplete:%d\n", BinaryTreeComplete(root));
	BinaryTreeDestroy(root);
	root = NULL;
	return 0;
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。