数据结构——树和二叉树

本文介绍了树和二叉树的基本概念,包括树的度、叶子结点、树的高度等术语。重点讲解了二叉树的性质、遍历方式,如先序、中序、后序及层次遍历。此外,还提到了二叉树的存储结构,包括顺序存储和链式存储。最后,讨论了如何通过遍历来确定二叉树。

树的基本概念:

树形结构中一个结点可以有一个或多个直接后继。
定义:
树是n(n>=0)个结点的有限集合,一棵树满足以下两个条件:
1.当n=0时,称为空树。
2.当n>0时,有且仅有一个称为根的结点。除根结点外,其余结点分为m(m>=0)个互不相交的非空集T1,T2,…,Tm,这些集合中每一个都是一棵树,称为根的子树。

上学时时常会去鄙夷一件事,这些概念有什么作用?能做成实际的东西才最重要。实际工作里却意识到,这完全是一个错误。

在遥远的后来才逐渐的明白遣词用句可以准确的描述一件事物是多么重要的事情,当意思可以明确的传达,工作的协调也将变得简单,而对这些基础概念的准确了解,也将在后续于计算机相关的工作里拥有更加准确的理解,不论是组织,还是实际参与制造过程,准确的理解自己要做的事情,以及要让别人做的事情,都是重中之重。

树的相关术语:

结点的度:树上任一结点所拥有的子树的树木称为该节点的度。
叶子:度为0的结点称为叶子或终端结点。
树的度:一棵树中所有结点的度的最大值称为该树的度。
————————————————————————
子节点:一个结点的子树的根称为该结点的孩子。
父节点(双亲结点):子节点的前驱结点。
结点的层次:从根开始算起,根的层次为1,其余结点的层次为其双亲的层次加1.
————————————————————————
树的高度:一棵树中所有的结点层次输的最大值称为该树的高度或深度。
有序树:树中各结点的子树从左到右是有次序的,不能互换。
无序树:树中各结点的子树是无次序的,可以互换。

树的基本运算:

1.求根Root(T):求树T的根节点
2.求双亲Parent(T,X):求结点X在树T上的双亲结点;若X是树T的根或X不在T上,则结果为一特殊标志。
3.求孩子Child(T,X,i):求树T上结点X的第i个孩子结点;若X不在T上或X没有第i个孩子,则结果为一特殊标志。
4.建树Create(X, T 1 T_1 T1,…, T k T_k Tk),k>1:建立一颗以X为根,以 T 1 T_1 T1,…, T k T_k Tk为第1,…,k棵子树的树;
5.剪枝Delete(T,X,i):删除树T上结点X的第i棵子树;若T无第i棵子树,则为空操作;
6.遍历TraverseTree(T):遍历树,即访问树中每个结点,且每个结点仅被访问一次。

二叉树:

在树的基本概念基础上多了一条束缚:
任一结点都有两颗互不相交的左子树和右子树(其中任何一个都可以是空子树),并且其之间存在次序关系,所以左子树与右子树也分别称为该结点的左孩子和右孩子

二叉树的运算,与树的基本运算的不同之处:

1.求左孩子Lchild(BT,X)和求右孩子Rchild(BT,X):分别求出二叉树BT上结点X的左、右孩子;若X为BT的叶子或X不在BT上,运算结果为NULL。


——以下均为字面意思,且每个结点有且只被访问一次,若BT为空,则运算为空操作。
2.先序遍历
3.中序遍历
4.后序遍历
5.层次遍历

二叉树的性质

1.第i(i>=1)层上至多有 2 i − 1 2^{i-1} 2i1个结点。
2.深度为k(k>=1)的二叉树至多有 2 k − 1 2^{k-1} 2k1个结点。
3.对任何一颗二叉树,若度数为0的结点(叶结点)个数为 n 0 n_0 n0,度数为2的结点个数为 n 2 n_2 n2,则 n 0 n_0 n0= n 2 n_2 n2+1.

满二叉树:即二叉树上的结点已经达到二叉树所能容纳的最大值,参考性质2。结点数为 2 k − 1 2^{k-1} 2k1
完全二叉树:如果对满二叉树,按从上到下,从左到右的顺序编号,并在最后一层删去部分结点(删后最后一层仍有结点)。这些结点的编号若是连续的,且含有最大编号,则这棵树是完全二叉树。

其下|x|表示不大于x的最大整数,||并非表示绝对值,因为竖线加一短横的符号暂未找到。

4.含有n个结点的完全二叉树深度为 ∣ l o g 2 n ∣ + 1 |log_2n|+1 log2n+1
5.如果将一颗有n个结点的完全二叉树按层编号, 每层从左到右的顺序依次标记。则对任意编号为i(1<=i<=n)的结点A有:
(1).若i=1,则结点A时根;若i>1,则A的双亲Parent(A)的编号为|i/2|;
(2).若2i>n,则结点A既无左孩子,也无右孩子;否则A的左孩子Lchild(X)的编号为2i;
(3).若2i+1>n,则结点A无右孩子;否则,A的右孩子Rchild(A)的编号为2i+1.

二叉树的存储结构:

