打印全排列思路

从n个不同的元素中取m个元素(m<=n),按照一定的顺序排列起来,

叫做从n个不同元素取出m个元素的一个排列。

当m=n时,所有的排列情况叫做全排列,比如3的全排列为:

1 2 3
1 3 2

2 1 3
2 3 1

3 1 2
3 2 1

我们先从简单的开始,要求写出代码打印上面的排列情况即可,顺序可以不一致。

分析过程:

首先,我们如何把三位的数字打印出来呢,有两种方式:

printf("%d\n" ,num);  //num=123

第二种:

printf("%d%d%d\n",a,b,c); //a=1 b=2 c=3

我认为采用第二种比较好,原因在于第一种需要对位数的考虑,

而我们问题需要对数字位置不断的进行交换,因此第二种也方便于交换。

现在知道了如何打印,那么如何交换数字的位置呢?很简单:

void swap(int *a,int *b){
  int tmp = *a;
  *a = *b;
  *b = tmp;
}

于是将上面的交换打印结合起来就可以,大概写个模型了

void output(int a,int b,int c){
   printf("%d%d%d\n",a,b,c);
   swap(&a,&b);   output(a,b,c);   //递归
}

如果此时执行 output(1,2,3),那么程序将会死循环打印出下面的片段

123
213..

因此我们有两个问题要解决:

1.怎样交换使得所有全排列都能被遍历到?

2.怎样使得递归能够终止?

从上面的死循环输出可以观察到,

交换仅仅发生在 第一个数字 与 第二个数字 之间。

如果我们尝试继续对 第二个数字 与 第三个数字 进行交换。

然后递归重复上面这个过程,

1 2 3   //初始值,第一个周期开始

2 1 3   //前两个数字做交换
2 3 1   //后两个数字做交换

...  递归  ...

3 2 1   //前两个数字做交换
3 1 2   //后两个数字做交换

...  递归  ...

1 3 2   //前两个数字做交换
1 2 3   //后两个数字做交换,进入下一个周期
...  递归  ...

...  无限循环 ...

你会发现所有的排列就可以被打印出来了,但是个死循环的打印。

如何终止?

只需要找到一个周期的开始特征即可,比如在上面的打印中

初始值是 1 2 3

而所有全排列情况打印完后,下个周期的开始也是 1 2 3

那么可以来个判断a b c 变量是否同时和一开始一样,如果是就退出函数返回。

解决两个问题后,修改一下output函数:

void output(int a,int b,int c){

   printf("%d%d%d\n",a,b,c); //打印初始值

   swap(&a,&b);    //交换前两个数字
   printf("%d%d%d\n",a,b,c);

   swap(&b,&c);    //交换后两个数字

   //一个周期结束的情况发生在这个位置
   if(a==1 && b==2)return;

   output(a,b,c);   //递归

}

然后在main函数里面 执行 output(1,2,3) 输出结果如下:

当然为了好看,你可以把交换的顺序改下,先交换后面两个数字,再交换首尾两个数字

现在,3的全排列打印出来了,现在我们尝试写个代码可以打印任意一个数的全排列

首先,假如打印4的全排列,如果继续使用上面的代码思路,那么将需要修改:

output(1,2,3)   =>  output(1,2,3,4);

printf("%d%d%d\n")  =>  printf("%d%d%d%d\n");  

很多地方需要像上面一样添加一个变量,这样的话打印100的全排列,岂不是要写100个变量 或者 参数??

这样很显然不科学,因此我们换一个问题:输入一个数字,然后打印这个数字的全排列。也就是:

上面原版是:输入三个数字,打印这三个数字全排列,output(1,2,3);

而现在改成:输入一个数字,打印这个数字的全排列,output(3),需要输出结果与上面相同。

现在参数变成了一个,要通过这个参数能将1 2 3 成员表示出来

要能交换这三个数字的其中两个,且不能使用变量多个变量,如何实现呢?

首先我们需要把 1 2 3 这三个数字通过 3 这个数字得到,并将其保存到数组里,很简单:

int i,arr[1024];
for(i=1;i<=3;i++)
   arr[i] = i;   //为了方便操作我们从1开始存储

