1. 程式人生 > >矩陣的壓縮儲存

矩陣的壓縮儲存

前言

  一入程式設計深似海,從此磚頭是愛人,日日搬,夜夜搬,搬到天荒地老,精盡人亡,直教人失去了自我,忘記了時間,忽然之間發現九月份快沒了,趕緊寫篇部落格打個卡,證明一下我還活著。。。

 

陣列與矩陣

  陣列是由一組相同型別的資料元素構成的有限序列,訪問資料元素的方式是使用元素各自的序號進行訪問,也就是下標。陣列它本身是線性表的推廣,一維陣列就是一個向量形式的線性表,二維陣列就是由一維陣列組成的線性表。

 

  在許多科學計算和工程應用中,經常要用到矩陣的概念,我們用的最多的其實就是Mysql的表,表資料都是行列儲存,這就是矩陣。

  由於矩陣具有元素數目固定以及元素按下標關係有序排列等特點,所以在使用高階語言程式設計時,一般都是用二維陣列來儲存矩陣。

 

陣列的順序儲存

為什麼是順序儲存?

  我想問這個問題就太低階了。因為它是陣列,資料的儲存方式分為順序儲存和鏈式儲存兩種,陣列一旦被定義,他的維數和維界就已固定,除結構的初始化和銷燬外,陣列只會有存取元素和修改元素的操作,不存在插入和刪除操作,所以陣列適合用順序儲存。

陣列存放在記憶體中的對映關係

  陣列可以是多維的,但是記憶體空間卻是一維的,所以我們就要把多維陣列通過一定的對映順序把它變成一維的,然後儲存到記憶體空間之中。

  在大多數高階程式語言中,多維陣列在記憶體中通常有兩種不同的順序儲存方式,按行優先順序儲存 和 按列優先順序儲存。

 

舉個例子,以下3行4列的一個二維陣列矩陣:

a1,a2,a3,a4
b1,b2,b3,b4
c1,c2,c3,c4

 

  按行優先順序儲存:

  按列優先順序儲存:

地址計算

  地址計算的意思就是給定陣列下標,求在一維記憶體空間的地址,從而取出資料。

 

我們先來看一維陣列的地址計算

  一維陣列內的元素只有一個下標,儲存方法和普通的線性表一樣。

  如一維陣列 A = [a1,a2,a3,......ai,.........,an],每個元素佔用size個儲存單元(就是記憶體大小),那麼元素ai的儲存地址為 A[0]的位置 + (i-1)*size 

 

再來看二維陣列的地址計算

  以二維陣列Amn為例,首元素為A[0][0],陣列中任意元素A[i][j]的地址為:A[0][0]的位置 + (n * (i-1) + (j-1))* size;

 

比如:一個5行4列的二維陣列A,按行儲存,其中每個元素佔2個儲存單元,首元素地址是1000,求第3行第2列的元素在記憶體中的地址。

我們把引數套進公式中,答案 = 1000 + (4 * (3-1) + (2-1)) * 2 = 1018;

 

如果把矩陣畫在紙上觀察就一目瞭然:

  公式的內容就是求出格子數,乘以每個格子所佔用的儲存單元,再加上首地址。

 

矩陣轉置

  設計一個演算法,實現矩陣A(m*n) 轉置為矩陣B(n*m),簡單的說,就是行列互換。

$arr = [
    ['張三','男','北京'],
    ['李四','女','上海'],
    ['王五','男','廣州'],
];

function transpose($a){
    $b = [];
    for ($i = 0;$i < count($a); $i ++){
        for($j = 0;$j < count($a[$i]); $j ++){
            $b[$j][$i] = $a[$i][$j];
        }
    }
    return $b;
}

$result = transpose($arr);

 

結果為:
$result = [
['張三','李四','王五'],
['男','女','男'],
['北京','上海','廣州'],
];

 

特殊矩陣

