排列问题_c++

全排列算法

暴力破解

时间复杂度:O(n*n!)  【n个数的全排列有n!种,每一个排列都有n个数据】

对一个数组进行全排列。
可以看成当前这个a[0]-a[i]固定,对后面进行全排列,就完成了这个a[0]-a[i]的全部情况
去重复:a[m]-a[i]之间出现过的数据,不再进行交换了,已经交换过


//在 str 数组中,[m,i) 中是否有与 a[i] 元素相同的
bool IsSwap(int a[], int m, int i)
{
	for (; m < i; m++)
	{
		if (a[m] == a[i])
			return false;
	}
	return true;
}
//数组,长度,当前的位往后
void Permutation(int a[],int len, int m)
{
	//跳出递归,到最后一个了
	if (m == len)
	{
		for (int i = 0; i < len; i++)
		{
			cout << a[i] << " ";
		}
		cout << endl;
	}
	else
	{
		for (int i = m; i < len; i++)
		{
			//交换,递归,换回
			//判断交不交换,m-i的部分出现过,就不用了
			if (IsSwap(a,m,i))
			{
				swap(a[m], a[i]);
				//排后面的所有可能
				Permutation(a, len, m + 1);
				//换回去
				swap(a[i], a[m]);
			}
		
		}
	}
}

字典序

时间复杂度:O(m*n)

【求一个排列的下一个排列的时间复杂度,我们可以看到是O(n)级别的。而这里要变换m次,所以总时间复杂度是O(n * m)级别的】

【数组开始应该是单调递增的】

  • 在原排列中从后往前找,找到第一个比它后面数小的数。(a[pos] < a[pos + 1])。简而言之就是找到原排列的最长单调递减的后缀的前一个数。
  • 在原排列的最长单调递减的后缀中从前往后找到最后一个大于a[pos]的数a[k]。
  • 调换a[pos]和a[k]。
  • 对a[pos+1]……a[n]进行转置,此时的排列就是原排列的下一个排列。
//二分搜索
int binary_sort(int a[], int start, int end,int num)
{
	int min = (start + end) / 2;
	if (a[min] == num) return min;
	if (a[min] < num)
		binary_sort(a, start, min,num);
	else
		binary_sort(a, min + 1, end,num);
	//没找到
	return -1;
}
//字典序列
/*
1、从最后开始找,找到连续的递减序列的前一个,第一个a[pos]<a[pos+1].
2、该序列从前到后遍历,找到最后一个比a[pos]大的数a[k]
3、swap(a[k],a[pos])
4、该序列进行置换 a[pos]~a[end]
*/
//数组,长度,当前的位往后
bool Permutation(int a[],int len)
{
	//找到最后一个,全递减就是做完了
	int pos = len - 2;
	for (; pos >=0; pos--)
	{
		if (a[pos] < a[pos + 1])
			break;
	}
	//全递减,做完了
	if (pos == -1) return false;
    //序列中找第一个比a[pos]小的前一个,交换
	int i = pos+1;
	for ( ; i < len; i++)
	{
		//这个i就是了
		if (a[pos] > a[i])
			break;
	}
	//交换
	swap(a[pos], a[i-1]);
	//进行一个置换
	for (int j = pos + 1; j <= (len+pos)/2; j++)
	{
		swap(a[j], a[len+pos-j]);
	}
	return true;
}

//main函数里面的调用
do{
	//输出数组内容
}while(Permutation(a,len));
利用STL的next_permutation()
void stl_Permutation(int a[],int n){
	//保存原来的数组
    int b[n];
    for(int i=0;i<n;i++){
        b[i]=a[i];
    }
    //先利用stl的函数进行正序排序
    sort(b,b+n);
    do{
        cout<<b[0];
        for(int i=1;i<n;i++) cout<<" "<<b[i];
        cout<<endl;
    }while(next_permutation(b,b+n));
}
输出第M个序列
int i=1;
do{
	if(i==m)
	{
	//输出数组内容
	}
	i++;
}while(Permutation(a,len));
该序列是第几个/第几N序列是什么

问题一:问一个数属于字典序中的第几个排列?
一个排列 p 1 p 2 p 3 … p n − 1 p_1p_2p_3…p_{n-1} p1p2p3pn1对应一个序数: a 1 a 2 … a n − 1 a_1a_2…a_{n−1} a1a2an1
a i a_i ai表示 p i p_i pi的右边比 p i p_i pi小的数字的个数。
在字典序中,排序为 p 1 p 2 p 3 … p n − 1 的 序 数 d : p_1p_2p_3…p_{n-1}的序数d: p1p2p3pn1d:
d = a 1 ∗ ( n − 1 ) ! + a 2 ∗ ( n − 2 ) ! + . . . + a n − i ∗ ( n − ( n − i ) ) ! + . . . + a n − 1 ∗ 1 ! d=a_1*(n-1)!+a_2*(n-2)!+...+a_{n-i}*(n-(n-i))!+...+a_{n-1}*1! d=a1(n1)!+a2(n2)!+...+ani(n(ni))!+...+an11!

