最近刷的一些关于树的题

树的遍历

》》》原题链接
题意:
树是二叉树。
输入n(二叉树的结点数)+二叉树的后序遍历序列+二叉树的中序遍历序列。输出该二叉树的层序遍历序列。
解法:
思路就是,在后序遍历序列得到根(t),然后在中序序列中定位根的位置,从而得到左子树(2* t)和右子树(2*t+1),递归,当遍历完左子树和右子树后,就让res[t]=根。
//这里有个小技巧,因为要定位根在中序序列的位置,所以中序序列可以用一下具有映射功能的map,<元素值,下标 > 。
这种思路要掌握掌握,基本思想。要学会这种问题的递归函数写法(是一个基础)。
参考代码:
我一开始用队列做,写了很长的代码。。。
在函数中多加一个参数t,然后用递归,代码就简介了很多。

#include <bits/stdc++.h>
using namespace std;

int n,a[33];
map<int,int> res,b;

void print(int a[],int al,int ar,int bl,int br,int t)
{
	if(al>ar || bl>br) return;
	int pos=b[a[ar]];
	print(a,al,al+pos-bl-1,bl,pos-1,2*t);
	print(a,al+pos-bl,ar-1,pos+1,br,2*t+1);
	res[t]=a[ar];
}

int main()
{
	cin >> n;
	for(int i=1;i<=n;++i)
		cin >> a[i];
	for(int i=1;i<=n;++i)
	{
		int t;
		cin >> t;
		b[t]=i; 
	}
	print(a,1,n,1,n,1);
	for(auto x=res.begin();x!=res.end();++x)
	{
		if(x!=res.begin()) cout << ' ';
		cout << x->second;
	}
	return 0;
}

代码解释:
为什么函数里需要bl,br参数?
因为需要计算左右子树结点个数。
注意,res中的t值不一定是连续的,因为会有结点没有左子树但有右子树这样的情况。

大家可以去跑一下以下这个代码,去感受一下,为什么res不能用数组。

#include <bits/stdc++.h>
using namespace std;

int n,a[33],res[33];
map<int,int> b;

void print(int a[],int al,int ar,int bl,int br,int t)
{
	if(al>ar || bl>br) return;
	int pos=b[a[ar]];
	print(a,al,al+pos-bl-1,bl,pos-1,2*t);
	print(a,al+pos-bl,ar-1,pos+1,br,2*t+1);
	res[t]=a[ar];
}

int main()
{
	cin >> n;
	for(int i=1;i<=n;++i)
		cin >> a[i];
	for(int i=1;i<=n;++i)
	{
		int t;
		cin >> t;
		b[t]=i; 
	}
	print(a,1,n,1,n,1);
	for(int i=1;i<=n+10;++i)
	{
		cout << res[i];
		if(i!=n)
			cout << ' ';	
	}
	return 0;
}

玩转二叉树

》》》原题链接
题意:
输入n(二叉树的结点个数)+中序遍历序列+前序遍历序列。输出该树反转后的层序遍历的序列。
解法:
思路和前面(“树的遍历”那道题)类似,提点:要输出反转后的层序遍历序列,就把左子树当右子树,右子树当左子树看待就行啦,即左子树是2t+1,右子树是2t 。
参考代码:

#include <bits/stdc++.h>
using namespace std;

int a[33];
int n;
map<int,int> b,res;

void print(int a[],int al,int ar,int bl,int br,int t)
{
	if(al>ar || bl>br) return;
	res[t] = a[al];
	int pos = b[a[al]];
	print(a,al+1,al+pos-bl,bl,pos-1,2*t+1);
	print(a,al+pos-bl+1,ar,pos+1,br,2*t);
}

int main()
{
	cin >> n;
	for(int i=1;i<=n;++i)
	{
		int t;
		cin >> t;
		b[t]=i;
	}
	for(int i=1;i<=n;++i)
		cin >> a[i];	
	print(a,1,n,1,n,1);
	for(auto i=res.begin();i!=res.end();++i)
	{
		if(i!=res.begin())
			cout << ' ';
		cout << i->second;
	}
	return 0;
}

完全二叉树的层序遍历

