HNU数据结构与算法--作业三

1.0-1-2树

这题我之前单独发过了,懒得再发一遍。

2.二叉树的带权路径长度

【问题描述】

树的路径长度是从树根到树中每一叶子结点的路径长度之和.在结点数目相同的二叉树中,完全二叉树的路径长度最短。

结点的权:赋予二叉树树中结点的一个有某种意义的实数。

结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积。

树的带权路径长度(Weighted Path Length of Tree):定义为树中所有叶结点的带权路径长度之和,通常记为:

T=∑ W i × l i {W_i \times l_i} Wi×li i=1,2,……,n i表示叶子节点的编号。

其中:n表示叶子结点的数目Wi和li分别表示叶结点i的权值和根到结点i之间的路径长度。树的带权路径长度亦称为树的代价。

现在给定n个叶子节点的权值,求出一颗二叉树,使得这棵二叉树的带权路径长度WPL最小,并且求出这个最小带权路径长度。

比如3个叶子节点的权值是1,2,3,其最小带权路径长度 W P L {W_{PL}} WPL=1×2+2×2+3×1=9。

【输入形式】

有多组测试数据,每组测试数据占两行,第一行是一个正整数n,表示叶子节点个数, n=0意味着输入结束并且不需要处理。

第二行是n个正整数,表示每个叶子节点的权值 W i {W_i} Wi,所有的 W i {W_i} Wi≤10000。

40%的测试数据3 ≤ n≤ 10
30%的测试数据3 ≤ n≤ 1 0 2 {10^2} 102
20%的测试数据3 ≤ n≤ 1 0 3 {10^3} 103
10%的测试数据3 ≤ n≤ 1 0 4 {10^4} 104

【输出形式】

对于每组测试数据,输出一行,包含一个正整数,表示最小带权路径长度WPL。测试数据保证正确的结果不超过 2 31 {2^{31}} 231

【思路分析】

此题就是哈夫曼树的经典问题,使用哈夫曼树即可轻松解决,不过出于练习目的,此处自建小根堆实现哈夫曼树,可以用优先队列代替自定义的minheap类。

【代码】

#include<iostream>//huffman树的简略版本 
using namespace std;
int Left[100005];
int Right[100005];
int num[100005];

struct node{
	int id;
	int weight;
}heap[10005];

class minheap{
	private:
		int cnt;
	public:
		minheap(int n){cnt=n;}
		void buildheap();
		void siftdown(int index);
		void push(node a);
		void pop();
		node& front();
		bool Comp(const node& a,const node& b);
		void swap(node& a,node& b);
		int size();
};

void inital(int n)
{
	for(int i=0;i<=n;i++)
	Left[i]=Right[i]=0;
	
	return ;
}

int ans=0;

void solve(int index,int depth)
{
	if(Left[index]==0 && Right[index]==0)
	{
		ans+=(depth*num[index]);
		return ;
	}
	
	solve(Left[index],depth+1);
	solve(Right[index],depth+1);
	
	return ;
}

int main()
{
	
	while(1)
	{
		int n;
		cin>>n;
		
		if(n==0)
		return 0;
		
		for(int i=1;i<=n;i++)
		{
			cin>>heap[i].weight;
			heap[i].id=i;
			num[i]=heap[i].weight;
		}
		
		inital(n);
		minheap q(n);
		q.buildheap();
		
		int size=n;
		node tmp1,tmp2,tmp3;
		while(q.size()>1)
		{
			tmp1=q.front();
			q.pop();
			tmp2=q.front();
			q.pop();
			
			tmp3.weight=tmp1.weight+tmp2.weight;
			tmp3.id=++size;
			Left[size]=tmp1.id;
			Right[size]=tmp2.id;
			num[size]=tmp3.weight;
			
			q.push(tmp3);
		}
		
		tmp1=q.front();
		ans=0;
		
		solve(tmp1.id,0);
		
		cout<<ans<<'\n';
		
	}
	return 0;
}

void minheap::buildheap()
{
	for(int i=cnt/2;i>=1;i--)
	siftdown(i);
	
	return ;
}

void minheap::pop()
{
	heap[1]=heap[cnt--];
	
	siftdown(1);
	return ;
}

