二叉树性质操作遍历总结

这几天面试都碰到了二叉树,在此将问题总结下,以备今后再遇。


参考文献:
1. 数据结构与算法分析——c语言描述
2. 百度百科 http://baike.baidu.com/view/88806.htm   http://baike.baidu.com/view/127820.htm

定义:二叉树是一棵树,其中每个节点都不能有多于两个儿子节点


性质:
1、二叉树的平均深度要比N小,为O(N^(1/2));二叉查找树,其深度的平均值为O(logN)
2、二叉树分为满二叉树和完全二叉树。
满二叉树:所有非叶子节点都达到最大的二叉树
完美二叉树:叶子节点仅在层次最大的两层出现,对于任意节点,左子树的高度等于右子数的高度加1
3、第i层的节点总数不超过2^(i-1)
4、深度为h的二叉树,最多有2^h-1个节点(h>=1),最少有h个节点
5、对于任意二叉树,如果其叶子节点数为N0,而度数为2的节点总数为N2,则有N0=N2+1
6、具有n个节点的完全二叉树,其深度为int(log2n)+1
7、有N个节点的完全二叉树,如果采用顺序方式存储,则节点所在顺序表中的位置有如下关系:
若节点i不为根节点,则其父节点编号为i/2;
若2*i<=N,则其左儿子的编号为i*2,若2*i>N,则无左儿子;
若2*i+1<=N,则其右儿子的编号为2*i+1;若2*i>N,则无右儿子。
8、给定N个节点,能构成h(N)中不同的二叉树。
h(N)为卡特兰树德第N项, h(N) = C(n, 2n)/(n+1)


类型:
1、二叉查找树:对于树中的每个节点x,其左子树的所有关键字的值小于关键字x的值,右子树中所有关键字的值,大于x

下面是二叉查找树的定义及常用方法的C实现:

struct TreeNode;
typedef struct TreeNode *Position;
typedef struct TreeNode *SearchTree;

typedef int ElementType;

SearchTree MakeEmpty(SearchTree T);
Position Find(ElementType x, SearchTree T);
SearchTree Insert(ElementType X, SearchTree T);
SearchTree Delete(ElementType X, SearchTree T);
Position findMin(SearchTree T);
Position findMax(SearchTree T);


struct TreeNode
{
	ElementType Element;
	SearchTree left;
	SearchTree Right;
};

//建立空树
SearchTree MakeEmpty(SearchTree T)
{
	if(T != NULL)
	{
		MakeEmpty(T->Left);
		MakeEmpty(T->Right);
		free(T);
	}
	return NULL;
}

Position Find(ElementType X, SearchTree T)
{
	if(T == NULL)
		return NULL;
	
	if(x < T->Element)
		return Find(X, T->Left);
	else if(X > T->Element)
		return Find(X, T->Right);
	else
		return T;
}

Position FindMin(SearchTree T)
{
	if(T == NULL)
		return NULL;
	else if(T->Left == NULL)
		return T;
	else	return FindMin(T->Left);
}

Position FindMax(SearchTree T)
{
	if(T != NULL)
	{
		while(T->Right != NULL)
			T = T->Right;
	}
	return T;
}

SearchTree Insert(ElementType X, SearchTree T)
{
	if(T == NULL)
	{
		//Create and return an one-node tree
		T = (SearchTree)malloc(sizeof(struct TreeNode));
		if(T == NULL)
			Error("out of space!\n");
		else
		{
			T->Element = X;
			T->Left = T->Right = NULL;
		}
	}
	else if(X < T->Element)
		T->Left = Insert(X, T->Left);
	else if(X > T->Element)
		T->Right = Insert(X, T->Right);
		
	return T;
}

关于二叉查找树的删除,存在三种情况:

1、叶子节点,直接删除即可;

2、如果只有一个儿子节点,将其父节点指向其儿子节点,然后free掉该节点,即可;

3、最复杂的情况,有两个节点,一般的删除策略是用其右子树的最小节点的数据,代替该节点的数据,并递归删除那个节点(即右子数最小节点)

代码如下:

SearchTree Delete(ElementType X, SearchTree T)
{
	Position TmpCell;
	
	if(T == NULL)
		Error("Element not found");
	else if(x < T->Element)
		T->left = Delete(X, T->Left);
	else if(X > T->Element)
		T->Right = Delete(X, T->Right);
	else if(T->Left&&T->Right)
	{
		TmpCell = FindMin(T->Right);
		T->Element = TmpCell->Element;
		T->Right = Delete(T->Element, T->Right);
	}
	else
	{
		TmpCell = T;
		if(T->Left == NULL)
			T = T->Right;
		else if(T->Right == NULL)
			T = T->Left;
		free(TmpCell);
	}
	
	return T;
}

