C#内功修炼(算法)——贪心算法(五)

对于许多最优化问题,使用动态规划算法来求最优解有些杀鸡用牛刀了,可以使用更加简单、更加高效的算法。贪心算法就是这样的算法,它在每一步做出当时看起来最佳的选择。也就是说它总是做出局部最优的选择,从而得到全局最优解。

对于某些问题并不保证得到最优解,但对很多问题确实可以求得最优解。


贪心算法 活动选择问题

有n个需要在同一天使用同一个教室的活动a1,a2,…,an,教室同一时刻只能由一个活动使用。每个活动ai都有一个开始时间si和结束时间fi。一旦被选择后,活动ai就占据半开时间区间[si,fi)。如果[si,fi]和[sj,fj]互不重叠,ai和aj两个活动就可以被安排在这一天。该问题就是要安排这些活动使得尽量多的活动能不冲突的举行(最大兼容活动子集)。例如下图所示的活动集合S,其中各项活动按照结束时间单调递增排序。

{a3,a9,a11}是一个兼容的活动子集,但它不是最大子集,因为子集{a1,a4,a8,a11}更大,实际上它是我们这个问题的最大兼容子集,但它不是唯一的一个{a2a4a9a11}

1,动态规划算法解决思路

  我们使用Sij代表在活动ai结束之后,且在aj开始之前的那些活动的集合,我们使用c[i,j]代表Sij的最大兼容活动子集的大小,对于上述问题就是求c[0,12]的解

      a, i>=j-1或者Sij中没有任何活动元素的时候,c[i,j]=0

      b,当i<j-1

          1Sij不存在活动,c[i,j]=0

          2Sij存在活动的时候,c[i,j]= max{c[i,k]+c[k,j]+1}  ak属于Sij,这里是遍历Sij的集合,然后求得最大兼容子集

[csharp]  view plain  copy
  1. static void Main(string[] args)  
  2. {  
  3.     int[] s = { 0, 1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12, 24 };  
  4.     int[] f = { 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 24 };  
  5.   
  6.     List<int>[,] result = new List<int>[13, 13];// 默认值null  
  7.     for (int m = 0; m < 13; m++)  
  8.     {  
  9.         for (int n = 0; n < 13; n++)  
  10.         {  
  11.             result[m, n] = new List<int>();  
  12.         }  
  13.     }//默认值就是空list集合  
  14.   
  15.         for (int j = 0; j < 13; j++)  
  16.         {  
  17.             for (int i = 0; i < j - 1; i++)  
  18.             {  
  19.                 // S[ij] i结束之后 j开始之前的 活动集合  
  20.                 // f[i]  s[j] 这个时间区间内的所有活动  
  21.                 List<int> sij = new List<int>();  
  22.                 for (int number = 1; number < s.Length - 1; number++)  
  23.                 {  
  24.                     if (s[number] >= f[i] && f[number] <= s[j])  
  25.                     {  
  26.                         sij.Add(number);  
  27.                     }  
  28.                 }  
  29.   
  30.                 if (sij.Count > 0)  
  31.                 {  
  32.                     //result[i,j]=max{ result[i,k]+result[k,j]+ k }  
  33.   
  34.                     int maxCount = 0;  
  35.                     List<int> tempList = new List<int>();  
  36.                     foreach (int number in sij)  
  37.                     {  
  38.                         int count = result[i, number].Count + result[number, j].Count + 1;  
  39.                         if (maxCount < count)  
  40.                         {  
  41.                             maxCount = count;  
  42.                             tempList = result[i, number].Union<int>(result[number, j]).ToList<int>();  
  43.                             tempList.Add(number);  
  44.                         }  
  45.                     }  
  46.                     result[i, j] = tempList;  
  47.                 }  
  48.   
  49.             }  
  50.         }  
  51.         List<int> l = result[0, 12];  
  52.         foreach (int temp in l)  
  53.         {  
  54.             Console.WriteLine(temp);  
  55.         }  
  56.     Console.ReadKey();  
  57. }  