然后遍历数组,对这个数组遍历输出一次,然后交换一次

int i,j;
for(i=1;i<3;i++){

   //输出当前的数字组合
   for(j=1;j<=3;j++)
      printf("%d",arr[j]);
   printf("\n");

   //交换数组两个数字
   swap(&arr[i],&arr[i+1]);

}

我们尝试执行 output(3)  ,结果输出如下

我们稍微做下修改,

1、当 i 遍历到数组的最后一个元素时,与第一个元素交换

2、将 3 统一用参数n来表示

于是output函数就可以写成

void output(int n){
  int i,j,arr[1024];
  for(i=1;i<=n;i++)    //保存n的全排列成员
     arr[i] = i;

  for(i=1;i<=n;i++){   //遍历数组

//输出当前数组组合
    for(j=1;j<=n;j++)
       printf("%d",arr[j]);
    printf("\n");

//交换数组的成员位置
    if(i==n) swap(&arr[i],&arr[1]);  //当遍历到最后一个时,与第一个成员做交换    else swap(&arr[i],&arr[i+1]);    //否则与下一个元素做交换
  }
}

那么假如n=3,将数组遍历一次的过程如下:

1 2 3  //初始状态 , i=1
2 1 3  //交换,i=2
2 3 1  //交换,i=3
1 3 2  //交换,i=4,退出循环

很显然遍历一次是不足以将所有的排列情况输出出来。

为了使得数组能够继续从下标1为开始遍历,思路就是使用递归

也就是第一次遍历完后,将遍历完后的数组 递归传给函数本身继续遍历

而要保持数组的状态,意味着数组要以参数的形式传递,其次数组的初始化不能再递归里初始。

所以初始化可以放在main函数里,当然既然用到了递归,就需要防止无休止的递归,也就是要找到退出状态

不妨我们再写代码之前分析一下:

传入数组
arr = [0, 1, 2, 3]  //0下标不使用,下面忽略

//开始遍历
[ 1, 2, 3]    // i=1,输出然后交换
[ 2, 1, 3]    // i=2,输出然后交换
[ 2, 3, 1]    // i=3,输出然后与第一个元素交换
[ 1, 3, 2]    // i=4,  退出循环

-----递归-----

传入数组
arr = [0, 1, 3, 2]  //保存上次的状态继续任务,0继续忽略

//开始遍历
[ 1, 3, 2]    // i=1 输出然后交换
[ 3, 1, 2]    // i=2 输出然后交换
[ 3, 2, 1]    // i=3 输出然后与第一个元素交换
[ 1, 2, 3]    // i=4 退出循环,至此我们不需要继续执行了

---因此需要在此写个递归结束判断条件----

递归的结束条件与上面思路一样,如果数组与最最开始的状态是一样的

那么表示一个周期已经完成:

int tag = 1;          //标志位
for(i=1;i<=n;i++){
   if(arr[i]!=i){     //如果存在不同继续递归
     tag = 0;
     break;
  }
}if(tag)return         //如果全相同退出递归

那么总的代码如下:

#include <stdio.h>
#include <stdlib.h>
#define MAX 1024

void swap(int *a,int *b){
  int tmp = *a;
  *a = *b;
  *b = tmp;
  return;
}
void output(int n,int arr[]){
  int i,j;
  for(i=1;i<=n;i++){

    //print
    for(j=1;j<=n;j++){
      printf("%d ",arr[j]);
    }
    printf("\n");

    //swap
    if(i==n)swap(&arr[i],&arr[1]);
    else swap(&arr[i],&arr[i+1]);

  }

  int tag = 1;
  for(i=1;i<=n;i++)
    if(arr[i]!=i){tag=0;break;}

  if(tag)return;

  output(n,arr);
}

int main(){
  int i;
  int n,arr[MAX];
  printf("enter a num:");
  scanf("%d",&n);
  for(i=1;i<=n;i++)
    arr[i]=i;

  output(n,arr);
  return 0;
}

执行结果

时间: 2024-10-13 07:58:55

打印全排列思路的相关文章

打印全排列和stl::next_permutation

