王道408考研课后习题---二叉树

一、二叉树的链式存储结构

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

二、习题

第三题:

非递归版的后续遍历
算法思想:
先访问左节点,然后右节点,最后根结点。
首先无脑访问做左的节点,中间遇到的非叶子结点全部记录在栈中,当已经访问到最左结点的时候,从栈中取栈顶一个元素,访问其右节点,然后重复一的步骤,然后弹出栈顶一个元素,访问根结点。
上诉过程中,两次访问根结点,第一次是从左子树过来要找到右子树,此时并不对其访问。第二次,从右子树结点过来,此时需要访问根结点了。因此可以设定一个标记位置,记录是从哪个节点过来的,来决定是否需要访问根结点。

void postOrder(BiTree T) {
	initStack(S);
	BiTree p = T, r = NULL;  // r指针用来记录p指针指向的前一个结点,为了判断
	while(p || !isEmpty(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);
				r = p;
				p = NULL;  // 将p置为NULL是为了 继续向栈中取结点
			}

		}
	}
}

非递归版的前中序遍历

void InOrder(BiTree T) {
	initStack(S);
	BiTree p = T;
	while(p || isEmpty(S)) {
		if(p) {
			push(S, p);
			p = p->lchild;
		} else {
			pop(S, p);
			visit(p);
			p = p->rchild;
		}
	}
}

void preOrder(BiTree T) {
	initStack(S);
	BiTree p = T;
	while(p || isEmpty(S)) {
		if(p) {
			visit(p);
			push(S, p);
			p = p->lchild;
		} else {
			pop(S, p);
			p = p->rchild;
		}
	}
}

第四题:

给出二叉树的自下而上、从右到左的层次遍历算法。
算法思路:按照层序遍历的顺序,然后依次入栈即可,最后出栈的顺序就是自下而上、从右往左的访问顺序

void InvertLevel(BiTree T) {
	BiTree p;
	initStack(S);
	initQueue(Q);
	if(T) {
		enqueue(Q, bt);
		while(!isEmpty(Q)) { // 当队列不空时
			dequeue(Q, p);
			push(p);
			if(p->lchild) enqueue(Q, p->lchild);
			if(p->rchild) enqueue(Q, p->rchild);
		}
	}
	while(!isEmpty(S)) {
		pop(S, p);
		visit(p);
	}
}

如果是自上而下,从右往左。这个就需要去记录每一层的结点个数,对于同一层次的结点 进行全入栈然后出栈 再入队,即可实现同一层次的逆序。

void InvertLevel(BiTree T) {
	BiTree p;
	initStack(S);
	initQueue(Q);
	initQueue(ans);  // 该队列为最终存的答案队列
	if(T) {
		enqueue(Q, T);
		while(Q) {
			int len = getLen(Q);  // 计算当前队列的长度
			while(len --) {
				dequeue(Q, p);
				push(S, p);
				if(p->lchild) enqueue(Q, p->lchild);
				if(p->rchild) enqueue(Q, p->rchild);
			}
			while(!isEmpty(S)) {
				pop(S, p);
				enqueue(ans, p);
			}
		}
	}
	while(!isEmpty(ans)) { // 访问ans队列的结果即可
		dequeue(ans, p);
		visit(p);
	}
}

第五题:

非递归算法计算 二叉树的高度
算法思路:宽搜

int getTreeHigh(BiTree T) {
	BiTree p;
	initQueue(Q);
	int high = 0;
	if(T) {
		enqueue(Q, T);
		while(Q) {
			int len = getLen(Q);  // 计算当前队列的长度
			while(len --) {
				dequeue(Q, p);
				if(p->lchild) enqueue(Q, p->lchild);
				if(p->rchild) enqueue(Q, p->rchild);
			}
			high ++;  // 访问完一层 树的高度加1
		}
	}
	return high;
}

还有一种逻辑类似的方法

