生成排列的非递归实现算法

来源:互联网 发布:申请淘宝达人的条件 编辑:程序博客网 时间: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)
操作1.png

②从6开始往右找到比它大的最小元素(这里是7),并将它们交换位置
操作2.png

③将交换后的右边的所有序列倒转(这里是9653)
操作3.png

#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;  }