尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手。由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论。以在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的元素,令为*j(j可能等于ii),将i,j元素对调;
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++)——递归 双向链表 水波算法实例 算法 平摊分析 算法表达中的抽象机制 随机数算法 台阶问题 通用冒泡排序 图遍历...
《数据结构与算法分析》是计算机科学领域内的一门核心课程,它研究如何高效地组织和操作数据。根据给定的文件信息“数据结构\数据结构与算法分析c语言描述_课后答案.pdf”,我们可以深入探讨书中涵盖的关键知识点,...
《数据结构与算法分析(Java版英文)》是一本深入探讨数据结构和算法的经典著作,专为Java程序员设计。本书旨在帮助读者理解如何在实际编程中有效地使用数据结构和算法,提升程序性能,并掌握解决问题的核心技术。 ...
《数据结构与算法分析:C++语言描述(第2版)》是计算机科学领域经典的一本教材,由Mark Allen Weiss著,奈霍夫翻译为英文版。这本书深入浅出地介绍了如何使用C++来理解和实现各种数据结构和算法,是学习和提升编程能力...
三维图形的消隐算法分析 实用算法(基础算法 递推法) 数据结构:哈夫曼树的应用 数据结构学习(C++)——递归 双向链表 水波算法实例 算法 平摊分析 算法表达中的抽象机制 随机数算法 台阶问题 通用冒泡排序 图遍历...
数据结构与算法分析是计算机科学的核心领域,对于任何想要深入..."数据结构与算法分析—C语言描述.pdf"这本书很可能是通过C语言这个通用且底层的语言,深入讲解这些概念,帮助读者更好地理解数据结构和算法的实现细节。
### 数据结构——实现进制转换的算法 #### 知识点概述 本篇文章将详细介绍如何通过编程实现将一个十进制数字转换为其他进制(如二进制、八进制、十六进制等)的过程,并特别关注使用顺序栈来完成这一任务。文章将...
### 数据结构与算法分析C++ #### 一、概述 在《数据结构与算法分析C++》这本书中,作者深入浅出地介绍了C++语言在数据结构和算法领域的重要性和应用。书中强调了为了有效地解决问题,程序员不仅需要掌握高效的数据...
### 数据结构与算法分析 C++描述 #### 一、引言与重要性 在软件开发领域,掌握数据结构和算法是构建高效、可维护及可靠软件系统的关键。本篇文章将根据给定的文件内容,深入探讨《数据结构与算法分析 C++描述》一...
### 数据结构与算法分析习题解答 #### 一、概览 《数据结构与算法分析》是一本由Clifford A. Shaffer编写的经典教材,该书详细介绍了计算机科学中的核心概念——数据结构和算法分析。这本习题解答手册为教材提供了...
同时,算法分析是关键,包括时间复杂度和空间复杂度的评估,以确保所设计的解决方案既有效率又节省资源。 课程的考核通常包括平时成绩、上机实验和考试或课程报告,对于未完成作业、缺课过多或实验不及格的学生,...
### 数据结构和算法分析 #### 重要性及基础概念 数据结构和算法是计算机科学的基础,对于软件开发人员来说尤其重要。数据结构涉及到如何组织和存储数据,以便于有效地访问和修改;而算法则是一系列解决问题的具体...
### 数据结构与算法分析C++版 #### 重要知识点概览 **1. 数据结构与算法的基本概念** - **数据结构**: 是计算机科学中的一个重要概念,指数据在计算机中的存储和组织方式。良好的数据结构可以帮助程序员更高效地...
### C#算法和数据结构学习知识点 #### 一、数据结构和算法概览 - **数据结构**: 是指相互之间存在一种或多种特定关系的数据元素的集合,它研究如何组织和存储数据,以便能够有效地访问和修改数据。本书涵盖了线性表...
《数据结构与算法分析:C++实现Dijkstra算法在塔斯马尼亚大学Assignment2中的应用》 在计算机科学中,数据结构与算法分析是至关重要的领域,它涉及到如何有效地存储和处理数据,以及如何设计高效的算法来解决问题。...
在编程领域,排列组合是算法设计中的一个重要概念,特别是在数据结构和算法分析中。易语言是一种以中文为编程语句的编程环境,旨在降低编程门槛,让更多人能理解和使用编程技术。本例程通过递归法实现了在易语言中...