二叉树链式存储

1.二叉树链式结构及实现

        上篇我们讲到,完全二叉树采用顺序存储,并讲到其应用堆。这一篇我们来讲讲二叉树的链式存储。

        二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是 链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们讲解的是二叉链。三叉链会多一个指针指向父亲节点。

typedef int DataType;
typedef struct BiTreeNode
{
	DataType data;
	struct BiTreeNode* lchild;
	struct BiTreeNode* rchild;
}BiTreeNode;

1.1.创建二叉树

        在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能进行其相关的基本操作。我们可以手动快速创建一棵简单的二叉树。

void test2()
{
	BiTreeNode* node1 = BuyTreeNode(1);
	BiTreeNode* node2 = BuyTreeNode(2);
	BiTreeNode* node3 = BuyTreeNode(4);
	BiTreeNode* node4 = BuyTreeNode(3);
	BiTreeNode* node5 = BuyTreeNode(5);
	BiTreeNode* node6 = BuyTreeNode(6);

	node1->lchild = node2;
	node1->rchild = node3;
	node2->lchild = node4;
	node3->lchild = node5;
	node3->rchild = node6;

}

        创建二叉树如左图

二叉树的概念:二叉树是: 1. 空树 2. 非空:根节点,根节点的左子树、根节点的右子树组成的。

二叉树的增删改并没有太大意义,所以我们主要看普通二叉树的遍历,理解递归的思想。

1..2二叉树的遍历

1.2.1 前序、中序以及后序遍历

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

按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

1. 前序遍历(Preorder Traversal亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。

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

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

        由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

//树的前序遍历
void PreOrder(BiTreeNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->lchild);
	PreOrder(root->rchild);
}
//树的中序遍历
void InOrder(BiTreeNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->lchild);
	printf("%d ", root->data);
	InOrder(root->rchild);
}

//树的后序遍历
void PostOrder(BiTreeNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->lchild);
	PostOrder(root->rchild);
	printf("%d ", root->data);
}

在这里我们要注意递归的使用:以中序遍历为例:

1.一般二叉树的递归都包含三个部分来处理,当前节点("root")处理,当前root可以分为不同的情况下进入分别,遍历左右子树再进行处理.

2.中序遍历中,若当前访问节点(节点1)存在左孩子节点(2),则会调用自己先访问左孩子节点(2),而左孩子节点发现自己也有左孩子节点(3),则递归进入以3为root的调用之中,结果发现3无左孩子,直接打印NULL,再return,返回之后执行 打印3,之后再到3的右孩子节点之中,也是NULL,打印NULL返回,这样以3为root的调用结束,即为2为root的左孩子调用结束,则打印2,再执行2的右孩子调用。

3.二叉树的问题之中,一般的起始判断条件是root为空的判断以及处理,这可以防止空指针异常。

1.2.2 层序遍历

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

// 树的层次遍历  非递归的
void LevelOrder(BiTreeNode* root)
{
	Queue q;
	QueueInit(&q);
	//如果根不为空,就把这个根节点入进入
	if (root)
		QueuePush(&q, root);
	// 因为队列只能取出队头或者队尾元素,所以,每次把它的左右孩子入队列之后需要  pop掉这个节点,下次再取下一个队头节点
	while (!QueueEmpty(&q))
	{
		BiTreeNode* front = QueueFront(&q);
		printf("%d ", front->data);
		if (front->lchild)
			QueuePush(&q, front->lchild);
		if (front->rchild)
			QueuePush(&q, front->rchild);
		QueuePop(&q);
	}
	QueueDestroy(&q);
}

1.树的层序遍历需要队列,且是非递归的遍历,先将根节点入队列

2.因为要将队列的孩子入队列,为了可以得到其孩子,队列中保存树节点指针而不是单纯的int值

3.队列只能在一边进行插入,另一边删除,且只能获取两边的元素,所以需要push后pop掉队头元素,也使得子节点的孩子节点可以入队。

1.2.3 节点个数以及高度和查找

// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

以上四个接口函数,一个个分析。

首先是求二叉树的节点个数,因为它的返回值是int,如果遍历到的当前节点是NULL,返回0,如果当前节点不为空,则返回1+左子树的节点个数+右子树的节点个数,这就实现了不断遍历。若采用局部静态变量,由于count只初始化一次,所以下次调用该函数就会出现问题,采用全局变量计数也是同样的问题。

int TreeSize(BiTreeNode* root)
{
	//static count = 0;  这样也可以,静态变量只初始化一次,第二次调用就无法初始化为0
	if (root == NULL)
	{
		return 0;
	}	
	return 1+ TreeSize(root->lchild)+TreeSize(root->rchild);
}

第二个是求叶子节点个数。

如果遍历到的当前节点是NULL,返回0,如果当前节点是叶子节点,不往后遍历,直接返回1,

而如果既不是叶子节点,也不为空,则继续往左右分别遍历,返回左右叶子节点数量之和。

int TreeLeefSize(BiTreeNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->lchild == NULL && root->rchild == NULL)
	{
		return 1;
	}
	return TreeLeefSize(root->lchild)+ TreeLeefSize(root->rchild);
}

第三个是求第k层节点个数。

如果k==1时,证明该节点是第k层节点,返回1即可,如果不是,则访问其孩子,同时层数减1.

int TreekLevelSize(BiTreeNode*root,int k)
{
	assert(k > 0);
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return TreekLevelSize(root->lchild, k - 1) + TreekLevelSize(root->rchild, k - 1);  //不断向深处遍历,k不断减小,如果刚好是第k层,返回1
}

最后一个是  二叉树查找值为x的节点

先判断是否为空,为空直接返回NULL,如果当前节点就是x节点,直接返回,上述情况如果都不满足,就判断左子树是否有x节点,如果左边有x节点,它的返回值节点一直上传,之后会将这个值进行返回,之后无需查找右边。

BiTreeNode* FindNode(BiTreeNode*root,DataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	if (FindNode(root->lchild, x))  //需要判断是否有x节点的返回值,如果左边有x节点,返回即可,这个节点一直上传,无需查找右边  
	{
		//每一层判断,若其不为空,返回给上一层    不断下来到 “3”时,返回给上一层的判断之中 ,之后上一层判断也不为空,继续再给上一层,
		return FindNode(root->lchild, x);
	}
	else {
		return FindNode(root->rchild, x);
	}
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值