void minheap::push(node a)
{
	heap[++cnt]=a;
	
	int root=cnt;
	int father=root/2;
	
	while(root!=1)
	{
		if(Comp(heap[root],heap[father]))
		swap(heap[root],heap[father]);
		else return ;
		
		root=father;
		father=root/2;
	}
	return ;
}

node& minheap::front()
{
	return heap[1];
}

bool minheap::Comp(const node& a,const node& b)
{
	return a.weight<b.weight;
}

void minheap::swap(node& a,node& b)
{
	int index=a.id,wgt=a.weight;
	
	a.id=b.id,a.weight=b.weight;
	
	b.id=index,b.weight=wgt;
	return ;
}

void minheap::siftdown(int index)
{
	int root=index;
	int l=root<<1,r=root<<1|1;
	
	while(l<=cnt)
	{
		if(r<=cnt && Comp(heap[r],heap[l]))
		l=r;
		
		if(Comp(heap[root],heap[l]))
		return ;
		
		swap(heap[root],heap[l]);
		
		root=l;
		l=root<<1,r=root<<1|1;
	}
	return ;
}

int minheap::size()
{
	return cnt;
}

3.二叉树遍历,从前序、中序到后序

【问题描述】

二叉树是一种非常重要的数据结构,非常多其他数据结构都是基于二叉树的基础演变而来的。对于二叉树,深度遍历有前序、中序以及后序三种遍历方法。

三种基本的遍历思想为:

前序遍历:根结点 —> 左子树 —> 右子树
中序遍历:左子树—> 根结点 —> 右子树
后序遍历:左子树 —> 右子树 —> 根结点

比如,求以下二叉树的各种遍历

前序遍历:1 2 4 5 7 8 3 6
中序遍历:4 2 7 5 8 1 3 6
后序遍历:4 7 8 5 2 6 3 1

需要你编写程序解决的问题是:已知一个二叉树的前序遍历和中序遍历的结果,给出该二叉树的后序遍历的结果。

【输入形式】

有多组测试数据,每组测试数据三行,每组测试数据第一行只有一个正整数n,表示二叉树节点的数目,n=0意味着输入结束并且不需要处理。

每组测试数据第二行是二叉树的前序遍历的结果,是一个长度为n的字符串,每个节点由一个字符表示,字符是大小写英文字母及10个数字,不同的节点用不同的字符表示,也即无论前序遍历和中序遍历的字符串中没有重复的字符。

每组测试数据第二行是二叉树的中序遍历的结果,也是一个长度为n的字符串。

40%的测试数据1 ≤ n≤ 10;
30%的测试数据1 ≤ n≤ 20;
20%的测试数据1 ≤ n≤ 40;
10%的测试数据1 ≤ n≤ 62;

【输出形式】

对于每组测试数据,输出一行,是一个长度为n的字符串,表示二叉树后序遍历的结果。

【思路分析】

此题即利用前序遍历和中序遍历的特殊性来确定整棵树,我们知道前序遍历是根结点 —> 左子树 —> 右子树,中序遍历是左子树—> 根结点 —> 右子树,后序遍历是左子树 —> 右子树 —> 根结点,因此只要根据前序遍历序列即可一直得到子树的根节点,利用根节点即可将中序遍历序列拆分为左右子树两个序列,然后再递归地拆分中序遍历序列即相当于再进行一遍前序遍历,我们先递归后记录每一次的根节点即为后序遍历。

【代码】

#include<iostream> 
using namespace std;
string postOrder;

void GetRoot(string& a,string& b)
{
	if(b.size()<1)
	return ;
	
	char root=a[0];
	string b1,b2;
	int division=b.find(root);
	
	for(int i=0;i<division;i++)
	b1.push_back(b[i]);
	
	for(int i=division+1;i<(int)b.size();i++)
	b2.push_back(b[i]);
	
	a.erase(a.begin());
	GetRoot(a,b1);
	GetRoot(a,b2);
	
	postOrder.push_back(root);
}

int main()
{
	string a, b;
	
	while(1)
	{
		int n;
		cin>>n;
		
		if(n==0)
		return 0;
		
		cin>>a>>b;
		
		postOrder.clear();
		GetRoot(a,b);
		
		cout<<postOrder<<'\n';
	} 
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值