全排列的非递归算法

1.全排列的定义和公式:

从n个数中选取m(m<=n)个数按照一定的顺序进行排成一个列,叫作从n个元素中取m个元素的一个排列。由排列的定义,显然不同的顺序是一个不同的排列。从n个元素中取m个元素的所有排列的个数,称为排列数。从n个元素取出n个元素的一个排列,称为一个全排列。全排列的排列数公式为n!,通过乘法原理可以得到。

2.时间复杂度:

n个数(字符、对象)的全排列一共有n!种,所以全排列算法至少时间O(n!)的。如果要对全排列进行输出,那么输出的时间要O(n?n!),因为每一个排列都有n个数据。所以实际上,全排列算法对大型的数据是无法处理的,而一般情况下也不会要求我们去遍历一个大型数据的全排列。

3.列出全排列的初始思想:

解决一个算法问题,我比较习惯于从基本的想法做起,我们先回顾一下我们自己是如何写一组数的全排列的:1,3,5,9(为了方便,下面我都用数进行全排列而不是字符)。 
【1,3,5,9】(第一个) 
首先保持第一个不变,对【3,5,9】进行全排列。 
同样地,我们先保持3不变,对【5,9】进行全排列。 
保持5不变,对9对进行全排列,由于9只有一个,它的排列只有一种:9。 
故排列为【1,3,5,9】 
接下来5不能以5打头了,5,9相互交换,得到 
【1,3,9,5】 
此时5,9的情况都写完了,不能以3打头了,得到 
1,5,3,9 
1,5,9,3 
1,9,3,5 
1,9,5,3 
这样,我们就得到了1开头的所有排列,这是我们一般的排列数生成的过程。再接着是以3、5、9打头,得到全排列。

我们现在做这样的一个假设,假设给定的一些序列中第一位都不相同,那么就可以认定说这些序列一定不是同一个序列,这是一个很显然的问题。有了上面的这一条结论,我们就可以同理得到如果在第一位相同,可是第二位不同,那么在这些序列中也一定都不是同一个序列。 
那么,这个问题可以这样来看。对 
T=【x1,x2,x3,x4,x5,........xn?1,xn】 
我们获得了在第一个位置上的所有情况之后(注:是所有的情况),对每一种情况,抽去序列T中的第一个位置,那么对于剩下的序列可以看成是一个全新的序列 
T1=【x2,x3,x4,x5,........xn?1,xn】 
序列T1可以认为是与之前的序列毫无关联了。同样的,我们可以对这个T1进行与T相同的操作,直到T中只一个元素为止。这样我们就获得了所有的可能性。所以很显然,这是一个递归算法。 
第一位的所有情况:无非是将x1与后面的所有数x2,x3,.......xn依次都交换一次

示意图如下:




4.全排列的非去重递归算法

算法思路:全排列可以看做固定前i位,对第i+1位之后的再进行全排列,比如固定第一位,后面跟着n-1位的全排列。那么解决n-1位元素的全排列就能解决n位元素的全排列了,这样的设计很容易就能用递归实现。

【附代码段:】

 1 #include<iostream>
 2 using namespace std;
 3 int arr[5]={0,1,2,3,4};
 4 void swap(int x,int y)//用于交换数组的两个数
 5 {
 6     int temp=arr[x];
 7     arr[x]=arr[y];
 8     arr[y]=temp;
 9 }
10 int resove(int n)//递归函数
11 {
12         if(n==5)//当尝试对不存在的数组元素进行递归时,标明所有数已经排列完成,输出。
13         {
14             for(int i=0;i<5;i++)
15             cout<<arr[i];
16             cout<<endl;
17             return 0;
18         }
19         for(int i=n;i<5;i++)//循环实现交换和之后的全排列
20         {//i是从n开始 i=n;swap(n,i)相当于固定当前位置,在进行下一位的排列。
21             swap(n,i);
22             resove(n+1);
23             swap(n,i);
24         }
25
26 }
27 int main()
28 {
29     resove(0);
30 }

排列模板

 1 void permutation1(char* str,int sbegin,int send)    //全排列的非去重递归算法
 2     {
 3         if( sbegin == send) //当 sbegin = send时输出
 4         {
 5             for(int i = 0;i <= send; i++)   //输出一个排列
 6                 cout << str[i];
 7             cout << endl;
 8         }
 9         else
10         {
11             for(int i = sbegin; i <= send; i++) //循环实现交换和sbegin + 1之后的全排列
12             {
13                 swap(str[i],str[sbegin]);   //把第i个和第sbegin进行交换
14                 permutation1(str,sbegin + 1,send);
15                 swap(str[i],str[sbegin]);   //【注1】交换回来
16             }
17         }
18     }  

【注1】swap(str[i],str[sbegin])//交换回来 
我们来仔细推敲一下循环体里的代码,当我们对序列进行交换之后,就将交换后的序列除去第一个元素放入到下一次递归中去了,递归完成了再进行下一次循环。这是某一次循环程序所做的工作,这里有一个问题,那就是在进入到下一次循环时,序列是被改变了。可是,如果我们要假定第一位的所有可能性的话,那么,就必须是在建立在这些序列的初始状态一致的情况下,所以每次交换后,要还原,确保初始状态一致。