》》》原题链接
题意:
输入n和一个长度为n的序列(序列为一棵完全二叉树的后序遍历),输出这棵完全二叉树的层序遍历。
解法1:
后序遍历最后一个元素是整棵树的根;基于它是一棵完全二叉树,根据左右子树的结点个数之和,可以确定左子树的个数和右子树的个数。
参考代码:
这是我一开始写的代码,觉得没有解法2的思路好。

#include <bits/stdc++.h>
using namespace std;

int n,post[33];
map<long long,int> res;
void print(int l,int r,long long t);

int main()
{
	cin >> n;
	for(int i=1;i<=n;++i)
		cin >> post[i];
	print(1,n,1);
	for(auto x=res.begin();x!=res.end();++x)
	{
		if(x!=res.begin()) cout << ' ';
		cout << (x->second);
	}
	return 0;	 
}

int cal(int num)
{
   int num1=num;
   int t=2;
   while(num1>=t)
   {
       num1-=t;
       t*=2;
   }
   return (num-num1)/2+min(t/2,num1);
}

void print(int l,int r,long long t)
{
	if(l>r) return;
	res[t]=post[r];
	int lnum=cal(r-l);
	print(l,l+lnum-1,2*t);
	print(l+lnum,r-1,2*t+1);
}

解法2:
因为它是一棵完全二叉树,所以可以用2t、2t+1这种东西。
模拟得到后序遍历序列的过程。
参考代码:
好代码好代码!学习学习!

#include <bits/stdc++.h>
using namespace std;

int n,a[35],res[35],num;

void print(int t)
{
	if(t>n) return;
	print(2*t);
	print(2*t+1);
	res[t]=a[++num];
}

int main()
{
	cin >> n;
	for(int i=1;i<=n;++i)
		cin >> a[i];
	print(1);
	for(int i=1;i<=n;++i)
	{
		cout << res[i];
		if(i!=n) cout << ' ';
	}
	return 0;
}

这是二叉搜索树吗

》》》原题链接
题意:
二叉搜索树:左子树<根<=右子树,左右子树都是二叉搜索树。二叉搜索树的”镜像“,将二叉搜索树所有结点的左右子树对换位置后所得到的树。
输入n和n个整数键值,判断这个整数序列是否是对一棵二叉搜索树或其镜像进行前序遍历的结果。是,输出”YES“和该树后序遍历的结果(注意格式要求)。不是,输出”NO“。
参考代码:
我第一次写的代码:

#include <bits/stdc++.h>
using namespace std;

int n,a[1100],c[1100],f=1,num,sw,path[1100],num1;
void check(int x,int y);

int main()
{
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		cin >> a[i];
		c[i]=a[i];	
	}
	sort(c+1,c+1+n);
	int pos=lower_bound(c+1,c+n+1,a[1])-c; //c[pos]=a[1] 
	//这个 判断镜像 的想法不对,对于树的东西,判断也要用递归来做才是对的 
	if(pos>1&&pos<n&&a[2]>=a[1]) //镜像树
		sw=2;
	else
		sw=1;
	check(1,n);
	if(f)
	{
		cout << "YES" << endl;
		for(int i=1;i<=n;i++)
		{
			cout << path[i];
			if(i!=n)
				cout << ' ';	
		}	
	}
	else
		cout << "NO";
	return 0;
}

void check(int x,int y)
{
	if(x>y)	return;
	int k=lower_bound(c+x,c+y+1,a[++num])-c;
	if(k==y+1)
	{
		f=0;
		return;
	}
	if(sw==1) 
	{
		check(x,k-1);
		check(k+1,y);
		path[++num1]=c[k];
	}
	else
	{
		check(k+1,y);
		check(x,k-1);
		path[++num1]=c[k];	
	}
}

我这个代码交上去能AC,但是我后来仔细思考了一下,发现这个代码有漏洞,是不对的。可以利用以下样例证明它是不对的。

