二叉树的链式结构(后篇)

1.二叉树的遍历

2.二叉树链式结构的实现

3.解决单值二叉树题

1.二叉树的遍历

1.1前序,中序以及后序遍历

二叉树的遍历是按照某种特定的规则,依次对二叉树的结点进行相应的操作,并且每个结点只操作一次。

二叉树的遍历有这些规则:前序/中序/后序/的递归结构遍历

1.前序遍历:先访问根结点,再访问左子树,最后访问右子树

2.中序遍历:先访问左子树,再访问根结点,最后访问右子树

3.后序遍历:先访问左子树,再访问右子树,最后根结点 

以前序的规则来遍历二叉树,进入1结点(值为1的结点),1结点为根结点所以先访问,然后是左子树也就是2结点,此时进入2结点后,2结点是根结点,先访问2结点再访问左子树3结点,进入3结点后,以3结点作为根结点,先访问3结点,后访问3结点的左子树,此时就到最后一行都是NULL结点,访问完NULL结点后(3结点的左子树),就访问3结点的右子树(NULL结点),这时候3结点已访问完,而3结点又是2结点的左子树,也就是说2结点的左子树访问完,就访问2结点的右子树(NULL结点),2结点也访问完了,2结点又是1结点的左子树,既1结点的左子树访问完,开始访问1结点的右子树,后续过程也是一样的,每进入一个结点都是新的根结点,按照前序的遍历,总的看就是一个递归过程了,只有3结点的左右子树以及自身被访问完后才去访问上面结点的右子树。

 

 访问的顺序不同也会带来不同的特点,前序访问则第一个是最上面的结点,中序访问最上面的结点会把其的左右子数分成俩边,后序则最上面的结点在最后面,中序和后序的过程与前序差不多,就是根结点的访问有所不同。

 2.二叉树链式结构的实现

2.1代码实现遍历二叉树数

下面是以前序为规则遍历的代码实现:

#include<stdio.h>
#include<stdlib.h>
typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	BinaryTreeNode* left;
	BinaryTreeNode* right;
}BTNode;
BTNode* BuyNode(int x)
{
	BTNode* a = (BTNode*)malloc(sizeof(BTNode));
	if (a == NULL)
	{
		perror("malloc fail:");
		return NULL;
	}
	a->data = x;
	a->left = NULL;
	a->right = NULL;
	return a;
}
BTNode* CreatNode()
{
	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;
}
void PrevOver(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	printf("%d ", root->data);
	PrevOver(root->left);
	PrevOver(root->right);
}
int main()
{
	BTNode* root = CreatNode();
	PrevOver(root);
	return 0;
}

结果如上面分析的一样。

只需要改变 PrevOver函数中代码的顺序就可以切换成其他的规则的遍历,把左子树放在根的访问前就是中序的遍历规则。

2.2实现计算二叉树中的结点的个数

错误代码1:

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

首先size是在栈区上开辟的空间,当出了这个函数,栈区就会被销毁,函数只会传size的值,size变量是被销毁了,但是没有接受返回的值,等于是去银行取钱,结果忘记拿钱了,这样是找不到累计的效果的,需要理解的是不是名字一样就是同一个变量,在递归的过程中,虽然都是size但是这些size是在不同栈区上开辟的不同变量,只是名字相同,所以最后返回的size是第一次调用的函数的size,最后会返回1.

错误代码2:

通过static可以是局部变量在出函数时不会被销毁掉,保留size的值,使得size能达到计数的效果,

但是也有缺陷,就是在主函数中多次调用计算结点数量的函数会出问题

int TreeNode(BTNode* root)
{
	static int size = 0;
	if (root == NULL)
	{
		return 0;
	}
	++size;
	TreeNode(root->left);
	TreeNode(root->right);
	return size;
}

多次调用计算函数:

会导致一直积累

错误代码3:

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

只需要把size从局部变量变成全局变量就行,这样递归过程中的每个函数的size都是同一个size了,全局变量的生命周期是程序结束,同static一样,但是相对static的方法安全一些,使用static需要谨慎。

合理代码:

int TreeNode(BTNode* root)
{
	return root == NULL ? 0 : TreeNode(root->left) + TreeNode(root->right) + 1;
}

把根结点分成左子树和右子树在加1(计入一个结点数量),每次进入一个结点都会有左子树和右子树,每进入一个结点就计入1,直到root=NULL时返回0,就把关于根和子树所计入的数返回到上一个结点去,通过这样会一直返回到最上面的结点,就把所有结点的数量都计入进去了,不会出现多次调用函数而发生数量的变化。

2.3计算二叉树的高度

因为根结点有左右俩个子树,树的高度是由最长的决定的,所有要对比左右俩个子树的长度,找到长度长的子树,再加上一(根结点算一个高度),这样就计算出树的高度了。

错误代码:


int TreeHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeHeight(root->left) > TreeHeight(root->right) ? TreeHeight(root->left)+1 : TreeHeight(root->right)+1;
}

这样虽然可以计算出树的高度,但是存在一个问题,函数会在树的最下面开始计入,比我后在返回值那里又要计算一次,因为这个是没有变量来存储计入的值,所有每次对比和返回值时都要在通过递归来得到计入的值,如果树的高度是大的,那么当递归到较为上面时,在往下递归就会导致程序运行慢,一直重复计算,计算最底下的结点的度会因为结点的上走而暴增,倒数第二层计算最后一层结点的度会执行俩次,倒数第三层则会计算最后一层结点四次,所以这个代码是不合适的。

改进代码:

分析:

通过递归会找到最下面的结点,既叶子结点,开始往上走,返回的地方就是比较那边大,就往那边加一(这里的一是根的计入),直到走到最上面结点,这时比较就是总的左子树和右子树的高度(不是最上面结点)。

int TreeHeight(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int heightleft = TreeHeight(root->left);
	int heightright = TreeHeight(root->right);
	return heightleft > heightright ? heightleft +1 : heightright +1;
}

创建临时变量来存储函数返的值,就不用重复计算,可以提高运行速度,减少消耗。

2.4计算树的叶子结点个数

叶子结点的左右子数都是NULL,可以以此为特点找。

错误代码:

int LeafNode(BTNode* root)
{
	if (root->left == NULL && root->right == NULL)
		return 1;
	return LeafNode(root->left) + LeafNode(root->right);
}

因为二叉树不是所有结点都有左右子树,有些子树是NULL,如果为NULL则是不能用->符号引用的,所以代码是不合理的。

改进代码:

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

先在最前面判断是否为空,在进行下面的操作,就可以避免遇到NULL而去解引。

3.解决单值二叉树题

 题意可以知道单值二叉树是结点的值是一样的,需要去遍历二叉树去判断是否所有的值都相等,

通过判断根和左右子树是否相等,如果每个根和左右子树都相等,则整体的全部值也会相等,就像a=b,b=c,所以a=b=c。

代码:

bool isUnivalTree(struct TreeNode* root) {
    if(root==NULL)
    return true;
    if(root->left&&root->left->val!=root->val)
    return false;
    if(root->right&&root->right->val!=root->val)
    return false;
    return isUnivalTree(root->left)&&isUnivalTree(root->right);
    
}

分析:

之所用如果不相等就返回false是因为后面的子结点是否相等还不知道,判断相等就返回true太早了,可能后面的值与前面的不相等,只有全部相等才能返回true。 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值