原文地址:https://www.cnblogs.com/curo0119/p/8401254.html

时间: 2024-11-07 07:07:33

全排列的非递归算法的相关文章

算法学习(二) 全排列问题的非递归算法——模拟堆栈

前一段时间总结了全排列问题的几种递归解法,今天再总结一下如何通过对系统栈行为的模拟来非递归的实现全排列问题. 我们用一个数组stack[]来表示一个栈,用一个top指针来表示栈顶,用一个flags[]数组来标示每一个数字的可用性:用i来表示当前的状态. 初始状态top=0:i=-1:flags数组全为1: i递增,如果i没有越界并且flags[i]==1,那么就将i写入栈中,栈顶往前移动一位:最后把flags[i]赋值为0,i回溯到初始状态-1: 当栈顶越界,就将整个栈的信息打印出来,然后top

字符串全排列-非递归算法

字符串的全排列非递归算法是每次都寻找比前序列大一点的序列,如: 起点:字典序最小的排列,例如12345 终点:字典序最大的排列,例如54321 过程:从当前排列生成字典序刚好比它大的下一个排列. 算法过程:后找.小大.交换.翻转 后找:字符串中最后一个升序的位置i,即S[k]>S[k+1](k>i),S[i]<S[i+1]: 查找(小大):S[i+1...N-1]中比Ai大的最小值Sj: 交换:Si,Sj: 翻转:S[i+1...N-1] 代码如下: 1 #include <ios

二叉树的前序、中序、后序遍历的递归和非递归算法实现

1 /** 2 * 二叉树的前序.中序.后序遍历的递归和非递归算法实现 3 **/ 4 5 //二叉链表存储 6 struct BTNode 7 { 8 struct BTNode *LChild; // 指向左孩子指针 9 ELEMENTTYPE data; // 结点数据 10 struct BTNode *RChild; // 指向右孩子指针 11 }; 12 13 /** 14 * 前序遍历 15 **/ 16 // 递归实现 17 void PreorderTraversal(BTNo

二叉树遍历非递归算法——后序遍历

在前面先后介绍了二叉树先序遍历的非递归算法和中序遍历的非递归算法,这里则来介绍二叉树后序遍历非递归算法,二叉树后序非递归遍历真的非常之 重要,因为它具有独特的特性(文章结尾会阐述),所以,在很多与二叉树相关的复杂算法中,经常要用到二叉树后序遍历的非递归算法.并且在互联网面试笔 试也经常考察该算法,所以,我们应该对二叉树后序遍历非递归算法乱熟于心. 和二叉树先序遍历.中序遍历非递归算法一样,后序遍历非递归算法同样是使用栈来实现:从根结点开始,将所有最左结点全部压栈,每当一个结点出栈时, 都先扫描该

数据结构算法实现-二叉树遍历的非递归算法

由于递归算法使用系统堆栈,性能较差,所以应尽可能使用非递归算法. 1.先序遍历 先序遍历,即得到节点时输出数据. // 先序遍历 function PreOrder(node){ if(node!==undefined){ console.log(node.data) } var stack=[] //模仿递归的栈 stack.push(node) for(var temp=node,i=0;temp!==undefined;temp=stack[stack.length-1]){ if(tem

二叉树的三种遍历的递归与非递归算法

今天复习了一下二叉树的前序遍历.中序遍历.后序遍历的递归与非递归算法,顺便记录一下: //TreeTest.h #include <iostream> struct TreeNode { int value; TreeNode* leftChild; TreeNode* rightChild; void print() { printf("%d ",value); } }; class MyTree { private: TreeNode* m_root; TreeNode

二叉树遍历的非递归算法

闲来无事,重看了<数据结构>一书,突然发现其中的很多代码写的很精妙,以下就是我对二叉树一部分的做的记录.一般遍历就是使用前序.中序.后序三种遍历,我自己平时都是使用递归算法,今天看书才发现递归算法不是最优解,因为函数调用栈层层叠加,还要保存函数的返回地址,实际参数传递,创建局部变量等等. 一.二叉树前序非递归算法 前序遍历的特点是:首先访问根,访问完根后再访问左子树,所以对每一个结点,在访问完该结点后,沿着左链访问下来 ,直到为左链为空,然后将所有访问过的结点进栈.然后结点出栈,每一个出栈的结

二叉树3种遍历的非递归算法

http://blog.csdn.net/pipisorry/article/details/37353037 c实现: 1.先序遍历非递归算法 #define maxsize 100 typedef struct { Bitree Elem[maxsize]; int top; } SqStack; void PreOrderUnrec(Bitree t) { SqStack s; StackInit(s); p=t; while (p!=null || !StackEmpty(s)) { w

二叉树先序中序非递归算法

一直想要写的 二叉树 中序 先序 后序遍历算法 当年学习DS最虚的就是这个,因为非递归算法复杂,测试数据不好弄,只能一个一个手动插入.感觉明显比图的难,虽然大家都觉得图更难..... 递归的太简单了,就不写了.关键是非递归版本. 先序: 我自己的版本: void RootPreTraverse(Node* p) { Stack S; while(S not empty) { p=S.top(); S.pop(); Show(p); if(p->right!=null) S.push(p->ri