链式二叉树的四种遍历

前言

我们在学习链表的时候,我们都学习他的增删查改,但是我们的二叉树,对于这种复杂的结构,我们的增删查改就没有意义,你要怎么插入,插入那个还是不是二叉树,这是一个很大的问题,但是我们的遍历是有意义的。

链式二叉树的遍历

链式二叉树的遍历一共有四种

  1. 前序遍历
  2. 中序遍历
  3. 后序遍历
  4. 层序

我们就来依次看看我们的遍历

前序遍历

遵循的原则就是根,左,右,就是先是根结点,然后左子树,右子树。

我先给一个树,然后在遍历。大家看看怎么遍历。

在这里插入图片描述

我们先给答案:
1 2 3 NULL NULL NULL 4 5 NULL NULL 6 NULL NULL
首先我们是根结点,第一个根是1,然后左子树,对于左子树的根是2,然后再分,2的左子树是的根是33的左子树是NULL,然后再进行3的右子树也是NULL,当讲2的左子树遍历完,就该遍历2的右子树,而2的右子树是NULL,然后将1的左子树遍历完了,就该遍历1的右子树,1的右子树的根是4,然后4的左子树的55的左子树是NULL,右子树是NULL,然后再遍历4的右子树的根是6,他的左右子树也是NULL
这样就遍历完了

中序遍历

遵循的原则就是左,根,右,就是先是然后左子树,根结点,右子树。

我先给一个树,然后在遍历。大家看看怎么遍历。

在这里插入图片描述

我们先给答案:
NULL 3 NULL 2 NULL 1 NULL 5 NULL 4 NULL 6 NULL
因为先遍历左子树,对于1来说,他是整个数的根,所以先遍历他的坐姿是,但是对于左子树来说,2就是他的根,对于2 这颗树他的左子树是3,3的左子树是NULL,所以第一个就是NULL,然后是他的根3,然后就是他的右子树NULL,当2的左子树的遍历完,然后就是根2,然后就是2的右子树NULL,这时就把1的左子树遍历完了,就是根1,然后就是1的右子树,下面的思路就不写了

后序遍历

遵循的原则就是左,右,根,就是先是然后左子树,右子树,根结点。

我先给一个树,然后在遍历。大家看看怎么遍历。

在这里插入图片描述

我们先给答案:
NULL NULL 3 NULL 2 NULL NULL 5 NULL NULL 6 4 1

层序

层序就是一层一层遍历,这个跟前三个完全不一样,
我直接写完就是1,2,4,3,5,6

用队列的方式实现层序

其大思路就是,我们队列进一个元素就是按照根结点进,但是出队列,出一个队列进他的根节点所对应的左右子节点。就这样依次进行就可以将我们想要的数打印出来。

//二叉树的平序遍历
void LeaveOrede(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);

	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		printf("%d ", front->data);
		QueuePop(&q);
		if (front->left)
		{
			QueuePush(&q, front->left);
		}
		if (front->right)
		{
			QueuePush(&q, front->right);

		}
	}
	printf("\n");
	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;
		}
		//否则就将删除的左右子树的根入队列
		else
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}
	//遇到空就会出来,然后我们进行检查
	//如果空之后还是空,那么就是完全二叉树
	//否则就不是完全二叉树
	//检查方法就是继续我们的队列,知道结束看我们后面能不能遇到空
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front != NULL)
		{
			//return 前我们要销毁队列
			QueueDestroy(&q);
			return false;
		}
	}


	QueueDestroy(&q);
	return true;
}

递归解决二叉树的问题

既然我们不进行二叉树的增删查改,但是我们要实现他的前序中序和后序,我们可以自己将创建几个结点,然后自己让他们手动链接起来.

自己创建一个二叉树

在这里插入图片描述
我们创建一个n个二叉树的结构体的结点,然后自己链接起来
请看下面代码

//开辟n个结点
	BTNode* n1 = (BTNode*)malloc(sizeof(BTNode));
	BTNode* n2 = (BTNode*)malloc(sizeof(BTNode));
	BTNode* n3 = (BTNode*)malloc(sizeof(BTNode));
	BTNode* n4 = (BTNode*)malloc(sizeof(BTNode));
	BTNode* n5 = (BTNode*)malloc(sizeof(BTNode));
	BTNode* n6 = (BTNode*)malloc(sizeof(BTNode));
	if (n1 == NULL && n2 == NULL && n3 == NULL && n4 == NULL && n5 == NULL && n6 == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	//然后根据图将他们链接起来
	n1->data = 1;
	n2->data = 2;
	n3->data = 3;
	n4->data = 4;
	n5->data = 5;
	n6->data = 6;
	n1->left = n2;
	n1->right = n4;
	n2->left = n3;
	n2->right = NULL;
	n3->left = NULL;
	n3->right = NULL;
	n4->left = n5;
	n4->right = n6;
	n5->left = NULL;
	n5->right = NULL;
	n6->right = NULL;
	n6->left = NULL;

现在我们就有了一个简易的二叉树,我们就用它分析就可以了。

(1)前序遍历画图解释递归

我们直接看代码

// 二叉树前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}
完成递归的两个条件
1.能够有条件让循环停下来
2.将我们大问题转换为小问题

首先我们问一下,我们的遍历满不满足递归的两个条件
将大问题转换为小问题,
我们要遍历一整棵树=>遍历左子树和右子树,对于左子树和右子树来说,他也有自己的左右子树,又分为了更小的问题,知道遇到了NULL就结束,这个就是它们的结束条件。

在这里插入图片描述

(2)中序遍历代码

而中序就是先走左子树,左子树走完然后再打印根,所以打印根的代码应该放到走左子树的下面。

// 二叉树中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	PreOrder(root->left);
	printf("%d ", root->data);
	PreOrder(root->right);
}