打印全排列是个有点挑战的编程问题.STL提供了stl::next_permutation完美的解决了这个问题. 但是,如果不看stl::next_permutation,尝试自己解决,怎么做? 很自然地,使用递归的办法: 1. 单个元素的排列只有1个. 2. 多个元素的排列可以转化为: 以每个元素为排列的首个元素,加上其他元素的排列. 有了思路,就可以编码了. 第一个版本: void printAllPermutations(const std::string& prefix, int set[

CF459C Pashmak and Buses 打印全排列

这题如果将最终的结果竖着看,每一列构成的数可以看成是k进制的数,一共有d列,任意两列都不相同,所以这就是一个d位k进制数全排列的问题,一共有k ^ d个排列,如果k ^ d < n,则打印-1. 打印最终结果时设第一列就为1 1 1 1 ... 1,然后依次每列增加1后(公交车编号从1开始,不是从0开始) ,注意,这里是k进制. #include <stdlib.h> #include <stdio.h> #include <algorithm> #include

JAVA求解全排列

一,问题描述 给定一个字符串,求出该字符串的全排列. 比如:"abc"的全排列是:abc.acb.bac.bca.cab.cba 二,实现思路 采用递归的方式求解.每次先选定一个字符,然后进行“若干次”交换,求出在选定这个字符的条件下,所有的全排列,并把字符“复位”再交换回来.至此,一趟全排列完成.第二趟,选定下一个字符,然后进行“若干次”交换,求出在选定这个字符的条件下,所有的全排列,并把字符“复位”再交换回来...... 就类似于:(参考网上的解释如下:) 设R={r1,r2,..

HDU 1043 Eight八数码解题思路(bfs+hash 打表 IDA* 等)

题目链接 https://vjudge.net/problem/HDU-1043 经典的八数码问题,学过算法的老哥都会拿它练搜索 题意: 给出每行一组的数据,每组数据代表3*3的八数码表,要求程序复原为初始状态 思路: 参加网站比赛时拿到此题目,因为之前写过八数码问题,心中暗喜,于是写出一套暴力bfs+hash,结果TLE呵呵 思路一:bfs+hash(TLE) 1 #include <cstdio> 2 #include <cstring> 3 #include <queu

46 Permutations(全排列Medium)

题目意思:全排列 思路:其实看这题目意思,是不太希望用递归的,不过还是用了递归,非递归的以后再搞吧 ps:vector这玩意不能随便返回,开始递归方法用vector,直接到500ms,换成void,到12ms 1 class Solution { 2 public: 3 vector<vector<int>> permute(vector<int>& nums) { 4 vector<vector<int> >ans; 5 permute

将字符串倒着打印出来demo

/* * 将输入的字符串倒着打印出来 * 思路:1.键盘输入字符串 * 2.将字符串转变成字符数组 * 3.将数组倒着打印出来 * char[] toCharArray() 将此字符串转换为一个新的字符数组. */ package cn.StringBuffer; import java.util.Scanner; public class StringFanXie { public static void main(String[] args) { Scanner sc = new Scann

从上往下打印二叉树-剑指Offer

从上往下打印二叉树 题目描述 从上往下打印出二叉树的每个节点,同层节点从左至右打印. 思路 数据结构:队列 将树按层遍历,先将根节点的左右子节点先后进入队列,然后再先后从队列取出,然后将取出的节点的左右子节点再入队列 循环进行,直到队列为空 代码 import java.util.ArrayList; import java.util.LinkedList; import java.util.Queue; /** public class TreeNode { int val = 0; Tree

Algorithm --&gt; 顺序打印矩阵

顺序打印矩阵 思路 参考代码 #include <iostream> using namespace std; void printNumAsClockwise(int a[][4], int row, int col) { if (row < 1 || col < 1) return; int up = 0, down = row -1, left = 0, right = col -1; int i = 0; while(up <= down && lef

剑指offer之【从上往下打印二叉树】

题目: 从上往下打印二叉树 链接: https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&tqId=11175&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking 题目描述: 从上往下打印出二叉树的每个节点,同层节点从左至右打印. 思路: 建立一个序列:对当前点的左右子节点进行扫描,非空