全排列算法的非递归实现与递归实现的方法(C++)_C 语言

(一)非递归全排列算法
基本思想是:
    1.找到所有排列中最小的一个排列P.
    2.找到刚刚好比P大比其它都小的排列Q,
    3.循环执行第二步,直到找到一个最大的排列,算法结束.
下面用数学的方法描述:
给定已知序列 P =  A1A2A3An ( Ai!=Aj , (1<=i<=n  , 1<=j<=n, i != j  ) )
找到P的一个最小排列Pmin = P1P2P3Pn  有  Pi > P(i-1) (1 < i <= n)
从Pmin开始,总是目前得到的最大的排列为输入,得到下一个排列.
方法为:
1.从低位到高位(从后向前),找出“不符合趋势”的数字。即找到一个Pi,使Pi < P(i+1)。
  若找不到这样的pi,说明我们已经找到最后一个全排列,可以返回了。
2.在 P(i+1)P(i+2)Pn 中,找到一个Pj,便得 Pj"刚刚好大于"Pi.
  ("刚刚好大于"的意思是:在 P(i+1)P(i+2)Pn 中所有大于Pi的元素构成的集合中最小的元素.)
3.交换 Pi , Pj 的位置.注意:此处不改变i和j的值,改变的是Pi和Pj.
4.交换后, P1P2P3Pn  并不是准确的后一个排列。因为根据第1步的查找,我们有P(i+1) > P(i+2) > . > Pn
  即使进行了Pi和Pj的交换,这仍然是这一部分最大的一个排列。将此排列逆序倒置(变成最小的排列)即为所求的下一个排列.
5.重复步骤1-4,直到步骤1中找不到“不符合趋势”的数字.

复制代码 代码如下:

//交换数组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);
    }
}

(二)递归算法
令E= {e1 , ..., en }表示n 个元素的集合,我们的目标是生成该集合的所有排列方式。令Ei 为E中移去元素i 以后所获得的集合,perm (X) 表示集合X 中元素的排列方式,ei . p e r m(X)表示在perm (X) 中的每个排列方式的前面均加上ei 以后所得到的排列方式。例如,如果E= {a, b, c},那么E1= {b, c},perm (E1 ) = ( b c, c b),e1 .perm (E1) = (a b c, a c b)。对于递归的基本部分,采用n = 1。当只有一个元素时,只可能产生一种排列方式,所以perm (E) = ( e),其中e 是E 中的唯一元素。当n > 1时,perm (E) = e1 .perm (E1 ) +e2 .p e r m(E2 ) +e3.perm (E3) + ⋯ +en .perm (En )。这种递归定义形式是采用n 个perm (X) 来定义perm (E), 其中每个X 包含n- 1个元素。至此,一个完整的递归定义所需要的基本部分和递归部分都已完成。
当n= 3并且E=(a, b, c)时,按照前面的递归定义可得perm (E) =a.perm ( {b, c} ) +b.perm ( {a,c} ) +c.perm ( {a, b} )。同样,按照递归定义有perm ( {b, c} ) =b.perm ( {c} ) +c.perm ( {b}), 所以a.perm ( {b, c} ) = ab.perm ( {c} ) + ac.perm ( {b}) = a b . c + ac.b = (a b c, a c b)。同理可得b.perm ( {a, c}) = ba.perm ( {c}) + bc.perm ( {a}) = b a . c + b c . a = (b a c, b c a),c.perm ( {a, b}) =ca.perm ( {b}) + cb.perm ( {a}) = c a . b + c b . a = (c a b, c b a)。所以perm (E) = (a b c, a c b, b a c, b c a,c a b, c b a)。注意a.perm ( {b, c} )实际上包含两个排列方式:abc 和a c b,a 是它们的前缀,perm ( {b, c} )是它们的后缀。同样地,ac.perm ( {b}) 表示前缀为a c、后缀为perm ( {b}) 的排列方式。程序1 - 1 0把上述perm (E) 的递归定义转变成一个C++ 函数,这段代码输出所有前缀为l i s t [ 0:k-1], 后缀为l i s t [ k:m] 的排列方式。调用Perm(list, 0, n-1) 将得到list[0: n-1] 的所有n! 个排列方式,在该调用中,k=0, m= n - 1,因此排列方式的前缀为空,后缀为list[0: n-1] 产生的所有排列方式。当k =m 时,仅有一个后缀l i s t [ m ],因此list[0: m] 即是所要产生的输出。当k<m时,先用list[k] 与l i s t [ k:m] 中的每个元素进行交换,然后产生list[k+1: m] 的所有排列方式,并用它作为list[0: k] 的后缀。S w a p是一个inline 函数,它被用来交换两个变量的值

复制代码 代码如下:

template <class T>
inline void Swap(T& a, T& b)
{
    // 交换a和b
    T temp = a; a = b; b = temp;
}

