生成排列的非递归实现算法
来源:互联网 发布:申请淘宝达人的条件 编辑:程序博客网 时间:2024/06/09 19:37
生成排列的非递归实现算法
1.使用标记数组
例如,我们要从集合{1,2,……,n}中选取r个元素排列输出。我们可以检查每一个位数不大于r的n进制数,如果它们是符合条件的排列(各个数位互不相同),则将其输出。
为了实现这样的检查,我们需要引入一个标记数组,用来标识哪些元素已经被选取过。
#include <stdio.h>#define N 99 //集合中最大的元素个数int main(){ int a[N + 1];//最终输出的排列结果为:a[1],……,a[r] int d[N + 1];//如果元素i已经被选取,那么d[i]=1,否则d[i]=0 int i, j; int n;//集合中的元素个数 int r;//要选取进行排列的元素个数 double count;//当前已经找到多少个符合条件的排列 while (1) { //读取用户输入的n和r scanf("%d%d", &n, &r); if ((n <= 0) || (n > N)) { break; } //初始排列为1,1,……,1(显然有重复选取,不符合条件) for (i = 1; i <= r; i++) { a[i] = 1; } count = 0; //每次循环生成一个备选的排序方案 while (1) { //标记数组清零 for (j = 1; j <= n; j++) { d[j] = 0; } d[0] = 1; a[0] = 0;//边界哨兵 //检查该排列方案是否存在重复选取的情况 i = r; d[a[i]]++; while (d[a[i]] == 1) { i--; d[a[i]]++; //当i等于0时,边界哨兵d[0]==2,跳出循环 } //i等于0说明该排列方案不存在重复选取的情况,可以输出 if (i == 0) { count++; printf("%6.0f: ", count); for (i = 1; i <= r; i++) { printf("%2d ", a[i]); } printf("\n"); } //生成下一个备选的排序方案 i = r; a[i]++; //如果有元素超出范围,需要重新调整(进位) while (a[i] > n) { a[i] = 1; i--; a[i]++; } //i等于0说明已经遍历完了所有的备选方案,可以跳出循环 if (i == 0) { break; } } //输出最终结果 printf("n=%d r=%d count=%16.0f\n", n, r, count); }}
存在的问题:
一共考虑了nr个备选排列方案,但实际上只有n!/(n-r)!个是符合条件的,造成了大量的性能浪费。
不光如此,这里为了简化编程,也没有很好地发挥标记数组的记忆功能,而是每次检测前都对其进行清零操作,然后需要重新遍历才能得出改排列方案是否合法的结论。
2.改进地使用标记数组
为了更好地发挥标记数组的功能,进一步提高检查合法性的效率,我们可以将代码改进为下面这样子:
#include <stdio.h>#define N 99int main() { int a[N + 1];//最终输出的排列结果为:a[1],……,a[r] int d[N + 2];//如果元素i已经被选取,那么d[i]=1,否则d[i]=0 int i, j; int k;//标识当前操作元素为a[k] int n;//集合中的元素个数 int r;//要选取进行排列的元素个数 double count;//当前已经找到多少个符合条件的排列 while (1) { //读取用户输入的n和r scanf("%d%d", &n, &r); if ((n <= 0) || (n > N)) { break; } //初始排列为1(后面的元素未确定),当前操作元素为a[k]=a[1] a[1] = 1; k = 1; for (j = 1; j <= n; j++) { d[j] = 0; } d[n + 1] = 1;//边界哨兵 count = 0; //生成不完整的排列方案 while (1) { if (d[a[k]] == 1)//元素a[k]已经被选取过,或者触发边界哨兵的条件d[n + 1] == 1 { if (a[k] == n + 1) { //如果触发边界哨兵的条件d[n + 1] == 1,则返回上一位操作元素 k--; if (k == 0) { break;//已经没有再上一位的操作元素了,可以退出程序 } //更新a[k]为未选取过 d[a[k]] = 0; } //a[k]取下一个值 a[k]++; } else//元素a[k]未被选取过 { //更新a[k]为已经被选取过 d[a[k]] = 1; //操作下一个元素 k++; a[k] = 1; //已经足够r个元素(它们都互不相同),可以输出 if (k > r) { count++; printf("%6.0f: ", count); for (i = 1; i <= r; i++) { printf("%2d ", a[i]); } printf("\n"); //k的值实际上变成r,因为a[k]要取下一个值,所以要更新a[k](旧值)为未选取过 k--; d[a[k]] = 0; //a[k]要取下一个值 a[k]++; } } } //输出最终结果 printf("n=%d r=%d count=%16.0f\n", n, r, count); }}
3.按字典序排列算法
注意: 该算法只适用于全排列,即不能只选取部分元素进行排列(上面的r只能等于n)。
其基本思想是:
1.对初始队列进行排序,找到所有排列中最小的一个排列Pmin。
2.找到刚刚好比Pmin大比其它都小的排列P(min+1)。
3.循环执行第二步,直到找到一个最大的排列,算法结束。
其核心就在于,如何找到那个比给定排列大的最小排列(字典序),算法如下:
①从右向左寻找第一个由增加转为减少的元素(这里是6)
②从6开始往右找到比它大的最小元素(这里是7),并将它们交换位置
③将交换后的右边的所有序列倒转(这里是9653)
#include <iostream> #include <cstring> using namespace std; //交换数组a中下标为i和j的两个元素的值 void swap(int *a,int i,int j) { a[i]^=a[j]; a[j]^=a[i]; a[i]^=a[j]; } //将数组a中的下标i到下标j之间的所有元素逆序倒置 void reverse(int a[],int i,int j) { for(; i<j; ++i,--j) { swap(a,i,j); } } void print(int a[],int length) { for(int i=0; i<length; ++i) cout<<a[i]<<" "; cout<<endl; } //求取全排列,打印结果 void combination(int a[],int length) { if(length<2) return; bool end=false; while(true) { print(a,length); int i,j; //找到不符合趋势的元素的下标i for(i=length-2; i>=0; --i) { if(a[i]<a[i+1]) break; else if(i==0) return; } for(j=length-1; j>i; --j) { if(a[j]>a[i]) break; } swap(a,i,j); reverse(a,i+1,length-1); } } int main(int argc, char **argv) { int a[4] = {1, 2, 3, 4}; combination(a, sizeof(a) / sizeof(int)); return 0; }
阅读全文
0 0
- 生成排列的非递归实现算法
- 全排列算法的非递归实现
- 全排列算法的递归与非递归实现
- 全排列算法的递归与非递归实现
- 全排列算法的递归与非递归实现
- 全排列的递归与非递归算法实现
- n个数全排列的非递归生成算法,C实现
- 全排列生成算法(非递归)
- 全排列算法--一种非递归算法的实现
- 排列的非递归实现
- 字符串的全排列非递归实现算法
- 全排列算法之非递归实现
- 比较某排列生成算法的递归版和非递归版的效率
- 全排列算法非递归实现和递归实现
- 全排列算法非递归实现和递归实现
- 全排列算法非递归实现和递归实现
- 求全排列的非递归算法
- 全排列的非递归算法
- smarty 逻辑判断及foreach
- 用secureCRT连接虚拟机中的Ubuntu系统,出现“远程主机拒绝连接”错误
- Java GC杂谈之对象的可达分析与回收算法
- Appium 新手贴:Windows 平台上的使用 Python 语言实现 appium 自动化程序 for Android (完整版)
- 七牛云融合CDN到底怎么配置?
- 生成排列的非递归实现算法
- csdn首篇
- 求 Appium iOS和 Android 入门教程?
- Java精选笔记_面向对象(多态、异常)
- C笔记小总结
- Java对象的线程状态
- Odoo 11 功能点
- appium环境搭建
- [29]Cookie与存储