/*
样例说明:
对于那种,树中某个结点只有 结点个数大于1的左子树或右子树,
而没有右子树或左子树,用我上面的代码跑出来就会错。
教训:对于树的东西,因为树具有很强的递归性质,
所以判断时不能想当然的一个判断语句就完了,要递归去判断。
递归 递归 递归 这个 想法/思想 很重要。
我把我一开始的代码,把单纯的if-else判断,改为递归,就比较正确 
譬如样例:
样例1://非镜像树,对于树的根结点,只有左子树,并且左子树的结点个数大于1 
输入: 
6
10 8 6 5 9 8
输出:
YES
5 6 8 9 8 10
 
样例2://镜像树,对于树的根结点,只有右子树,并且右子树的结点个数大于1 
输入: 
6
10 8 9 8 6 5 
输出:
YES
8 9 5 6 8 10
 
样例3://非镜像树,对于树的根结点,只有右子树,并且右子树结点个数大于1 
输入: 
6
2 7 5 6 9 8
输出:
YES
6 5 8 9 7 2
 
样例4://镜像树,对于树的根节点,只有左子树,并且左子树的结点个数大于1 
输入: 
6
2 7 9 8 5 6
输出:
YES
8 9 6 5 7 2
 
*/

后来,我改进我的代码:

#include <bits/stdc++.h>
using namespace std;

int n,a[1100],c[1100],f=1,num,sw=1,path[1100],num1;
bool check(int x,int y);

int main()
{
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		cin >> a[i];
		c[i]=a[i];	
	}
	sort(c+1,c+1+n);
	if(check(1,n))
	{
		cout << "YES" << endl;
		for(int i=1;i<=n;i++)
		{
			cout << path[i];
			if(i!=n)
				cout << ' ';	
		}	
	}
	else
		cout << "NO";
	return 0;
} 
//check(int x,int y)函数表示在区间[x,y]中是否会有元素a[num]
//以此作为区分二叉搜索树(或其“镜像”)和一般树的依据
bool check(int x,int y)
{
	if(x>y)	return true;
	//取根-》左子树 右子树-》之后,把元素放进path里,这样就得到了后序遍历序列 
	int k=lower_bound(c+x,c+y+1,a[++num])-c; //a[++num]是根 //前序遍历序列的根在前面。
	if(k==y+1 || c[k]!=a[num]) //这里用到了运算符短路
		return false;
	if(sw==1)
	{
		if(check(x,k-1) && check(k+1,y)) //因为我有++num,所以我先check谁就有区别了
		{
			path[++num1]=c[k];
			return true;	
		}
		if(x==1 && y==n) //回到最初的那一层
		{
			sw=2; //换成镜像树算一算
			num=num1=0;
			return check(x,y);
		} 
        return false;
	} 
	if(sw==2)
	{
		if(check(k+1,y) && check(x,k-1))
		{
			path[++num1]=c[k];
			return true;
		}
		return false;
	}
}

心得:
1.根据 根 ,然后确定左子树 和 右子树,我们用到lower_bound()去定位。
//lower_bound()是返回第一个>=a[num]的数的下标,刚好本题的右树是>= 左树是< 才可以 lower_bound()
//如果右树是> 左树是<= 的话 可以用 upper_bound() 。
//所以呢,我们就不用那种for()去找了,就用lower_bound()和upper_bound()去找足够了。
2.体悟:递归,就是多层。有最初的那一层,也有最底的那一层,要利用递归,就是 想想 你在每一层要做什么操作。
3.学习二叉搜索树的特征:
二叉搜索树的中序遍历序列是 从小到大的有序序列;二叉搜索树的“镜像” 的中序遍历序列是 从大到小的有序序列。
4.//二叉搜索树 不是 完全树,所以不能用2t 2t+1这种东西
//不是完全二叉树得,需要通过某些方式,取根,确定左子树、右子树,然后递归。
//一般是在前序/后序序列中 取根,在中序序列中区分 左子树 和 右子树。
//二叉搜索树,可以利用 有序序列 是它的 中序序列 这一特性
5.学习套路:(针对于非完全二叉树)边界没有了,返回,还有,就在前序序列或后序序列取根,然后在中序序列中确定位置,区分出左子树和右子树。


二叉搜索树的最近公共祖先

