当前位置: 首页 > article >正文

二叉树链式结构的实现——C语言

目录

一、提前说明

二、二叉树的遍历 

2.1前序遍历

2.2中序遍历

2.3后序遍历

2.4代码 

三、二叉树结点个数 

3.1整体思路

3.2代码 

四、二叉树叶子结点个数 

4.1整体思路

4.2代码 

五、二叉树的高度(深度)

5.1整体思路

5.2代码

六、二叉树第k层节点个数

6.1整体思路: 

6.2代码 

七、二叉树查找值为x的节点

7.1整体思路 

7.2代码 

八、二叉树的创建

8.1整体思路​编辑

8.2代码 

九、二叉树的销毁

十、二叉树的层序遍历 

10.1层序遍历的概念 

​编辑10.2整体思路 

 10.3代码

10.4番外篇

十一、检查二叉树是否为完全二叉树

11.1整体思路

11.2代码


一、提前说明

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。我们在这里先手动构建一棵“死树”,学完基本操作以后我们再来重新构建。

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

TreeNode* CreateTreeNode(int x)
{
	TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
	assert(node);

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

	return node;
}

TreeNode* CreateTree()
{
	TreeNode* node1 = CreateTreeNode(1);
	TreeNode* node2 = CreateTreeNode(2);
	TreeNode* node3 = CreateTreeNode(3);
	TreeNode* node4 = CreateTreeNode(4);
	TreeNode* node5 = CreateTreeNode(5);
	TreeNode* node6 = CreateTreeNode(6);
	TreeNode* node7 = CreateTreeNode(7);


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

	return node1;
}

二、二叉树的遍历 

遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础

2.1前序遍历

 前序遍历(Preorder Traversal)——访问结点的操作发生在遍历其子树之前。

2.2中序遍历

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

2.3后序遍历

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

2.4代码 

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

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

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

紧接着我们用我们的“死树”来验证一下我们的代码:

我们看我们的二叉树其实是非常丑的,但是我们通过验证也证明了我们代码的正确性。

而且在验证的时候还有一个小插曲:当我发现实际和输出不对等时,我没有怀疑代码的正确性,而是发现我的二叉树图画错了。

三、二叉树结点个数 

3.1整体思路

求结点个数那么遍历整个二叉树肯定是必要的,这就是为什么说遍历是最重要的操作的原因 

我们可以看出,如果root为NULL时,返回0,否则都是向上返回左右结点之和,那么我们就可以直接相加,还要加上它本身。

3.2代码 

