多元Huffman编码问题简单(增补空单元)解决
在一个操场的四周摆放着n堆石子,现将石子有次序地合并成一堆。规定每次至少选2堆最多选k堆石子合并成新的一堆,合并的费用为新的一堆的石子数。试设计一个算法,计算出将n堆石子合并成一堆的最大总费用和最小总费用。
问题分析
求最大费用时只需将石堆排列后从大到小,两两进行合并即可
求最小费用时,将石堆排列后,要尽可能的合并最少的次数且每次合并的石堆数为K堆
在求最小费用时,有时会出现特例,即每次合并K堆,最后一次合并时无法以K堆进行合并,这样的话合并的结果就不是最小费用了,我们要将最小的堆合并最多次这样结果才会最小,所以就要先判断原总堆数是否能使每次合并的堆数都为K堆,如果不能的话就要在原堆数前面加上 X 个个数为0的堆来补齐缺少的堆数
例如共7堆最大合并5堆
石堆数 45 13 12 5 9 22 16
这时排序后为5 9 12 13 16 22 45
如果先合并前5堆 这样结果就为177
如果补零的话 0 0 5 9 12 13 16 22 45,每次合并K堆,结果为148
代码
#include<iostream>
#include<fstream>
using namespace std;
extern void QuickSort(int a[], int p, int r);
void BinarySearchInsert(int a[], int x, int n,int t)
{
int l = t;
int left = t;
int right = n;
while (left < right)
{
left++;
if (left == n)
{
a[t] = x;
break;
}
else if (a[left] < x)
{
a[t] = a[left];
t++;
}
else
{
a[t] = x;
break;
}
}
}
int Maxcost(int a[],int n)//最大花费合并时合并次数最多,合并从大到小两两进行合并
{
int max = a[n-1]+a[n-2];
int m = max;
while(n--)
{
if (n - 2 < 0)
break;
else
{
m += a[n - 2];
max += m;
}
}
return max;
}
int Mincost(int a[], int n,int k,int t)//合并次数最少,合并从小到大,以k个堆为一组进行合并
{
int min =0;
int m=0,cn=n-t;//m是用来记录上一次合并时的花费,cn用于记录n个数在上一次合并以后还剩余的数量
int i;
while (cn >= k)//如果剩余的石堆比k大,为得到最小花费,则不管上一次合并的石堆再选取k个石堆进行合并
{
int count = k;//计数用,一次合并k个元素
for (i = t; i < n; i++)//合并k个石堆
{
if (count > 0)
{
m += a[i];//记录合并石堆的花费,因为是采用分组合并,每组k个石堆,所以m的值已经包含了前面合并的花费
t = i;
}
else
break;
count--;
if (count == 0)
{
min += m;
BinarySearchInsert(a, m, n, t);//将合并后的堆重修插入到序列中,以便下次的合并
m = 0;
}
}
cn = cn - k+1;//判断数组中还剩多少元素
}
return min;
}
int main()
{
int n, k,z,p,q=0;//z为可以分的组数,q为需要补0来凑k堆的堆数和,p为如果不能合并为k堆的倍数剩余的堆数
int a[] = { 0 };
int b[] = { 0 };
int maxcost, mincost;
ifstream myfile("input.txt");//从input.txt文件中读取内容
ofstream outfile("output.txt", ios::trunc);//声明一个输出流,将想要输出的内容生成一个output.txt文件
if (!myfile.is_open())
{
cout << "can not open this file" << endl;
return 0;
}
myfile >> n;//读取n的值
myfile >> k;//读取k的值
for (int i = 0; i < n; i++)
{
myfile >> a[i];
}
QuickSort(a, 0, n - 1);
for (int i = 0; i < n; i++)
{
b[i] = a[i];
}
maxcost = Maxcost(a, n);
z = n / k; //数组中元素每k个一组可以分的组数
p = (n / k + (n - z * k)) % k; //计算数组中的元素经过多次合并后还剩余的数量
if (p==0)
{
mincost = Mincost(a, n, k,0);
}
else
{
for (int j = 0; j < n+k-p; j++)//对原数组进行补零操作
{
if (j < k - p)
{
a[j] = 0;
}
else
{
a[j] = b[j - k + p];
}
}
mincost = Mincost(a, n+k-p, k,0);
}
outfile << "最大花费为:" << maxcost << endl;
outfile << "最小花费为:" << mincost << endl;
system("pause");
return 0;
}
测试用例1:
测试用例2:
测试用例3: