Detailed explanation of the underlying principles of C++ string full arrangement (recursive method) and (iterative method) and next_permutation

foreword

next_permutation/prev_permutation is a practical algorithm in C++ STL;

The function is: In the form of an iterator, change the content of a container to its next (or previous) full permutation combination;

insert image description here

The use of next_permutation

Assuming that the full permutation of the string abcd needs to be printed sequentially , we can use the next_permutation function to facilitate the operation:

Instructions:

  1. Generally sort into ascending order first ; (prev_permutation reverses the full arrangement and uses the opposite rule)

  2. Then do while to call the full arrangement , and loop the output until all the output of the arrangement returns false;

insert image description here

operation result:

insert image description here

Two Algorithms to Realize Full Arrangement

Of course, there is nothing difficult about just using it. If the interviewer suddenly asks you how this full permutation algorithm in STL is implemented?

1. Recursive method (the method of full arrangement is convenient for understanding and memory, as a backup method)

If the above is all arranged, suddenly the head is broken, or the packaged library functions are not allowed to be used in the exam;

In order not to even have the idea of ​​a full arrangement, you can use a relatively easy-to-understand recursive method to make a full arrangement :

Algorithm thinking: (recursive problem: process a process according to the rules, and the rest of the process is handled in the same way, then the function can be called recursively)

  1. Select each element in turn from the set as the first element in the array ;
  2. Then **do the same for the remaining elements (after the first element)**;

So recursive processing , so as to get the full arrangement of all elements.

eg:

  • Taking the full arrangement of the string abc as an example, we can do this: take abc as an example

Fix a , recursively find the arrangement of the following bc: Find the best: abc, after acb, exchange b to the first position, and get bac, fix b as follows and recurse the arrangement behind b:
fix b , find the arrangement of ac: bac, bca, After finding it, switch c to the first position to get cba, fix c and recurse the arrangement after c as follows:
fix c , and find the arrangement of ba: cba, cab; at the end, a, b and c are respectively used as the first element After the full permutation, the algorithm ends;

(Note: 1. Every time you switch to the next position, you need to swap back to ensure the original sequence, and then swap the letters in the next position to go to the first position. 2. You need to consider the situation of repeating the same and adjacent numbers. Pruning is required at this time)

Recursion is more abstract, you can use a simple example abc to simulate and draw on paper to understand;

Implementation code (without repeated elements)

#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;
}

operation result:

insert image description here

There are repeated elements

insert image description here

In this case, the code needs to be optimized, otherwise, according to the above algorithm, there will be a repeated arrangement of repeated numbers;

Optimization is simple:

If a certain number has been changed to the first position in the previous arrangement, then this exchange will not be performed; (Secondly, when dfs exchanges i==l for the first time, it needs to be performed even if it appears);

The integration is if(i==l || s[i] does not appear) --> exchange)

code:

#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. Iterative method (the underlying principle of next_permutation)

More abstract, difficult to understand, to understand according to personal circumstances;

A full array can be regarded as a string, and the string can have a prefix and a suffix.
Regulations: Generate the next permutation of a given full permutation –> The so-called next permutation of a string is the string after the change is limited to the shortest possible suffix ; this requires this one to be the same as The next has the longest possible common prefix ; variation is restricted to the shortest possible suffix
eg:

839647521 is the arrangement of 1-9. 1-9 arrangement is 123456789 at the front and 987654321 at the bottom;

If scanning from right to left is all increasing, it will reach 987654321, and there will be no next one. Otherwise find out where the drop first occurred.


How to get the next one of 346987521?

  • First sort the native strings. This iterative algorithm is based on the full array of strings sorted in lexicographical order ; (so then iterates from the end to the forward cycle, changing the suffix as short as possible each time, and so on)
  1. Find the first position where P(i-1) < P(i) from the tail to the front ;: 346987521 kinds, finally find that 6 is the first number that becomes smaller, and record the position i-1 of 6
  2. Find the last number greater than 6 from the found i position backward : finally find the position of 7 in 346987521, and the record position is m (m == r-1)
  3. swap(r-1,i-1) : 3 4 7 9 8 6 5 2 1
  4. Flip all data after position i in reverse order: 3 4 7 1 2 5 6 8 9
  5. Carry out a do-while loop until after the first step, it is judged that i==0 break; all arrangements are completed

Very abstract, but the idea is the next full permutation of a string: there is a common prefix as long as possible ; the change is limited to the shortest possible suffix

Know the steps roughly: Then try the above process with the sorted 123456789, and you will find that changing the suffix as short as possible each time is a bit like recursion, approaching the string 0, index step by step, and it ends perfectly at this time; Or use the example of 123 to experience how the 6 full permutations are amazing

The above process can be understood after a few more times; I probably know that it is okay to talk about it during the ideological interview =-=

Implementation code (repetition does not affect)

#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;
}

This algorithm is a bit exaggerated for me to understand, woo woo;

Niuke topic link

Guess you like

Origin blog.csdn.net/wtl666_6/article/details/128818981