数据结构复习(四)树(1)

引入

之前的章节,我们已经熟悉了线性存储结构。线性存储结构就像一根根的绳子,如果我们抓住其中的一端,让另一端散开,就能得到一种跟毽子一样的形状。

我们将这个结构倒置,就会得到一颗长得像树的图案。所以这种结构被叫做

接下来我们用这颗树举例,来回顾一些概念。

1.节点

每一个圈圈都是一个节点。

A是整棵树的根节点。

A是B、E、F的父节点,B、E、F是A的子节点。G、H是F的子节点,F是G、H的父节点

G、H这种没有子节点的叫做叶节点。

A、F都是G的祖先节点。A、B、C都是D的祖先节点

2.度

节点的度是某个节点的子节点个数。A的度为3,F的为2.

树的度是整棵树中节点的度最大的那一个。所以这棵树的度是3.

3.层次与深度、高度

规定根节点在第一层,往下每一个子节点的层数都是父节点层数+1.例如B在第二层,C在第三层。

反过来,叶节点的高度为1,所有父节点的高度是其子节点高度+1。

整棵树的高度就是根节点的高度。

整棵树的深度就是最大的层次数。

二叉树

定义

二叉树就是每个节点最多有两个子节点的树。

斜二叉树就是退化成链表的二叉树,用人话说就是每个节点只有一个或零个子节点。

完美二叉树就是所有层次都是满的二叉树。

完美二叉树:

 完全二叉树就是叶节点只能出现在最下层或次下层,且集中在树的左边。

完全二叉树:

 二叉树的链式存储

根据定义,我们不难想象出二叉树的节点结构体。首先我们肯定要一个东西来存储数据,其次我们需要两个指针分别指向左边和右边。所以下面给出结构体定义。

typedef struct TNode * Position;
typedef Position BinTree;
struct TNode{
	ElementType Data;
	Position Left;
	Position Right;
};

我们在进行插入的时候,只需要先malloc节点,然后将父节点的指针指向新节点即可。

二叉树的遍历

接下来我们来了解一下二叉树的几种常见遍历方式。首先,按照节点可以分成先序遍历中序遍历后序遍历

我们暂且通过这张图来研究一下三种遍历方式。根据名字来看,先序遍历就是遍历ABC,因为它最先遍历父节点。中序遍历就是遍历BAC,因为在中间遍历根节点。后序遍历是遍历BCA。无论什么遍历方式,右边的节点永远是在左边节点之后的,不同名字仅仅代表了父节点的顺序,通过这个逻辑我们就能更好的记忆不同遍历的顺序了。

前序遍历:先遍历父节点,其次左节点,最后右节点。

中序遍历:先遍历左节点,其次父节点,最后右节点。

后序遍历:先遍历左节点,其次右节点,最后父节点。

接下来我们用一张大一点的树来研究不同的遍历。

对于这棵树,前序遍历是从父节点A开始,然后向左遍历到B,然后向左遍历到C,然后向左到G;现在对于子树C-G-H而言,已经遍历了C和G,所以接下来遍历到H。目前的遍历结果是ABCGH。然后我们遍历到D,接下来是E,最后是F。所以结果为ABCGHDEF。

中序遍历将从最左边的叶子节点开始,进行GCHBDAEF的遍历。

后序遍历的顺序是GHCDBFEA。

接下来想想怎么用代码实现呢。这种周期性重复的工作,我们可以使用递归去完成。所以对于先序遍历,我们如何构思呢?

任何一个递归都需要有三个要素:函数作用、结束条件、等价关系。对于先序遍历的函数,函数作用肯定是获得一棵二叉树的先序遍历结果。那么结束条件应该是什么呢?参考刚刚所述的遍历过程我们不难发现,每一次对子树遍历的结束都是以某个节点再也找不到满足条件的其它节点为止的。所以我们认为,此递归的结束条件就是如果不存在左、右节点的时候为终止。等价关系在此递归中并不重要,因为不需要返回值。

上一段的内容,假如你并不理解,没有关系。请仔细领悟接下来的代码,可以自己画一棵树,然后对着代码一步步去进行遍历。

