【王道数据结构】树的编程题(一)

1、编写后序遍历二叉树的非递归算法。

算法思想:

void PostOrder(BiTree T)
{
	InitStack(S);
	p = T;
	r = NULL;
	while (p || !IsEmoty(S))
	{
		if (p)
		{
			push(S, p);
			p = p->lchild;
		}
		else
		{
			GetTop(S, p);
			if (p->rchild && p->rchild != r)
				p = p->rchild;
			else
			{
				pop(S, p);
				visit(p->data);
				r = p;
				p = NULL;
			}
		}//else	
	}//while
}

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

算法思想:利用原有的层次遍历算法,出队的同时将各结点指针入栈,在所有结点入栈后再从栈顶开始依次访问。

具体实现如下:

  • 把根结点入队列。
  • 把一个元素出队列,遍历这个元素。
  • 依次把这个元素的左孩子、右孩子入队列。
  • 若队列不空,则跳到第二步,否则结束。
void InvertLevel(BiTree bt)
{
	Stack s; Queue Q;
	if (bt != NULL)
	{
		InitStack(s);
		InitQueue(Q);
		EnQueue(Q, bt);
		while (IsEmpty(Q) == false)
		{
			DeQueue(Q, p);
			Push(s, p);
			if (p->lchild)
				EnQueue(Q, p->lchild);
			if (p->rchild)
				EnQueue(Q, p->rchild);
		}
		while (IsEmpty(s) == false)
		{
			Pop(s, p);
			visit(p->data);
		}
	}//if
}

3、假设二叉树采用二叉链表的存储结构,设计一个非递归算法求二叉树的高度。

算法思想:采用层次遍历的算法,设置变量level记录当前结点所在的层数,设置变量last指向当前层的最右结点,每次层次遍历出队时与last指针比较,若两者相等,则层数加1,并让last指向下一层的最右结点,直到遍历完成。level的值即为二叉树的高度。

int Btdepth(BiTree T)
{
	if (!T)
		return 0;
	int front = -1, rear = -1;
	int last = 0, level = 0;
	BiTree Q[MaxSize];
	Q[++rear] = T;
	BiTree p;
	while (front < rear)
	{
		p = Q[++front];
		if (p->lchild)
			Q[++rear] = p->lchild;
		if (p->rchild)
			Q[++rear] = p->rchild;
		if (front == last)
		{
			level++;
			last = rear;
		}
	}
	return level;
}

求某层的结点个数、每层的结点个数、树的最大宽度等,都采用与此题类似的思想。当然,此题可编写递归算法,其实现如下:

int Btdepth2(BiTree T)
{
	if (T == NULL)
		return 0;
	ldep = Btdepth2(T->lchild);
	rdep = Btdepth2(T->rchild);
	if (ldep > rdep)
		return ldep + 1;
	else
		return rdep + 1;
}

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

算法思想:由先序序列和中序序列可以唯一确定一棵二叉树。

  • 根据先序序列确定树的根结点。
  • 根据根结点在中序序列中划分出二叉树的左、右子树包含哪些结点,然后根据左、右子树结点在先序序列中的次序确定子树的很结点,即回到步骤1。
  • 如此重复上述步骤,直到每棵子树仅有一个结点(该子树的根结点)为止。
BiTree PreInCreat(ElemType A[], ElemType B[], int l1, int h1, int l2, int h2)
{
	root = (BiTNode*)malloc(sizeof(BiTNode));
	root->data = a[l1];
	for (i = 12; B[i] != root->data; i++);
	llen = i - 12;
	rlen = h2 - i;
	if (llen)
		root->lchild = PreInCreat(A, B, l1 + 1, l1 + len, l2, l2 + llen - 1);
	else
		root->lchild = NULL;
	if (rlen)
		root->rchild = PreInCreat(A, B, h1 - rlen + 1, h1, h2 - rlen + 1, h2);
	else
		root->rchild = NULL;
	return root;
}

5、二叉树按二叉链表形式存储,写一个判别给定二叉树是否是完全二叉树的算法。

