浙大数据结构-二叉树的遍历算法及拓展

1.递归版本

1.先序遍历

  1. 访问根结点
  2. 先序遍历左子树
  3. 先序遍历右子树
typedef struct TreeNode *BinTree;
typedef struct{
	int Data;
	BinTree lchild;
	BinTree rchild;	
} TreeNode;
void PreOrderTraversal( BinTree BT){
	if( BT ){
		printf("%d", BT->Data);
		PreOrderTraversal( BT->lchild );
		PreOrderTraversal( BT->rchild );
	}
} 

2.中序遍历

  1. 中序遍历左子树
  2. 访问根结点
  3. 中序遍历右子树
void InOrderTraversal( BinTree BT ){
	if( BT ){
		InOrderTraversal( BT->lchild );
		printf("%d", BT->Data);
	}	InOrderTraversal( BT->rchild );
}

3.后序遍历

  1. 后序遍历左子树
  2. 后序遍历右子树
  3. 访问根结点
void PostOrderTraversal( BinTree BT ){
	if( BT ){
		PostOrderTraversal( BT->lchild );
		PostOrderTraversal( BT->rchild );
		printf("%d", BT->Data); 
	}
}

这三种遍历均属于深度优先遍历,经过结点的路线相同,只不过经过的时机不同。

先序遍历得前缀表达式,中序遍历得中缀表达式,后序遍历得后续表达式。但是中缀表达式受运算符优先级的影响,不准,往往需要输出左子树时先加左括号,最后再加右括号。这种需要存储的特性可以借助堆栈实现。

2.非递归遍历

递归函数实质上调用系统栈,并做了注入保护现场和恢复现场等复杂的操作。

有两种改进算法:

①用户定义栈代替系统栈,效率提高

②将二叉树线索化,有很大的效率提升

1.深度优先遍历的非递归版本

1.非递归的先序遍历

#define maxsize 100;
typedef struct TreeNode *BinTree;
struct TreeNode{
	int Data;
	struct TreeNode *lchild;
	struct TreeNOde *rchild;
};
void PreOrderTraversal( BinTree BT){
	if( BT ){
		BinTree stack[maxsize]; int top = -1;
		BinTree p;
		stack[++top] = BT;
		while( top != -1 ){
			p = stack[top--];
			visit(p);
			if( p->rchild )
				stack[++top] = p->rchild;
			if( p->lchild )
				stack[++top] = p->lchild;
		}
	}
} 

按照先序序列,判断不空时先入栈,在栈不空时循环:出栈并访问,递归地访问左子树,递归地访问右子树。

注意先序遍历先访问左子树,因此先压入右子树。

2.非递归的中序遍历

void InOrderTraversal( BinTree BT ){
        if( BT ){
		BinTree stack[maxsize]; top = -1;
		BinTree p = BT;
		while( p != NULL || top != -1 ){
			while( p!= NULL ){
				stack[++top] = p;
				p = p->lchild;
				
			}
			while( top != -1 ){
				p = stack[top--];
				visit(p);
				p = p->rchild
			}
		}
	}
}

中序遍历有一个特点,进栈、出栈过程中会存在一个中间状态栈空,即遍历完根结点的时候。但是此时仍未遍历根结点的右子树,因此循环和判别条件为树未遍历完或栈不空。

3.非递归的后序遍历

void PostOrderTraversal( BinTree BT ){
	if( BT ){
		BinTree stack1[maxsize]; int top1 = -1;
		BinTree stack2[maxsize]; int top2 = -1;
		stack1[++top1] = BT;
		while( BT ){
			BinTree p = stack1[top1--];
			stack2[++top2] = p;
			if( p->lchild ) stack1[++top1] = p->lchild;
			if( p->rchild ) stack2[++top2] = p->rchild;
		}
		while( top2 != -1 ){
			p = stack2[top2--];
			visit(p);
		}
	}
} 

逆后序序列和先序序列类似,只不过左右子树压入栈的顺序相反,因此得到逆后序序列后再压入第二个栈再弹出。

2.线索二叉树

对于n个结点的二叉树,如果用二叉链表结构存储,有n+1个空指针域,将空指针域利用起来,以便找到每个结点的直接前驱和直接后继。

二叉树被线索化后近似于一个线性结构,分支解雇的遍历操作转化为近似于线性结构的遍历操作,通过线索的辅助使得寻找当前结点前驱或者后继的平均效率大大提高(虽然栈不再使用,但是每个结点中多了两个标记域,它所导致的额外空间开销是否比非递归遍历算法中的开销少,视情况而定)

