盘点二叉树的相关算法题目

 1、已知一棵二叉树按顺序存储结构进行存储,设计一个算法,求编号为i和j的两个节点的最近的公共祖先结点的值。

#include 

/*
*	二叉树按顺序存储 
*	我们应该明白一点。二叉树的顺序存储是按照完全二叉树进行存储的!!
*	对完全二叉树按从上到下,从左到右的顺序依次编码1,2...n:它是有以下关系的: 
*	当i>1时,结点的双亲编号为i/2(向下取整),即当i为偶数时,其双亲的编号为i/2,它是双亲的左孩子;它是奇数时,其双亲的编号为(i-1)/2,它是双亲的右孩子
*	当2i<=n时,结点i的左孩子编号为2i,否则无左孩子;
*	当2i+1<=n时,结点i的右孩子编号为2i+1,否则无右孩子;
*	结点i所在的层次(深度)为log2i+1 
*
*   解题思路:
*	(1)若i>j,则结点i所在层次大于等于结点j,那么若结点i的双亲结点=j,那么便是其最近公共祖先结点,否则令i=i/2,即以结点i的双亲结点为起点,继续查找
*	(2) 若j>i,则结点i所在层次大于等于结点j,那么若结点j的双亲结点=i,那么便是其最近公共祖先结点,否则令j=i/2,即以结点j的双亲结点为起点,继续查找 
*/

int common_ancestor(int* T,int i,int j){
	if(i==j)
		return i;
	else if(i>j)
		common_ancestor(T,i/2,j);//这里除以2就是它的双亲节点(上面提到的完全二叉树顺序存储关系)
	else
		common_ancestor(T,i,j/2);	
} 


int main(){
	int T[]={0,1,2,3,0,4,0,5,0,0,6,0};
	printf("%d",common_ancestor(T,2,10));
} 

非递归解法:

int common_ancestor2(int* T,int i,int j){
	if(T[i]!='#'&&T[j]!='#'){
		while(i!=j){
			if(i>j)
				i=i/2;
			else
				j=j/2;
		}
	}
} 

2、试给出二叉树的自下而上,从右向左的层次遍历算法。

层次遍历

#include 

/*
*	我们首先想到的层次序列都是自上而下,从左到右。欸,刚好相反,那么相反我们想到的是什么?没错,就是栈
*	首先就是利用原有的层次遍历算法,然后再出队的同时把各结点压入栈中。在所有结点都入栈之后,我们再从栈顶开始依次访问。
*	具体实现步骤:
*	(1)首先把根结点入队列
*	(2)把一个元素出队,接着把该结点入栈
*	(3)如果该结点的左孩子不为空,那么把左孩子入队列; 如果该结点的右孩子不为空,那么把右孩子入队列;
*	(4)若队列不为空。跳转到(2)继续执行
*	(5)队列为空,跳出循环。对栈进行一次遍历。 
*/

void InverLevel(BiTree bt){
	Stack s,Queue Q;//创建栈和队列 
	BitNode* p;
	if(bt!=NULL){
		InitStack(s);//初始化栈 
		InitQueue(Q);//初始化队列 
		EnQueue(Q,bt);//根节点入队
		while(!QueueEmpty(Q)){
			DeQueue(Q,p);//出队列 
			Push(s,p);//并且入栈
			if(p->lchild)
				EnQueue(Q,p->lchild);//如果该结点的左孩子不为空,那么把左孩子入队列
			if(p->rchild)
				EnQueue(Q,p->rchild);//如果该结点的右孩子不为空,那么把右孩子入队列;
		}
		//跳出循环后,对栈进行一次遍历
		while(!StackEmpty(s)){
			Pop(s,p);
			visit(p->data);
		}
	}
} 

5、编程求以孩子兄弟表示法存储的森林的叶子结点

#include 


/*
	思想:
		(1)孩子兄弟表示法,第一个指针指向第一个孩子,第二个指针指向下一个兄弟结点,
		那么很显然,若结点的孩子指针为空,那它必是叶结点。
		(2)总的叶子结点个数就是孩子子树上的叶子数和兄弟子树上的叶子数 
	
	步骤:
		(1)如果树为空,返回0
		(2)如果结点的孩子指针为空,返回叶结点(个数1)和其兄弟子树中的叶结点数
			否则,返回孩子子树上的叶子数和兄弟子树上的叶子数 
*/ 

typedef struct node{
	ElemType data;
	struct node *fch, *nsib;
}*CSTree; 