int Btdepth(BiTree T) {
	if(!T) return 0;  // 树空 直接返回0

	// 定义个静态队列
	int hh = -1, rr = -1;
	int last = 0, level = 0; // last 指向的是当前层最右结点
	BiTree Q[MAXSIZE];
	Q[++ rear] = T;
	BiTree p;
	while(hh < rr) { // 当队列不为空的时候
		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 getTreeLevel(BiTree T) {
	if(!T) return 0;
	ldep = getTreeLevel(T->lchild);
	rdep = getTreeLevel(T->rchild);
	return ldep < rdep ? rdep + 1 : ldep + 1;
}

第六题:

根据给定的先序和中序遍历序列,然后构建一个二叉链表。
算法思路:每次先找到根结点,然后递归分解成构建左子树和右子树

// 数组下标从1开始 假设有n个结点  所以 l1~r1属于[1,n]
BiTree preInCreate(int A[],int B[],int l1,int r1,int l2,int r2) {
	BiTree root = new BiTNode;
	root->data = A[l1]; // 先序遍历的顺序第一个元素一定是根结点
	int i;
	for(i = l2; B[i] != root->data; i ++); // 在中序遍历中找到A中的第一个元素,然后分块 继续递归
	llen = i - l2;
	rlen = r2 - i;

	if(llen) root->lchild = preInCreate(A,B,l1 + 1, l1 + llen,l2,l2 + llen - 1);
	else root->lchild = NULL;

	if(rlen) root->rchild = preInCreate(A,B,r1 - rlen + 1, r1, r2-rlen + 1,r2);
	else root->rchild = NULL;

	return root;
}

// 最后类似的给出后续遍历和中序遍历, 只需要调整参数的区间即可

第七题:

判断一个二叉树是否是完全二叉树
算法思路:完全二叉树 只需要考虑其最后一层,只能是最左边有元素,当然可能该层全满
用一个队列 记录当前层次的所有后继结点,然后遍历后继结点,如果出现NULL,那么后面所有结点都是NULL
但凡出现一个非NULL那么该二叉树一定是非完全二叉树

bool isComplete(BiTree T){
	BiTree p;
	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;
}

第八题:

计算一个二叉树所有双分支结点个数

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;
	else return DsonNodes(T->lchild) + DsonNodes(T->rchild;)
}

第九题:

交换左右子树
算法思路:使用递归算法实现,先交换T结点的左孩子的左右子树,然后交换T结点右孩子的左右子树,最后交换T结点的左右孩子

void swap(BiTree T){
	if(T){
		swap(T->lchild);
		swap(T->rchild);
		auto tmp = T->lchild;
		T->lchild = T->rchild;
		T->rchild = tmp;
	}
}

第十题:

求先序遍历的第K个结点的值
算法思路:可以定义一个全局变量去记录当前已经访问过的结点个数

int cnt = 0;  // 全局变量 统计当前已经访问的结点个数
int preOrder(BiTree T,int k){
	BiTree p = T; 
	initStack(S);
	while(p || !isEmpty(S)){
		if(p){
			cnt ++;
			if(cnt == k) return p->data;
			push(S, p);
			p = p->lchild;
		}else{
			pop(S, p);
			p = p->rchild;
		}
	}
}

第十一题:

对于树中每个元素值为x的结点,删除以他为根的子树,并释放相应的空间。
算法思路:首先遍历树的所有结点(层次遍历),找到数据域为x的结点,递归删除释放其后代

// 递归删除子树的方法
void delete(BiTree T){
	if(T){
		delete(T->lchild);
		delete(T->rchild);
		free(T);
	}
}

// 层次遍历删除
void searchX(BiTree T,int x){
	initQueue(Q);  // 定义一个队列
	BiTree p;      // 定义一个工作指针
	if(T){
		if(T->data == x) delete(T), return;  // 如果是根结点那么 删除完可以直接退出了
		enqueue(Q, T);
		while(!isEmpty(Q)){
			dequeue(Q, p);
			if(p->lchild){
				if(p->lchild->data == x){
					delete(p->lchild);
					p->lchild = NULL;
				}else{
					enqueue(Q, p->lchild);
				}
			}
			if(p->rchild){
				if(p->rchild->data == x){
					delete(p->rchild);
					p->rchild = NULL;
				}else{
					enqueue(Q, p->rchild);
				}
			}
			
		}
	}
}

第十二题:

值为x的结点的所有祖先,x不多于一个

算法思路:采用非递归的后续遍历,最后访问根结点,那么当访问到x的时候,栈中所有元素即为该结点的祖先结点

void PostOrder(BiTree T,int x){
	initStack(S);
	BiTree p = T, r = NULL;
	while(p || !isEmpty(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);
				if(p->data == x) break;  // 结束循环此时栈中均为x的祖宗结点
				r = p;
				p = NULL;
			}
			
		}
	}
	while(!isEmpty(S)){ // 输出栈中元素
		pop(S, p);
		cout << p->data << ' ';
	}
}

第十三题:

两结点的最近公共祖先
采用后续遍历分别访问p和q,此时p和q的后续遍历的工作栈中即为该结点的祖宗序列,去匹配倒数第一个相同的元素。

Stack S1[], S2[];
BiTree ancestor(BiTree root, BiTree a, BiTree b){
	initStack(S1);
	BiTree p = T, r = NULL;
	while(p || !isEmpty(S1)){
		if(p){
			push(S1, p);
			p = p->lchild;
		}else{
			getTop(S1, p);
			if(p->rchild && p->rchild != r){
				p = p->rchild;
			}else{
				pop(S1, p);
				if(p == a) break;  // 结束循环此时栈中均为x的祖宗结点
				r = p;
				p = NULL;
			}
			
		}
	}
	
	initStack(S2);
	BiTree p = T, r = NULL;
	while(p || !isEmpty(S2)){
		if(p){
			push(S2, p);
			p = p->lchild;
		}else{
			getTop(S2, p);
			if(p->rchild && p->rchild != r){
				p = p->rchild;
			}else{
				pop(S2, p);
				if(p == b) break;  // 结束循环此时栈中均为x的祖宗结点
				r = p;
				p = NULL;
			}
			
		}
	}
	for(int i = S1.top;i >= 0;i --){
		for(int j = S2.top; j >= 0;j --){
			if(S1[i] == S2[j]) return S1[i];  
		}
	}
	return NULL;  // 没有找到相同元素 返回NULL
	
}

