“抽象”《大话数据结构》第六章——二叉树

二叉树的性质

二叉树的性质1

根据满二叉树的特点可知,普通二叉树每层的节点数是不会比满二叉树还多的,由于满二叉树的前后层数上的结点数目是按照比例2递增的,a1 = 1,a2 = 2,a3 = 4,a4 = 8.。。。所以ai = 2^( i - 1)
在二叉树的第 i 层上至多有2^( i - 1个结点。

二叉树的性质2

讨论二叉树总共多少结点,以满二叉树位例,假设满二叉树的深度为4,则总结点数是比例为2的等比数列求和
a 1 ∗ ( 1 − q i − 1 ) / ( 1 − q ) a1*(1 - q^{i - 1})/(1-q) a11qi1/(1q)
带入a1 = 1,q = 2.得
2 n − 1 2^{n} - 1 2n1
所以有深度为i的二叉树至多有 2 n − 1 2^{n} - 1 2n1个结点。

二叉树的性质3

辅助记忆:1,2,4,8。等比数列的前四项都有如下关系,1+2 = 4-1,1+2+4 = 8-1,也就是某一层的结点数是前面的结点数加起来再加1。这样就有
对任何一颗二叉树T,如果其终端节点数为n0,度为2的结点数n2,则有n0 = n2 +1

二叉树的性质4

已知满二叉树的结点数是 n 1 = 2 k − 1 n1 = 2^{k} - 1 n1=2k1,完全二叉树的结点n不会大于满二叉树的结点,但是不会小于 n 2 = 2 k − 1 − 1 n2 = 2^{k - 1} - 1 n2=2k11
所以 n2 <= n <= n1.。不等式全部加1, n2 + 1<= n +1 <= n1 + 1,两边再取对数,
深度k 具有n个结点的完全二叉树的深度为【log2(n)】+1

二叉树的性质5

如果对一颗有n个结点的完全二叉树的结点按层序编号,对任意结点 i 有:
1 如果 i= 1,则结点 i 是二叉树的根,无双亲;如果 i> 1,则有双亲,双亲序号为【 i/2 】取整。
2 如果2i > n,则结点 i 无左孩子。否则左孩子序号为2i。
3 如果2i+1>n,则结点i无右孩子;否则其右孩子结点为2i+1.

二叉树的存储结构

顺序存储结构只用于完全二叉树,若是其它类型的树,如右斜树,会造成空间的浪费。
链式存储结构
结点分为数据域和指针域两个部分
数据域:存储数据
指针域:lchild指针指向左孩子的地址,rchild指针指向右孩子的地址

typedef struct link_Tree
{
	char data;
	struct link_Tree *lchild,*rchild;
	
}TreeNODE,*TREEPTR;

遍历二叉树

二叉树的遍历是指从根结点出发,按照某种次序一次访问二叉树中所有的结点,使得每个结点被访问一次且仅被访问一次。

二叉树遍历方法

前序遍历法

若二叉树为空,则空操作返回,否则先访问根结点,然后遍历左子树,再前序遍历右子树。
代码如下:

void lookup_tree(TREEPTR T)
{
	if(T == NULL)
		return;
	printf("%c",T->data);
	lookup_tree(T ->lchild);
	lookup_tree(T ->rchild);

}

中序遍历法

若树为空,则操作返回,否则从根结点开始,中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。

void lookup_tree_bymin(TREEPTR T)
{
	if(T == NULL)
		return;
	lookup_tree(T ->lchild);
	printf("%c",T->data);
	lookup_tree(T ->rchild);
	
}

后序遍历法

若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。

void lookup_tree_bylast(TREEPTR T)
{
	if(T == NULL)
		return;
	lookup_tree(T ->lchild);
	lookup_tree(T ->rchild);
	printf("%c",T->data);
	
}

所以:已知前序和中序遍历序列能确定一棵树,已知后序和中序序列也能确定一棵树。
但是已知前序和后序是不能确定树的。

二叉树的建立

约定输入#符号代表空树,输入其它字符则开创一个结点,存储数据。
与遍历树一样,这里使用前序遍历的思想,创建一颗树。

  1. 传入树结点的指针
  2. 接受输入字符
  3. 判断输入字符,若是#,则指向NULL;若是其它字符,则开辟一个结构体类型的空间,并把地址赋给指针
  4. 再次调用函数本身,把当前结点的 lchild 指针的地址作为参数
  5. 再次调用函数本身,把当前结点的 rchild 指针的地址作为参数
    代码如下:
 void creat_tree(TREEPTR T)
{
	char ch;
	scanf("%c",&ch);
	if(ch == '#')
		T = NULL;
	else
		{
			T = (TREEPTR)malloc(sizeof(TreeNODE));
			if(!T)
				exit(OVERFLOW);
			T ->data = ch;
			creat_tree(&T ->lchild);
			creat_tree(&T ->rchild);
		}
}

线索二叉树

充分利用二叉树结点中指向NULL的空指针,若使用中序遍历,使lchild指针指向前驱,rchild指针指向后继结点,这些指针就称为线索,加上线索的二叉树链表称为线索链表,相应的二叉树就是线索二叉树了。
为了区分左右孩子指针是指向孩子还是前驱或者后继,需要增加两个标记,ltag和rlag,它们是存放0和1的布尔型变量
所以每个结点有 lchild ltag data rtag rchild 五个元素
其中,

  1. ltag为0时指向该结点的左孩子,为1时指向该结点的前驱
  2. rtag为0时指向该结点的右孩子,为1时指向该结点的后继
    经过上述操作后,二叉树实际上被转换成一个双向链表了,链表的结点顺序与根据中序遍历二叉树的结点顺序相同。

线索二叉树结构实现

正如前面所述,结点内包含 lchild ltag data rtag rchild 五个元素。

typedef enum {Link,Thread} PointerTag;
typedef struct BiThrNode
{
	char data;
	struct BiThrNode *lchid,*rchild;
	PointerTag ltag;
	PointerTag rtag;
}BiThrNode,*BiThrTree;

中序遍历线索化的递归函数代码如下:
1 判断树的根结点是否为空
2 根据中序遍历思想,调用递归函数,先遍历左子树,不断入栈,直到最左边的叶子,没有左孩子,开始出栈。函数层层往上出栈。
3 到了双亲结点层面的函数,执行前驱,后继线索的赋值,再递归调用函数,遍历右孩子。
4 倒数第二层函数结束后,就出栈第三层函数。
函数代码如下:

BiThrTree pre = NULL;
BiThrTree InThreading(BiThrTree current,BiThrTree head)
{
	if(current)
		{
			InThreading(current->lchid);
			if(!current->lchid)
				{
					if(pre == NULL)
						{
							current->ltag =Thread;
							current->lchid = head;
						}
					current->ltag = Thread;
					current->lchid = pre;
				}
			if(!pre->rchild)
				{
					pre->rtag = Thread;
					pre->rchild = current;
				}
			pre = current;
			InThreading(current->rchid);
		}
	return current;
	
}
head->lchid = current;
BiThrTree last =  InThreading(current,head);
last->rchild = head;
last->rtag = Thread;
head->rchild = last;
head->rtag = Thread;

你可能感兴趣的:(大话数据结构自学旅途)