特殊矩陣的壓縮儲存

  特殊矩陣指的是具有許多相同元素或者零元素,並且這些元素的分佈有一定規律性的矩陣。

  這種矩陣如果還使用前面的方式來儲存,就會產生大量的空間浪費,為了節省儲存空間,可以對這類矩陣採用壓縮儲存,壓縮儲存的方式是把那些呈現規律性分佈的相同元素只分配一個儲存空間,對零元素不分配儲存空間。

 

三角矩陣

  三角矩陣我們以下三角來做例子,如圖所示:

  所有空格之中裝的資料都是null或者都是同一常量,也就是空格中全都是相同的資料。

  按行方式儲存的情況下,一維儲存記憶體空間的大小是:1+2+3+4+5+6+7 = n(n+1)/2 = 7 * (7+1) / 2 = 28,當然,在最後還要加一個儲存空間,用來儲存上三角中相同的資料。

 

  那麼對於任意元素aij,在一維儲存記憶體空間中的地址仍然是要靠計算格子來得到,先算出佔滿行的總格子數,再加上當前行的格子數:a[0][0]的位置 + (i * (i+1) / 2 + j) * size;

  我們使用公式來驗證一下,a42的所在格子數 = (i * (i+1) / 2 + j) = 4 * 5 / 2 + 2 = 12

 

帶狀矩陣

  帶狀矩陣也叫做對角矩陣,如圖所示:

  帶狀矩陣的特徵是:所有非0元素都集中在以主對角線為中心的3條對角線區域,其他區域的元素都為0。

  除了第一行和最後一行僅2個非零元素,其餘行都是3個非零元素,換句話說就是每行都是3個非零元素,但是第一行少了1個,最後一行少了1個,所以所需的一維空間大小為:3n - 2;

 

那麼對於任意一個元素 aij,怎麼計算它在記憶體空間的地址呢? 

  經過觀察可以得知i和j都在對角線附近,相減後的結果與分佈情況分別如下

  j - i = 1;對角線上面
  j - i = 0; 對角線
  j - i = -1;對角線下面

 

  不管是在對角線的哪個位置,我們都可以使用通用的辦法來計算地址,也就是先計算出上面行所佔的格子,再加上當前行的格子。

  上面的行數:i,由於行列都是0開頭計數,所以上面的行數就是i這個值。

  上面的格子數: 3 * i - 1,減1是因為第一行少一個格子。

  當前行格子數: j - i + 1;根據i和j的關係,我們把相減後的值加1,得到當前行的格子數。

 

  那麼最後aij的記憶體地址 = a00首地址 + ((3 * i -1) + ( j-i+1)) * size;  size為每個資料所佔用的儲存單元大小。

  比如首地址為1000,每個資料佔用2個儲存單元,那麼a45在記憶體中的地址 = 1000 + 13 * 2 = 1026;

 

稀疏矩陣的壓縮儲存

  由於特殊矩陣中非零元素的分佈是有規律的,所以總是可以找到矩陣元素與一維陣列下標的對應關係,但還有一種矩陣,矩陣中大多數元素都為0,一般情況下非零元素個數只佔矩陣元素總數的30%以下,並且元素的分佈是沒有任何規律的,這樣的矩陣我們稱為稀疏矩陣。

 

  如果採用常規方法儲存稀疏矩陣,就會相當浪費儲存空間,因此我們需要只儲存非零元素。由於稀疏矩陣中非零元素的分佈是沒有規律的,所以除了儲存非零元素的值之外,我們還需要同時儲存非零元素的行、列位置,也就是三元組(i,j,aij)。

 

如圖:

  所謂三元組,也就是一個矩陣,一個二維陣列,每一行都三個列,分別為行號、列號、元素值。

  由於三元組在稀疏矩陣與記憶體地址間扮演了一箇中間人的角色,所以稀疏矩陣進行壓縮儲存後,便失去了隨機存取的特性。

&n