題目:
原地矩陣旋轉,只使用O(1)空間進行旋轉
分析:
假如是一個一維數組,我們很容易進行旋轉,但是對於二維矩陣,沒有好的思路,不過大體思路應該和一維類似,找到網上有人介紹,直接轉載過來,還有點看不明白,但是代碼實現了,作者是正確的。
-------------------------------
矩陣的存儲結構是用一塊線性內存來存儲數據,然後記錄寬度、高度,大致如下
class Matrix { double* data; int width, height; };
【問題分析】
因爲沒有額外空間可用,所以改變數據只能通過“交換”,也就是把矩陣中的兩個元素交換位置,這個操作可以不使用任何額外存儲空間。那麼通過交換操作,可以衍生出來的更復雜的操作就是循環移位,大致意思就是一組數據向左或者向右移動一個位置,比如原來是
{ x a1 a2 a3 ... an y },那麼左移位後就是 { a1 a2 ... an y x },顯然通過連續的數據交換是可以實現這個操作的。那麼我們的算法中可以使用的數據移動操作就只有這兩種:swap(i, j)完成數據交換,move_data(src, dst)完成{src, dst}之間的數據循環移位。(要說明的是,顯然數據循環移位操作,不僅僅侷限於連續的數據)
矩陣轉置,實質上是對存儲索引的重新排列(permutation),和排序有點類似,但不一樣的是,排序是值排序,算法決定一個數據元素的最終位置的時候只要知道這個數據的數值就可以了,但是轉置是對索引位置的排序,我們不關心數據值的大小,只關心它在源序列中的位置,而如果不用額外存儲空間,這個信息是很難保存的。
比如對於一個3*4的矩陣
1 2 3 4
5 6 7 8
9 10 11 12
轉置後爲:
1 5 9
2 6 10
3 7 11
4 8 12
觀察在內存中的存儲形式,源矩陣是
S = { 1 2 3 4 5 6 7 8 9 10 11 12 }
目標矩陣是
D = { 1 5 9 2 6 10 3 7 11 4 8 12 }
轉置問題就是對於S,求出D即可。S是先驗可知的,只是和矩陣的寬度高度有關,D是S的一個置換。
【正推】
比較容易想到的思路就是對S中的每個元素S[i],求出它在D中的位置j1,我們把S[j1]先保存一下,然後再看S[j1]應該在D中的位置,比如是j2,那麼再看S[j2]應該被放到的位置j3,這樣繼續下去,就得到了一個子序列 { i, j1, j2, ... i },最後一個一定是i,也就是說這個子序列最後必然要回到i,很容易想到啊,最後總有一個數據要填充到第一個數據的位置上,因爲就這麼些個數據,不可能出來一個洞洞啊,呵呵。我們把這個子序列稱爲T(i)
那麼對這樣一個子序列完成一個move_data操作之後,這些位置上的數據就安放好了,然後我們再尋找下一個子序列,從i+1開始,循環下去,直到所有的數據都放好了,就收工了。
這個算法的一個問題就是當我完成一個子序列T(i)後,如果i+1包含在T(i)中,那麼T(i+1)和T(i)是一樣的,必須要跳過i+1,看i+2,如果i+2也在T(i)中,那就繼續跳過,所以我需要一個標誌數組來標誌整個序列中那些位置的數據是已經放好的了,那些數據是還沒有放好的,我只要對那些沒有放好的數據做move_data。那麼這個額外的標誌數組的大小和矩陣是一樣大的,呵呵額外存儲空間啊,不行,所以這個方法失敗。
不用標誌數組也可以,我可以事先先把整個序列劃分成若干個互不重疊的子序列,然後再進行操作,但是劃分結果保存在那裏呢?呵呵,所以,這個方法還是不行。
【反推】
換個思路,也許就好了。
如果我們從目標矩陣開始呢,我們考慮D中的一個位置i,這個i應該由S中的S[j]來填充,那麼我們直接把S[i..j]之間做一個循環移位,這樣i位置就被正確的設置了,同時[i+1,j]之間的數據仍然是按原來在S中的順序。如果我們考慮把i從第一個位置開始依次循環遞加,那麼每次操作之後,我都可以保證[1, i]之間是放好的,[i, n]之間是沒有放好的,呵呵看出來好處了麼?我們不再需要任何額外的存儲空間來保存哪些數據是已經放好的了。
雖然原理是很容易,但是寫出程序還是要費一點心思,關鍵是在於對於[i, n]之間的這些待放置數據,它們之間的相對順序雖然沒有亂,但是位置都變動了,所以你要能夠一直跟蹤這些位置變動信息。這個比看上去的要複雜一點。
最後的程序如下
void TransposeInplace()
{
int from, to, offset, step;
int i, j;
for (i = to = offset = 0, step = width; i < width; ++i, --step, offset += height-1) {
for (j = 0, from = i + offset; j < height; ++j, ++to, from += step) {
_move_data(from, to);
}
}
swap(width, height);
}
顯然,如果以move_data爲單元操作,這個算法的複雜度是O(n)的,考慮到move_data本身也是O(n)的,那麼這個算法的複雜度是O(n^2)
轉載地址:http://space.itpub.net/67063/viewspace-169250
------------------------------
算法代碼如下:
#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;
int width = 4;
int height = 3;
int a[][4] =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 } };
void _move_data(int from, int to)
{
int *p = &a[0][0];
int i;
int temp = *(p + from);
for (i = from; i > to; i--)
{
*(p + i) = *(p + i - 1);
}
*(p + to) = temp;
return;
}
void TransposeInplace()
{
int from, to, offset, step;
int i, j;
for (i = to = offset = 0, step = width; i < width;
++i, --step, offset += height - 1)
{
for (j = 0, from = i + offset; j < height; ++j, ++to, from += step)
{
_move_data(from, to);
}
}
//swap(width, height);
}
int main()
{
int i;
int *p = &a[0][0];
for (i = 0; i < width * height; i++)
cout << *(p + i) << " ";
cout << endl;
TransposeInplace();
for (i = 0; i < width * height; i++)
cout << *(p + i) << " ";
cout << endl;
return 0;
}
總結:
思路比較巧妙,還要多看、理解。