(2)后序遍历代码

// 二叉树后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL) {
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

(4)求二叉树的结点个数

还是大问题转换为小问题,求节点的个数=左子树的结点个数+右子树的结点个数+1(表示自身节点个数)。遇到空指针,然回0,表示个数为0.

//求二叉树结点的个数
int TreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeSize(root->left) + TreeSize(root->right) + 1;
}

(5)求叶子结点的个数

我们还是跟上面差不多,就是遇到叶子结点+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);
}

(6)求树的高度

对于这个高度,大家掌握了前面的,大部人人第一回写会写成这样

//求树的高度
int TreeHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int height = TreeHeight(root->left) > TreeHeight(root->right) ?
		TreeHeight(root->left) + 1 : TreeHeight(root->right) + 1;
	return height;
}

其实这个会造成极大的浪费,我们光看代码没有任何的问题,但是我们再算出左面和右面的高度,并没有保存,然后后面在比较的时候有进行了依次递归,然后才取出我们的高度。大家不要小看这个,高度越高,我们的最后一层进行的次数越多,改进方法就是将我们的高度保留下来,在比较。

//求树的高度
int TreeHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int height1 = TreeHeight(root->left);
	int height2 = TreeHeight(root->right);
	return height1 > height2 ? height1 + 1 : height2 + 1;
}

(7)第K层的节点个数 k >= 1

分解成子问题,就是第k层=左子树第k-1层的个数+右子树的第k-1层的个数
知道k==1,并且不等于NULL返回1.

//求第K层的节点个数 k >= 1
int TreeKLevelSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1 && root != NULL)
	{
		return 1;
	}
	return TreeKLevelSize(root->left, k - 1) + TreeKLevelSize(root->right, k - 1);
}

(8)二叉树查找值为x的结点

先给一个错误代码,是我第一次写犯得一个大错误,大家看一个下

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

	return NULL;

}

解释:为什们会写出这样的代码,首先遇到空指针,就说明找不到我们就不找了,返回,遇到等于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;

}

(9)二叉树的销毁

用后序遍历销毁,我们先将左右子树销毁,然后再销毁根结点。要明白我在说的是们遍历一遍后序遍历就可以了,然后我们看销毁的顺序。

void TreeDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);
	//制空不制空无所谓,形参的改变不影响实参

}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
二叉树链式存储结构可以通过定义一个结构体来实现,结构体中包含一个数据域和两个指向左右子树的指针。具体实现如下: ``` typedef struct TreeNode{ int data; struct TreeNode *left; struct TreeNode *right; }TreeNode, *Tree; Tree createTree(){ int data; scanf("%d", &data); if(data == -1){ // -1表示空节点 return NULL; } Tree root = (Tree)malloc(sizeof(TreeNode)); root->data = data; root->left = createTree(); root->right = createTree(); return root; } ``` 以上代码实现了二叉树链式存储结构的创建,其中createTree()函数使用前序遍历的方式输入二叉树的节点数据,-1表示空节点。 接下来是二叉树遍历,分别实现前序、中序和后序遍历: ``` void preOrder(Tree root){ if(root == NULL){ return; } printf("%d ", root->data); preOrder(root->left); preOrder(root->right); } void inOrder(Tree root){ if(root == NULL){ return; } inOrder(root->left); printf("%d ", root->data); inOrder(root->right); } void postOrder(Tree root){ if(root == NULL){ return; } postOrder(root->left); postOrder(root->right); printf("%d ", root->data); } ``` 以上代码分别实现了前序、中序和后序遍历,其中preOrder()函数实现了前序遍历,inOrder()函数实现了中序遍历,postOrder()函数实现了后序遍历。 最后是主函数功能菜单的创建,可以使用switch语句实现: ``` int main(){ Tree root = NULL; int choice; do{ printf("1. 创建二叉树\n"); printf("2. 前序遍历\n"); printf("3. 中序遍历\n"); printf("4. 后序遍历\n"); printf("0. 退出\n"); scanf("%d", &choice); switch(choice){ case 1: printf("请输入二叉树的节点数据,-1表示空节点:\n"); root = createTree(); break; case 2: printf("前序遍历结果为:"); preOrder(root); printf("\n"); break; case 3: printf("中序遍历结果为:"); inOrder(root); printf("\n"); break; case 4: printf("后序遍历结果为:"); postOrder(root); printf("\n"); break; case 0: printf("程序已退出!\n"); break; default: printf("输入有误,请重新输入!\n"); break; } }while(choice != 0); return 0; } ``` 以上代码实现了一个简单的二叉树遍历程序,用户可以通过菜单选择需要的功能。注意,在实际使用中,需要在程序结束时释放二叉树的内存空间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桐桐超努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值