int Leaves(CSTree t){
	if(t==NULL) //如果树为空,返回0
		return 0;
	if(t->fch==NULL) //如果结点的孩子指针为空,返回叶结点(个数1)和其兄弟子树中的叶结点数
		return 1 + leaves(t->nsib);
	else  //否则,返回孩子子树上的叶子数和兄弟子树上的叶子数 
		return leaves(t->fch) + leaves(t->nsib);	
}

7、设一棵二叉树中各结点的值互不相同,其先序序列和中序序列分别存于两个一维数组A[1...n],B[1...n]中,试编写算法建立该二叉树的二叉链表。

#include 

/*
*	思路: 
	首先我们需要根据先序序列确定根结点A,然后通过A在中序序列中划分出二叉树的左右子树。
	基本思路就是这样,仔细一想,是不是和快速排序很相似(先确定枢轴,然后通过枢轴划分。
	再划分好的区域继续划分,思路基本上是一样的,所以我们这个算法采用的也是递归算法)
	
	这个算法最后是自己动手画两个图,一个先序序列,一个后序序列,然后通过根结点去确定左子树和右子树的长度表达式
	
	步骤 :
	(1)根据先序序列确定树的根结点
	(2)通过根结点在中序序列中划分出二叉树的左右子树。然后根据左右子树结点在先序序列中的次序确定根结点,
	再继续跳回第(1)步,然后不断划分 
	如此重复上述步骤,直到每棵子树仅有一个结点为止。	 
*/

//A为先序序列,B为中序序列
BiTree PreInCreat(ElemType A[],ElemType B[],int l1,int r1,int l2,int r2){
	//l1,r1为先序序列的第一和最后一个结点下标
	//l2,r2为中序序列的第一和最后一个结点下标 
	BiTNode *root=(BiTNode *)malloc(sizeof(BiTNode));
	root->data=A[l1]; //通过先序序列确定的根结点 
	for(int i=l2,B[i]!=root->data;i++) //确定中序序列的根结点 
		;
	int lLen=i-l2; //左子树长度 
	int rLen=r2-i; //右子树长度 
	if(lLen)
		root->lchild=PreInCreat(A,B,l1+1,l1+lLen,l2,i-1); //最后的i-1写成l2+len-1也一样 
	else
		root->lchild=NULL;
	if(rLen)
		root->rchild=PreInCreat(A,B,r1-rLen+1,r1,r2-rLen+1,r2);
	else
		root->rchild=NULL;
	return root;
} 

9、假设二叉树采用二叉链表存储结构存储,试设计一个算法,计算一个给定二叉树的所有双分支结点。

递归

#include 

/*
	这道题相对来说还是比较简单的,就是先判断根结点是否是双分支结点,然后再判断其左右子树。直到所有子树都遍历完。
	步骤:
	递归出口:
		如果树为空,返回0
	递归体:
		如果该结点左右子树不为空,递归其左右子树并 + 1
		其他情况(该结点为叶子结点或单分支结点),递归其左右子树。 
*/ 

int DsonNodes(BiTree T){
	if(T==NULL)
		return 0;
	else if(T->lchild!=NULL&&T->rchild!=NULL)
		return DsonNodes(T->lchild) + DsonNodes(T->rchild) + 1; //是双分支结点,+1 
	else 
		return DsonNodes(T->lchild) + DsonNodes(T->rchild);
}

10、设树B是一棵采用链式结构存储的二叉树,编写一个把树B中所有结点的左、右子树进行交换的函数。

递归

#include 

/*
	步骤: 
	(1)先把结点b的左孩子的左右子树进行交换,
	(2)再对结点b的右孩子的左右子树进行交换 
	(3)最后交换结点b的左右孩子
	当结点为空时递归结束!!
	
	思想: 
	乍一看是不是很眼熟,没错,其实就是递归版的后序遍历!!再后序遍历的基础上增加了交换结点而已。 
*/

void swap(BiTree T){
	if(T){
		swap(T->lchild);//递归地交换左子树 
		swap(T->rchild);//递归地交换右子树 
		//下面就是交换左右孩子 
		BiTree temp=T->lchild;
		T->lchild=T->rchild;
		T->rchild=temp;
	} 
} 

11、假设二叉树采用二叉链表存储结构存储,设计一个算法,求先序遍历序列中第k(1<=k<=二叉树结点个数)个结点的值。

递归

#include 

/*
	思想:
		在先序遍历的基础上增加一个全局变量i,当i==k时返回结点即可
		
	步骤:
		(1)设置一个全局变量i记录已访问过的结点的序号,其初值根结点在先序序列的序号,即1
		(2)如果是空树,返回特殊字符'#'
		(3)当i==k时,表明找到目标结点,返回结点的值
		(4)若i!=k,i++,并递归地在该结点的左子树中查找,若找到则返回,否则递归地在右子树中查找 
*/ 

