查看单个帖子
  #1 (permalink)  
旧 2008-05-11
liuxinyu 的头像
liuxinyu liuxinyu 当前离线
高级会员
 
注册日期: 2006-02-09
帖子: 311
文章: 49
liuxinyu 正向着好的方向发展
默认 递归版的排列算法

本来打算用fp实现一个排列算法。思路不大清楚,就先用C++实现了一个,本质仍是递归的。

大致思路如下:求n个数中取出r个数的排列可以如下产生:
1. 生成第i位数字时,从[1..n]中选择一个j
2. 检查j是否已经在1到i-1位使用过了,若使用过,则重复1,否则另j为第i位的数字
3. 比较i是否等于r,若等于r,则找到了一个排列,打印输出;否则i+1,递归调用生成第i+1位数字。

代码如下:
代码:
#include <iostream> #include <vector> using namespace std; bool contains(const vector<int>& res, int i){ for(vector<int>::const_iterator it=res.begin(); it!=res.end(); ++it) if(*it==i) return true; return false; } void print_res(const vector<int>& res){ for(vector<int>::const_iterator it=res.begin(); it!=res.end(); ++it) std::cout<<*it<<","; std::cout<<"\n"; } void generate(const vector<int>& res, int i, int n, int r){ for(int j=1; j<=n; ++j){ if(!contains(res, j)){ vector<int> new_res(res); new_res.push_back(j); if(i==r) print_res(new_res); else generate(new_res, i+1, n, r); } } } void permutation(int n, int r){ vector<int> res; generate(res, 1, n, r); } int main(int, char**){ permutation(5, 3); }
运行结果为:
1,2,3,
1,2,4,
...
5,4,3,

然后我开始尝试用haskell实现纯fp排列算法。后来实在忍不住上网google了一下,看到这个算法[1]:

代码:
import Data.List permutation [] = [[]] permutation xs = [x:ys | x <- xs, ys <- permutation (delete x xs)]
我略微解释一下,这个算法是求某个list的全排列,而非n元素取r个元素的排列。这个算法相当于这样的数学描述:
排列 list 的结果是:
平凡情况:若待排列list为空,则结果为空;
一般情况:排列结果={x和后继部分ys,其中x属于list,后继部分ys属于对list中删除x后进行排列的结果}

举个例子,[1,2,3]的全排列结果是:
x为1或2或3,
ys为 [2,3]或[1,3]或[1,2]全排列
x和ys组合在一起就是最终结果。

这里利用了Haskell的list comprehension的语法包装,它其实是filter和map的组合。

有了一个全排列算法,n,r排列就很容易了:
代码:
permutation xs n r= if length xs <= n-r then [[]] else [x:ys | x <- xs, ys <- permutation (delete x xs) n r]
运行结果如下:
permutation [1..5] 5 3
[[1,2,3],[1,2,4],...,[5,4,3]]

这与最初的C++代码比,真是完全不同的思路了。

--
[1]来自:List comprehension和递归的巧妙结合 - Haskell - Tech - JavaEye论坛
回复时引用此帖