【数据结构】链式二叉树实现


1、 二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构

1.1 顺序结构

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树
会有空间的浪费。而现实中使用中只有才会使用数组来存储,关于堆我们后面的章节会专门讲
解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
在这里插入图片描述

1.2 链式结构

二叉树的链式存储结构是指用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的
方法是链表中每个结点由三个域组成,数据域和左右指针域左右指针分别用来给出该结点左孩
右孩子所在的链结点的存储地址 。链式结构又分为二叉链三叉链,当前我们学习中一般都
二叉链,学到高阶数据结构如红黑树等会用到三叉链,三叉链想必与二叉链多了一个指向父亲的指针

在这里插入图片描述

// 二叉链
struct BinaryTreeNode
{
    struct BinTreeNode* pLeft;   // 指向当前节点左孩子
    struct BinTreeNode* pRight; // 指向当前节点右孩子
    BTDataType _data; // 当前节点值域
};
// 三叉链
struct BinaryTreeNode
{
    struct BinTreeNode* pParent; // 指向当前节点的双亲
    struct BinTreeNode* pLeft;   // 指向当前节点左孩子
    struct BinTreeNode* pRight; // 指向当前节点右孩子
    BTDataType _data; // 当前节点值域
};

2、 链式二叉树的创建

2.1 结构定义

typedef int BTDataType;

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

2.2 创建一个树结点

BTNode* CreateBTNode(BTDataType x)
{
	BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
	assert(newNode);
	newNode->data = x;
	newNode->left = NULL;
	newNode->right = NULL;

	return newNode;
}

2.3 创建和链接

	BTNode* n1 = CreateBTNode(1);	
	BTNode* n2 = CreateBTNode(2);	
	BTNode* n3 = CreateBTNode(3);	
	BTNode* n4 = CreateBTNode(4);	
	BTNode* n5 = CreateBTNode(5);	
	BTNode* n6 = CreateBTNode(6);
	
	// 链接
	//			 1
	//		2		 4
	//	  3   #    5	6
	//	#   #    #  #  #  #
	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n4->left = n5;
	n4->right = n6;

3、 二叉树遍历

每棵树都有三个部分,根,左子树,右子树
在这里插入图片描述


3.1 前中后序遍历(深度优先遍历)

先序:根 -> 左子树 -> 右子树
中序:左子树 -> 根 -> 右子树
后序:左子树 -> 右子树 -> 根

观察顺序,我们可以发现,遍历过程中根(root)的在什么位置,就是什么遍历,如:前面就是前序。

同时我们只要直到前序+中序,或中序+后序就可以还原出完整二叉树

为什么前序+后序不能还原出来?

前序和后序都能确定出根节点,但是不能区分出根的左右子树,所以得出还原出二叉树必须要确定根节点根节点的左右区间


3.1.1 前序代码

// 前序
// 根 -> 左子树 -> 右子树
void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	
	printf("%d ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}

3.1.2 中序代码

// 中序
// 左子树 -> 根 -> 右子树
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}

	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

3.1.3 后序代码

// 后序
// 左子树 -> 右子树 -> 根
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

3.2 层序遍历(广度优先遍历)

思路:

  1. 我们需要一个队列来实现
  2. 首先需要入一个数据(需要判空),后面的数据就更加方便的出栈和入栈
  3. 出队头数据并打印,在依次把该结点的左右结点给入到队列里(空结点不能入队)
  4. 重复上面的步骤直到队列为空,就可以一层一层的遍历整棵树了。
  • 这里的原理就是队列的先进先出,每一个节点出队后都会带着左右子树进队列
  • 主要思想:上一层节点带下一层节点,空节点不带

在这里插入图片描述

// 层序
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");
	
	QueueDestroy(&q);
}

3.3 判断是否完全二叉树

完全二叉树性质:前n-1层满的,最后一层不满,但是最后一层节点是从左到右连续的

思路:

  1. 和层序区别是:空节点也要入队
  2. 重复着上一层带下一层的思路,遇到空节点就结束
  3. 在剩下的队列数据里,看是否还有非空的数据
  • 还有数据证明:不是完全二叉树
  • 没有数据:就是完全二叉树

这里很巧妙的利用了完全二叉树和队列的性质,先进先出,遇到空后,就能判断是否是完全二叉树了

// 是否完全二叉树
bool isCompeleteBinTree(BTNode* root)
{	
	Queue q;
	QueueInit(&q);

	if (root)
		QueuePush(&q, root);

	// 每次都带下一层节点进来,直到头部数据为空就终止循环
	while (QueueFront(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		
		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);
	return true;
}

3.4 是否平衡二叉树

一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1

在这里插入图片描述

注意下图就不是平衡二叉树,虽然他的根节点左右子树高度是一样的,但是他左右子树的子树高度差不满足
在这里插入图片描述

  1. 返回值是bool型,形参为根
  2. 终止条件:遇到NULL就是真
  3. 单层逻辑:求出当前节点的左右子树的高度,求绝对值是否大于1,大于就不是,小于就继续递归直到为NULL返回true,两个子树都为true,就是平衡二叉树
bool isBalanced(struct TreeNode* root){
    if (root == NULL)
        return true;
    
    // 调用最大深度函数
    int leftDepth = maxDepth(root->left);
    int rightDepth = maxDepth(root->right);

    // 写法1
    // 三个条件有一个不满足就不是,根左右子树都满足就是平衡二叉树
	return abs(leftDepth - rightDepth) < 2 && isBalanced(root->left) && isBalanced(root->right); 

	// 写法2
    return abs(leftDepth - rightDepth) > 1 ? false : isBalanced(root->left) && isBalanced(root->right); 
    

4、 树相关的操作

4.1 节点个数

  1. 返回值是int型,形参为根
  2. 终止条件:遇到NULL就返回
  3. 单层逻辑:左子树和右子树的数量+1(根节点)
// 节点个数
int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

4.2 叶子个数

  1. 返回值是int型,形参为根
  2. 终止条件:遇到NULL就返回
  3. 单层逻辑:左右子树都是NULL,就+1
// 叶子节点个数
int LeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	
	if (root->left == NULL && root->right == NULL)
		return 1;
	
	return LeafSize(root->left) + LeafSize(root->right);
}

4.3 树的最大深度

  1. 返回值是int型,形参为根
  2. 终止条件:遇到NULL就返回
  3. 单层逻辑:左右子树深度相比谁更深,返回深的子树在+1(根节点)
// 树的最大深度
int MaxDepth(BTNode* root)
{
	if (root == NULL)
		return 0;
	
	int leftDepth = MaxDepth(root->left);
	int rightDepth = MaxDepth(root->right);

	if (leftDepth > rightDepth)
		return leftDepth + 1;
	else
		return rightDepth + 1;
}

4.4 销毁

  1. 返回值是int型,形参为根
  2. 终止条件:遇到NULL就返回
  3. 单层逻辑:后序编辑逻辑,先free完左子树,在free右子树,最后free根
// 销毁
void TreeDestroy(BTNode** root)
{
	if (root == NULL)
		return;
	
	free((*root)->left);
	free((*root)->right);
	free(*root);
	
	*root = NULL;
}

5、 总结

学习树逻辑基本上都是递归,递归有三部曲一定要弄清楚

  • 确定递归函数的参数和返回值
  • 确定终止的条件
  • 确定单层递归的逻辑
  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值