字典序全排列的求解

全排列的求解问题

求一段数字的全排列,即是求这些数字所有排列组合。
按字典序输出的全排列,可以理解为某种意义上的“从小到大”排列顺序。
例如:{1,2,3}按字典序的全排列为:123,132,213,231,312,321

填数法:它的函数实现可以理解为,将原数组的数,优先选择较小的数,填入新数组中(当然,较大的数也会填入,不过要在较小数的所有情况都填完之后)

int a[3]={1,2,3};
int b[3]={0};
①将1填入b[0],此时b[1]还有2,3两种选择。
②将2填入b[0],此时b[1]还有1,3两种选择。
③将3填入b[0],此时b[1]还有1,2两种选择。
注意:这里选择数字填入是有顺序的,要优先选择当前可选择的数中,自己未选过的最小的。

它的函数实现为:

#include<iostream>
#include<algorithm>
using namespace std;

int a[5] = { 0 };    //用来标记这个数是否使用过
int b[5] = { 1, 4, 3, 2, 5 }; //用来存放数字
int c[5] = { 0 };    //b中的数字依次填入c

void print_per(int b[], int m, int n) 
//按字典序输出数组b的全排列
//m表示已经排了几个数,n表示总共有几个数
{
 if (m == n)//如果全部排好了
 {
  for (int i = 0; i < n; i++)
  {
   cout << c[i] << " ";
  }
  cout << endl;
  return;
 }
 else
 {
  for (int i = 0; i < n; i++)
  {
   if (a[i] == 0) //如果第i个数还未使用
   {
    a[i] = 1; //标记为使用了
    c[m] = b[i];//填入数组c
    print_per(b, m + 1, n);
    a[i] = 0;
   }
  }
 }
}

int main()
{
 sort(b, b + 5);  //对数组b,从小到大排列
 print_per(b, 0, 5);
 return 0;
}

运行结果:部分截图
在这里插入图片描述
但是,当原数组中存在相同大小的数时,前面那种方法就会出错(排列出相同的组合),下面介绍另一种思路(其实’填数法’修改修改也能用,改起来挺麻烦的就是,这里主要是为了多介绍一种方法)

交换法:{1,2,3,4,5},将2,3,4,5依次换到1的位置上当“老大”,同理,在递归的下一层将3,4,5依次换到2的位置上当“老二”。由于递归的结算次序,实际输出的排列是先交换后面的位置,即4和5的位置。(实际操作的时候,1和1自身也要进行形式上的交换,以保证一开始1是在老大的位置)
那么,如果数组中存在重复的时候,如{1,2,2,3,4},应该怎么处理呢?

当1和第二个2想要进行交换的时候,它发现它已经和第一个2交换过了,所以此时不进行交换,叫下一个数来交换(continue;)。用代码判断就是,当前要交换的数a[i]和上一个数a[i-1]是不是相同的,如果是,就不交换(即只交换遇到的第一个’2’)。

函数实现为:

#include<iostream>
#include<algorithm>
using namespace std;

void swap(int a[], int i, int j)
{
 int t = a[i];
 a[i] = a[j];
 a[j] = t;
}

void print_per2(int a[], int index, int n)
//index表示正在使用第几个数进行交换
//n表示总共有几个数
{
 if (index == n)
 {
  for (int i = 0; i < n; i++)
  {
   cout << a[i] << " ";
  }
  cout << endl;
  return;
 }
 int c[5];
 for (int i = 0; i < n; i++)
 //将数组a复制一遍,防止接下来的操作破坏a的顺序
 {
  c[i] = a[i];
 }
 sort(c + index, c + n);
 for (int i = index; i < n; i++)
 {
  if (c[i] == c[i - 1] && i > index)
  //如果这次要交换的数,和上次交换过的数一样,就不交换
   continue;
  swap(c, index, i); //交换数组c的第index和第i个的值
  print_per2(c, index + 1, n);
  swap(c, i, index); //还原成一开始的样子
 }
}

int main()
{
 int a[5] = { 1, 2, 2, 3, 4 };
 sort(a, a + 5);
 print_per2(a, 0, 5);
 return 0;
}

运行结果:部分截图
在这里插入图片描述
PS.上述两种方法都是给定原数组,求全排列。如果给你一个全排列中的某种排列,让你求按字典序的下一个排列?怎么办?

由字典序我们知道,下一个排列肯定比上一个排列更大,另外还有一个附加条件,大的尽量少(即第一个比它大的排列)。so,求出全排列依次去比较,不行(求全排列->麻烦,比较->也麻烦)。

其实理解了交换法的话,我们知道,每一种排列之间其实就只交换了两个数字而已,所以我们只需要找到下一步应该交换哪两个数?
ex:{4,2,3,2,1}->{4,3,1,2,2},交换了a[1]==2和a[2]==3,然后把a[2]到a[4]从小到大排序。

步骤为:
①从右向左,找到第一个非递增的数,用i标记
②从右向左,找第一个大于a[i]的数,用j标记
③交换第i和第j个数
④对i+1之后的数,从小到大排序

函数实现:

#include<iostream>
#include<algorithm>
using namespace std;

int get_next_permutation(int a[])
{
 //1.从右向左,找到第一个非递增的数,用i标记
 //2.从右向左,找第一个大于a[i]的数,用j标记
 //3.交换第i和第j个数
 //4.对i+1之后的数,从小到大排序
 bool f = false;
 int i, j;
 for (i = 4; i >= 0; i--)
 {
  if (a[i - 1] < a[i] && i - 1 >= 0)
  {
   i--;
   f = true;
   break;
  }
 }
 if (!f)
 {
  return 0; //查找下一个全排列失败;
 }
 for (j = 4; j >= 0; j--)
 {
  if (a[j]>a[i])
   break;
 }
 int t = a[j];
 a[j] = a[i];
 a[i] = t;
 sort(a + i + 1, a + 5);
 return 1;
}

int main()
{
 int a[5] = { 4, 2, 3, 2, 1 };
 get_next_permutation(a);
 //next_permutation(a, a + 5);
 //添加<algorithm>头文件后,使用上面这句有同样效果
 for (int i = 0; i < 5; i++)
  cout << a[i];
 return 0;
}

结果:
在这里插入图片描述
理解不了的话?那就来点黑科技
①#include"algorithm"
②next_permutation(a, a + 5);

求全排列的时候就:
do
{输出语句或判断语句}
while(next_permutation(a, a + 5));
//如果是最后一个排列就会退出
//顺便说一下,此时就是使用非递归的方式求出全排列

求下一个排列的时候就:
next_permutation(a, a + 5);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值