int BinaryTreeSize(TreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

四、二叉树叶子结点个数 

叶结点或终端结点:度为0的节点称为叶节点

4.1整体思路

我们的想法是上一个函数的基础上修改一下返回条件:

4.2代码 

int BinaryTreeLeafSize(TreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

五、二叉树的高度(深度)

5.1整体思路

首先要判断根是否存在,如果根存在,继续向下遍历,再次遍历的时候,先判断其左右子树是否存在,若存在,再遍历其左右子树,此时就不用再进行根判断了,根判断的代码只在进入函数时有效执行。 以下是我们的大致思路,但是递归的图着实太难画了,实在不理解可以仔细参照文字或者自己画: 

在画图的过程中我们也发现了,我们每次都要返回左右子树遍历后的较大值,如何做?


这时我们可以借用C语言库中的较大值函数,而且可以进行代码的简化: 

5.2代码

int BinaryTreeHeight(TreeNode* root)
{
	if (root == 0)
	{
		return 0;
	}
	return fmax(BinaryTreeHeight(root->left), BinaryTreeHeight(root->right)) + 1;
}

六、二叉树第k层节点个数

6.1整体思路: 

6.2代码 

int BinaryTreeLevelKSize(TreeNode* root, int k)
{
	assert(k > 0);
	if (root == NULL)
		return 0;
	if (k == 0)
		return 0;
	else if (k == 1)
		return 1;
	else
		return BinaryTreeLevelKSize(root->left, k - 1) + 
		BinaryTreeLevelKSize(root->right, k - 1);
}

七、二叉树查找值为x的节点

7.1整体思路 

我认为思路的重点是怎么递归左右子树并当返回其中不为空的值。 

我们要如何验证代码的正确性呢?在return 0处打断点:

另外我们的printf要用p%打印出地址,观察监视的值与输出是否相同。

7.2代码 

TreeNode* BinaryTreeFind(TreeNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	else
	{
		TreeNode* leftans = BinaryTreeFind(root->left, x);
		TreeNode* rightans = BinaryTreeFind(root->right, x);
		return leftans == NULL ? rightans : leftans;
	}
}

八、二叉树的创建

我们以前序遍历为例子,假设我们提供一个数组“123NN8NN45N7NN6NN”,需要以前序遍历来恢复这棵二叉树,我们要怎么办呢?

8.1整体思路

其中需要注意的点是,因为我们要辨别空指针,所以我们传的是一个 char 数组,因为我们 root 接收的永远是数组中的元素,所以我们要有一个能每次递归都改变值的下标 i ,所以我们使用了传址调用的方式来调用 i 

但是我们代码的正确性怎么检查呢?各位可以专门写一个打印函数,把它打印出来,我就偷个懒,打了断点来监视,根据监视画了图:

大家看这个图是不是有点眼熟(虽然我画的比较丑),但前序遍历的数组我就是抄之前的代码,现在我把之前的“死树”粘贴过来,大家对比一下,根据监视窗口画出来的树和实际的树是不是完全一样? 

8.2代码 

TreeNode* CreateTree(char* a, int* i)
{
	if (a[*i] == 'N')
	{
		(*i)++;
		return NULL;
	}
	TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
	if (root == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	root->data = a[*i];
	(*i)++;
	root->left = CreateTree(a, i);
	root->right = CreateTree(a, i);
	return root;
}

九、二叉树的销毁

二叉树的销毁就容易多了,我们直接看代码吧!

void DestroyTree(TreeNode* root)
{
	if (root == NULL)
		return;
	DestroyTree(root->left);
	DestroyTree(root->right);
	free(root);
	root = NULL;
}

十、二叉树的层序遍历 

10.1层序遍历的概念 

其实二叉树并非只有前中后序的遍历,还有层序遍历,即使我们的二叉树一层一层的输出:

10.2整体思路 

我们可以用队列来实现,当我们的根被Pop时,就把他的两个子结点Push入队,以此类推,大家看图吧!

我们先把之前写的队列的代码Copy一份放在我们现在的代码中:

接着我们就可以在右侧列表中看到我们的Queue了! 

因为我们使用的是队列,所以千万别忘了使用队列前的操作: 
因为我们使用队列存放的是二叉树的结点,所以我们的QueueDataType也要改为Tree Node* ! 
接着我们就可以写出下面的代码,经过测试,不论死树还是活树,我们的层序遍历都是对的

 10.3代码

void LevelOrder(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root != NULL)
		QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%c ", front->data);
		if (front->left)
			QueuePush(&q, front->left);
		if (front->right)
			QueuePush(&q, front->right);
	}
	QueueDestroy(&q);
}

10.4番外篇

我们来看一下我们的层序遍历的输出,这个输出好像有点丑,明明是层序输出,但是为什么那么没有层次感呢?下面请我们思考一下如何一层一层的输出呢?
我们来观察一个规律,当每一层的全部结点都被Pop出来时,它的下一次结点已经全部入队列了:

根据这个特点我们就可以每Pop一层后得到下一层的个数,然后继续Pop个数次: 
紧接着我们就可以看到我们的打印:

代码:

void LevelOrder(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root != NULL)
		QueuePush(&q, root);
	int LevelSize = 1;
		while (!QueueEmpty(&q))
		{
			while (LevelSize--)
			{
				TreeNode* front = QueueFront(&q);
				QueuePop(&q);
				printf("%c ", front->data);
				if (front->left)
					QueuePush(&q, front->left);
				if (front->right)
					QueuePush(&q, front->right);
			}
			printf("\n");
			LevelSize = QueueSize(&q);
		}
		
	QueueDestroy(&q);
}

十一、检查二叉树是否为完全二叉树

11.1整体思路

如果我们学完层序遍历后,不难发现,如果我们不对二叉树的左右子树做检查,让它们不管空或非空都入队列,当前面队列出空时,若队列中还有元素,则为不完全二叉树:

11.2代码

bool TreeComplete(TreeNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	int levelSize = 1;
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front == NULL)
			break;

		QueuePush(&q, front->left);
		QueuePush(&q, front->right);
	}

	// 前面遇到空以后,后面还有非空就不是完全二叉树
	while (!QueueEmpty(&q))
	{
		TreeNode* front = QueueFront(&q);
		QueuePop(&q);

		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}

完结散花! 


http://www.kler.cn/a/159744.html

相关文章:

  • 自由学习记录(21)
  • (六)Spark大数据开发实战:豆瓣电影数据处理与分析(scala版)
  • JavaWeb后端开发知识储备1
  • C++单例模式与多例模式
  • C++编程:利用环形缓冲区优化 TCP 发送流程,避免 Short Write 问题
  • SHELL脚本(Linux)
  • PG 常用维护性 SQL
  • git报错invalid object xxx和unable to read tree xxxxxx
  • 播放器开发(六):音频帧处理并用SDL播放
  • 三部曲法求未定式极限中的1无穷次方型
  • 【探索Linux】—— 强大的命令行工具 P.20(多线程 | 线程互斥 | 互斥锁 | 死锁 | 资源饥饿)
  • 【教程】Conda更换镜像源安装pytorch
  • Git篇如何搭建自己的git仓库
  • 前端知识笔记(二十五)———JS中的异步编程与Promise
  • 如何给自己的网站加密
  • C++大小写字母转换
  • 【PID学习笔记 6 】控制系统的性能指标之二
  • Zookeeper 安装与部署
  • Java 中最常用的设计模式之一,工厂模式模式的写法,
  • 不同场景下如何构建高品质的SD-WAN网络?
  • 【libcurl库】安装及其编程访问百度首页(一)
  • threejs WebGLRenderer 像素比对画布大小的影响
  • 如何查看linux块大小
  • 基于Spring,SpringMVC,MyBatis的校园二手交易网站
  • 【泛型-胡乱砍】
  • php5和php7有什么区别