前序遍历:

void PreOrderTraversal(BinTree t){
	if(t){
		printf("%d", t->Data);
		PreOrderTraversal(t->Left);
		PreOrderTraversal(t->Right);	
	}
}

中序遍历:

void InOrderTraversal(BinTree t){
	if(t){
		InOrderTraversal(t->Left);
		printf("%d", t->Data);
		InOrderTraversal(t->Right);
	}
}

后序遍历:

void PostOrderTraversal(BinTree t){
	if(t){
		PostOrderTraversal(t->Left);
		PostOrderTraversal(t->Right);
		printf("%d", t->Data);
	}
}

告诉大家一个巧办法,是什么遍历,printf就在哪里。(狗头)

但是,递归虽然简单,不过效率却比较慢。所以我们尽量用非递归的办法去解决问题。接下来我将给出非递归的思路。

我们就以中序遍历为例子,具体地一起研究一下遍历思路。

对于中序遍历而言,我们首先要一直左寻找叶子结点。这一段很easy,我们可以如下表示。

while(BT->Left!=NULL){
	BT=BT->Left;	
}

直到找不到左节点了,我们才输出内容。

while(BT->Left!=NULL){
	BT=BT->Left;	
}
printf("%d", BT->Data);

但是接下来我们要回到上一个节点去输出内容并且找其右节点,这就有点难办了,因为我们只能通过父节点去找子节点,不能通过子节点找父节点。所以我们需要一个新的链式存储结构来存放之前通过的节点。我们要找的上一个节点是最后进入的,但是会被最先取出。所以很明显,我们需要一个栈来存放每次经过的节点。

Stack s=CreateStack(1000);
while(BT){
	Push(s, BT);
	BT=BT->Left;	
}
BT=Pop(s);
printf("%d", BT->Data);
BT=BT->Right;

但是这样显然只能遍历最小的子树,所以我们还需要在最外层套一个循环,来确保找到最右边的节点之后也能够回到根节点的右子树。

void InOrderTraversal(BinTree t){
	BinTree BT;
    // 防止原来的树被破坏
	Stack s=CreateStack(1000);
    // 新建栈空间
	BT=t;
	while(BT || !IsEmpty(s)){
        // 节点存在并且栈不为空。
		while(BT){
			Push(s, BT);
			BT=BT->Left;	
		}
		BT=Pop(s);
		printf("%d", BT->Data);
		BT=BT->Right;
	}
}

这就是中序遍历的非递归算法,抽丝剥茧下来,就非常简单了。

刚才所述的是二叉树根据节点的遍历。二叉树也可以根据层次来进行遍历。这叫二叉树的层序遍历

层序遍历是指二叉树从顶层开始逐层遍历,对于每一层,顺序都是从左到右。那么算法该如何实现呢?很简单,用队列。

所以层序遍历的操作就可以细化成四个小步骤。

第一步我们将根节点入队。

第二步我们从队列中取出一个元素,并且访问其所在节点,并且打印。

第三步如果该节点的左右子节点非空,则将子节点入队。

最后我们按照队列一个个取出打印即可。

void LevelOrderTraversal(BinTree t){
	if(!t){
		printf("空树");
		return;
	}else{
		BinTree BT=t;
		Queue q=CreateQueue();
		AddQ(q, BT);
		while(!IsEmpty(BT)){
			BT=DeleteQ(q);
			printf("%d", BT->Data);
			if(BT->Left!=NULL) AddQ(q, BT->Left);
			if(BT->Right!=NULL) AddQ(q, BT->Right);
		}
	}
}

二叉树求高度

根据上述定义,二叉树的高度就是其根节点的高度,根节点的高度又是子节点的最大高度+1。所以我们只需要用一个递归,分别找到左右节点,然后层层递归上去即可。

int GetHeight(BinTree t){
	int HL,HR,MaxH;
	// 左子树高度,右子树高度,树的高度。 
	if(t){
		HL=GetHeight(t->Left);
		HR=GetHeight(t->Right);
		MaxH=HL>HR?HL:HR;
		// 三目运算符
		return (MaxH+1); 
	}else{
		return 0
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值