1. 程式人生 > >全排列(來自藍橋杯)

全排列(來自藍橋杯)

做了道藍橋杯的題,發現並不會做哭,不過這個題做了也算漲了個知識點。

題目:

相信大家都知道什麼是全排列,但是今天的全排列比你想象中的難一點。我們要找的是全排列中,排列結果互不相同的個數。比如:aab 的全排列就只有三種,那就是aab,baa,aba

程式碼框中的程式碼是一種實現,請分析並填寫缺失的程式碼

下面是題目的程式碼

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e3;
char str[N], buf[N];//buffer
int vis[N], total, len;
void arrange(int num) {
    if (num == len){
        printf("%s\n", buf);
        total++;
        return;
    }
	for (int i = 0; i < len; ++i) {
        if (!vis[i]) {
            int j;
            for (j = i + 1; j < len; ++j)
	   {
                if (/*在這裡填寫必要的程式碼*/)
		{
                    break;
                }
            }
            if (j == len) {
                vis[i] = 1;
                buf[num] = str[i];
                arrange(num + 1);
                vis[i] = 0;
            }
        }
    }
}
int main() {
    while (~scanf("%s",str)) {
        len = strlen(str);
        sort(str, str + len);
        total = 0;
        buf[len] = '\0';
        arrange(0);
        printf("Total %d\n", total);
    }
    return 0;
}


那麼下面是完整的程式碼:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e3;
char str[N], buf[N];//buffer
int vis[N], total, len;
void arrange(int num) 
{
    if (num == len)
   {
        printf("%s\n", buf);
        total++;
        return;
    }
	for (int i = 0; i < len; ++i)
	 {
           if (!vis[i])
          {
              int j;
              for (j = i + 1; j < len; ++j)
	    {
                if ( str[i] == str[j] && vis[j]/*在這裡填寫必要的程式碼*/) 
		{
                      break;
                  }
            }
            if (j == len) 
	   {
                vis[i] = 1;
                buf[num] = str[i];
                arrange(num + 1);
                vis[i] = 0;
            }
        }
    }
}
int main() {
    while (~scanf("%s",str))
 {
        len = strlen(str);
        sort(str, str + len);
        total = 0;
        buf[len] = '\0';
        arrange(0);
        printf("Total %d\n", total);
    }
    return 0;
}

填空的地方的意思是,如果i的後面有和s[i] 相等且訪問過的字元,則不可以(break),一旦break,j 就不能等於len,就不能繼續遞迴。那麼怎麼理解這個呢?

先想一下簡化的問題吧,假如輸入的字串不重複,例如abcd,那麼就是簡單的dfs了,一個for迴圈加一個vis判斷,如果判斷可以,繼續遞迴。

當有重複的字元時候就比較麻煩了,比如aab,單純的用遞迴會輸出重複的。那麼怎麼加上限定條件呢。

這裡,我們讓重複的這些字元只順序輸出一遍,這樣就不會重複

這是什麼意思呢,比如說aabc,我們只允許第一個a訪問後再訪問第二個a,不允許訪問第二個,再第一個。

再如,abacda,那三個a只能按順序訪問。

原理是什麼呢,用了點高中學的排列組合的知識,先排重複的,例如我們搞abacda這個例子, 先排三個a, 就是 aaa,那麼剩下的就相當於直接插入到aaa中,那麼如果我們aaa如果按多種順序排,就會產生多種結果,所以只能按順序訪問。

那麼又如何用演算法實現呢,直接加個if判斷就行了,判斷i之後的有沒有訪問過的且相等的。例如,aabc這個例子,我們第一輪選完之後,到了第二個a,然後進入遞迴,for迴圈又從0開始,到了第一個a,然後從這個之後去判斷有沒有訪問過的a,結果判斷有,違反了順序,所以結束。

這個題目的關鍵也就是排除重複的