贪心策略解决木板截取最小开销问题------类哈夫曼编码问题
一、贪心法:遵循某种规则,不断贪心的选择当前最优策略的算法设计方法。常见问题有:最少硬币问题、区间问题、字典序最小问题、点覆盖问题、木板截取问题等。
二、木板截取问题
三、算法分析与设计
由于已经给出了截取后得到的每块木板的长度,要让总开销最小,那么我们按照贪心的思想让每次截取开销最小即可,
因此我每次选择最小的两截木板作为被截开木板,因此我们不难发现这其实是一个变形的哈夫曼编码问题。
/// <summary>
/// 贪心策略解决木板截取最小开销问题,类哈夫曼编码问题
/// </summary>
/// <param name="n"></param>
/// <param name="everyChildBoardLen"></param>
/// <returns></returns>
public int CaluateMinSpendInCutBoard(int n,int[] everyChildBoardLen)
{
int spends = 0;
int maxSpends = 0;
foreach (int i in everyChildBoardLen)
{
maxSpends += i;
}
while (true)
{
//获取数组中最小的两个木板的下标
GetMinNumAndSecondMinNumIndex(everyChildBoardLen,out int minNumIndex,out int secondMinNumIndex);
//计算这两个木板的开销
int tempSpends=everyChildBoardLen[minNumIndex] + everyChildBoardLen[secondMinNumIndex];
//将开销加入到总开销中
spends += tempSpends;
everyChildBoardLen[minNumIndex] = tempSpends;
//这里将已经遍历过的其中一个数标记成-1(达到删除的状态)
everyChildBoardLen[secondMinNumIndex] = int.MaxValue;
if (tempSpends == maxSpends)
break;
}
return spends;
}
/// <summary>
/// 获取数组最小和次小的数据(至少有两个数)
/// </summary>
/// <param name="a"></param>
/// <param name="minNum"></param>
/// <param name="secondMinNum"></param>
public void GetMinNumAndSecondMinNumIndex(int[] a,out int minNumIndex,out int secondMinNumIndex)
{
minNumIndex = a[0]>a[1]?1:0;
secondMinNumIndex = a[0] > a[1] ? 0 : 1;
for (int i=2;i<a.Length;i++)
{
if (a[i]!=int.MaxValue)
{
if (a[i] < a[minNumIndex])
{
secondMinNumIndex = minNumIndex;
minNumIndex = i;
}
else if(a[i]<a[secondMinNumIndex])
{
secondMinNumIndex = i;
}
}
}
}
本算法中需要每次去获取木板数组中最短和次短的模板,因此涉及到高效获取最短和次短的模板的问题,我们可以采用先从小到大排序数组,然后取出前两个数即可,但是排序最快的快速排序的复杂度是nlogn,似乎有点不太理想。
那么如何不依赖排序来实现获取最小和次小的问题,我们可以按照GetMinNumAndSecondMinNumIndex方法的思想来实现复杂度为O(n)算法。其基本思路如下:
设置两个存储变量minNum和secondMinNum,初始值为数组下边为0,1中最小和最大的,然后进行遍历,当发现某个数小于当前最小值,则原有最小值赋值给secondMinNum,赋值minNum为更小的值,否则将当前数和secondMinNum比较,如果小于secondMinNum,则给secondMinNum赋值当前值,一直到遍历结束。