树(二)<C语言>

二叉树的遍历

 遍历:顺着某一条搜索路径巡访二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次(又称周游)。

 遍历的目的:得到树中所有结点的一个线性排列。

 遍历的用途:它是树结构插入,删除,修改,查找和排序运算的前提,是二叉树一切运算的基础和核心

遍历方法 

 采用递归思想,依次遍历二叉树中的三个组成部分,便是遍历了整个二叉树。

 令:L:遍历左子树 D:访问根节点 R:遍历右子树,则有LDR,LRD,DLR,DRL,RDL,RLD六种遍历方案。若规定先左后右,则只有DLR(先序遍历),LDR(中序遍历),LRD(后序遍历)三种遍历方式。

DLR先序遍历 

 若二叉树为空,则空操作,否则:(1)访问根结点 (2)先序遍历左子树 (3)先序遍历右子树

算法实现:

#include<stdio.h>
#include<stdlib.h>
typedef struct Tree{
	int data;
	Tree* right, * left;
}*BiTree,TreeNode;
void PreOrderTraverse(BiTree T) {
	if (T == NULL)
		return;
	else {
		printf("%d", T->data);
		PreOrderTraverse(T->left);
		PreOrderTraverse(T->right);
	}
}
LDR中序遍历 

若二叉树为空,则空操作,否则:(1)中序遍历左子树 (2)访问根结点(3)中序遍历右子树

算法实现: 

#include<stdio.h>
#include<stdlib.h>
typedef struct Tree{
	int data;
	Tree* right, * left;
}*BiTree,TreeNode;
void InOrderTraverse(BiTree T) {
	if (T == NULL)
		return;
	else {
		InOrderTraverse(T->left);
		printf("%d", T->data);
		InOrderTraverse(T->right);
	}
}
LRD后序遍历 

 若二叉树为空,则空操作,否则:(1)后序遍历左子树 (2)中序遍历右子树(3)访问根结点

算法实现: 

#include<stdio.h>
#include<stdlib.h>
typedef struct Tree{
	int data;
	Tree* right, * left;
}*BiTree,TreeNode;
void PostOrderTraverse(BiTree T) {
	if (T == NULL)
		return;
	else {
		PostOrderTraverse(T->left);
		PostOrderTraverse(T->right);
		printf("%d", T->data);
	}
}

  如果不考虑输出语句,从递归的角度来看,三种算法是完全相同的,或说这三种算法访问路径是相同的,只是访问结点的不同。 

  时间效率O(n):每个结点访问一次。

  空间效率O(n):栈占用的最大辅助空间。

中序序列非递归的方法遍历

基本思想:(1)建立一个栈 (2)根结点进栈,遍历左子树 (3)根结点出栈,输出根结点,遍历右子树。

层次遍历 

  对于一棵二叉树,从根结点开始,按从上到下,从左到右的顺寻访问每一个结点,每一个结点仅仅访问一次。

算法设计思路:使用一个队列

(1)将根结点进队;

(2)队列不为空时循环,从队列中出列一个结点*p,访问它;

1.若有左孩子结点,将左孩子进队。

2.若有右孩子结点,将右孩子进队。

使用队列类型定义如下:

算法实现: 

 遍历序列确定二叉树

(1)若二叉树中各结点的值均不相同,则二叉树结点的先序序列,中序序列,后序序列都是唯一的。

(2)二叉树的先序序列和中序序列,或二叉树的中序序列和后序序列可以确定唯一一棵二叉树。

例:已知二叉树的先序遍历 {A,B,,C,D,E,F,G,H,I,J},中序遍历{C,D,B,F,E,A,I,H,G,J},构造出相应的二叉树。

分析:由先序序列确定根,由中序序列确定左右子树。

1.由先序知根为A,则由中序列知左子树为{C,D,B,F,E},右子树为{I,H,G,J}。

2.以同样方式,再分别在左子树和右子树的序列找出根,左子树序列,右子树序列。 

3.以此递归,直到得到二叉树。 

二叉树的建立 

先序遍历建立二叉树

 由于单独先序遍历无法确定唯一二叉树,故用#代表次孩子结点,代表为空。

 如构建ABC##DE#G#F###

#include<stdio.h>
#include<stdlib.h>
typedef struct Tree{
	char data;
	Tree* right, * left;
}*BiTree,TreeNode;
void CreateBiTree(BiTree& T) {
	char ch;
	scanf_s("%s", &ch);
	if (ch == '#')
		T = NULL;
	else
	{
		T = (BiTree)malloc(sizeof(TreeNode));
		T->data = ch;
		CreateBiTree(T->left);
		CreateBiTree(T->right);
	}
	return;
}
复制二叉树

如果是空树,递归结束。

否则,申请新结点空间,复制根结点,递归复制左子树,递归复制右子树。

算法实现

#include<stdio.h>
#include<stdlib.h>
typedef struct Tree{
	int data;
	Tree* right, * left;
}*BiTree,TreeNode;
int CopyTree(BiTree T, BiTree& NewT) {
	if (T == NULL) {
		NewT = NULL;
		return 0;
	}
	else
	{
		NewT = (BiTree)malloc(sizeof(TreeNode));
		NewT->data = T->data;
		CopyTree(T->left, NewT->left);
		CopyTree(T->right, NewT->right);
	}
}
计算二叉树深度

如果为空树,则深度为0。否则,递归计算左子树的深度记为m,递归计算右子树深度为n,二叉树深度为m+1或n+1较大者。

算法实现

#include<stdio.h>
#include<stdlib.h>
typedef struct Tree{
	int data;
	Tree* right, * left;
}*BiTree,TreeNode;
int DepthTree(BiTree T) {
	if (T == NULL)
		return 0;
	else {
		int m = DepthTree(T->left);
		int n = DepthTree(T->right);
		if (m > n)return(m + 1);
		else
		{
			return(n + 1);
		}
	}
}
计算二叉树结点个数

如果是空树,结点数为0,否则结点个数为左子树的结点个数+右子树结点个数+1。

算法实现

计算二叉树叶子结点个数 

如果是空树,叶子结点数为0,否则结点个数为左子树的叶子结点个数+右子树叶子结点个数+1。

线索二叉树

当用二叉链表作为二叉树的储存结构时,可以很方便找到某个结点的左右孩子,但一般情况下,无法直接找到该结点在某种遍历序列的前驱和后继结点。那如何寻找特定遍历序列中二叉树结点的前驱和后继?

1.通过遍历寻找,但费时间。

2.再增设前驱和后驱指针域,但浪费空间

3.利用结点的空指针域

  如果某个结点的左孩子为空,则将空的左孩子指针域指向其前驱,同理,若右孩子为空,则将空的右孩子指针域指向其后驱,这种改变指向的指针称为线索,加上线索的二叉树称为线索二叉树,对二叉树按某种遍历次序使其变为线索二叉树称为线索化

为区分左孩子和右孩子指针到底是指向孩子的指针,还是指向前驱或后继的指针,对二叉链表的每一个结点增设两个标志域,ltag和rtag,并规定:

这样指针的结构就为:

表示如下 

typedef struct BiThrNode {
	int data;
	int ltag, rtag;
	struct BiThrNode* right, * left;
}BiThrNode, * BiThrTree;

为避免指针悬空,可以增设一个头结点 ltag=0,left指向根结点,rtag=1,right指向遍历序列中最后一个结点,遍历序列中的第一个结点的left和最后一个结点的right都指向头结点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值