算法思想:根据完全二叉树的定义,具有n个结点的完全二叉树与满二叉树中编号从1~n的结点一一对应。

采用层次遍历算法,将所有结点加入队列(包括空结点)。遇到空结点时,查看其后是否有非空结点。若有,则二叉树不是完全树。

bool IsComplete(BiTree T)
{
	InitQueue(Q);
	if (!T)
		return 1;
	EnQueue(Q, T);
	while (!IsEmpty(Q))
	{
		DeQueue(Q, p);
		if (p)
		{
			EnQueue(Q, p->lchild);
			EnQueue(Q, p->rchild);
		}
		else
			while (!IsEmpty(Q))
			{
				DeQueue(Q, p);
				if (p)
					return 0;
			}
	}
	return 1;
}

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

算法思想:本题可以设置一个全局变量Num,每遍历到一个结点时,判断每个结点是否为分支结点(左、右结点都不为空,注意是双分支),若是则Num++。

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

算法思想:采用递归算法实现交换二叉树的左、右子树,首先交换b结点的左孩子的左、右子树,然后交换b结点的右孩子的左、右子树,最后交换b结点的左、右孩子,当结点为空时递归结束(后序遍历思想)。

void swap(BiTree b) 
{
	if (b)
	{
		swap(b->lchild);
		swap(b->rchild);
		temp = b->lchild;
		b->lchild = b->rchild;
		b->rchild = temp;
	}
}

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

算法思想:设置一个全局变量 i 记录已访问过的结点的序号,其初值是根结点在先序序列中的序号,即1。当二叉树b为空时返回特殊字符'#',当i==k时,表示找到了满足条件的结点,返回b->data;当i!=k时,递归地在左子树中查找,若找到则返回该值,否则继续递归地在左子树中查找,并返回其结果。

ElemType PreNode(BiTree b, int k)
{
	if (b == NULL)
		return '#';
	if (i == k)
		return b->data;
	i++;
	ch = PreNode(b->lchild, k);
	if (ch != '#')
		return ch;
	ch = PreNode(b->rchild, k);
	return ch;
}

本题实际上就是一个遍历算法的实现,只不过用一个全局变量来记录访问的序号,求其他遍历序列的第k个结点也采用相似的方法。

 

9、已知二叉树以二叉链表存储,编写算法完成:对于树中每个元素值为x的结点,删去以它为根的子树,并释放相应的空间。

算法思想:

 删除以元素值x为根的子树,只要能删除其左、右子树,就可以释放值为x的根结点,因此宜采用后序遍历。

删除值为x的结点,意味着应将其父结点的左(右)子女指针置空,用层次遍历易于找到某结点的父结点。本题要求删除树中每个元素值为x的结点的子树,因此要遍历完整棵二叉树。

void DeleteXTree(BiTree& bt)
{
	if (bt)
	{
		DeleteXTree(bt->lchild);
		DeleteXTree(bt->rchild);
		free(bt);
	}
}
//在二叉树上查找所有以x为元素值的结点,并删除以其为根的子树
voidSearch(BiTree bt, ElemType x)
{
	BiTree Q[];
	if (bt)
	{
		if (bt->data == x)
		{
			DeleteXTree(bt);
			exit(0);
		}
		Init Queue(Q);
		EnQueue(Q);
		while (!IsEmpty(Q))
		{
			DeQueue(Q, p);
			if (p->lchild)
				if (p->lchild->data == x)
				{
					DeleteXTree(p->lchild);
					p->lchild = NULL;
				}
				else
					EnQueue(Q, p->lchild);
			if (p->rchild)
				if (p->rchild->data == x)
				{
					DeleteXTree(p->rchild);
					p->rchild = NULL;
				}
				else
					EnQueue(Q, p->rchild);
		}
	}
}

10、在二叉树中查找值为x的结点,试编写算法(用C语言)打印值为x的结点的所有祖先,假设值为x的结点不多于一个。

算法思想:采用非递归后序遍历,最后访问根结点,访问到值为x的结点时,栈中所有元素均为该结点的祖先,依次出栈打印即可。

