矩陣在計算機圖形學、工程計算中占有舉足輕重的地位。在數據結構中考慮的是如何用最小的內存空間來存儲同樣的一組數據。所以,我們不研究矩陣及其運算等,而把精力放在如何將矩陣更有效地存儲在內存中,並能方便地提取矩陣中的元素。
數組的存儲結構
一個數組的所有元素在內存中占用一段連續的存儲空間。
- 以行優先方式存儲
- 以列優先方式存儲
數組 A[0..5,0..6]的每個元素占五個字節,將其按列優先次序存儲在起始地址為 1000 的內存單元中,則元素 A[5,5]的地址是( A )。
A. 1175 B. 1180 C. 1205 D. 1210
需要注意的是,當求出數組A[0,0]到特定元素A[5,5]的那部分時,不要着急着寫出地址。
因為我們所求出來的所占空間,是A[0,0]的首地址從1開始數(或者說 從A[0,0]的首地址開始到A[5,5]的尾地址(即A[6,1]的首地址))計算時的元素個數6*6=36(即 從A[0,0]到A[5,5],包含A[5,5]有36個元素,但是題目的起始地址一般為0。
所以應該A[0,0]的首地址從0開始數(或者說 從A[0,0]的首地址開始到A[5,5]的首地址)計算的話,則是36-1=35(即 從A[0,0]到A[5,5],不包含A[5,5]有35個元素)。例如,首地址從0開始數,即意味着當首地址為5時,只存了一個元素的空間,但是地址卻是第二個元素的(首)地址。
1 抽象數據類型數組的說明
2 數組的物理結構
3 特殊矩陣的壓縮存儲: 對稱矩陣與三對角矩陣的壓縮存儲
4 稀疏矩陣的壓縮存儲:三元組順序表與十字鏈表
5 稀疏矩陣的運算(轉置算法)
6 廣義表的概念:概念、物理結構、遞歸算法
- 計算數組中給頂元素的地址
- 數組的存儲方式(按行或按列優先存放)
- 計算給定元素的前面的元素個數s
- 每個元素的存儲空間k
- 該元素地址=起始元素地址+s*k
特殊矩陣
特殊矩陣壓縮存儲后仍然具有隨機存儲特性。(只用計算公式即可,無需查找時間)
二維數組→線性
注意:(因為一維數組下標是從0開始的,個數要-1,相當於減掉了自身,所以只用計算前面的元素個數,)(如果一位數組從1開始,就不用-1了,那么就不是前面了,就要加上本身aij,計算自身和前面的元素個數)(如果從1開始,那么下面的公式就要+1)
對稱矩陣
對稱矩陣:矩陣中的元素滿足\(a_{i,j}\)=\(a_{j,i}\),另外矩陣必須是方陣。
- 問題:假設有一個n*n的對稱矩陣,第一個元素是\(a_{0,0}\),請用一種存儲效率較高的存儲方式將其存儲在一位數組中。
- 解答:將對稱矩陣A[0...n-1,0...n-1],存放在一維數據B[n(n+1)/2]中,即 元素\(a_{i,j}\)存放在\(b_k\)中。(只存放主對角線和下三角區的元素)
注意:(因為一維數組下標是從0開始的,個數要-1,相當於減掉了自身,所以只用計算前面的元素個數,)(如果一位數組從1開始,就不用-1了,那么就不是前面了,就要加上本身aij,計算自身和前面的元素個數)
- 在數組B中(下標從0開始),位於\(a_{i,j}\)(i≥j)前面的元素個數為:
- 第0行:1個元素
- 第1行:2個元素
- ...
第i-1行:i個元素
注意:0到i-1是等差序列(都是完整的)
- 第i行(不完整):j個元素
- 共計i(i+1)/2+j個元素
- 元素下標k與(i,j)之間的對應關系如下:
\[k=\begin{cases} {{i(i+1)}\over{2}}+j & \text{i≥j,下三角區及對角線}\\ {{j(j+1)}\over{2}}+i & \text{i<j,上三角區}\\ \end{cases}\]
上三角矩陣
其中下三角元素均為常數C或0的n階矩陣稱為上三角矩陣。
- 問題:假設有一個n*n的上三角矩陣,第一個元素是\(a_{0,0}\),請用一種存儲效率較高的存儲方式將其存儲在一位數組中。
- 解答:將對稱矩陣A[0...n-1,0...n-1],存放在一位數據B[n(n+1)/2]中,即 元素\(a_{i,j}\)存放在b_k中。(只存放主對角線和下三角區的元素)
- 在數組B中(下標從0開始),位於\(a_{i,j}\)(i≥j)前面的元素個數為:
- 第0行:n個元素
- 第1行:n-1個元素
- ...
第i-1行:n-(i-1)=n-i+1個元素
注意:0到i-1是等差序列(都是完整的)
第i行(不完整):(n-i)-(n-j)=j-i個元素
注意:n-i為第i行總元素,n-j為j及j后面的元素個數,相減則為j前面的元素個數
- 共計i(2n-i+1)/2+j-i個元素
- 元素下標k與(i,j)之間的對應關系如下:
\[k=\begin{cases} {{i(2n-i+1)}\over{2}}+j-i & \text{i≤j,上三角區及對角線}\\ {{n(n+1)}\over{2}} & \text{i>j,下三角區(存放常量C)(放在最后一個位置)}\\ \end{cases}\]
下三角矩陣
其中上三角元素均為常數C或0的n階矩陣稱為下三角矩陣。
- 問題:假設有一個n*n的下三角矩陣,第一個元素是\(a_{0,0}\),請用一種存儲效率較高的存儲方式將其存儲在一位數組中。
- 解答:將對稱矩陣A[0...n-1,0...n-1],存放在一位數據B[n(n+1)/2]中,即 元素\(a_{i,j}\)存放在\(b_k\)中。(只存放主對角線和下三角區的元素)
- 在數組B中(下標從0開始),位於\(a_{i,j}\)(i≥j)前面的元素個數為:
- 第0行:1個元素
- 第1行:2個元素
- ...
- 第i-1行(0到i-1是等差序列):i個元素
- 第i行(不完整):j個元素
- 共計i(2n-i+1)/2+j-i個元素
- 元素下標k與(i,j)之間的對應關系如下:
\[k=\begin{cases} {{i(i+1)}\over{2}}+j & \text{i≥j,下三角區及對角線}\\ {{n(n+1)}\over{2}} & \text{i<j,上三角區(存放常量C)(放在最后一個位置)}\\ \end{cases}\]
對角矩陣
對角矩陣:也稱帶狀矩陣,它的所有非零元素都集中在以主對角線為中心的兩側的帶狀區域內(對角線的個數為奇數)。換句話說,除了主對角線和主對角線兩邊的對角線外,其他元素的值均為0。
問題:對於一個按照行優先存儲的三對角矩陣,求出第i行帶狀區域內第一個元素X在一維數組中的下標
當b=1時,稱為三對角矩陣,其壓縮地址計算公式如下:\[k=2i+j\]
稀疏矩陣
稀疏矩陣:如果\(A_{m*n}\)中非零元素的個數遠遠小於總元素的個數,則這個矩陣可以被稱為是稀疏矩陣。
稀疏矩陣壓縮存儲后並不具有隨機存儲特性。(使用順序或者鏈式存儲,需要查找時間)
稀疏矩陣的壓縮存儲方法是只存儲非0元素。
順序存儲
三元組表示法
稀疏矩陣中的每一個非0元素需由一個三元組(i,j,value)唯一確定。(約定非零元素通常以行序為主序順序排列)
其中,i表示行,j表示列,value表示元素值。
typedef struct {
float val;
int i;
int j;
}Trimat; //三元組結構體
Trimat trimat[maxSize+1];
偽地址表示法
偽地址是指該元素在矩陣中(包括0元素在內)按照行優先順序的相對位置,用(value,location)的形式進行存儲。
其中,value表示矩陣的具體數值,loaction表示其偽地址值。
偽地址/列數=行號,偽地址%列數=列號
鏈式存儲
鄰接表表示法
- 鄰接表表示法:創建一個一維數組,數組的下標與矩陣的行號相對應,而數組中的每個元素是一個指針。指針指向一個存儲了相應行元素信息的線性表,其中的信息包括元素值,元素列號以及下一個元素的指針。
十字鏈表表示法
- 每行的所有結點鏈起來構成一個帶行頭結點的循環單鏈表。以hi作為第i行的頭結點。
- 每列的所有結點鏈起來構成一個帶列頭結點的循環單鏈表。以hi作為第i列的頭結點。
- 行列頭結點可以共享。
- 行列頭結點個數=MAX(m,n)
增加一個總頭結點,並把所有行、列頭結點鏈起來構成一個循環單鏈表。
十字鏈表表示法:每個十字鏈表都有一個頭結點,頭結點共有五個域,分別是矩陣的行數、列數、非0元素的個數、矩陣元素的行數組以及矩陣元素的列數組。兩個數組都存儲了指向矩陣中非零元素的指針。
在十字鏈表中,為每個非0元素申請一個元素結點,結點包括五個域,分別是行號、列號、元素值、指向同一行下個元素的指針以及指向同一列下個元素的指針。
矩陣轉置
矩陣轉置:把原本第i行第j列的元素存儲在新矩陣中第j行第i列的位置。
廣義表
廣義表(Lists,又稱列表)是線性表的推廣。線性表定義為n≥0個元素a0,a1,...,an的有限序列。線性表的元素僅限於原子項,院子是作為結構上不可分割的成分,它可以是一個數或一個結構,若放松對表元素的這種限制,容許它們具有自身結構,這樣就產生了廣義表的概念。
- 可共享:一個廣義表的子表可以是其他已經被定義的廣義表的引用,可以為其他廣義表所共享。
- 可遞歸:一個廣義表可以是遞歸定義的,自己可以作為自己的子表。
- 廣義表的長度:為表中最上層元素的個數
- 廣義表的深度:為表中括號的最大層數,求深度時可以將子表全部展開。
- 廣義表的表頭(Head)和表尾(Tail):當廣義表非空時,第一個元素為廣義表的表頭,其余元素組成的表是廣義表的表尾。
操作
根據表頭、表尾的定義可知:任何一個非空廣義表的表頭是表中第一個元素,它可以是原子,也可以是子表,而其表尾必定是子表。
也就是說,廣義表的head操作,取出的元素是什么,那么結果就是什么。但是tail操作取出的元素外必須加一個表——“()”
- 取表頭:原子或子表,即 第一個元素(可能是原子或列表)
- 取表尾:(子表集),即 其余元素組成的表(一定是列表)
- 當只有一個元素時,表頭當然存在,但是表尾為“()”空(因為沒有其余的元素)
舉一個簡單的列子:已知廣義表LS=((a,b,c),(d,e,f)),如果需要取出這個e這個元素,那么使用tail和head如何將這個取出來。
利用上面說的,tail取出來的始終是一個表,即使只有一個簡單的一個元素,tail取出來的也是一個表,而head取出來的可以是一個元素也可以是一個表。
解:
tail(LS) = ((d,e,f))
head(tail(LS)) = (d,e,f)
tail(head(tail(LS))) = (e,f) //無論如何都會加上這個()括號
head(tail(head(tail(LS)))) = e //head可以去除單個元素
頭尾鏈表存儲結構