组合的算法 出处: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;
}