数据结构10——二叉树链式结构的实现

前言

我们知道二叉树有两种存储表示方式:1.数组结构;2. 链式结构。
数组表示法用于完全二叉树的存储非常有效,但表示一般二叉树,特别是形态剧烈变化的,存储空间的利用不是很理想。使用链式结构表示,可以克服这些缺点。
这一节介绍二叉树链式结构的实现以及二叉树常见的一些基础问题。

前置声明:

这篇博客将二叉树的创建和销毁放在了二叉树的遍历后面,因为二叉树的创建需要使用到一些关于二叉树遍历的思想。
所以我们需要先学习二叉树的遍历,这里我们先手动创建一个简单的二叉树(该方法不是真正创建二叉树的方法,只是为了方便学习二叉树的遍历而快速简单地创建的一颗二叉树)

#include<stdio.h>
#include<stdlib.h>

typedef int BTDataType;
typedef struct BTNode {
   
   
	struct BTNode* _left;
	struct BTNode* _right;
	BTDataType _data;
}BTNode;

BTNode* BuyBTNode(BTDataType data)
{
   
   
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
   
   
		printf("fail : malloc");
		exit(-1);
	}

	newnode->_left = NULL;
	newnode->_right = NULL;
	newnode->_data = data;

	return newnode;
}

BTNode* CreateBinTree()
{
   
   
	BTNode* node1 = BuyBTNode(1);
	BTNode* node2 = BuyBTNode(2);
	BTNode* node3 = BuyBTNode(3);
	BTNode* node4 = BuyBTNode(4);
	BTNode* node5 = BuyBTNode(5);
	BTNode* node6 = BuyBTNode(6);
	BTNode* node7 = BuyBTNode(7);
	
	node1->_left = node2;
	node1->_right = node3;
	node2->_left = node4;
	node2->_right = node5;
	node3->_left = node6;
	node3->_right = node7;

	return node1;
}

int main()
{
   
   
	BTNode * tree = CreateBinTree();
	return 0;
}

创建出来的结构是这样的:在这里插入图片描述

二叉树的遍历

二叉树是最基本的树形结构,也是我们重点研究的对象,在二叉树中所有可用的操作中,遍历是最常用的操作,所谓二叉树的遍历,就是遵从某种次序,访问二叉树中的所有节点,使得每一个节点被访问一次,而且只访问一次。

学习遍历二叉树之前,我们需要知道树是一种递归结构,每一颗树都是由三部分组成:根、左子树、右子树。
左子树也是一棵树,同样也被分为了三部分:根、左子树、右子树…

遍历二叉树时中重要的就是递归思想:递归思想是将一个问题一直分解为子问题,直到分解为不可分割的子问题 ,就结束了。

二叉树有四种遍历结构:前序遍历、中序遍历、后序遍历、层序遍历。
前面三种遍历也被称为深度优先遍历,层序遍历也被称为广度优先遍历。

前序遍历

也叫做先根遍历。即一棵树的访问顺序是:根节点、左子树、右子树。

遍历规则是:先访问一棵树的根节点,然后访问到这棵树的左子树、如果该树的左子树已经遍历完全,那么接下来就遍历右子树。访问到空节点或者访问完右子树后才能返回。

我们利用前面实现好的这棵树来作为例子:

在这里插入图片描述

这是一颗完全二叉树,每一颗节点中存储的数据都是一个整形数据。

第一步,我们需要访问这棵树的根节点,即节点1(在演示过程的时候根节点都使用淡蓝色来注明)。

第二步:访问这棵树的左子树:在访问左子树的时候,我们需要先将整个左子树看作一颗完整的树,然后遍历完这棵树中所有数据。

在这里插入图片描述

同样,需要先访问这棵树的根节点:节点2,然后我们还需要去访问节点2的左子树:以节点4为根节点的树。

和每一个节点一样,节点4也有两个指针用来指向它的左右子树,但是因为这个节点是叶子节点,所以这两个指针都是空指针。

思考:我们知道节点4 和节点5 都是叶子节点,在先序遍历节点2为根节点的树的过程中:遍历的顺序是 2、4、5。但是程序是怎样判断出2的左子树已经遍历完全了呢?(换一种问法:遍历函数在什么情况下会返回呢?)
其实,遍历函数在两种情况下会返回:

  1. 遍历的时候遇到了空指针。例如,当我们遍历节点4的左子树的时候,发现指向做指数的指针是空指针,表示该节点没有左子树,此时就需要返回。
  2. 遍历完全的时候。当程序遍历完一棵树的左右子树的时候就需要返回。例如,程序遍历以节点2 为根节点的树的时候,如果左子树4 与右子树5 都已经遍历完全的时候,就需要返回上一级遍历。

如图,节点4的左右指针为空指针,在遍历这一颗树的时候,首先访问了根节点:节点4,然后访问左子树:空指针。当出现空指针的时候,就说明节点4 已经没有左子树需要遍历。我们接下来需要遍历节点4 的右子树,指向右子树的指针也是空指针,所以节点4 也没有右子树需要遍历。这时,我们就可以返回上一层遍历了。

我们利用函数栈帧的知识来解释(每一次函数调用都会创建一个函数栈帧):

在这里插入图片描述

这时就代表一节点4 为根节点的树已经遍历完全了。同时也代表了以节点2 为根节点的树的左子树遍历完全,接下来可以遍历该树的右子树:以节点5为根节点的树。

在这里插入图片描述

下面是前序遍历的代码实现:(为了可以更明确的看出整个过程,这段代码将遍历到的空指针也打印出来了)
这里我们采用打印的方法来遍历整棵树:

void PrevOrder(BTNode* root)
{
   
   
	if (root == NULL)
	{
   
   
		printf("NULL ");
		return;
	}
	printf("%d ", root->_data);		//先访问根节点
	PrevOrder(root->_left);			//再访问左子树
	PrevOrder(root->_right);		//再访问右子树
}

代码的逻辑:

dayin在这里插入图片描述
打印(遍历)的结果:

在这里插入图片描述

可以利用这张图片去看到完整的遍历过程:(n表示空指针)
在这里插入图片描述

中序遍历

中序遍历和先序遍历的唯一区别就是访问顺序。中序遍历的访问顺序是:左子树、根、右子树。

访问每一棵树的时候都需要先遍历该树的左子树。左子树遍历完成后访问根节点,然后遍历这棵树的右子树。

void InvOrder(BTNode* root
评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值