通過交換操作,調整數組元素位置


問題描述:有一個長度為N的整形數組row,由0至N-1這N個數字亂序組成(每個數組出現且僅出現一次)。現在你可以對這個數組的任意兩個不同的元素進行交換。問:對於一個給定的這種數組,若要把這個數組變為從小到大排好序的操作(即,對於數組的任意下標,均有 I == row[i] 成立),最少需要進行多少次交換?

 

首先,舉幾個簡單的例子:

 

例子1:

 

下標

0

1

2

3

4

0

3

2

1

4

 

只需1次交換即可:把row中下標為1的元素和下標為3的元素進行交換,記為swap(row, 1, 3)。

 

 

例子2:

下標

0

1

2

3

4

0

2

1

4

3

 

需要兩次交換:

第一次:swap(row, 1, 2)

第一次交換后:

下標

0

1

2

3

4

0

1

2

4

3

 

第二次:swap(row, 3, 4)

 

 

例子3:

下標

0

1

2

3

4

0

4

2

1

3

 

需要兩次交換:

第一次:swap(row, 1, 4)

第一次交換后:

下標

0

1

2

3

4

0

3

2

1

4

 

第二次:swap(row, 1, 3)

 

 

注意,在例子3中,下標為1、3、4的三個元素的初始位置形成了一個“環”。即(接下來的話很重要),位置1上的元素本應該在位置4;位置4上的元素本應該在位置3;位置3上的元素本應該在位置1。這段很重要的話太啰嗦了,簡記如下:1-->4-->3-->1。

 

任何一個亂序的數組,都會包含一個或多個形如“1-->4-->3-->1”的“環”。

 

注意,這個“環”的開頭的結尾肯定是同一個下標,絕不會出現如下的形式:

“1-->4-->3-->……-->3”。這是因為,如果數字3出現了兩次,那就意味着原始數組row中的兩個不同的位置的元素都“本應該出現在位置3”。

 

所以,“通過交換的方式對數組進行排序”,其實就是“對上述的‘環’中的下標進行操作”。

 

下面來計算對每個“環”需要進行多少次交換。

 

首先定義“環”的長度如下:

“1-->4-->3-->1”的長度為3,

“1-->4-->1”的長度為2

“1-->1”的長度為1(長度為1的情況就是“該元素的處於正確位置”的情況)

 

對於長度為1的環,所需的交換次數是0,SWAP(1) = 0

對於長度為2的環,所需的交換次數是1,SWAP(2) = 1

對於長度為k的環,交換其中的任何兩個元素,把當前的“撕裂”為兩個更小的環,且兩個小環的長度加起來剛好等於k。例如:

對於環:

……i-->j-->k-->……r-->s-->t-->……

執行swap(row, j, s),會生成如下的兩個環(需要思考兩分鍾):

環1:……i-->j-->t-->……

環2:k-->……r-->s->k

(對於j和s在邊界的情況,上述結論也成立。)

為什么環1中,j的后面是t,也就是說,為什么位置j的元素本應位於位置t?

因為,在執行了swap(row, j, s)之后,原來位置s的元素就跑到了位置j上。交換前,“s-->t”表示“位置s的元素本應位於位置t”,所以,在位置s的元素跑到位置j后,就成了“位置j的元素本應位於位置t”。同樣的道理可以解釋為什么環2中s的后面是k。

 

所以,對於長度為k的環,所需的交換次數SWAP(k)=SWAP(k1) + SWAP(k2) +1,其中k1+k2=k

 

 

根據

SWAP(1) = 0,

SWAP(k)=SWAP(k1) + SWAP(k2) +1,其中k1+k2=k

可以用第二數學歸納法證明(第二數學歸納法是啥,見文末),

SWAP(n) = n - 1

 

注意,對長度為k的環,交換其中的任意兩個元素都可以把環撕裂為兩個小環。那么,如果我們把第一個元素交換到“它本應出現的位置”,會怎樣呢?

對於環“1-->4-->3-->1”,下標1上的元素本應出現在位置4,所以我們執行swap(row, 1, 4),然后就把“1-->4-->3-->1”撕裂為兩個小環:

環1:“1-->3-->1”

環2:“4-->4”

 

環2是“已經搞定了”的狀態,接下來只需處理環1,swap(row, 1, 3)。

 

上述算法的直觀感覺就是,不停地把“當前位置的元素”和“它應該去的地方”的元素進行交換,這樣,“當前位置的元素”就去了“它應該去的地方”,同時,“被換過來的元素”又成了“當前位置的元素”,直到“被換過來的元素”就應該放在“當前位置”為止。

 

代碼如下:

int sort(int[] row) {

        printArray(row);

       

        int nSwapTimes = 0;

        for (int i = 0; i < row.length; ++i) {

                 for (int j = row[i]; j != i; j = row[i]) {

                         swap(row, i, j);

                         ++ nSwapTimes;

       

                         // 可以在每次交換后把row的當前狀態打印出來感受一下

                         printArray(row);

                 }

        }

        return nSwapTimes;

}

 

上述解法可以推廣到如下的問題:

有N對夫婦隨機坐成一排,現在要經過若干次交換座椅,使得每對夫婦的座位都挨在一起。求最小的交換次數。座位以整形數組row表示,下標從0至2N-1。每一對夫婦都用有序數對表示:(0, 1)、(2, 3)、(2N-2, 2N-1)。數組row的第i個元素row[i]代表位置i上坐着的人。

 

為了解決這個問題,需要一個和row的用處剛好相反的輔助數組pos:row[i]代表位置i上坐着的人;pos[i]代表人i所在的位置。

 

為了方便起見,定義getPartner函數如下

int getPartner(int n) {

        return (n % 2 == 1) ? (n - 1) : (n + 1);

}

這個函數返回下標n的配偶(n既可以是人也可以是座位。請思考兩分鍾,當n是座位時getPartner的含義)。

 

這個問題和亂序數組的排序問題只有一個區別:

亂序數組排序:下標i所在的元素的期望位置是row[i]

本問題:下標i所在元素的期望位置是getPartner(pos[getPartner(row[i])])

getPartner(pos[getPartner(row[i])])的含義:

最內層row[i],位置i上的人是誰

再加一層getPartner,此人的配偶是誰

再加一層pos,此人的配偶在row中的實際座位

再加一層getPartner,此人的期望座位(思考兩分鍾)

 

代碼如下:

int nResult = 0;

for (int i = 0; i < row.length; i += 2) {

        for (int j = getPartner(pos[getPartner(row[i])]); j != i; j = getPartner(pos[getPartner(row[i])])) {

                 swap(row, i, j);

                 swap(pos, row[i], row[j]);

                 ++nResult;

        }

}

 

return nResult;

 

 

至此,結束

 

第一數學歸納法,若對自然數的命題P(n),滿足:

1、  P(1)成立。

2、  若P(k)成立,則P(k+1)成立

則P(n)對全體自然數成立。

 

 

第二數學歸納法,若對自然數的命題P(n),滿足:

1、  P(1)成立。

2、  若P(1),P(2),……,P(k)成立,則P(k+1)成立

則P(n)對全體自然數成立。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM