1. 程式人生 > >通過交換操作,調整數組元素位置

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

ray int round 排序 nta 操作 只有一個 n) 位置

問題描述:有一個長度為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在邊界的情況,上述結論也成立。)

所以,對於長度為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)對全體自然數成立。

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