0
点赞
收藏
分享

微信扫一扫

请回答数据结构【二叉树和堆(下)】

BingWallpaper22

请回答数据结构【二叉树和堆(下)】

image-20220415131736757

1. Before BinaryTree

二叉树操作回顾二叉树的结构表示和概念

1.0 二叉树结构表示(二叉链)

image-20211127105248267

1.1 二叉树的概念

二叉树是:

  1. 空树
  2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

image-20220331142344416

1.2 二叉树有什么不同?

二叉树加上算法可以生成哈夫曼树,产生的哈夫曼编码可以用于文件压缩
二叉树真正有意义的是搜索,也就是产生搜索树
这样的树可能就是左边比父亲小,右边比父亲大

binary search

找一个节点遍历的次数最多就只有高度次,效率很高

但是极端情况下效率退化成O(N),那么还是和链表差不多,怎么解决呢?于是产生了平衡树,也就是红黑树和AVL树

2. 二叉树的遍历

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

2.1 四种遍历顺序:

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

  1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。
遍历方式遍历顺序
前序遍历也叫先根遍历根->左子树->右子树
中序遍历也叫中根遍历左子树->根->右子树
后序遍历也叫后根遍历左子树->右子树->根
层序遍历h=1,2,3,4…

2.2 诸如此例

比如说像下面这样一个树

image-20220409213623639

前序遍历A->B->D->NULL->NULL->NULL->C->E->NULL->NULL->F->NULL->NULL
中根遍历NULL->D->NULL->B->NULL->A->NULL->E->NULL->C->NULL->F->NULL
后根遍历NULL->NULL->D->NULL->B->NULL->NULL->E->NULL->NULL->F->C->A

2.3 前序的递归解释:根->左子树->右子树

image-20220409213946333

2.4 前序遍历图解

image-20220409215714067

2.5 代码实现

2.5.1 实现之前

学习二叉树的基本操作前,需先要创建一棵二叉树,此处采用手动快速创建一棵简单的二叉树,快速进入二叉树操作

typedef int BTDataType;

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

BTNode* BuyBTNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	node->data = x;
	node->left = node->right = NULL;
	return node;
}

BTNode* CreatBinaryTree()
{
	BTNode* node1 = BuyBTNode(1);
	BTNode* node2 = BuyBTNode(2);
	BTNode* node3 = BuyBTNode(3);
	BTNode* node4 = BuyBTNode(4);
	BTNode* node5 = BuyBTNode(5);
	BTNode* node6 = BuyBTNode(6);

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

	return node1;
}

2.5.2 PreOrder