template<class T>
void Perm(T list[], int k, int m)
{
    //生成list [k:m ]的所有排列方式
    int i;
    if (k == m)
    {
         //输出一个排列方式
         for (i = 0; i <= m; i++)
             cout << list [i];
         cout << endl;
    }
    else // list[k:m ]有多个排列方式
    {
         // 递归地产生这些排列方式
         for (i=k; i <= m; i++)
         {
             Swap (list[k], list[i]);
             Perm (list, k+1, m);
             Swap (list [k], list [i]);
         }
    }
}

时间: 2024-10-29 09:06:34

全排列算法的非递归实现与递归实现的方法(C++)_C 语言的相关文章

概率的问题:使用递归与多次试验模拟的分析_C 语言

多次枚举: 实例1 口袋中有5只红球,4只白球.随机从口袋中取出3个球,取出1个红球2个白球的概率 复制代码 代码如下: <SPAN style="FONT-SIZE: 18px"> srand( (unsigned)time( NULL ) ); int n = 0; for(int i=0; i<100000; i++) {  char x[] = {1, 1, 1, 1, 1, 2, 2, 2, 2};//5个红球用5个1表示 4个白球用4个2表示  int a

c++递归解数独方法示例_C 语言

复制代码 代码如下: #include<iostream> using namespace std; void init();void function(int m); int canplace(int row,int col,int c); void outputresult(); int a[9][9], maxm = 0; int main() {   init(); function(0);  return 0; } void init(){ int i, j; for(i = 0;

C实现的非阻塞方式命令行端口扫描器源码_C 语言

该实例是一个C实现的基于命令行模式端口扫描代码,并且是以非阻塞方式来实现对IP和端口的连接测试.为了大家使用和学习方便,已在代码中尽可能多的地方加入了注释,相信对于帮助大家理解C端口扫描有很大帮助. 具体功能代码如下: #include <afxext.h> #include <winsock.h> // 编译时需使用的库 #pragma comment(lib,"wsock32.lib") // select()成员定义 #define ZERO (fd_se

代码-【递归】全排列算法的问题

问题描述 [递归]全排列算法的问题 class AnagramApp { static char[] arrChar; static int size; public static void main(String[] args) { String input = "abc"; arrChar =input.toCharArray(); size =input.length(); doAnagram(input.length()); } public static void doAna

c#-C#不依赖任何系统库函数,编写一个快速排序算法的程序,要求使用递归,越简单越好

问题描述 C#不依赖任何系统库函数,编写一个快速排序算法的程序,要求使用递归,越简单越好 C#不依赖任何系统库函数,编写一个快速排序算法的程序,要求使用递归,越简单越好 解决方案 先mark下,没有环境,回头给你写 解决方案二: 二分查找的递归算法和非递归算法 解决方案三: 快速排序的递归和非递归实现

C语言二叉树的非递归遍历实例分析_C 语言

本文以实例形式讲述了C语言实现二叉树的非递归遍历方法.是数据结构与算法设计中常用的技巧.分享给大家供大家参考.具体方法如下: 先序遍历: void preOrder(Node *p) //非递归 { if(!p) return; stack<Node*> s; Node *t; s.push(p); while(!s.empty()) { t=s.top(); printf("%d\n",t->data); s.pop(); if(t->right) s.pus

C++实现二叉树非递归遍历方法实例总结_C 语言

一般来说,二叉树的遍历是C++程序员在面试中经常考察的,其实前中后三种顺序的遍历都大同小异,自己模拟两个栈用笔画画是不难写出代码的.现举一个非递归遍历的方法如下,供大家参考. 具体代码如下: class Solution { public: vector<int> preorderTraversal(TreeNode *root) { vector<int> out; stack<TreeNode*> s; s.push(root); while(!s.empty()

java 汉诺塔Hanoi递归、非递归(仿系统递归)和非递归规律 实现代码_java

程序如下: 复制代码 代码如下: View Code  /*  * Hanoi塔游戏 问题描述:  * 汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具.  * 大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照  * 大小顺序摞着64片黄金圆盘.大梵天命令婆罗门把圆盘从下面开始按大小  * 顺序重新摆放在另一根柱子上.并且规定,在小圆盘上不能放大圆盘,在  * 三根柱子之间一次只能移动一个圆盘.  *   * fuction:实现 hanoi塔  *       

全排列算法的原理和实现代码_C 语言

全排列是将一组数按一定顺序进行排列,如果这组数有n个,那么全排列数为n!个.现以{1, 2, 3, 4, 5}为例说明如何编写全排列的递归算法. 1.首先看最后两个数4, 5. 它们的全排列为4 5和5 4, 即以4开头的5的全排列和以5开头的4的全排列. 由于一个数的全排列就是其本身,从而得到以上结果. 2.再看后三个数3, 4, 5.它们的全排列为3 4 5.3 5 4. 4 3 5. 4 5 3. 5 3 4. 5 4 3 六组数. 即以3开头的和4,5的全排列的组合.以4开头的和3,5的