注 意 : d = 0 , 1 , 2 , … , n ! − 1 注意:d=0,1,2,…,n!−1 d=0,1,2,,n!1

eg:有一个序列为123456,问431256是字典排序的第几个序列
d = 3 ∗ 5 ! + 2 ∗ 4 ! + 0 ∗ 3 ! + 0 ∗ 2 ! + 0 ∗ 1 ! = 3 ∗ 120 + 2 ∗ 24 = 408 d =3*5!+2*4!+0*3!+0*2!+0*1!\\=3*120+2*24=408 d=35!+24!+03!+02!+01!=3120+224=408
408是从0开始的,第几个数列是从1开始的,所以结果是409

问题二:问字典序中的第几个排列是多少?
eg: 假设一个字典序排列由1到4组成,问第21个排列是多少?(排名从0开始数,当然也可以从1开始)

  • 该字典排序的种类有:n=4!=432*1=24种
  • 第一位数为1:

最小排序:1234
d = 0 ∗ 3 ! + 0 ∗ 2 ! + 0 ∗ 1 = 0 d=0*3!+0*2!+0*1=0 d=03!+02!+01=0
最大排序:1432
d = 0 ∗ 3 ! + 2 ∗ 2 ! + 1 ∗ 1 ! = 5 d=0*3!+2*2!+1*1!=5 d=03!+22!+11!=5

  • 第二位数为2的距离:1*3! ~ 2*3!-1 = 6 ~ 11
    第三位数为3的距离 :2*3! ~ 3*3!-1=12 ~ 17
    第四位数为4的距离:3*3! ~ 4*3!-1 = 18~23
    所有:0~23 :24种
  • 确定第一位为4,开始查找下一位,与查找第一位类似

4123: d=33!+02!+0*1!=18
21-18=3=1*2!+1*1!
则序列为:4231

数组之间的排列遍历输出

  • vector<vector>strArr:存储二维数组
  • vectorvalue:存储strArr中一维数组的长度
  • vectorNUM(value.size(), 0):记录当前遍历到的位置
//遍历的全排序输出函数
void Arrange(vector<vector<string>>strArr, vector<int>value)
{
	//定义一个数组,并初始化为0
	vector<int>NUM(value.size(), 0);

	int n = 1;
	int i = 0;
	while (i < value.size())
	{
		n *= value[i];
		i++;
	}
	cout << n << endl;
	int j = 0;
	while (n > 0)
	{
		i = 0;
		//一次遍历输出
		while (i < strArr.size())
		{
			cout << strArr[i][NUM[i]] << " ";
			i++;
		}

		//修改定位
		j = 0;
		NUM[j]++;//NUM[0]
		//刚好到临界值
		while (NUM[j] == value[j] && j < strArr.size() - 1)
		{
			//修改值
			NUM[j] = 0;
			//后一个增加一
			NUM[j + 1]++;
			//看后一个是否改变
			j++;
		}
		cout << endl;
		n--;
	}
}

相关的字符串切割内容

  • 重要的函数:
    • getline(cin,s)
      类似cin.getlin(char*,len)函数,这里读取后的类型为string

    • stringstream ss(s);
      定义一个stringstream类,用string类型初始化它

    • getline(ss, line, ’ ‘)
      把stringstream类的一串数据按空格’ ’ 分割,并存放在string类型的line中。

#include<string>
#include<sstream>//stringstream ss(s);
void inputString()
{
	string s;
	vector <string> str;
	vector <vector<string>> strArr;
	string line;
	int num=0,i=0;
	vector<int>value;
	cin >> num;
	getline(cin, s);//先把数字的那一个读取掉
	while (i<num)
	{
		getline(cin, s);
		//把string s转为stringstream类型,可以遍历
		stringstream ss(s);
		while (getline(ss, line, ' '))//按空格分割
		{
			if (!line.empty())
				str.push_back(line);
		}
		strArr.push_back(str);
		value.push_back((int)str.size());
		//cout << value[i] << " ";
		str.clear();
		i++;
	}
	Arrange(strArr, value);
}

参考资料

全排列暴力
字典排序
各种问法
按换行输入,空格分割 sstream

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值