》》》原题链接
题意:
有N个结点的二叉搜索树,有M次询问,每次询问给出一对结点<u,v>,判断给出的结点是否存在,存在的话,判断是否有最近公共祖先(注意题目要求的输出格式)。
解法:
树(这里指普通树,不一定是二叉搜索树)的最近公共祖先问题,就是常说的LCA问题,对于LCA问题,有3种解法(两种在线解法,一种离线解法)。这里不展开讲,读者感兴趣的话可以自行学习。
这道题的树是二叉搜索树,对于特殊的树,可以有更便捷的特殊解法。
二叉搜索树的特殊性质是:
1.中序序列:从小到大的有序序列。
2.左子树的元素都小于根节点,右子树元素都大于根节点(题目说N个元素都是不同的)。
记ai为树的某个结点:
根据二叉搜索树的性质,我们会得到以下结论:
如果ai == u,u是u、v的最近公共祖先。
如果ai ==v,v是u、v的最近公共祖先。
如果u、v在ai的同一边,拿下一个结点递归判断。
如果u、v的值大小在ai的两边,那么ai是u、v的最近公共祖先。
想到以上这种关系,就能解决这道题了!
解决这道题的一些基本功:你要知道怎么从一个中序序列和前序序列(或后序序列)得到树的根、左子树、右子树,只有能准确得到这些信息,你才能写递归是把,才能在递归里加上其他操作是吧。
参考代码:

#include <bits/stdc++.h>
using namespace std;

int n,m,a[10005],b[10005];

void dfs(int u,int v,int a[],int al,int ar,int b[],int bl,int br)
{
	if(al>ar || bl>br) return;
	if(a[al]==u)
	{
		printf("%d is an ancestor of %d.\n",u,v); 
		return;
	}
	if(a[al]==v)
	{
		printf("%d is an ancestor of %d.\n",v,u);
		return;
	}
	if(u<a[al] && v<a[al])
	{
		int pos = lower_bound(b+bl,b+br+1,a[al])-b;
		dfs(u,v,a,al+1,al+pos-bl,b,bl,pos-1); //pos-bl得到左树的结点数 
	}	
	else if(u>a[al] && v>a[al])
	{
		int pos = lower_bound(b+bl,b+br+1,a[al])-b;
		dfs(u,v,a,al+pos-bl+1,ar,b,pos+1,br);
	}
	else
	{
		printf("LCA of %d and %d is %d.\n",u,v,a[al]);
		return;
	}
}

inline bool check(int k)
{
	int id = lower_bound(b+1,b+1+n,k)-b;
	if(id<=n && b[id]==k)
		return true;
	return false;
}

int main()
{
	cin >> m >> n;
	for(int i=1;i<=n;i++)
	{
		cin >> a[i];
		b[i]=a[i];	
	}
	sort(b+1,b+1+n); //b中序序列
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin >> x >> y;
		bool f1=check(x);
		bool f2=check(y);
		if(!f1 && !f2)
			printf("ERROR: %d and %d are not found.\n",x,y);
		else if(!f1)
			printf("ERROR: %d is not found.\n",x);
		else if(!f2)
			printf("ERROR: %d is not found.\n",y);
		else
			dfs(x,y,a,1,n,b,1,n);
	}
	return 0;
}

插入排序还是归并排序

》》》原题链接
解法:
一开始以为要写很复杂的归并啥的,后来学到了,归并就是4行代码。。。
参考代码:

#include <bits/stdc++.h>
using namespace std;

int n,a[1100],b[1100];

bool judge()
{
	for(int i=1;i<=n;++i)
		if(a[i]!=b[i])
			return false;
	return true;
}

int main()
{
	cin >> n;
	for(int i=1;i<=n;++i)
		cin >> a[i];
	for(int i=1;i<=n;++i)
		cin >> b[i];
	int i=2;
	for(;i<=n && b[i]>=b[i-1]; ++i);
	int j=i;
	for(;j<=n && a[j]==b[j]; ++j);
	if(j==n+1)
	{
		cout << "Insertion Sort\n";
		if(i<=n)
			sort(b+1,b+i+1); //一开始wa是因为写成:sort(b+1,b+i+2); 
		cout << b[1]; //觉得这种输出格式控制的写法比较简洁 
		for(int k=2;k<=n;++k)
			cout << ' ' << b[k];
		cout << endl;
	}
	else
	{
		cout << "Merge Sort\n";
		int i=2; //i代表个数 
		for(;!judge();i+=i)
			for(int j=1;j<=n;j+=i) //感觉老师写的那个代码比较对是咋回事???
				sort(a+j,a+min(j+i,n+1));
		for(int j=1;j<=n;j+=i)
			sort(a+j,a+min(j+i,n+1));
		cout << a[1];
		for(int j=2;j<=n;++j)
			cout << ' ' << a[j];
		cout << endl; 
	}
	return 0;
}