int i=1; //置一个全局变量i记录已访问过的结点的序号
ElemType PreNode(BiTree T,int k){
	
	if(T==NULL)
		return '#';
	if(i==k)
		return T->data;
	i++; //下一个结点 
	ElemType ch=PreNode(T->lchild,k); //递归地在该结点的左子树中查找
	if(ch!='#')
		return ch; //在左子树中,则返回该结点 
	ch=PreNode(T->rchild,k); //递归地在右子树中查找
	return ch;
}

12、设有一棵满二叉树(所有结点值均不同),已知其先序序列为pre,设计一个算法求其后序序列post。

#include 

/*
	思想: 
		对于一般二叉树,单纯通过先序序列是无法确定后序序列的。
		但是对于满二叉树来说,
			(1)其任意一个结点的左右子树的结点数相等。
			(2)同时,其先序序列的第一个结点为后序序列的最后一个结点!!
	
	步骤:
		递归体:
			(1)转换左子树,参数为先序序列的左子树,及其第一个结点,最后一个结点,后序序列的左子树,及其第一个结点,最后一个结点	 
			(2)转换右子树,参数为先序序列的右子树,及其第一个结点,最后一个结点,后序序列的右子树,及其第一个结点,最后一个结点 
*/

void PreToPost(ElemType pre[],int l1,int r1,ElemType post[],int l2,int r2){
	int half;
	if(l1<=r1){
		post[r2]=pre[l1];
		half=(r1-l1)/2;
		PreToPost(pre, l1+1, l1+half, post, l2, l2+half-1);//转换左子树
		PreToPost(pre, l1+half+1, r1, post, l1+half, r2-1);//转换右子树
	}
}

13、设计一个算法将二叉树的叶结点按从左到右的顺序连成一个单链表,表头指针为head。二叉树按二叉链表方式存储,链接时用叶结点的右指针域来存放单链表指针。

中序遍历

#include 

/*
	思想: 
		题目说是将二叉树的叶结点按从左到右的顺序连成一个单链表,
		那么就需要遍历单链表,通常我们所用的先序、中序、后序遍历对于叶结点的访问顺序都是从左到右,
		所以我们只需要任选一种遍历算法,再此基础上对于遍历到的结点进行判断,如果是叶结点那么将其链入链表即可。 
	
	步骤:
		(1)设置全局变量的头结点head和前驱结点pre(初始为空)。
		(2)进行递归地中序遍历。 
		(3)对于遍历到的叶结点,第一个叶结点(其pre=NULL)需要另外处理:第一个叶结点由指针head指向
			其余叶结点,则pre->rchild指向当前叶结点。
			对于最后一个结点,其rchild指针为空	
*/

LinkedList head,pre=NULL; //设置全局变量的头结点head和前驱结点pre(初始为空)。
LinkedList InOrder(BiTree bt){
	if(bt){
		InOrder(bt->lchild); //中序遍历左子树 
		if(bt->lchild==NULL&&bt->rchild==NULL){
			if(pre==NULL){ //处理第一个结点 
				head=bt;
				pre=bt;
			}else{
				pre->rchild=bt; //前驱结点指针pre的右指针指向当前结点 
				pre=bt;
			}
		} 
		InOrder(bt->rchild); //中序遍历右子树
		pre->rchild=NULL; //设置链表尾 
	}
	return head; 
} 

15、设计求二叉树的带权路径长度WPL的算法。(二叉树的路径长度WPL,是二叉树所有叶结点的带权路径长度之和)

(1)递归

这道题递归相对比较简单

#include 

/*
	思想: 
		基于递归地先序序列遍历,在此基础上多了一个判断叶结点以及多了一个深度的参数。
		并且用一个静态的变量记录wpl 
	
	步骤:
		(1)若该结点是叶结点,则变量wpl加上该结点的深度与权值之积。
		(2)若该结点是非叶结点,
				则左子树不为空时,对左子树调用递归算法;
				右子树不为空时,对右子树调用递归算法;
				深度参数均为当前层次的深度+1;
		(3)最后返回计算出的wpl即可 
*/

