日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C++中字符串全排列算法及next_permutation原理詳解_C 語言

作者:謎一樣的男人1 ? 更新時間: 2023-03-29 編程語言

前言

next_permutation/prev_permutation是C++ STL中的一種實用算法;

功能是: 以迭代器的方式,將一個容器內容改變為他的下一個(或prev上一個)全排列組合;

next_permutation的使用

假設需要將字符串abcd的全排列依次打印,我們可以用next_permutation函數方便操作:

使用方法:

1.一般先sort成升序;(prev_permutation倒著全排列使用規則相反)

2.然后再do while配合調用全排列,循環輸出,直到排列情況全部輸出 返回false;

運行結果:

實現全排列的兩種算法

當然僅僅只會用沒有什么困哪的,如果面試官突然問你STL中這個全排列算法咋實現的呢?

1. 遞歸法(全排列方便理解記憶的方法,作為備用方法)

如果上面全排列,突然腦袋斷片了,或者說考試中不讓用封裝好的庫函數;

為了不至于連個全排列的思想都不會,可以用用相對好理解的遞歸法全排列:

算法思想:(遞歸問題:按規則處理一個過程,剩下的過程是相同的處理方式,那么就可以進行函數遞歸調用)

1.從集合中依次選出每一個元素,作為排列的第一個元素;

2.然后**對剩余的元素(第一個元素之后的)進行 同樣操作 **;

如此遞歸處理,從而得到所有元素的全排列。

eg:

以對字符串abc進行全排列為例,我們可以這么做:以abc為例

  • 固定a,遞歸求后面bc的排列:求好: abc,acb后,b交換到第一位置,得到bac,如下固定b遞歸b后面的排列:
  • 固定b,求后面ac的排列:bac,bca,求好后,c交換到第一位置,得到cba,如下固定c遞歸c后面的排列:
  • 固定c,求后面ba的排列:cba,cab;結束 一共a,b,c分別當第一個元素進行了全排列,算法結束;

(注意:1. 每次交換下一個位置的時候,需要swap換回來,保證原始序列,再交換下一個位置的字母去第一個位置。2. 需要考慮有重復相同且挨著的數字情況,此時需要剪枝)

遞歸比較抽象,可以用簡單地例子abc在紙上模擬畫一下好理解;

實現代碼(無重復元素情況)

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

//dfs實現全排列(無重復元素情況)
void dfs(string &s, int l, int r)
{
	if (l == r) {//遞歸終止,當前s可以輸出了,已經是某一輪的完整排列,不能再排列了
		cout<<s<<endl;
		return;
	}

	for (int i = l; i < r; i++) {
        
            swap(s[l],s[i]);
			dfs(s,l+1,r);//遞歸
			swap(s[l], s[i]);//進行下一輪的swap dfs,需要先swap換回來原來的位置!否則會出現重復排列!
		
	}
}


int main()
{
	string s = "abcd";
    //sort(s.begin(),s.end())//sort一下,再配合dfs算法,可以實現按照字典序處理
    int len = s.size();
	dfs(s,0,len);
	return 0;
}

運行結果:

有重復元素情況

這種情況需要對代碼做一個優化,不然的話按照上面的算法,會出現重復數字的重復排列情況;

優化很簡單:

如果某個數字在之前的排列被換到首位置進行排列過,那本次交換就不進行;(其次,當dfs首次交換i==l的時候,即便出現過也需要進行);

整合一下就是if(i==l || s[i]沒出現) -->進行交換)

代碼:

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

//dfs實現全排列(含重復元素情況)
void dfs(string &s, int l, int r)
{
	if (l == r) {
		cout<<s<<endl;
		return;
	}
	set<char>st;//檢測重復的set
	for (int i = l; i < r; i++) {
		if (i == l || st.find(s[i])==st.end()) {//防止后續進行重復排列
			st.insert(s[i]);//滿足  記錄這個字符 

			swap(s[l], s[i]);
			dfs(s, l + 1, r);
			swap(s[l], s[i]);
		}
	}
}


int main()
{
	string s = "aba";
	int len = s.size();
	dfs(s,0,len);
	return 0;
}

2. 迭代法(next_permutation底層原理)

比較抽象,難以理解,根據個人情況來理解;

一個全排列可看做一個字符串,字符串可有前綴、后綴。

規定: 生成給定全排列的下一個排列–> 所謂一個字符串的下一個排列,就是這個個字符串變化限制在盡可能短的后綴上,變化后的那個字符串; 這就要求這一個與下

一個有盡可能長的共同前綴;變化限制在盡可能短的后綴上

eg:

839647521是1—9的排列。1—9的排列最前面的是123456789,最后面的987654321;

從右向左掃描若都是增的,就到了987654321,也就沒有下一個了。否則找出第一次出現下降的位置。

如何得到346987521的下一個?

首先對原生字符串排序,這個迭代算法是基于字典序排序好的字符串全排列;(所以之后從尾部,向前循環迭代,每次變化盡可能短的后綴,以此類推)

1.從尾部往前找第一個P(i-1) < P(i)的位置;: 346987521 種 最終找到6是第一個變小的數字,記錄下6的位置i-1

2.從找到的 i 位置往后找到最后一個大于6的數:346987521 中最終找到7的位置,記錄位置為m(m == r-1)

3.swap(r-1,i-1) : 3 4 7 9 8 6 5 2 1

4.倒序翻轉i位置后的所有數據 : 3 4 7 1 2 5 6 8 9

5.進行do-while循環,直到第一步之后,判斷出 i==0 break; 全部排列完畢

很抽象,但是思想就是 一個字符串的下一個全排列:有盡可能長的共同前綴;變化限制在盡可能短的后綴上

大概知道步驟: 那么用排好序的123456789試試上面的過程,你會發現,每次變化盡可能短的后綴,有點像遞歸的感覺,一步一步逼近字符串0,index,此時完美結束; 或者用123這個例子體驗一下6個全排列是咋來的 amazing

上面的過程多悟幾遍就理解了; 大概知道思想面試的時候說說也行=-=

實現代碼(有無重復不影響)

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

//dfs實現全排列



int main()
{
	string s = "abcd";
	
	sort(s.begin(),s.end());//記得先排序
	
	int len = s.size();
	do
	{
		cout << s << endl;//打印某次排列

		int i = s.size() - 1;
		int j;
		while (i > 0 && s[i] <= s[i - 1]) i--;//1.從后向前 找 第一個 s[i-1]<s[i]

		if (i == 0) break;

		j = i;

		while (j<len && s[j]>s[i - 1]) j++;//2.從i向后 找 最后一個 s[m]>s[i-1] 用j找,所以最后m==j-1

		swap(s[i - 1], s[j - 1]);//3. swap (i-1,m(j-1))

		reverse(s.begin() + i, s.end());  //4. 翻轉 i后面的子串

		

	} while (1); //do while為了讓第一個 abcd 也正常打印再全排列

	return 0;
}

這個算法理解起來對于我來說有點夸張,嗚嗚嗚;

原文鏈接:https://blog.csdn.net/wtl666_6/article/details/128818981

欄目分類
最近更新