• 组合算法<转>


    组合的算法    出处:http://community.csdn.net/Expert/topic/3143/3143703.xml?temp=.5916101 

                           原作者:cxjddd (空浮无根)

    内容简介:

      本文讲述求一种求排列组合中的“组合”的算法。本算法通过组织选入组合
    的元素和未选入组合的元素之间次序,以方便求得下一个组合——更大的或更小
    的。算法采用了与 STL 中的(next、prev)permutation 类似的表达方法,将
    所有组合纳入一个“由小到大”的线性序列里。


    关键字:

    组合 人字形 算法 STL 排列


    正文:

      受 STL 的排列算法(next_permutation 和 prev_permutation)的影响,
    想写一个组合的算法,其结果就是一个类似于“人”字的算法。形如“人”字,
    所有的元素被处理成左边升序,右边降序。


      先说一下组合之间的次序,以 {1, 2, 3, 4} 选二为例,最“小”的组合是:
    {1, 2},然后是 {1, 3},{1, 4},{2, 3},{2, 4},最“大”的组合则是 {3,
    4}。如果是从小到大的列出组合,则只要从剩余的元素里选出下一个更大的元素,
    然后就可以很快地求出下一个更大的组合。比如对组合 {1, 2},可从剩下的 3、
    4 里选取 3 来代替 2,求得下一个组合为 {1, 3}。而对组合 {2, 4},可从剩
    下的 1、3 里选取 3 来代替 4 求得前一个组合 {2, 3}。


      数据表示:用左闭右开区间表示,待选元素的集合由输入区间 [first,
    last) 表示,入选组合的元素在区间 [first, middle),剩下的元素在区间
    [middle, last)。

     △△△◎◎◎●
     ↑  ↑  ↑
    first middle last

    图中“△”为组合中的元素,“◎”为剩下的元素,下同。

      区间 [first, middle) 一定是升序的;而 [middle, last) 则由升序和降
    序两部分组成,如果没有左边的升序,则全部是降序,相反,没有右边的降序,
    则全部是升序。而且,[first, middle) 与 [middle, last) 的升序部分是连续
    的。左升右降,所以如“人”字形。以下称 [first, middle) 为前部;称
    [middle, last) 为后部;且称后部中的升序部分为升部,其降序部分为降部。

          ◎     ◎    △       ◎     △   
         ◎◆    ◎◆    ■◎     △◆    △■   
        ◎◆◆   △◆◆    ■◆◎   △■◆   △■■   
       △◆◆◆   ■◆◆◎   ■◆◆◎  ■■◆◎  ■■■◎  
      △■◆◆◆  △■◆◆◆  △■◆◆◆ △■■◆◆  ■■■◆◎ 
     △■■◆◆◆ △■■◆◆◆ △■■◆◆◆ ■■■◆◆◎ ■■■◆◆◎
     ■■■◆◆◆ ■■■◆◆◆ ■■■◆◆◆ ■■■◆◆◆ ■■■◆◆◆
     ■■■◆◆◆ ■■■◆◆◆ ■■■◆◆◆ ■■■◆◆◆ ■■■◆◆◆
        ↑↑↑    ↑↑↓    ↓↓↓    ↑↓↓    ↓↓↓
       a  e   b e    be     be     be  

    图中“↑”表示元素属于升部,“↓”则属于降部。

      令前部的最后一个元素是 b,则所有升部的元素都是不小于 *b 的,而所有
    属于降部的元素都是小于 *b 的。

      按照这个特点,可以写出规范化的函数 adjust_combination():先将前部
    和后部的元素分别排序,然后以后面的元素以 *b 为“轴”对折,即可。代码大
    致如下:

      // 规范化
      adjust_combination (first, middle, last)
      {
        sort_combination (first, middle);
        sort_combination (middle, last);

        j = lower_bound (middle, last, *b);
        /* 此时 [middle, j) 为降部的元素,而 [j, last) 为升部的元素
         */

        reverse (j, last);  // 把 [j, last) 反转
        reverse (middle, last); // 再把 [middle, last) 反转
        /* 升部还是升序,调到左边;
         * 降部已经是降序,调到右边了。
         */
      }

      这个算法中有两个 STL 的函数(算法),在实现中是大量使用了的。这样
    的函数大概有如下几个:

    iter_swap (i, j):交换两个迭代器指向的空间 *i 和 *j;

    reverse (f, l):反转,把 [f, l) 里的元素前后对调;

    inplace_merge (f, m, l):合并,把两个有序区间 [f, m) 和 [m, l) 合并成
    有序区间 [f, l);

    lower_bound (f, l, v):在有序区间 [f, l) 里找出第一个不小于 v 的位置;

    upper_bound (f, l, v):在有序区间 [f, l) 里找出第一个大于 v 的位置。

      除了上面这些 STL 的函数,还有一个 sort_combination() 用来排序的,
    其实现以 inplace_merge() 为辅助,使用归并排序。

      依照后部中元素与 *b 的大小关系,可以这样区分后部中的升部与降部:令
    [middle, last) 中最大的元素为 e,e 后第一个小于的元素为 f(若无,则令
    f = last)。则若 *e >= *b,一定有 [middle, f) 是升部,[f, last) 为降部;
    相反若 *e < *b,一定有 e == middle 且 [middle, last) 为降部。


      下面以“无重复元素,组合从小到大”的条件来分析。

      先考察一下这样的程序,{1, 2, 3, 4, 5, 6} 选三:

      for (a = 1; a <= 6-2; a++)
        for (b = a + 1; b <= 6-1; b++)
          for (c = c + 1; c <= 6; c++)
            out (a, b, c);

      这个程序就是“从小到大”的,其中的 c 对应前部的最后一个元素,总体
    {a, b, c} 对应前部。如果 c 的值不是最大的,则找出 c 的下一个更大的值代
    替 c。如果 c 已经是最大的值,则让 b 的值变大;如果 b 也到了最大的值,
    就让 a 的值变大。如果 a 不能再大,则结束。当然,a 实际最大值也就是 4,
    b 是 5。如果是 a 或 b 的值变了,那么其后的元素的值也要一起改变,就如组
    合 {1, 4} 变成 {2, 3}。


      从上面可以看出,最重要的事情是,从当前组合里选出要被替换的元素,从
    剩下的元素里选出用来替换的元素,然后就交换、调整一下。简单地说,就是要
    从前部里找出最大的可以变大的元素,并变大。这个算法里安排这样的人字形结
    构,就是为了便于找出两个关键的元素。

      如果存在升部(*b < *e,或是 *b < *middle),则表示后面有比 *b 更大
    的元素,所以选取升部的第一个元素 *middle 与 *b 交换。加上调整的话,可
    以用“冒泡”法把 *b 往后移。

      // 存在升部,替换掉 *b
      if (*b < *e)
        {
          j = b;
          i = j++;
          while ((j != last) && (*i < *j))
            iter_swap (i++, j++);
        }

      如果只有降部,那么有两种情况:一是 *first > *e,组合已经是最大的了;
    另外就不是最大的组合了,可以求下一个更大的。前面一种情况很简单,调整到
    最小的组合即可。后一种情况,则要求出前部中要替换掉的元素和降部中要选出
    的元素。

      // 只有降部,且已经是最大的组合
      if (*e < *first)
        {
          reverse (first, middle);
          reverse (first, last);
        }

      (后一种情况)前部中要替换掉的元素 *i 可以由 *middle 求出,也就是
    前部里小于 *middle 的最大的元素。求出了要被替换的元素 *i,然后就可以求
    出用来替换的元素 *j 了:也就是降区里大于 *i 的最小的元素。除了交换 *i
    和 *j,还要把 i 到 j 之间的元素调整好。这里先后两次很有意思,前面一次
    是小于里的最大的,后面一次是大于里的最小的。

        // 只的降部时,求下一个更大的组合
        // 要被替换的元素:
        i = b;
        while (!(*--i < *middle))
          ;
        // 用来替换的元素:
        j = last;
        while (!(*i < *--j))
          ;

        // 交换
        iter_swap (i, j);

        // 调整 i 至 j 的元素
        reverse (++i, middle);  // 最大的元素在前
        reverse (i, j);         // 现在最小的元素在前了

      前面所说的,就是当“没有重复元素,且从小到大”时的算法。可以区分成
    三种情况:一是存在升部;二是恰好最大;三是从降部里选出元素。这样一个处
    理无重复元素的 next_combination 就出来了,也可以命名成
    next_combination_unique()。


      如果有重复元素,那么就要多上“等于”比较,上面三种情况就要做些改变。
    这里还是只用“小于”比较,等于和大于就都要从小于里变化出来。第一种情况
    可改成 *b 小于 *middle。第二种情况要改一下判断的条件,改成 !(*first <
    *e)。剩下的情况,则要分两种:一是 *b < *e,表示存在升部且有比 *b 大的
    元素,从升部里找出比 *b 大的元素,替换 *b;另外则与无重复元素时的第三
    种类似,从前部里找出最大的比 *e 小的元素 *i,从降部里找出最小的比 *i
    大的元素 *j,交换 i 和 j 并调整。

      (汗!写到前面,发现一个思路上的 bug,还好不会出问题。)

      总结“有重复元素,从小到大”的代码大致如下:

      /* 此代码未验证,以源代码为准 */
      if (*b < *middle)
        {
          j = b;
          i = j++;
          while ((j != last) && (*i < *j))
            iter_swap (i++, j++);
     
          return true;
        }
     
      if (!(*first < *e))
        {
          reverse (first, middle);
          reverse (first, last);
          return false;
        }

      if (*b < *e)
        {
          bb = b;
          while ((++b != e) && !(*b < *bb))
            ;
          reverse (bb, f);
          reverse (b, f);
          return true;
        }
      else
        {
          i = b;
          while (!(*--i < *e))
            ;
          j = last;
          while (!(*i < *--j))
            ;
          iter_swap (i, j);
          reverse (++i, middle);
          reverse (i, j);
          return true;
        }


    下面讨论从大到小的算法,同样,先假设没有相同的元素。从大到小的算法,
    简单地说是,找出最大的可以变小的元素,并变小。

      如果不存在升部(*middle < *b 或说 *e < *b),则 *middle(*e) 就是
    要新选入的元素,而要选出的元素就是第一个比 *e 大的元素。这种情况是最简
    单的了。

      if (*middle < *b)
        {
          i = upper_bound (first, middle, *middle);
          iter_swap (i, middle);
        }

      如果存在升部(*b < *middle),也分两种情况;一是 f == last,表示只
    有升部而没有降部,到达最小的组合;另一种,*f 就是要新选入的元素,找出
    第一个比 *f 大的元素,即为要选出的元素(当然,要选入选出的元素可能更
    多)。

      /* 只有升部,已经是最小的组合 */
      if (f == last)
        {
          reverse (first, last);
          reverse (first, middle);
        }

      第二种情况,找出第一个比 *f 大的元素 *i,则 *i 就是要换出的元素,
    而 (i, middle) 则要换成所有最大的元素(*b 应该是全部元素中最大的元素,
    然后其前依次是次小的元素)。

      /* 有升部也有降部,做较大调整 */
      i = upper_bound (first, middle, *f);
      iter_swap (i, f);
      reverse (++i, f);
      reverse (i, middle);


      如果有重复元素,从大到小这种算法主要的改动是要判断换出的位置是否为
    b,进行不同的处理。主要是因为相同元素的存在,使交换元素后的调整变得更
    复杂一些,因而做相应变化。

      if (*middle < *b)
        {
          i = upper_bound (first, middle, *middle);
          if (i != b)
            iter_swap (i, middle);
          else
            {
       s = middle;
       while ((++s != last) && !(*s < *middle))
         ;
       reverse (b, s);
     }
          return true;
        }

      if (f == last)
        {
          reverse (first, last);
          reverse (first, middle);
          return false;
        }

      i = upper_bound (first, middle, *f);
      if (i == b)
        {
          s = f;
          while ((++s != last) && !(*s < *f))
            ;
          reverse (b, f);
          reverse (b, s);
        }
      else
        {
          iter_swap (i, f);
          reverse (++i, f);
          reverse (i, middle);
        }
      return true;


      至此,组合算法 combination 已经说完了,但是写得比较含糊。在下篇里
    将细说一些东西,但完整的程序实现这里已经有了。

    template
    void
    sort_combination (BiIterator first, BiIterator last)
    {
      if (first == last) // $(AC;SPT*KX
    (B    return;

      BiIterator i = first;
      ++i;
      if (i == last)     // $(AR;8vT*KX
    (B    return;
     
      int half = distance (first, last) / 2;  // half of the length
      BiIterator middle = first;
      advance (middle, half);  // middle += half

      sort_combination (first, middle);     // sort first part
      sort_combination (middle, last);      // sort second part

      inplace_merge (first, middle, last);  // merge two parts
    }

    template
    void
    adjust_combination (BiIterator first, BiIterator middle, BiIterator last)
    {
      // the front part or the back part have no elements
      if ((first == middle) || (middle == last))
        return;

      sort_combination (first, middle);
      sort_combination (middle, last);

      BiIterator b = middle;
      --b;
      BiIterator j = lower_bound (middle, last, *b);
      reverse (j, last);
      reverse (middle, last);
    }

    template
    void
    init_combination (BiIterator first, BiIterator middle, BiIterator last,
        bool min)
    {
      sort_combination (first, last);

      if (min == false)
        {
          // the max combination
          reverse (first, last);
          reverse (first, middle);
        }
    }

    template
    bool
    next_combination (BiIterator first, BiIterator middle, BiIterator last)
    {
      if ((first == middle) || (middle == last))
        return false;

      // last element of [first, middle)
      BiIterator b = middle;
      --b;

      if (*b < *middle)
        {
          BiIterator j = b;
          while ((++b != last) && (*j < *b))
     {
       iter_swap (j, b);
       j = b;
     }
          return true;
        }

      BiIterator e = last;
      --e;
      while (e != middle)
        {
          BiIterator k = e;
          --k;
          if (!(*k < *e))
     e = k;
          else
     break;
        }
     
      BiIterator f = e;
      ++f;
      while ((f != last) && !(*f < *e))
        ++f;

      if (!(*first < *e))
        {
          reverse (first, middle);
          reverse (first, last);
          return false;
        }

      if (*b < *e)
        {
          BiIterator bb = b;
          while ((++bb != e) && !(*b < *bb))
     ;
          reverse (bb, f);
          reverse (b, f);
        }
      else
        {
          BiIterator i = b;
          while (!(*--i < *e))
     ;
         
          BiIterator j = last;
          while (!(*i < *--j))
     ;

          iter_swap (i, j);
          reverse (++i, middle);
          reverse (i, j);
        }
      return true;
    }

    template
    bool
    prev_combination (BiIterator first, BiIterator middle, BiIterator last)
    {
      if ((first == middle) || (middle == last))
        return false;
     
      BiIterator b = middle;
      --b;
     
      if (*middle < *b)
        {
          BiIterator i = upper_bound (first, middle, *middle);
          if (i != b)
     iter_swap (i, middle);
          else
     {
       BiIterator s = middle;
       while ((++s != last) && !(*s < *middle))
         ;
       reverse (b, s);
     }

          return true;
        }
     
      BiIterator e = last;
      --e;
      while (e != middle)
        {
          BiIterator k = e;
          --k;
          if (!(*k < *e))
     e = k;
          else
     break;
        }
     
      BiIterator f = e;
      ++f;
      while ((f != last) && !(*f < *e))
        ++f;

      if (/*!(*e < *b) && */(f == last))
        {
          reverse (first, last);
          reverse (first, middle);
          return false;
        }

      BiIterator i = upper_bound (first, middle, *f);
      if (i == b)
        {
          BiIterator s = f;
          while ((++s != last) && !(*s < *f))
     ;

          reverse (b, f);
          reverse (b, s);
        }
      else
        {
          iter_swap (i, f);
          reverse (++i, f);
          reverse (i, middle);
        }
      return true;
    }

  • 相关阅读:
    栈的概念
    什么是 JavaConfig?
    数据库连接池的原理。为什么要使用连接池。
    根据你以往的经验简单叙述一下MYSQL的优化
    动态横切
    横切技术
    什么是AOP
    BIO ,NIO ,AIO 有什么区别?
    简述Comparable和Comparator两个接口的区别
    Spring Boot 客户端?
  • 原文地址:https://www.cnblogs.com/junnyfeng/p/192313.html
Copyright © 2020-2023  润新知