typedef struct BiTNode{
	int weight;
	struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

int WPL(BiTree root){
	return wpl_PreOrder(root,0);
} 

int wpl_PreOrder(BiTreee root,int deep){
	static wpl=0;
	if(root->lchild==NULL&&root->rchild==NULL)
		wpl+=root->weight*deep;
	if(root->lchild!=NULL){
		wpl_PreOrder(root->lchild,deep+1);
	}
	if(root->rchild!=NULL){
		wpl_PreOrder(root->rchild,deep+1);
	}
	return wpl;
}

(2)层次遍历

层次遍历相对递归来说繁杂了点,但是如果层次遍历比较熟悉的话,也不是很难。其深度的判断和前面第3题的思路是一样的,就当做练习做做还是不错的。

#include 

/*
	思想:层次遍历的基础上,判断出队的结点是否是叶结点,若是叶结点,变量wpl加上该结点的深度与权值之积。
	(可以参考第3题,就是多了个判断叶结点,然后计算wpl而已!!) 
	
	步骤:
		(1)队列出队,当出队的结点为叶结点时,累计wpl。
		(2)若为非叶结点,则把该结点的左、右子树加入队列
		(3)接着把出队的结点和last指针比较,若两者相同则层次level+1。
		队列为空时遍历结束,返回wpl 
*/

#define MaxSize 100
typedef struct BiTNode{
	int weight;
	struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

int wpl_levelOrder(BiTree root){
	BiTree Q[MaxSize]; //声明队列,front为头指针,rear为尾指针
	int front=rear=-1; 
	int last=0; //指向当前层次最右结点,初始为根节点
	int level=0; //记录当前结点的层次
	int wpl=0; //初始化wpl 
	BiTree p; //暂存出队的结点 
	Q[++rear]=root; //根结点入队 
	while(frontlchild==NULL&&p->rchild==NULL)
			wpl+=p->weight*level;
		if(p->lchild)
			Q[++rear]=p->lchild;
		if(p->rchild)
			Q[++rear]=p->rchild;
		if(front==last){
			level++;
			last=rear; //last指向下一层最右结点  
		}			
	}
	return wpl; 
}

16、设计一个算法,将给定的表达式树(二叉树)转化为等价的中缀表达式(通过括号反映操作符的计算次序)并输出。二叉树结点定义如下:

typedef struct node{
    char data[10];
    struct node *left,*right;
}BiTree;
#include 

/*
	思想:
		表达式树的中序序列加上必要的括号即为中缀表达式。
		表达式树中分支结点所对应的子表达式的计算次序,由该分支结点所处的位置决定。
		所以,需要在生成遍历序列的同时,在适当的位置添加括号。
		显然,表达式的最外层(对应根结点)和操作数(对应叶结点)时不需要添加括号的。所以函数还需要多加一个深度的参数 
		记住一点:分支结点对应表达式,叶结点对应操作数
		
	步骤:
		(1)如果是空树,返回
		(2)若为叶结点,输出操作数,不加括号
		(3)其他情况,
				若深度大于1(根结点不需要加括号),加一层左括号
				递归左子树
				输出操作符 
				递归右子树
				若深度大于1(根结点不需要加括号),加一层右括号 		 
*/ 

void BtreeToE(BiTree *root){
	BtreeToExp(root,0);
} 

void BtreeToExp(BiTree *root,int deep){
	if(root==NULL) //如果是空树,返回
		return;
	else if(root->left==NULL&&root->right==NULL) //若为叶结点,输出操作数,不加括号
		printf("%s",root->data);
	else{
		if(deep>1) //若深度大于1(根结点不需要加括号),加一层左括号
			printf("%s","(");
		BtreeToExp(root->left,deep+1); //递归左子树
		printf("%s",root->data;) //输出操作符 
		BtreeToExp(root->right,deep+1); //递归右子树
		if(deep>1) //若深度大于1(根结点不需要加括号),加一层右括号 		 
			printf("%s",")");
	}
}

17、设计一个算法,求出给定二叉排序中最小和最大的关键字

#include 

/*
	思想:
		利用二叉排序树的性质,左孩子结点 < 根结点 < 右孩子结点
		所以最小的关键字应该是最左下结点,最大的关键字应该是最右下结点 
*/ 

KeyType MinKey(BiTree T){
	while(T->lchild)
		T=T->lchild;
	return T->data;
}

KeyType MaxKey(BiTree T){
	while(T->rchild)
		T=T->rchild;
	return T->data;
}

18、设计一个算法,从大到小输出二叉排序树中所有值不小于k的关键字

#include 

/*
	思想:
		利用二叉排序树的性质,左孩子结点 < 根结点 < 右孩子结点
		所以为了从大到小输出,先遍历右子树,再访问根结点,最后遍历左子树 
*/

void OutPut(BiTree T,KeyType k){
	if(T==NULL)
		return;
	if(T->rchild)
		OutPut(T->rchild,k);
	if(T->data>=k)
		printf("%d",T->data);
	if(T->lchild)
		OutPut(T->lchild,k);
}

你可能感兴趣的:(数据结构,算法,数据结构)