2、一般二叉树
二叉树的遍历,按照当前节点的遍历顺序,分别由前序,中序和后序,此处的前中后,是根据当前节点的遍历次序来说的。比如前序,就是先处理当前的节点P,再处理其左右节点。
void PreOrderTraverse(Tree T)
{
	if(T != NULL)
	{
		PrintElement(T->Element);
		PreOrderTraverse(T->Left);
		PreOrderTraverse(T->Right);
	}
}

中序和后序的递归遍历和上面相似,就是在中间或最后打印当前节点。下面分析树的非递归遍历,二叉树前序非递归遍历:
void PreOrderTraverseNew(Tree T)
{
	InitStack(S);
	Position p = T;
	while(p || !StackEmpty(S))
	{
		if(p)
		{
			PrintElement(p->Element);
			Push(S, p);
			p = p->lchild;
		}
		else
		{
			Pop(S, p);
			p = p->rchild;
		}
	}
}
中序的非递归遍历为:
void InOrderTraverseNew(Tree T)
{
	InitStack(S);
	Position p = T;
	while(p || !StackEmpty(S))
	{
		if(p)
		{
			Push(S, p);
			p = p->lchild;
		}
		else
		{
			Pop(S, p);
			PrintElement(p->Element);
			p = p->rchild;
		}
	}
}
可见,前序和中序的非递归遍历很相近,采用栈来实现,当左子树不为空时,就不断往栈中压入节点,直到左儿子为空,则弹出栈顶元素,并且指向其右儿子,然后重复前面的过程。直到栈中节点全部出栈,且p指向空。唯一的区别在于前序在当前节点入栈前就处理(本例中为打印)该节点,然后再入栈;而中序则是在该元素出栈时打印处理。可见,在非递归的实现中,遍历指针p总会指向null,因此遍历的判断条件仅有一个,具体来讲,就是是否print一个节点,前序的判断为入栈前就要打印,而中旬的判断为其左子树为空或已打印处理,从堆栈的角度来看,左子树为空和左子树已经打印,对于当前节点来说,栈的状态都是一样的,即要打印的节点都是栈顶元素,而且不用考虑右子树的状态,因此出栈打印即可。
但对于后序遍历来说,上面的方法是不行的。因为,在后序遍历中,一个节点是否打印处理,有两种情况,一是该节点左右子树都为空,另一种情况则是该节点的左右子树都已经打印。在判断一个节点是否出栈处理时,首先要确定该节点的右子树是否已经处理,若无,则还要进入右子数处理,因为需要设置一个tag,随着每个节点进入栈中,来记录该节点的右子树是否已经处理。
因此,后序的非递归遍历为:
void PostOrderTraverse(Tree T)
{
	InitStack(S);
	Position p = T;
	while(p || !StackEmpty)
	{
		while(p)
		{
			Push(S, p, 0);
			p = p->lchild;	
		}
		while(!StackEmpty(S) && GetTopTag(S) == 1)
		{
			Pop(S, p);
			PrintElement(p->Element);
		}
		if(!StackEmpty(S))
		{
			setTop(S, 1);
			p = GetTop(S);
			p = p->rchild;
		}
		else
			break;
	}
}

树的深度优先遍历即为先前提到的先序、中序和后序,而树的广度优先遍历则用队列来实现。
void BreadthSearch(Tree T)
{
	Queue Q;
	InitQueue(Q);
	Position p = T;
	EnQueue(Q, p);
	while(!QueueEmpty(Q))
	{
		DeQueue(Q, p);
		PrintElement(p->Element);
		if(p->lchild)
		{
			EnQueue(Q, p->lchild);
		}
		else if(p->rchild)
		{
			EnQueue(Q, p->rchild);
		}
	}
}

树的高度
采用递归的方式即可求取
int Height(Tree T)
{
	if(T == NULL)
		return 0;
	else
	{
		return Max(Height(T->Left), Height(T->Right)) + 1;
	}
}

3、哈夫曼树
给定n个权值作为n个叶子节点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也叫做哈夫曼树。

路径:在一棵树中,从一个节点往下可以到达的孩子或子孙节点之间的通路,成为路径;
路径长度:通路中分支的数目称为路径长度,若规定根节点的层数为1,则从根节点到第L层节点的路径长度为L-1;
节点的权:若将树中节点赋给一个有着某种含义的数值,则称该数值为该节点的权;
节点的带权路径长度:从根节点到该节点之间的路径长度与该节点的权的乘积。
树德带权路径长度:所有叶子节点的带权路径长度之和,记为WPL。

哈夫曼树的构造:
假设有n个权值,则构造出的哈夫曼树有n个叶子节点,n个权值分别设为分别设为w1、w2、...、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、...、wn看成是有n棵树的森林(每棵树仅有一个节点);
(2) 在森林中选出两个根节点的权值最小的树合并,作为一棵新树的左右子树,且新树的根节点权值为其左右子树的根节点权值之和;
(3) 从森林中删除选取的两棵树,并将新树加入森林;
(4) 重复(2) (3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值