`
xiao
  • 浏览: 161510 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

(转自网络)数据结构:排列组合通用算法分析

阅读更多

尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手。由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论。以在n个数中选取m(0<m<=n)个数为例,问题可分解为:

  1. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止。

  2. n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m

  很明显,上述方法是一个递归的过程,也就是说用递归的方法可以很干净利索地求得所有组合。

  下面是递归方法的实现:

  /// 求从数组a[1..n]中任选m个元素的所有组合。

  /// a[1..n]表示候选集,n为候选集大小,n>=m>0

  /// b[1..M]用来存储当前组合中的元素(这里存储的是元素下标)

  /// 常量M表示满足条件的一个组合中元素的个数,M=m,这两个参数仅用来输出结果。

void combine( int a[], int n, int m, int b[], const int M )

{
  for(int i=n; i>=m; i--) // 注意这里的循环范围
  {
    b[m-1] = i - 1;
    if (m > 1)
        combine(a,i-1,m-1,b,M);
    else // m == 1, 输出一个组合
    {
        for(int j=M-1; j>=0; j--)
            cout << a[b[j]] << \" \";
        cout << endl;
    }
  }
}
 

  因为递归程序均可以通过引入栈,用回溯转化为相应的非递归程序,所以组合问题又可以用回溯的方法来解决。为了便于理解,我们可以把组合问题化归为图的路径遍历问题,在n个数中选取m个数的所有组合,相当于在一个这样的图中(下面以从1,2,3,4中任选3个数为例说明)求从[1,1]位置出发到达[m,x](m<=x<=n)位置的所有路径:

  1 2 3 4

  2 3 4

   3 4

  上图是截取n×n右上对角矩阵的前m行构成,如果把矩矩中的每个元素看作图中的一个节点,我们要求的所有组合就相当于从第一行的第一列元素[1,1]出发,到第三行的任意一列元素作为结束的所有路径,规定只有相邻行之间的节点,并且下一行的节点必须处于上一行节点右面才有路径相连,其他情况都无路径相通。显然,任一路径经过的数字序列就对应一个符合要求的组合。

  下面是非递归的回溯方法的实现:

  /// 求从数组a[1..n]中任选m个元素的所有组合。

  /// a[1..n]表示候选集,m表示一个组合的元素个数。

  /// 返回所有排列的总数。

  int combine(int a[], int n, int m)

{
  m = m > n ? n : m;
  int* order = new int[m+1];  
  for(int i=0; i<=m; i++)
    order[i] = i-1; // 注意这里order[0]=-1用来作为循环判断标识
  int count = 0; 
  int k = m;
  bool flag = true; // 标志找到一个有效组合
  while(order[0] == -1)
  {
    if(flag) // 输出符合要求的组合
    {
        for(i=1; i<=m; i++) 
            cout << a[order[i]] << \" \";
        cout << endl;
        count++;
        flag = false;
    }
    order[k]++; // 在当前位置选择新的数字
    if(order[k] == n) // 当前位置已无数字可选,回溯
    {
        order[k--] = 0;
        continue;
    }
    if(k < m) // 更新当前位置的下一位置的数字 
    {
        order[++k] = order[k-1];
        continue;
    }
    if(k == m)
    flag = true;
  }
  delete[] order;
  return count;
}
 

  下面是测试以上函数的程序:

   int main()

{
  const int N = 4;
  const int M = 3;
  int a[N];
  for(int i=0;i<N;i++)
    a[i] = i+1;
  // 回溯方法
  cout << combine(a,N,3) << endl;
  // 递归方法  
  int b[M];
  combine(a,N,M,b,M);
  return 0;
}
 

  由上述分析可知,解决组合问题的通用算法不外乎递归和回溯两种。在针对具体问题的时候,因为递归程序在递归层数上的限制,对于大型组合问题而言,递归不是一个好的选择,这种情况下只能采取回溯的方法来解决。

  n个数的全排列问题相对简单,可以通过交换位置按序枚举来实现。STL提供了求某个序列下一个排列的算法next_permutation,其算法原理如下:

  1. 从当前序列最尾端开始往前寻找两个相邻元素,令前面一个元素为*i,后一个元素为*ii,且满足*i<*ii

  2. 再次从当前序列末端开始向前扫描,找出第一个大于*i的元素,令为*jj可能等于ii),将ij元素对调;

  3. ii之后(含ii)的所有元素颠倒次序,这样所得的排列即为当前序列的下一个排列。

  其实现代码如下:

   template <class BidirectionalIterator>

  bool next_permutation(BidirectionalIterator first, BidirectionalIterator last)
  {
    if (first == last) return false; // 空範圍
    BidirectionalIterator i = first;
    ++i;
    if (i == last) return false; // 只有一個元素
    i = last; // i 指向尾端
    --i;
    for(;;)
    {
        BidirectionalIterator ii = i;
        --i;
        // 以上,鎖定一組(兩個)相鄰元素
        if (*i < *ii) // 如果前一個元素小於後一個元素
        {
        BidirectionalIterator j = last; // 令 j指向尾端
        while (!(*i < *--j)); // 由尾端往前找,直到遇上比 *i 大的元素
        iter_swap(i, j); // 交換 i, j
        reverse(ii, last); // 將 ii 之後的元素全部逆向重排
        return true;
    }
    if (i == first) // 進行至最前面了  
    {
        reverse(first, last); // 全部逆向重排
        return false;
    }
  }
}
 

  下面程序演示了利用next_permutation来求取某个序列全排列的方法:

   int main()

{
  int ia[] = {1,2,3,4};
  vector<int> iv(ia,ia+sizeof(ia)/sizeof(int));
  copy(iv.begin(),iv.end(),ostream_iterator<int>(cout,\" \"));
  cout << endl;
  while(next_permutation(iv.begin(),iv.end()))
  {
    copy(iv.begin(),iv.end(),ostream_iterator<int>(cout,\" \"));
    cout << endl;
  }
  return 0;
}
 

  注意:上面程序中初始序列是按数值的从小到大的顺序排列的,如果初始序列无序的话,上面程序只能求出从当前序列开始的后续部分排列,也就是说next_permutation求出的排列是按排列从小到大的顺序进行的。 

分享到:
评论

相关推荐

    数据结构与算法综合资料库

    三维图形的消隐算法分析 实用算法(基础算法 递推法) 数据结构:哈夫曼树的应用 数据结构学习(C++)——递归 双向链表 水波算法实例 算法 平摊分析 算法表达中的抽象机制 随机数算法 台阶问题 通用冒泡排序 图遍历...

    数据结构与算法综合资料库.CHM

    三维图形的消隐算法分析 实用算法(基础算法 递推法) 数据结构:哈夫曼树的应用 数据结构学习(C++)——递归 双向链表 水波算法实例 算法 平摊分析 算法表达中的抽象机制 随机数算法 台阶问题 通用冒泡排序 图遍历...

    C语言通用范例开发金典.part1.rar

    第1章 数据结构. 1 1.1 数组和字符串 2 1.1.1 一维数组的倒置 2 范例1-1 一维数组的倒置 2 ∷相关函数:fun函数 1.1.2 一维数组应用 3 范例1-2 一维数组应用 3 1.1.3 一维数组的高级应用 5 范例1-3 一维数组...

    Windows编程循序渐进.part2

    17.3.1 SEH相关数据结构的介绍 314 17.3.2 异常处理链结构图 315 17.3.3 实例:单嵌套异常块演示程序 316 17.3.4 实例:多嵌套异常块演示程序 318 17.3.5 VC++编译器级异常帧结构 320 17.3.6 VC中的顶层异常...

    Windows编程循序渐进.part3

    17.3.1 SEH相关数据结构的介绍 314 17.3.2 异常处理链结构图 315 17.3.3 实例:单嵌套异常块演示程序 316 17.3.4 实例:多嵌套异常块演示程序 318 17.3.5 VC++编译器级异常帧结构 320 17.3.6 VC中的顶层异常...

    c++运动会评分系统

    摘要:[目录]一、需求分析二、概要设计三、详细设计四、源代码:五、调试分析:六、运行结果分析:七、用户使用说明:八、收获及体会:[原文]一、需求分析1、本程序设计中,可以输入参加运动会学校编号及项目编号;...

    C语言通用范例开发金典.part2.rar

    第1章 数据结构. 1 1.1 数组和字符串 2 1.1.1 一维数组的倒置 2 范例1-1 一维数组的倒置 2 ∷相关函数:fun函数 1.1.2 一维数组应用 3 范例1-2 一维数组应用 3 1.1.3 一维数组的高级应用 5 范例1-3 一维数组...

    软件工程-理论与实践(许家珆)习题答案

    B) 对系统的数据结构进行描述 C) 对目标系统的层次结构进行描述 D) 作为分析和设计的工具 8. 数据字典是数据流图中所有元素的定义的集合,一般由以下四类条目组成(C)。 A) 数据说明条目、控制流条目、加工条目...

    网新中英文企业手机电脑一体化建站通用版 v4.9.rar

    始终致力于开发最优化的算法和数据结构,企业网站管理系统营销旗舰版从每个表和字段的设计、每个索引的构建到每个查询的算法, 都经过缜密的考虑和最佳的优化,使用了众多知名的高效算法,并独创了一系列高效的处理...

    Design-of-Computer-Programs-Udacity

    熟悉Python语法,熟悉以下编程概念:数据结构、基本算法、lambda函数 教学大纲 第 1 课:赢得扑克牌 设计过程的步骤; 为清晰和通用而开发; 程序正确性的论据; 实验和模拟。 设计权衡; 简单明了。 可分解性和可...

    donald e. knuth - the art of computer programming volume2.part1

    卷3为分拣和搜索,这是本书的第1个修订版,它是对计算机分拣和搜索的一流技术的最全面的研究,它扩展了卷1中数据结构的处理方法,将大小数据库以及内存和外部存储都包含在内。本书包括对计算机方法仔细检查的选择...

    计算机程序设计艺术(全三卷).7z

    它扩充了第1卷对数据结构的处理,以将大小数据库和内外存储器一并考虑;遴选了精心核验的计算机方法,并对其效率做了定量分析。第3卷的突出特点是对“最优排序”一节的修订和对排列论与通用散列法的讨论。

    计算机程序设计艺术(第一卷)

    卷3为分拣和搜索,这是本书的第1个修订版,它是对计算机分拣和搜索的一流技术的最全面的研究,它扩展了卷1中数据结构的处理方法,将大小数据库以及内存和外部存储都包含在内。本书包括对计算机方法仔细检查的选择...

    donald e. knuth - the art of computer programming volume2.part2

    卷3为分拣和搜索,这是本书的第1个修订版,它是对计算机分拣和搜索的一流技术的最全面的研究,它扩展了卷1中数据结构的处理方法,将大小数据库以及内存和外部存储都包含在内。本书包括对计算机方法仔细检查的选择...

    JAVA上百实例源码以及开源项目源代码

    像坐标控制、旋转矩阵、定时器、生成图像、数据初始化、矩阵乘法、坐标旋转、判断是否是顺时针方向排列、鼠标按下、放开时的动作等,都可在本源码中得以体现。 Java编写的显示器显示模式检测程序 2个目标文件 内容...

    JAVA上百实例源码以及开源项目

    像坐标控制、旋转矩阵、定时器、生成图像、数据初始化、矩阵乘法、坐标旋转、判断是否是顺时针方向排列、鼠标按下、放开时的动作等,都可在本源码中得以体现。 Java编写的显示器显示模式检测程序 2个目标文件 内容...

    asp.net知识库

    随机排列算法 理解C#中的委托[翻译] 利用委托机制处理.NET中的异常 与正则表达式相关的几个小工具 你真的了解.NET中的String吗? .NET中的方法及其调用(一) 如何判断ArrayList,Hashtable,SortedList 这类对象是否...

    C 开发金典

    第1章 数据结构. 1 1.1 数组和字符串 2 1.1.1 一维数组的倒置 2 范例1-1 一维数组的倒置 2 ∷相关函数:fun函数 1.1.2 一维数组应用 3 范例1-2 一维数组应用 3 1.1.3 一维数组的高级应用 5 范例1-3 一维数组...

    《计算机程序设计艺术》(1,2,3卷)([美]Donald E·Knuth)

    《计算机程序设计艺术排序和查找(第3卷)(第2版)》内容简介:这是对第3卷的头一次修订,不仅是对经典计算机排序和查找技术的最全面介绍,而且还对第1卷中的数据结构处理技术作了进一步的扩充,通盘考虑了将大小型...

Global site tag (gtag.js) - Google Analytics