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

前置声明:在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在大家对二叉树结构掌握还不够深入,为了降低大家学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

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

BTNode* BuyNode(int x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	node->data = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}

BTNode* CratBinaryTree()
{
	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 = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
	return node1;
}

注意:上述并不是创建二叉树的方式,真正创建二叉树的方式期待后续博客

再看二叉树基本操作前,再回顾下二叉树的概念,二叉树是:

  1. 空树

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

从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。


1.前序遍历

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

我们先来看看前序遍历:

前序遍历(Preorder Traversal 亦称先序遍历)------访问根结点的操作发生在遍历其左右子树之前。简单来说遍历的顺序为:根 左子树 右子树

递归的本质:拆成把当前问题和子问题,返回条件:最小规模的子问题

我们可以把大问题化成小问题,首先访问根然后访问左子树那么左子树又可以拆成根左子树右子树,结束条件就是树为空。

代码如下:

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

如图可以看出:

前序遍历的结果为:1 2 3 4 5 6

加上空树为(NULL简写成N):1 2 3 N N N 4 5 N N 6 N N

前序遍历递归图解:

注意:任何一棵树的访问都要符合前序,拆成根->左子->树右子树


2.中序遍历

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

中序遍历的访问顺序为:左子树 根 右子树

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

}

中序遍历的结果如下:

递归展开图如下:


3.后序遍历

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

后序遍历的访问顺序为:左子树 右子树 根

//后序
void Postorder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	Postorder(root->left);
	Postorder(root->right);
	printf("%d ", root->data);
}

后序遍历的结果如下:


4.层序遍历

还有一种遍历是层序遍历(也称为广度优先搜索,BFS)

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根结点所在层数为1,层序遍历就是从所在二叉树的根结点出发,首先访问第一层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

层序遍历的代码是如何实现的呢?

这里要用到我们之前学的队列,我们让根节点先入队列,再让根节点出队列出队列的同时根的左子树和右子树入队,前提是左右孩子节点不为空,以此内推,直到队列为空时循环停止。

代码如下:

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

5.二叉树节点个数

我们如何用代码统计二叉树的节点个数呢,我们知道每棵树都有左子树和右子树,如果左子树为空就说明左边没有节点我们就返回0给上一层,右子树如果为空那就说明右边没有节点我们也返回0给上一层,不为空就返回左子树+右子树+1也就是加上自身节点,我们使用递归,为了节省效率,可以用三目操作符。

代码如下:

//节点个数
int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : 
		TreeSize(root->left) + TreeSize(root->right) + 1;

}

6.二叉树叶子节点个数

如果是空树就返回0,如果左子树和右子树为空就是叶子节点那么就返回1,统计左子树和右子树的节点即可。

代码如下:

//求叶子节点
int TreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;

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

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

7.求二叉树的高度

一棵树的高度等于左子树和右子树深度较高的那个+1,通过不断的递归找到最高的深度,空树则返回0。

//求树的高度(深度)
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;
}

8.二叉树第K层节点个数

我们可以拆成子问题来解决,每一层是下一层的K-1层,当K=1的时候说明已经到第K层了就返回1

//二叉树第K层节点个数
int TreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	//子问题
	return TreeLevelKSize(root->left, k - 1) + 
		TreeLevelKSize(root->right, k - 1);
}

9.二叉树查找值为x的节点

查找x的节点我们可以先序遍历一下,如果找到值为x的节点则用临时变量保存下来不能直接返回,因为返回是返回给递归调用的上一层并不能完全返回出来,所以需要使用临时变量保存一下。

//二叉树查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	BTNode* ret1 = TreeFind(root->left, x);
		if (ret1)
			return ret1;
	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2)
		return ret2;
	return NULL;
}

10. 判断二叉树是否是完全二叉树

判断是否是完全二叉树我们可以使用层序遍历,完全二叉树中最后一层非空和空一定是连续的,这里我们不管是非空还是空都入队列,出队列的时候如果遇见第一个为空的就要开始判断队列中是否还有非空的如有则不是完全二叉树否则为完全二叉树。

代码如下:

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

11.二叉树销毁

二叉树的销毁我们通过后序遍历来销毁,这里不能使用前序来销毁因为销毁了根节点就不能找到左子树和右子树了,中序也是一样的道理

代码如下:

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

}

12.以下是二叉树实现的所有代码

.h

#pragma once
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeBNode
{
	BTDataType data;
	struct BinaryTreeBNode* left;
	struct BinaryTreeBNode* right;
}BTNode;


