/****
//后的注解是自己的理解.*****/
树是一种数学上的抽象,在算法的设计与分析中起到了核心的作用,
我们使用树来描述算法的动态性质;
我们创建并用显式的数据结构,这些数据结构都是树的具体实现.
一棵树:满足某种要求的节点与边的一个非空集合.//边是虚拟的,节点才是实的。
对于树来说:任意两点只有唯一一条路径.
自由树>有根树>有序树>M叉树.
二叉树表示:
typedef struct node * link;
struct node{
Item item;
link left , right;
};
二叉树与有序森林之间,存在一一对应关系。
图:
定义:一个图由节点集合和连接不同节点对的边的集合组成(任何一对节点至多有一条边相连)。
概念: 简单路径 - 从A节点到B节点的边的序列(节点不重复出现);
连通 - 存在一条简单路径连接任何节点;
环路 - 开头与末尾节点相同的一条简单路径。
图是树的条件(任一条都可以):
* G有N-1条边,并且没有环路;
* G有N-1条边,并且是连通的;
* 只有一条简单路径连接G中的每一对节点;
* G是连通的,但在任一条边被删除后不再保持连通。
**********************************************************************************************************
二叉树的性质:
&& 一棵有N个内部节点的二叉树有N+1个外部节点.
&& 一棵有N个内部节点的二叉树有2N个链接:N-1个连接到内部节点,N+1个链接到外部节点。
&& 任何带有N个内部节点的二叉树的外部路径长度比内部路径长度大2N。//每一个节点都致使外部路径长度比内部路径长度大2.
&& 带有N个内部节点的二叉树的高度至少是lg N,至多是N-1.
&& 带有N个内部节点的二叉树的内部路径长度至少是N lg(N/4),至多是N(N-1)/2.
*********************************************************************************************************
树的遍历:
*前序:节点、节点左子树、节点右子树;
*中序:节点左子树、节点、节点右子树;
*后序:节点左子树、节点右子树、节点;
//递归遍历
/**
visit函数的放置顺序不同,得到不同的遍历。
**/
void traverse( link h , void( * visit ) (link) ){
if (h == null)
return ;
( *visit )(h);
traverse( h->left , visit );
traverse( h->right , visit );
}
//使用下推栈遍历
从一个抽象栈开始考察,这个栈能够保存数据项或树,以将被遍历的树初始化。然后我们进入一个循环,在这个循环中我们弹出并处理在栈顶的元素,如此继续,直到栈空为止。如果弹出的是一个数据项,visit(),若弹出的是一棵树,就按照希望的顺序执行一系列的入栈操作:
*对于前序,压入右子树,压入左子树,再压入节点;
*对于中序,压入右子树,压入节点,再压入左子树;
*对于后序,压入节点,压入右子树,再压入左子树;
前序遍历的非递归实现(一):
//按照栈的特点与树的遍历关系,简洁易懂!
void traverse( link h , void ( *visit ) (link) ){
STACKinit(max);
STACKpush(h);
while( !STACKempty() ){
(*visit)(h = STACKpop() ) ;
if( h->right != NULL )
STACKpush( h->right );
if( h->left != NULL )
STACKpush( h->left );
}
}
前序遍历的非递归实现(二)://按照遍历的路线行进,程序冗长。但转中序遍历则极为方便,只需把访问函数放到STACKpop()前即可。
void traverse( link h ){
STACKinit(max);
while( h || !STACKempty() ){
while(h){
(*visit)( h );
STACKpush( h );
h=h->left;
}
if( !STACKempty() ){
h = STACKpop();
h = h->right;
}
}
}
中序遍历的非递归实现:
上述已写,此处略。
后序遍历的非递归实现:
//需要设置访问标记(想了很久,感觉不标记不行,有想到的,请不吝告知)。
程式一,不好的地方:内部结点要入栈出栈再入栈。
void traverse( link h ){
STACKinit(max);
do{
while( h ){
STACKpush( h );
h.flag = 0; //右节点未访问,可设置为默认值。
h = h->left;
}
if( !STACKempty() ){
h = STACKpop();
if( h->right == null || h.flag == 1 ){
(*visit)( h );
//h.flag = 1; //标记右节点已访问,无所谓标记不标记
h = null; //防止再入栈,很重要!否则栈将无限增大!
}else{
h.flag = 1;
STACKpush( h ); //右节点未访问,再入栈。
h=h->right;
}
}
}while( !STACKempty() );
}
程式二:
//设置一个标记数组
void traverse( link h ){
STACKinit(max);
do{
while( h ){
STACKpush( h );
flag[ STACKtop() ]=0; //每次栈顶置0
//h.flag = 0; //右节点未访问,可设置为默认值。
h = h->left;
}
if( !STACKempty() ){
if( flag[ STACKtop() ] == 1 ){
h = STACKpop();
(*visit)( h );
h = null ;
}else{
h = getSTACKtop();
flag[ STACKtop() ] = 1;
h = h->right;
}
/***
h = STACKpop();
if( h->right == null || h.flag == 1 ){
(*visit)( h );
//h.flag = 1; //标记右节点已访问,无所谓标记不标记
h = null; //防止再入栈,很重要!否则栈将无限增大!
}else{
h.flag = 1;
STACKpush( h ); //右节点未访问,再入栈。
h=h->right;
}
**/
}
}while( !STACKempty() );
}
层序遍历二叉树:
//队列的 先进先出 特性,类似于层递归思想。
void traverse( link h , void (*visit)(link) ){
QUEUEinit(max);
QUEUEput(h);
while( !QUEUEempty() ){
( *visit )( h = QUEUEget() );
if( h->left != NULL )
QUEUEput( h->left );
if( h->right != NULL )
QUEUEput( h->right );
}
}
******************************************************************************************************
能高效计算二叉树的内部路径长度的程序就是较大的挑战。
*******************************************************************************************************
递归计算树的节点的个数:
int count ( link h ){
if( h==null )
return 0;
return count( h->left )+count( h->right )+1;
}
递归计算树的高度:
int height( link h ){
int u , v ;
if( h == null )
return -1;
u = height( h->left );
v = height( h->right );
return u>v?u:v;
}
********************************************************************************************************
快速打印树:
void printnode( char c , int h ){
int i ;
for( i=0; i<h ;++i )
printf(" ");
printf("%c\n",c);
}
void show( link x ,int h ){
if( x==null ){
printnode('*',h);
return;
}
show(x->left ,h+1);
printnode(x->item,h);
show(x->right,h+1);
}
***************************************************************************************************
联赛的构造:要为N>1的数据项构造一个联赛,使用分治策略,把数据项分成两半,为每一半构造一个联赛,并创建一个新的节点,该节点具有到两个联赛的链接,并且有一个数据项,该数据项是两个联赛的根节点的较大数据项的副本。
typedef struct node * link;
struct node {
Item item;
link left,right;
};
link New( Item item, link left ,link right) {
link x = malloc(sizeofof *x);
x->item = item;
x->left = left;
x->right = right;
return x;
}
link max(Item a[] ,int left ,int right) {
int m=(left+right)/2;
Item u,v;
link x =New( a[m] ,null,null);
if( left=right )
return x;
x->left = max( a, left, m);
x->right = max(a ,m+1 ,right);
u = x->left->item;
v = x->right->item;
x->item =u>v:u?v;
return x;
}
***********************************************************************************************
使用前缀表达式构造一棵树:
char *a;
int i;
typedef struct Tnode * link;
struct Tnode{
char token;
link left,right;
};
link New( char token ,link left ,link right) {
link x = malloc(sizeof *x );
x->token = token;
x-left = left;
x->right = right;
return x;
}
link parse() {
char t = a[i++];
link x = New( t, null ,null );
if( t>='+' && t<='/'){
x->left = parse();
x->right = parse();
}
return x;
}