顺序存储结构:依靠二叉树的性质5,使用一维数组 Btree 实现,为了方便起见将Btree[0]空置。当完全二叉树上的所有结点按层编号,则结点编号之间的关系可以准确的反映结点之间的逻辑关系。
由于性质5对于不完全二叉树并不成立,所以当需要使用顺序存储时,对于不完全二叉树首先需要将其转化为完全二叉树,为此可增加若干个虚拟结点。(缺点会造成空间的浪费)
链式存储结构:常见的两种,二叉链表与三叉链表。
二叉链表结构结点:

指向左孩子的指针域数据域指向右孩子的指针域
lchilddatarchild

三叉链表结构结点:

指向左孩子的指针域数据域指向双亲的指针域指向右孩子的指针域
lchilddataparentrchild

二叉树的遍历:遍历即其在二叉树上的递归实现。

基本的三个步骤:

  1. 访问根节点
  2. 遍历根的左子树(即依次访问左子树上的全部结点)
  3. 遍历根的右子树(即依次访问右子树上的全部结点)

假设visit已定义,visit函数为访问指针bt所指结点。

先序遍历:

void preorder(BinTree bt)
//先序遍历根指针为bt的二叉树
{if(bt!=NULL{
		visit(bt);//访问根结点bt
		preorder(bt->lchild);//先序遍历左子树
		preorder(bt->rchild);//先序遍历右子树
	}
}

中序遍历:

void inorder(BinTree bt)
//中序遍历根指针为bt的二叉树
{if(bt!=NULL{

		inorder(bt->lchild);//中序遍历左子树
		visit(bt);//访问根结点bt
		inorder(bt->rchild);//中序遍历右子树
	}
}

后序遍历:

void postorder(BinTree bt)
//后序遍历根指针为bt的二叉树
{if(bt!=NULL{
		postorder(bt->lchild);//后序遍历左子树
		postorder(bt->rchild);//后序遍历右子树
		visit(bt);//访问根结点bt
	}
}

求树高度

int Height(BinTree bt)
{
	int lh,rt;
	if(bt == NULL) return 0;
	else
	{
		lh = Height(bt->lchild)
		rh = Height(bt->rchild)
		return 1+(lh>rh ? lh:rh);
	}
}

层次遍历(队列的使用)

void levelorder(BinTree bt)
{
	LkQue Q;//使用队列用以保证二叉树结点按照层次顺序从左往右进入队列
	InitQueue(&Q);//初始化队列
	if(bt!=NULL)
	{
		EnQueue(&Q,bt);//根节点入队列
		while(!EmptyQueue(Q))
		{
			p = Gethead(&Q);
			outQueue(&Q);//结点出队列
			visit(p);//被访问结点
			if(p->lchild!=NULL) EnQueue(&Q,p->lchild);//左孩子结点入队列
			if(p->rchild!=NULL) EnQueue(&Q,p->rchild);//右孩子结点入队列
		}
	}
}

遍历的非递归实现(栈的使用)

void PreOrder(BinTree t)//非递归先序遍历二叉树
{
	BinTree p;//LS为指向链栈的指针
	LkStk *LS;
	if(t == NULL) return;
	InitStack(LS);
	p=t;
	while(p!=NULL||EmptyStack(LS))//循环条件是当指针或栈至少有一个不为空
	{
		if(p!=NULL)
		{
			Visit(p->data);//访问结点的数据
			Push(LS,p);//将当前指针压入栈中
			p=p->lchild;//将指针指向p的左孩子
		}
		else
		{
			p=Gettop(LS);//取栈顶元素
			Pop(LS);//出栈
			p=p->rchild;//指针指向它的右孩子
		}
		
	}
}
Created with Raphaël 2.2.0 开始 指针是否为空? 结束 指针和栈是否都为空? 结束 指针是否为空? 栈是否为空? 结束 出栈,指针指向右孩子 访问结点,结点入栈,指针指向右孩子 yes no yes no yes no yes no

应用举例

利用先序遍历和中序遍历的序列来确定二叉树。

BinTree Create(char a[],char b[],int i,int j,int m, int n)
//数组a存储先序序列,i和j分别是数组a的下标的上、下界
//数组b存储中序序列,m和n分别是数组b的下标的上、下界
//函数返回由a[i]至a[j]的先序序列和b[m]至b[j]的中序序列的构成的二叉树的根
{
	int k;
	BinTree p;
	if(n<0) return NULL;
	p = malloc(sizeof(BinTree));//建立根结点
	
	//先序序列的第一个结点可以确定该二叉树的根结点
	//
	p->data = a[i];
	
	k = m;
	while((k<=n)&&(b[k]!=a[i])) k++;//在中序列中找根
	if(k>n) error(); //未找到根
	p->lchild = Create(a,b,i+1,i+k-m,m,k-1);//递归构造左子树
	p->rchild = Create(a,b,i+k-m+1,j,k+1,n);//递归构造右子树
	return p;//返回根结点指针
}

写到这里,实际上自己对于树的认知还是比较模糊的。能隐约的认识到主要在于对于三种序列的理解。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值