typedef struct{
	char data;
	int ltag, rtag;
	struct TBTNode *lchild;
	struct TBTNode *rchild; 
} TBTNode; 
//ltag = 0,lchild为指针,指向结点的左孩子;
//ltag = 1,lchild为线索,指向结点的直接前驱 
//rtag = 0,rchild为指针,指向结点的右孩子 
//rtag = 1,rchild为线索,指向结点的直接后继 

对一棵二叉树中所有结点的空指针按照某种遍历方式加线索的过程叫做线索化。

void InThread( TBTNode* BT, TBTNode* &pre ){
	if( !BT ) return;
	InThread( BT->lchild, pre);
	if( p->lchild == NULL ){
		p->lchild = pre;
		p->ltag = 1;
	}
	if( pre != NULL && pre->rchild == NULL ){
		pre->rchild = p;
		pre->rtag = 1;
	}
	pre = p;
	InThread( BT->rchild, pre);
} 

void CreateInThread( TBTNode *root){
	TBTNode *pre = NULL;
	InThread( root, pre );
	pre->rchild = NULL;
	pre->rtag = 1;
	//虽然遍历之后最左边和最右边的叶子结点均NULL,但是tag=1 
}

TBTNode First( TBTNode *p ){
	while( p->ltag == 0 )
		p = p->lchild
	return p; 
}
TBTNode Last( TBTNode *p ){
	while( p->rtag == 0 )
		p = p->rchild;
	return p;
}
TBTNode Prior( TBTNode *p ){
	if( p->ltag == 0 )	return Last( p->lchild);
	else return p->lchild;
}
TBTNode Successor( TBTNode *p){
	if( p->rtag == 0 ) return First( p->rchild );
}	else return p->rchild;

 

先序、后序大致相似,都有遍历框架

void PreThread( TBTNode *BT, TBTNode *&pre ){
	if( BT->lchild == NULL ){
		BT->lchild = pre;
		BT->ltag = 1;
	}
	if( pre != NULL && pre->rchild == NULL ){
		pre->rchild = BT;
		pre->rtag = 1; 
	}
	pre = BT;
//为避免转圈,需要在入口加上限制条件,左右指针不是线索才能继续递归 
	if( BT->ltag == 0 )
		InThread( BT->lchild, pre);
	if( BT->rtag == 0 )
		InThread( BT->rchild, pre);
}

3.层次遍历

层次遍历二叉树的核心问题是二维结构的线性化。从结点依次访问其左右儿子结点,但是按照之前的遍历算法,访问一个子结点后,会丢失另一个子结点的地址,因此需要一个存储结构保存暂时不访问的结点,堆栈和队列均可,不妨以队列为例。

建立循环队列,如果树非空则先根结点入队,然后循环执行:

  1. 出队一个结点
  2. 访问该结点
  3. 所该结点的左、右孩子结点非空,则将其左、右孩子的指针顺序入队(FIFO,因此左孩子先入)
//sequencial queue
#define maxsize 100;
typedef BinTree Position;
void LevelOrderTraversal( BinTree BT ){
	int front, rear;
	BinTree que[maxsize];
	front = rear = 0;
	Position p;
	if( !BT ){
		rear = (rear + 1)%maxsize;
		que[rear] = BT
		while( rear != front ){
			front = (front + 1)%maxsize;
			p = que[front];
			visit(p);
			if( p->lchild ){
				rear = (rear + 1)%maxsize;
				que[rear] = p->lchild;
			}
			if( p->rchild ){
				rear = (rear + 1)%maxsize;
				que[rear] = p->rchild;
			}
		}
	}
}
#define maxsize 100;
typedef struct TreeNode *BinTree;
typedef BinTree Position;
typedef struct{
	int Data;
	BinTree lchild;
	BinTree rchild;	
} TreeNode;

struct QNode{
	BinTree Data[maxsize];
	int rear;
	int front; 
};
typedef struct QNode *Queue; 
Queue CreateQueue( int maxsize ){
	Queue Q;
	Q.front = Q.rear = 0;
	return Q;
}
void AddQ( Queue &Q, BinTree T){//引用传递
	rear = (rear + 1)%maxsize;
	Q.Data[rear] = T->Data;
}
BinTree DeleteQ( Queue &Q ){
	Q.front = (Q.front + 1)%maxsize;
	BinTree q = Q.Data[front];
	return q;
}
int IsEmpty( Queue Q ){
	if( Q.front = Q.rear ) return 1;
	else return 0;
} 
void LevelOrderTraversal( BinTree BT){
	Queue Q; BinTree T;
	if( !BT ) return;
	Q = CreateQueue( maxsize );
	AddQ( Q, BT );
	while( !IsEmptyQ( Q ) ){
		T = DeleteQ( Q );
		printf("%d\n", T->Data)
		if( T->lchild ) AddQ( Q, T->lchild );
		if( T->rchild ) AddQ( Q, T->rchild );
	}
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值