全排列,去重全排列的递归与非递归实现
全排列的概念:从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。
看到全排列的时候第一反应可能会是那么多情况怎么去写,就跟我们的排列组合一样。
那么要怎样处理全排列呢,我们来看一个例子:123
首先可能的排列有:123,132, 213, 231, 321, 312.我们来看看这个图:
可以很直接地看到,全排列是从第一个元素开始,每个元素都和它后面的元素进行位置的互换从而得到。
所以我们就可以这样来实现:
1 void fullPermutation(int *arrayL, int k, int len) { 2 //k是开始的下标,len代表数组的长度 3 if (k== len - 1) 4 output(arrayL, len); 5 else { 6 for (int i = k; i != len; i++) { 7 swap(arrayL[i], arrayL[k]); 8 fullPermutation(arrayL, k + 1, len); 9 swap(arrayL[i], arrayL[k]); 10 } 11 } 12 }
我们从第一个元素开始,先交换两个元素,然后进行一次全排列,然后恢复这两个元素,因为是某个元素和它后面的元素都交换一遍,在交换的时候其他的元素位置应该是没变的。
下面是全部的实现代码:
1 #include <iostream> 2 #include <stdio.h> 3 #include <ctime> 4 5 using namespace std; 6 7 int count_ = 0; 8 9 void output(int* arrayL, int len) { 10 for (int i = 0; i != len; i++) 11 printf("%d ", arrayL[i]); 12 printf("\n"); 13 } 14 15 void swap(int& a, int& b) { 16 int temp = b; 17 b = a; 18 a = temp; 19 } 20 21 void fullPermutation(int *arrayL, int k, int len) { 22 //k是开始的下标,len代表数组的长度 23 if (k == len - 1) { 24 output(arrayL, len); 25 count_++; 26 } 27 else { 28 for (int i = k; i != len; i++) { 29 swap(arrayL[i], arrayL[k]); 30 fullPermutation(arrayL, k + 1, len); 31 swap(arrayL[i], arrayL[k]); 32 } 33 } 34 } 35 36 37 int main(int argc, char const *argv[]) 38 { 39 int start = clock(); 40 { 41 int lenOfArray; 42 int *arrayL; 43 printf("Enter the number lrngth of the array: "); 44 scanf("%d", &lenOfArray); 45 46 arrayL = new int[lenOfArray]; 47 printf("Enter the elements of the array: "); 48 for (int i = 0; i != lenOfArray; i++) 49 scanf("%d", &arrayL[i]); 50 51 fullPermutation(arrayL, 0, lenOfArray); 52 53 printf("The total seq number is: %d\n", count_); 54 } 55 printf("The run time: %.3lfms\n",double(clock()-start)/CLOCKS_PER_SEC); 56 57 return 0; 58 }
因为有时候需要进行全排列的序列是有重复的,那么这个时候如果还按照上面的方法进行全排列的话,就会产生一些重复的序列,比如 1 2 2,按照上面的方法是会得到这样的序列:1 2 2, 1 2 2, 2 1 2, 2 2 1, 2 2 1, 2 1 2. 这样就不正确了。那么应该怎么去掉重复的呢?
可能开始会想到是不是遇到和自己相同的就不交换,因为交换后结果一样?的确,这个交换后结果会一样,但只是这样并不够,如122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。
我们来看一个序列:1 2 3 2.我们用这个方法,在遇到和前面已经交换过的元素的值相同的元素就不交换,像1 2 3 2,我们在 1 和第一个 2 交换后不再和第二个 2 交换,可能你会说,2 1 3 2 和 2 2 3 1 是不一样的,怎么能不交换呢?因为如果交换的话,变成 2 2 3 1, 但 1 和第一个 2 交换后得到的序列 2 1 3 2 的1 和最后那个 2 交换后又会得到 2 2 3 1,这样就重复了。很明显,像 2 2 3 1 这样的序列会在前面的交换结果的基础上再交换得到:
。
这种和字符串匹配的KMP算法有点像,重复的元素可以在前面交换的结果的基础上再交换的到。这个自己化一下应该就可以得到了。
下面是实现源码:
1 bool isSwap(int* arrayL, int begin, int end) { 2 for (int i = begin; i != end; i++) 3 if (arrayL[i] == arrayL[end]) 4 return false; 5 6 return true; 7 } 8 void fullPermutation(int *arrayL, int k, int len) { 9 //k是开始的下标,len代表数组的长度 10 if (k == len - 1) { 11 output(arrayL, len); 12 count_++; 13 } 14 else { 15 for (int i = k; i != len; i++) { 16 if (isSwap(arrayL, k, i)) { 17 swap(arrayL[i], arrayL[k]); 18 fullPermutation(arrayL, k + 1, len); 19 swap(arrayL[i], arrayL[k]); 20 } 21 } 22 } 23 }
下面是非递归实现:...下次补充。。。