第十四题:

二叉树的宽度,即最大的层次结点的个数
算法思路:层次遍历

int getWidth(BiTree T){
	int maxv = -1;  // 初始化最大值
	BiTree p;
	initQueue(Q);
	enqueue(Q, T);
	while(!isEmpty(Q)){
		int size = length(Q); // 计算当前队列Q的长度
		maxv = max(maxv, size);
		while(size --){
			dequeue(Q, p);
			if(p->lchild) enqueue(Q, p->lchild);
			if(p->rchild) enqueue(Q, p->rchild);
		}		
	}
	return maxv;
}

第十五题:

已知一棵满二叉树的先序序列,求其后序序列

void pretToPost(char pre[],int l1, int r1,char post[], int l2, int r2){
	int half;
	if(r1 >= l1){  
		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, l2 + half, r2 - 1);
	}
}

第十六题:

将二叉树的叶子结点按从左到右的顺序连成一个单链表,表头指针为head,链接时用叶结点的有指针来存放单链表的指针

算法思想:采用中序遍历,遇到结点为孩子,则上一个叶结点指向当前结点

LinkList inOrder(BiTree T){
	BiTree pre = NULL;
	LinkList head;
	if(T){
		inOrder(T->lchild);
		if(T->lchild == NULL && T->rchild == NULL){
			if(pre == NULL){
				head = T;
				pre = T;
			}else{
				pre->rchild = T;
				pre = T;
			}
		}
		inOrder(T->rchild);
	}
	return head;
}

第十七题:

判断二叉树的相似
算法思路:同时遍历两个二叉树

int similar(BiTree T1, BiTree T2){
	if(T1 == NULL && T2 == NULL) return 1; // 如果他们同时走到为NULL的话,相似
	else if (T1 == NULL || T2 == NULL) return 0;  // 一个非空一个空 不相似
	else return similar(T1->lchild,T2->lchild) && similar(T1->rchild,T2->rchild);
}

第十八题:

中序线索二叉树里查找指定结点p在后序的前驱结点的算法
算法思路:

  • 如果p结点是非叶子结点,其有右子女,则右子女一定是后序前驱
  • 没有右子女,有左子女,则左子女一定是后序前驱。
  • 如果p结点是叶子结点,顺着左线索往前找一个结点,如果不是中序遍历的第一个结点,那么其左子树就是其后续前驱
  • 如果该结点为中序遍历的第一个结点(其为空),那么直接返回NULL即可
BiTree InPostPre(BiTree T,BiTree p){
	BiTree q;
	if(p->rchild == 0) q = p->rchild;
	else if(p->lchild == 0) q = p->lchild;
	else if(p->lchild == NULL) q == NULL;  //  该点为中序序列的第一个结点,无前驱
	else{
		if(p->ltag == 1 && p->lchild != NULL) p = p->lchild;
		if(p->ltag == 0) q = p->lchild;
		else q = NULL;   // p结点就是第一个后序结点, 所以无前驱
	}
}

第十九题:

计算二叉树的带权路径长度(WPL)

int wpl = 0;   // 全局变量
int preOrder(BiTree root,int deep){
	
	if(root->lchild == NULL && root->rchild == NULL) wpl += deep * root->data;
	if(root->lchild != NULL) preOrder(root->lchild, deep+1);
	if(root->lchild != NULL) preOrder(root->rchild, deep+1);
}

第二十题:

输出二叉树的中序序列,每一颗子树都用括号括起来

void ToExe(BiTree *root, int deep){
	if(root == NULL) return;
	else if(root->lchild == NULL && root->rchild == NULL) cout << root->data;
	else{
		if(deep > 1) cout << "(";
		ToExe(root->lchild, deep+1);
		cout << root->data;
		ToExe(root->rchild, deep+1);
		if(deep > 1) cout << ")";
	}
}

第二十一题:

判断一个用顺序存储的二叉树是否为二叉搜索树
算法思路:若一个树为二叉搜索树,则中序遍历得到的的序列一定是递增的,由此可以判断

int a[], cnt;  // 用来存储访问到的序列 cnt为其元素个数
int inOrder(BiTree T){
	if(T){
		inOrder(T->lchild);
		a[cnt++] = T->data;
		inOrder(T->rchild);
	}
}
bool judge(){
	for(int i = 1;i < cnt;i ++){
		if(a[i - 1] > a[i]) return false;
	}
	return true;
}

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