typedef struct
{
	BiTree t;
	int tag;
}stack;
//在二叉树bt中,查找值为x的结点,并打印其所有祖先
void Search(BiTree bt, ElemType x)
{
	stack s[];
	top = 0;
	while (bt != NULL || top > 0)
	{
		while (bt != NULL && bt->data != x)
		{
			s[++top].t = bt;
			s[top].tag = 0;
			bt = bt->lchild;
		}
		if (bt->data == x)
		{
			printf("所查结点的所有祖先结点的值为:\n");
			for (i = 1; i <= top; i++)
				printf("%d", s[i].t->data);
			exit(1);
		}
		while (top != 0 && s[top].tag == 1)
			top--;
		if (top != 0)
		{
			s[top].tag = 1;
			bt = s[top].t->rchild;
		}
	}//while(bt!=NULL||top>0)
}

11、设一棵二叉树的结点结构为(LINK,INFO,PLINK),ROOT为指向该二叉树根结点的指针,p和q分别为指向该二叉树中任意两个结点的指针,试编写算法ANCESTOR(ROOT,p,q,r),找到p和q的最近公共祖先结点r。

算法思想:后序遍历最后访问根结点,即在递归算法中,根是压在栈底的。本题要找p和q的最近公共祖先结点r,不失一般性,设p在q的左边。

采用后序非递归算法,栈中存放二叉树结点的指针,当访问到某结点时,栈中所有元素均为该结点的祖先。后序遍历必然先遍历到结点p,栈中元素均为p的祖先。先将栈复制到另一辅助栈中。继续遍历到结点q时,将栈中元素从栈顶开始逐个到辅助栈中去匹配,第一个匹配(即相等)的元素就是结点p和q的最近公共祖先。

typedef struct
{
	BiTree t;
	int tag;
}stack;
stack s[], s1[];
//本算法求二叉树中p和q指向结点的最近公共结点
BiTree Ancestor(BiTree ROOT, BiTNode* p, BiTNode* q)
{
	top = 0; bt = ROOT;
	while (bt != NULL || top > 0)
	{
		while (bt != NULL)
		{
			s[++top].t = bt;
			s[top].tag = 0;
			bt = bt->lchild;
		}
		//假定p在q的左侧,遇到p时,栈中元素均为p的祖先
		while (top != 0 && s[top].tag == 1)
		{
			if (s[top].t == p)
			{
				for (i = 1; i < top; i++)
					s1[i] = s[i];
				top1 = top;
			}
			if (s[top].t == q)
				for (i = top; i > 0; i--)
				{
					for (j = top1; j > 0; j--)
						if (s1[j].t == s[i].t)
							return s[i].t;
				}
			top--;					
		}//while(top != 0 && s[top].tag == 1)
		if (top != 0)
		{
			s[top].tag = 1;
			bt = s[top].t->rchild;
		}
	}//while(bt != NULL || top > 0)
	return NULL;
}

12、假设二叉树采用二叉链表存储结构,设计一个算法,求非空二叉树b的宽度(即具有结点树最多的那一层的结点个数)。

算法思想:采用层次遍历的方法求出所有结点的层次,并将所有结点和对应的层次放在一个队列中。然后通过扫描队列求出各层的结点总数,最大的层结点总数即为二叉树的宽度。

typedef struct
{
	BiTree data[MaxSize];
	int level[MaxSize];
	int front, rear;
}Qu;
int BTWidth(BiTree b) 
{
	BiTree p;
	int k, max, i, n;
	Qu.front = Qu.rear = -1;
	Qu.rear++;
	Qu.data[Qu.rear] = b;
	Qu.level[Qu.rear] = 1;
	while (Qu.front < Qu.rear)
	{
		Qu.front++;
		p = Qu.data[Qu.front];
		k = Qu.level[Qu.front];
		if (p->lchild != NULL)
		{
			Qu.rear++;
			Qu.data[Qu.rear] = p->lchild;
			Qu.level[Qu.rear] = k + 1;
		}
		if (p->rchild != NULL)
		{
			Qu.rear++;
			Qu.data[Qu.rear] = p->rchild;
			Qu.level[Qu.rear] = k + 1;
		}
	}//while
	max = 0; i = 0;
	k = 1;
	while (i <= Qu.rear)
	{
		n = 0;
		while (i <= Qu.rear && Qu.level[i] == k)
		{
			n++;
			i++;
		}
		k == Qu.level[i];
		if (n > max)
			max = n;
	}
	return max;
}

