一些常用集合算法——之组合生成

本文深入探讨了组合生成算法的原理与应用,通过实例展示了如何利用数组和字典进行组合生成,包括初始化、查找特定组合及更新组合的步骤,并提供了代码实现与运行结果分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【转自:http://yishan.cc/blogs/gpww/archive/2009/11/01/1271.aspx】

一些常用集合算法——之组合生成

问题

在开发过程中常常需要处理集合,因此我写了一些常用算法,贴出大家提提意见。

本帖介绍组合生成算法。

分析

开一个数组,数组元素的值为1表示其下标代表的数被选中,为0则没选中。

首先初始化,将数组前m个元素置1,表示第一个组合选中前m个元素。

然后找到从左到右的第一个“10”组合,将其变为“01”组合,

同时将其左边的所有“1”全部移动到数组的最左端,例如:

1 1 1 0 0 //1,2,3

1 1 0 1 0 //1,2,4

1 0 1 1 0 //1,3,4

0 1 1 1 0 //2,3,4

1 1 0 0 1 //1,2,5

1 0 1 0 1 //1,3,5

0 1 1 0 1 //2,3,5

1 0 0 1 1 //1,4,5

0 1 0 1 1 //2,4,5

0 0 1 1 1 //3,4,5

解法

具体实现的时候,不需要每次“从左往右扫描”——我们可以这样考虑:每次将“10”变为“01”,可能产生新的翻转位置,那么就只需要记录新的翻转位置。

 public static List<T[]> Combination<T>(IList<T> list, int n)
    {           

        n = Math.Min(list.Count, n);

        var combs = new List<T[ ]>(); 

        var selection = new Dictionary<int>(n);//用于记录被选中元素的位置
         Stack<int> pins = new Stack<int>();//记录翻转位置
         for (int i = 0; i < n; i++) selection.TryAdd(i);//初始的时候选中前n个元素
         if (n < list.Count) pins.Push(n - 1);//初始翻转位置

         while (true)
        {
             <添加生成的组合>

             if (pins.Count == 0) break;

             var i = pins.Pop();
             selection.Remove(i);//去掉i
             var j = i + 1;
             selection.TryAdd(j);//选中j
                
             if (j + 1 < list.Count && !selection.ContainsKey(j + 1))
                pins.Push(j);//增加翻转点

               bool adjusted = false;
             <将i左边这些连续的1全部移动到最左>

             if (!adjusted && i - 1 >= 0 && selection.ContainsKey(i - 1))
               pins.Push(i - 1);//增加翻转点 
         }
       return combs;
   }


其中:
<添加生成的组合>代码为:
        int cnt = 0;
       var newComb = new T[ n ];
       foreach (var index in selection) newComb[cnt++] = list[index];
       combs.Add(newComb);
<将i左边这些连续的1全部移动到最左>代码为:
       if (i >= 2)
      {
          int z = 0;//从左数0的数量
            while (!selection.ContainsKey(z)) z++;
           if (z > 0 && z < i)
           {
              var m = Math.Min(z, i - z);
              for (int k = 0; k < m; k++) selection.TryAdd(k); //选中k
              for (int k = i - 1; k >= i - m; k--) selection.Remove(k); //去掉k
              pins.Push(i - z - 1);
              adjusted = true;
           }
       }

运行结果

输入abcdef,选3个:

1:abc  2:dab  3:dac  4:dbc  5:abe  6:ace  7:bce  8:ade  9:bde  10:cde  11:abf  12:acf  13:bcf  14:adf  15:bdf  16:cdf  17:aef  18:bef  19:cef  20:def

讨论

网上大多是“扫描算法”,这里用“翻转点”做了改进——不断构造新的翻转点,直到没办法再翻转。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值