关于堆的判断

》》》原题链接
解法:
首先,先知道一下二叉堆是什么?
/*
堆 是 一种经过排序的完全二叉树
最大堆 和 最小堆 是 二叉堆的 两种形式。
最小堆/最大堆 和 二叉排序树 是不一样的
最小堆/最大堆 :非终端结点的数据值均不大于其左子节点,也不大于其右子节点
二叉排序树 :一边大 一边小
*/
这道题,主要是,你把堆建出来,就完事了,其他的就是判断判断~。

参考代码:

#include <bits/stdc++.h>
using namespace std;

int n,m,h[1100],num,f,f1;

void adjust()
{
	int tmp = num;
	while(tmp/2>0&&h[tmp/2]>h[tmp]) 
	{
		swap(h[tmp],h[tmp/2]);
		tmp/=2;
	}
}

void find(int st, int x,int y) //x孩子 y父亲 
{
	if(h[st]==x)
	{
		if(h[st^1]==y) f1=1; //兄弟关系 
		if(st/2>0&&h[st/2]==y) //父子关系 
			f=1;
		return;
	}
	if(st*2<=num)
		find(st*2,x,y); //左子树找找 
	if(st*2+1<=num)
		find(st*2+1,x,y); //右子树找找 
} 

int main()
{
	cin >> n >> m;
	for(int i=1;i<=n;i++)
	{
		cin >> h[++num];
		adjust();
	}
	for(int i=1;i<=m;i++)
	{
		int t1,t2;
		string s;
		cin >> t1 >> s;
		if(s=="and") cin >> t2;
		cin >> s;
		cin >> s;
		if(s=="root")
		{
			if(h[1]==t1)
				cout << "T\n";
			else 
				cout << "F\n";
		}
		else if(s=="parent")
		{
			cin >> s >> t2;
			f=0;
			find(1,t2,t1);
			if(f)
				cout << "T\n";
			else
				cout << "F\n";
		}
		else if(s=="child")
		{
			cin >> s >> t2;
			f=0;
			find(1,t1,t2);
			if(f)
				cout << "T\n";
			else
				cout << "F\n";
		}
		else
		{
			f1=0;
			find(1,t1,t2);
			if(f1)
				cout << "T\n";
			else
				cout << "F\n";
		}
	}
	return 0;
} 

L3-010 是否完全二叉搜索树(30分)

》》》原题链接
参考代码:

/*
给左右子树编号按 t 2*t 2*t+1,然后放进map<编号,结点>,这样顺序输出,就是二叉搜索树的层序遍历序列,而且根据键值是否连续可以判断是否是完全二叉树
*/ 
#include <bits/stdc++.h>
using namespace std;

int n;
map<int,int> mp;

//学习如何按输入顺序插入结点,使其构成一棵完全二叉树 
void add(int vex,int id) //参数:插入点,下标 
{
	int tmp=mp[id];
	int id1=0;
	if(vex>tmp) //1.寻找插入点:根据题目二叉搜索树的定义,看vex应放入哪边 
		id1=2*id; //注:插入位置怎么编号,这个要看你自己的,在本题中,我们采取 t 2*t 2*t+1 的方式编号。 
	else
		id1=2*id+1;
	if(mp.find(id1)==mp.end()) //2.看插入点是否已经被放了元素:如没有,就将vex插入,若已经被放了,就接着往下找 
	{
		mp[id1]=vex;
		return;
	}
	else
		add(vex,id1);
}

int main()
{
	cin >> n;
	for(int i=1;i<=n;++i)
	{
		int t;
		cin >> t;
		if(i==1)
			mp[1]=t;
		else
			add(t,1);
	}
	auto it=mp.begin();
	cout << it->second;
	int f=1,sq=1;
	++it;
	for(;it!=mp.end();++it)
	{
		if(it->first-1!=sq)
			f=0;
		else
			sq++;
		cout << ' ' << it->second;
	}
	if(f) cout << "\nYES\n";
	else cout << "\nNO\n";
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值