void PrevOrder(BTNode* root) {
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	printf("%d ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

思考调用递归的过程

image-20220409215754069

2.5.3 Inorder

void InOrder(BTNode* root) {
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

2.5.4 PostOrder

void PostOrder(BTNode* root)
{
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d", root->data);
}

这和之前诸如此例走的是一模一样的

image-20211125165041666

2.5.5 TreeSize

下面的问题都涉及到了使用一种思想,也就是分治思想,将一个复杂问题,把它分成规模更小的问题,子问题再分成问题更小规模的子问题,直到子问题不能再分割,直接能出结果

image-20211127110444583

2.5.5.1 想法一
//想法1
void BTreeSize(BTNode* root)
{
	int count = 0;
	if (root == NULL)
		return ;

	++count;
	BTreeSize(root->left);
	BTreeSize(root->right);
}

递归有很多的栈帧,导致每一个count都可能是不一样的,不可行

2.5.5.2 想法二
//想法2
 int count = 0;
void BTreeSize(BTNode* root)
{
	if (root == NULL)
		return count;

	++count;
	BTreeSize(root->left);
	BTreeSize(root->right);
}

全局变量不是存在栈帧上的,是存在静态区的,所以不会产生之前的问题

2.5.5.3 想法三
//想法3
int BTreeSize(BTNode* root)
{
	static int count = 0;
	if (root == NULL)
		return count;

	++count;
	BTreeSize(root->left);
	BTreeSize(root->right);

	return count;
}

静态变量需要添加返回值,其实和全局变量类似

2.5.5.3 想法四

传地址就可以了

//想法四
//思想:遍历 + 计数
void BTreeSize(BTNode * root, int* pCount)
{
	if (root == NULL)
		return;

	++(*pCount);
	BTreeSize(root->left, pCount);
	BTreeSize(root->right, pCount);
}
2.5.5.4 想法五
int BTreeSize(BTNode* root) {
	return root == NULL ? 0 :
		BTreeSize(root->left)
		+ BTreeSize(root->right) + 1;
}

2.5.6 BTreeLeafSize

return 0
自己是叶子节点return 1
都不是return 左子树叶子节点+右子树叶子节点
int BTreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;

	if (root->left == NULL && root->right == NULL)
		return 1;

	return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}

2.5.7 BTreeKLevelSize

求第k层的节点的个数,k >= 1

空树返回0
非空,且k==1返回1
非空且k>1继续往下走往左边和右边递归加起来计算总数
int BTreeKLevelSize(BTNode* root, int k)
{
	assert(k >= 1);

	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

	return BTreeKLevelSize(root->left, k - 1)
		+ BTreeKLevelSize(root->right, k - 1);
}

2.5.8 BTreeDepth

获取左右子树中深度更深的子树,然后返回最大值即可

int BTreeDepth(BTNode* root)
{
	if (root == NULL)
		return 0;

	int leftDepth = BTreeDepth(root->left);
	int rightDepth = BTreeDepth(root->right);

	return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

2.5.9 BTreeFind

空树return NULL
要找的不是根节点往左树递归
左边没找到往右树递归左边找到了return 左子树
右边也没找到,返回NULL,然后往上走重复判断右边找到了return 右子树
// 二叉树查找值为x的结点
BTNode* BTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;

	if (root->data == x)
		return root;

	BTNode* ret1 = BTreeFind(root->left, x);
	if (ret1)
		return ret1;

	//return BTreeFind(root->right, x);
	BTNode* ret2 = BTreeFind(root->right, x);
	if (ret2)
		return ret2;

	return NULL;
}

2.5.10 BTreeDestory

可以看成后序删除相当于先把child删除再删除根

// 二叉树销毁
void BTreeDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BTreeDestory(root->left);
	BTreeDestory(root->right);
	free(root);
}

image-20211127161010731

2.6 二叉树的深度优先遍历与广度优先遍历

2.6.1 深度优先DFS

前序遍历属于深度优先遍历,是以走深度为主,如果无法再深了就回到之前

2.6.2 广度优先BFS

Binary Tree Zigzag Level Order Traversal at Level

一层一层走,一般借助于队列来实现,比如说层序遍历,比如如图结构的遍历

  1. 先把根入队列,借助队列先进先出的性质

  2. 出队头的数据,把他的下一层传进去

image-20220415101325032

特点:是队列先进先出的性质,然后上一层出的时候,带入下一层

//前置声明
struct BinaryTreeNode;
typedef struct BinaryTreeNode* QDataType;
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BTDataType data;
}BTNode;
typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QNode;
typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

二叉树的广度优先遍历

// 层序遍历
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);
		}
	}

	printf("\n");
	QueueDestory(&q);
}

用纯C写确实非常复杂,还要自己写一个Queue,下面用cpp写一个试试看

#include <iostream>
#include<queue>
using std::queue;
using std::cout;
using std::endl;
namespace allen
{
	void LevelOrder(BTNode*  root)
	{
		queue<BTNode*> q;
		//若根节点不为空,放入根
		if (root)
		{
			q.push(root);
		}
		while (! q.empty())//依次拿出根的左右孩子,并cout
		{
			BTNode* front = q.front();
			q.pop();//删除指向节点的指针
			cout << front->data<<" ";
			
			//放入左
			if (front->left)
			{
				q.push(front->left);
			}
			
			//放入右
			if (front->right)
			{
				q.push(front->right);
			}
		}
		cout << endl;
	}
}

2.6.3 BTreeComplete判断一颗树是不是完全二叉树

借助层序遍历的原理,来判断是不是完全二叉树

如果是层序排列走的话,那么节点应该是连续的,此时我们考虑空节点这次也进队列

思考一下,如果是一个完全二叉树的话,当空节点从队列里面出来之后,队列剩下的应该是全空,如果后面还有节点,那么肯定说明不是完全二叉树

// 判断二叉树是否是完全二叉树
bool BTreeComplete(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;
}

3. 缮甲厉兵

1.给以下两种顺序可不可以由此来建一个唯一的树

顺序1顺序2可以/不可以建树
前序中序可以
后序中序可以
前序后序不可以

2. 一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足( )

  • 所有的结点均无左孩子

  • 所有的结点均无右孩子

  • 只有一个叶子结点

  • 至多只有一个结点

3.如果一颗二叉树的前序遍历的结果是ABCD,则满足条件的不同的二叉树有( )种

  • 13
  • 14
  • 15
  • 16

4. 已知某二叉树的前序遍历序列为5 7 4 9 6 2 1,中序遍历序列为4 7 5 6 9 1 2,则其后序遍历序列为( )

  • 4 2 5 7 6 9 1

  • 4 2 7 5 6 9 1

  • 4 7 6 1 2 9 5

  • 4 7 2 9 5 6 1

选择题做完了,再做几道编程题

入门二叉树-一起来递归【下】

入门二叉树-一起来递归【上】

最后给到我的代码仓库,有关代码已经放入仓库https://gitee.com/allen9012/c-language/tree/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/BinaryTree3

举报

相关推荐

0 条评论