2,贪心算法

  想要使用贪心算法的话,得先找到适合贪心算法的规律(局部最优选择)

  对于任何非空的活动集合S,假如amS中结束时间最早的活动,则am一定在S的某个最大兼容活动子集中。

(如何证明上面的结论?反证法)

      递归解决

[csharp]  view plain  copy
  1. static void Main(string[] args)  
  2. {  
  3.   
  4.     List<int> list = ActivitySelection(1, 11, 0, 24);  
  5.     foreach (int temp in list)  
  6.     {  
  7.         Console.WriteLine(temp);  
  8.     }  
  9.     Console.ReadKey();  
  10.   
  11. }  
  12. static int[] s = { 0, 1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12 };  
  13. static int[] f = { 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };  
  14. public static List<int> ActivitySelection(int startActivityNumber,int endActivityNumber,int startTime ,int endTime)  
  15. {  
  16.     if (startActivityNumber > endActivityNumber || startTime >= endTime)  
  17.     {  
  18.         return new List<int>();  
  19.     }  
  20.     //找到结束时间最早的活动   
  21.     int tempNumber = 0;  
  22.     for (int number = startActivityNumber; number <= endActivityNumber; number++)  
  23.     {  
  24.         if (s[number] >= startTime && f[number] <= endTime)  
  25.         {  
  26.             tempNumber = number;  
  27.             break;  
  28.         }  
  29.     }  
  30.     List<int> list = ActivitySelection(tempNumber + 1, endActivityNumber, f[tempNumber], endTime);  
  31.     list.Add(tempNumber);  
  32.     return list;  
  33. }  

      迭代解决

[csharp]  view plain  copy
  1. static void Main(string[] args)  
  2. {  
  3.     int[] s = { 0, 1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12 };  
  4.     int[] f = { 0, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };  
  5.   
  6.     int startTime = 0;  
  7.     int endTime = 24;  
  8.     List<int> list = new List<int>();  
  9.     for (int number = 1; number <= 11; number++)  
  10.     {  
  11.         if (s[number] >= startTime && f[number] <= endTime)  
  12.         {  
  13.             list.Add(number);  
  14.             startTime = f[number];  
  15.         }  
  16.     }  
  17.     foreach (int i in list)  
  18.     {  
  19.         Console.WriteLine(i);  
  20.     }  
  21.     Console.ReadKey();  
  22. }  


贪心算法 钱币找零问题

这个问题在我们的日常生活中就更加普遍了。假设1元、2元、5元、10元、20元、50元、100元的纸币分别有c0,c1, c2, c3, c4, c5, c6张。现在要用这些钱来支付K元,至少要用多少张纸币?用贪心算法的思想,很显然,每一步尽可能用面值大的纸币即可。

intCount[N]={3,0,2,1,0,3,5}; 

intValue[N]={1,2,5,10,20,50,100};  

[csharp]  view plain  copy
  1. static void Main(string[] args)  
  2. {  
  3.     int[] count={3,0,2,1,0,3,5};    
  4.     int[] amount={1,2,5,10,20,50,100};  
  5.     //            0 0 2 1   0   0 3  
  6.     int[] result = Change(324, count, amount);  
  7.     foreach (int i in result)  
  8.     {  
  9.         Console.Write(i + " ");  
  10.     }  
  11.     Console.ReadKey();  
  12.   
  13. }  
  14.   
  15. public static int[] Change(int k, int[] count, int[] amount)  
  16. {  
  17.     if (k == 0) return new int[amount.Length + 1];  
  18.     int total = 0;  
  19.     int index = amount.Length - 1;  
  20.     int[] result = new int[amount.Length+1];  
  21.     while (true)  
  22.     {  
  23.         if (k <= 0 || index <= -1) break;  
  24.         if (k > count[index] * amount[index])//我们有的纸币的钱的总额,比k要小  
  25.         {  
  26.             result[index] = count[index];  
  27.             k -= count[index] * amount[index];  
  28.         }  
  29.         else  
  30.         {  
  31.             result[index] = k / amount[index];  
  32.             k -= result[index] * amount[index];  
  33.         }  
  34.         index--;  
  35.     }  
  36.     result[amount.Length] = k;  
  37.     return result;  
  38. }  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值