BTNode* BuyNode(int x);
void PrevOrder(BTNode* root);
void InOrder(BTNode* root);
void Postorder(BTNode* root);
//节点个数
int TreeSize(BTNode* root);
//求叶子节点
int TreeLeafSize(BTNode* root);
//求树的高度(深度)
int TreeHeight(BTNode* root);
//二叉树第K层节点个数
int TreeLevelKSize(BTNode* root, int k);
//二叉树查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x);

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);

// 层序遍历
void TreeLevelOrder(BTNode* root);

// 判断二叉树是否是完全二叉树
bool TreeComplete(BTNode* root);

// 二叉树销毁
void TreeDestory(BTNode* root);

.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Tree.h"
#include "Queue.h"
BTNode* BuyNode(int x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	node->data = x;
	node->left = NULL;
	node->right = NULL;

	return node;
}
//前序
void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->data);
	PrevOrder(root->left);
	PrevOrder(root->right);
}
//中序
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);

}
//后序
void Postorder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	Postorder(root->left);
	Postorder(root->right);
	printf("%d ", root->data);
}

//节点个数
int TreeSize(BTNode* root)
{
	return root == NULL ? 0 : 
		TreeSize(root->left) + TreeSize(root->right) + 1;

}
//求叶子节点
int TreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;

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

	return TreeLeafSize(root->left)
		+ TreeLeafSize(root->right);
}
//求树的高度(深度)
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;
}

//二叉树第K层节点个数
int TreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	//子问题
	return TreeLevelKSize(root->left, k - 1) + 
		TreeLevelKSize(root->right, k - 1);
}
//二叉树查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	BTNode* ret1 = TreeFind(root->left, x);
		if (ret1)
			return ret1;
	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2)
		return ret2;
	return NULL;
}

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

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

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

.test

#define _CRT_SECURE_NO_WARNINGS 1
#include "Tree.h"
#include "Queue.h"
BTNode* CratBinaryTree()
{
	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 = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
	return node1;
}
int main()
{
	BTNode*root = CratBinaryTree();
	//前序
	PrevOrder(root);
	printf("\n");
	
	//中序
	InOrder(root);
	printf("\n");

	//后序
	Postorder(root);
	printf("\n");

	//二叉树第K层节点个数
	int ret = TreeLevelKSize(root,3); 
	printf("TreeLevelKSize:%d \n", ret);

	//二叉树查找值为x的节点
	BTNode* ret1 = TreeFind(root, 3);
	printf("TreeFind:%d \n", root->data);

	
	printf("TreeSize:%d\n", TreeSize(root));
	printf("TreeLeafSize:%d\n", TreeLeafSize(root));
	printf("TreeHeight:%d\n", TreeHeight(root));

	//层序遍历
	TreeLevelOrder(root);
	printf("\n");
	//判断是否是完全二叉树
	bool ret4 = TreeComplete(root);
	printf("%d ", ret4);

	TreeDestory(root);
	root = NULL;
	return 0;
}
相关推荐
青椒大仙KI117 分钟前
24/9/19 算法笔记 kaggle BankChurn数据分类
笔记·算法·分类
^^为欢几何^^11 分钟前
lodash中_.difference如何过滤数组
javascript·数据结构·算法
豆浩宇11 分钟前
Halcon OCR检测 免训练版
c++·人工智能·opencv·算法·计算机视觉·ocr
浅念同学26 分钟前
算法.图论-并查集上
java·算法·图论
何不遗憾呢35 分钟前
每日刷题(算法)
算法
立志成为coding大牛的菜鸟.39 分钟前
力扣1143-最长公共子序列(Java详细题解)
java·算法·leetcode
鱼跃鹰飞40 分钟前
Leetcode面试经典150题-130.被围绕的区域
java·算法·leetcode·面试·职场和发展·深度优先
liangbm31 小时前
数学建模笔记——动态规划
笔记·python·算法·数学建模·动态规划·背包问题·优化问题
潮汐退涨月冷风霜1 小时前
机器学习之非监督学习(四)K-means 聚类算法
学习·算法·机器学习
B站计算机毕业设计超人1 小时前
计算机毕业设计Python+Flask微博情感分析 微博舆情预测 微博爬虫 微博大数据 舆情分析系统 大数据毕业设计 NLP文本分类 机器学习 深度学习 AI
爬虫·python·深度学习·算法·机器学习·自然语言处理·数据可视化