本题队列中的结点,在出队后仍需要保留在队列中,以便求二叉树的宽度,所以设置的队列采用非环形队列,否则在出队后可能被其他结点覆盖,无法再求二叉树的宽度。

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

算法思想:对一般二叉树,仅根据先序或后序序列,不能确定另一个遍历序列。但对满二叉树,任意一个极点的左、右子树均含有相等的结点数,同时,先序序列的第一个结点作为后序序列的最后一个结点,由此得到将先序序列pre[l1..h1]转换为后序序列post[l2..h2]的递归模型如下:

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

算法思想:通常我们所用的先序、中序和后序遍历对于叶结点的访问顺序都是从左到右,这里我们选择中序递归遍历。

设置前驱结点指针pre,初始为空。第一个叶结点由指针head指向,遍历到叶结点时,就将它前驱的rchild指针指向它,最后一个叶结点的rchild为空。

LinkedList head, pre = NULL;
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 = bt;
			}
		InOrder(bt->rchild);
		pre->rchild = NULL;
	}
	return head;
}

上述算法的时间复杂度为O(n),辅助变量使用head和pre,栈空间复杂度为O(n)。

15、试设计判断两棵二叉树是否相似的算法。所谓二叉树T1和T2相似,指的是T1和T2都是空的二叉树或都有一个根结点;或T1的左子树和T2的左子树是相似的,且T1的右子树和T2的右子树是相似的。

算法思想:本题采用递归的思想求解,若T1和T2都是空树,则相似;若有一个为空另一个不空,则必然不相似;否则递归地比较它们的左、右子树是否相似。递归函数的定义如下:

(1)f(T1,T2)=1;若T1==T2==NULL。

(2)f(T1,T2)=0;若T1和T2之一为NULL,另一个不为NULL。

(3)f(T1,T2)=f(T1->lchild,T2->lchild)&&f(T1->rchild,T2->rchild);若T1和T2均不为NULL。

int similar(BiTree T1, BiTree T2)
{
	int leftS, rightS;
	if (T1 == NULL && T2 == NULL)
		return 1;
	else if (T1 == NULL || T2 == NULL)
		return 0;
	else
	{
		leftS = similar(T1->lchild, T2->lchild);
		rightS = similar(T1->lchild, T2->rchild);
		return leftS && rightS;
	}
}

16、写出在中序线索二叉树里查找指定结点在后序的前驱结点的算法。

算法思想:在后序序列中,若结点p右右子女,则右子女是其前驱,若无右子女而有左子女,则左子女是其前驱。若结点p左、右子女均无,设其中序左线索指向某祖先结点f(p是f右子树中按中序遍历的第一个结点),若f有左子女,则其左孩子是结点p在后序下的前驱;若f无左孩子,则顺其前驱找双亲的双亲,一直找到双亲有左孩子(这时左孩子是p的前驱)。还有一种情况,若p是中序遍历的第一个结点,则结点p在中序和后序下均无前驱。

BiThrTree InPostPre(BiThrTree t, BiThrTree p)
{
	BiThTree q;
	if (p->rtag == 0)
		q = p->rchild;
	else if (p->ltag == 0)
		q = p->lchild;
	else if (p->lchild == NULL)
		q = NULL;
	else
	{
		//顺左线索向上找p的祖先,若存在,再找祖先的左孩子
		while (p->ltag == 1 && p->lchild != NULL)
			p = p->lchild;
		if (p->ltag == 0)
			q = p->lchild;
		else
			q = NULL;
	}
	return q;
}

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