Transcript Chap.2

第二章-
陣列
1
2.3
多項式抽象資料型態
陣列不僅只是陣列資料結構而已,我們也可以利用它來製作其他的抽象資料型態。例
如,一種簡單而常見的資料結構:有序串列( ordered list )或線性串列( linear list ) 。我
們可以找到許多此種資料結構的實例,例如:
(1) 每週之星期( Sunday, Monday,…, Saturday )
(2) 一疊紙牌之點數(A,1,2,…,k)
在串列上可以執行許多的運算,包括:
(1)
找出串列的長度n
(2)
由左而右(或由右而左)讀取串列中的項目
(3)
從串列中擷取第i個項目,
0in
2
(4)
替換串列中第i個項目之值,
(5)
在串列的第i個位置插入一個新的項目,0  i  n 。先前編號為i, i+1 ,…,
n-1的各個項目變成i+1, i+2, …,n
(6)
自串列的第i個位置刪除一個項目, 0  i  n 。先前編號為i+1, i+2,…,
n-1的各個項目變成i, i+1, …, n-2
0in
最常用的實作法是以陣列來表示一個有序串列,其中串列的元素 itemi 關聯到陣列
的索引i。我們稱此為循序映射( Sequential mapping ),因為如果採用陣列標準的實作
法, itemi , itemi 1 將會儲存在陣列中連續的i和i+1位置內。
所以,我們可以在常數時間內擷取一個項目,替換一個項目,或找出一個串列的長度。
我們也可以從任何一個方向讀取串列中的項目,只要簡單的改變註標的控制項即可。
現在我們要對表示法做一些決定。非常合理的,第一個決定要求不同的指數
3
以降冪順序安排。這個要求可以簡化許多的運算。使用我們的定義和此規定,我
們可以寫出和C函數非常接近的ADD函數(程式2.4) 。
Polynomial Polynomial::Add(Polynomial b)
{// 回傳多項式 *this與b的和
Polynomial c;
int aPos = 0, bPos = 0;
while ((aPos < terms) && (bPos < b.terms))
if (termArray [aPos].exp = = b.termArray [bPos].exp) {
float t = termArray [aPos].coef + b.termArray [bPos].coef;
If (t) c.NewTerm (t, termArray [aPos].exp);
aPos++; bPos++;
}
else if (termArray [aPos].exp < b.termArray [bPos].exp) {
c.NewTerm (b.termArray [bPos].coef, b.termArray [bPos].exp);
bPos++;
}
else {
c.NewTerm (termArray [aPos].coef,termArray [aPos].exp);
aPos++;
}
4
// 把 *this中剩下的項給加進來
for ( ; aPos < terms ; aPos++)
c.NewTerm (termArray [aPos].coef, termArray [aPos].exp);
// 把 b(x) 中剩下的項給加進來
for ( ; bPos < b.terms ; bPos++)
c.NewTerm (b.termArray [bPos].coef, b.termArray [bPos].exp);
return c;
}
這個演算法的工作方式為比較兩個多項式之各項,直到一個或兩個多項式變成空
的。Switch指令執行的工作為比較和將適當的項次相加以形成新的多項式,d。
如果有一個多項式變成空的,我們將非空的多項式餘下的項次複製到d。
5
class Polynomial {
// p(x) = ;一個 <ei,ai> 的有序對之集合,
// 其中ai是一個非零的 float 係數而是ei一個非負的整數指數。
public:
Polynomial( );
// 建立多項式p(x) = 0。
Polynomial Add(Polynomial poly);
// 回傳 *this與poly兩個多項式之和。
Polynomial Mult(Polynomial poly);
// 回傳 *this與poly兩個多項式之積。
float Eval(float f );
// 求出當多項式 *this為 f時的值並且回傳它的結果。
};
6
2.4
稀疏矩陣
簡介
在數學上,一個矩陣包含m列和n行的元素。
3
4
 27
 6
82  2

 109  64 11 


8
9 
 12
 48
27 47 
(a)
15
0

0

0
91

 0
22 0  15
11 3
0 0 0 
0 0 6 0 0 

0 0
0 0 0 
0 0
0 0 0 

0 28 0 0 0 
0
0
(b)
圖:兩個矩陣
7
一種矩陣的標準表示法為二維陣列,如定義a[MAX_ROWS][MAX_COLS]。以這種表
示法,我們可以用a[i][j]快速的找到一個元素,其中i為列索引,j為行索引。
還是有一些問題存在標準表示法中。例如:圖(b),他包含了許多的0。我們稱此
為稀疏矩陣( sparse matrix )。
因為稀疏矩陣很浪費空間,我們必須考慮不同的表示法。如果一個矩陣大多數都
為0的元素,我們會浪費相當大的空間。因此,稀疏矩陣表示法應該只儲存不為
0的元素。
在矩陣上執行的運算中,最少的運算集合包含了矩陣建立,相加,相乘,和轉置
(transpose)。
8
class SparseMatrix
{// 三元組,<列,行,值>,的集合,其中列與行為非負整數,
// 並且它的組合是唯一的;值也是個整數。
public:
SparseMatrix(int r, int c,int t);
// 建構子函式,建立一個有r列c行並且具有放t個非零項的容量
SparseMatrix Transpose( );
//回傳將 *this中每個三元組的行與列交換後的SparseMatrix
SparseMatrix Add(SparseMatrix b);
// 如果 *this和b的維度一樣,那麼就把相對應的項給相加,
// 亦即,具有相同列和行的值會被回傳;否則的話丟出例外。
SparseMatrix Multiply(SparseMatrix b);
// 如果*this中的行數和b中的列數一樣多的話,那麼回傳的矩陣d就是 *this和b
//(依據d[i][j]=Σ(a[i][k].b[k][j],其中d[i][j]是第 (i, j) 個元素)相乘的結果。k的範圍
// 從0到*this的行數減1;如果不一樣多的話,那麼就丟出例外。
};
9
在實做這些運算之前,必須先建立稀疏矩陣的表示法。我們可以用一個三項式的
陣列來表示一個稀疏矩陣。因為我們想要轉置運算可以有效率的進行,我門必須
將三項式按照列索引的升冪順序來排列。
我們可以更進一步的要求,任一列的三項式都依照其行索引的升冪順序來儲存。
此外,為了保證運算會結束,我門必須知道矩陣中的行數,列數,和矩陣中非0
元素的個數。
10
圖2.4:以三項式儲存的稀疏矩陣及其轉置矩陣
a[0]
row col value
6
6
8
b[0]
row col value
6
6
8
a[1]
0
0
15
b[1]
0
0
15
a[2]
a[3]
0
0
3
5
22
 15
b[2]
b[3]
0
1
4
1
91
11
a[4]
1
1
11
b[4]
2
1
3
a[5]
a[6]
1
2
2
3
3
6
b[5]
b[6]
2
3
5
0
28
22
a[7]
4
0
91
b[7]
3
2
6
a[8]
5
2
28
b[8]
5
0
 15
(a)
(b)
因此a[0].row包含列數; a[0].col包含行數;而a[0].value包含全部不為零之元素個數。
位置1到8儲存了代表不為0之元素的三項式。列索引在欄位row中;而元素值在
欄位value中。三項式以列順序排列,各列中以行順序排列。
11
轉置矩陣
要將矩陣轉置,我們必須將行和列互換。就是說,在原始矩陣中的每一個元素
a[i][j]變成轉置矩陣中的b[i][j]。因為我們已將原始的矩陣依序排列,我們或許會以
為下列的演算法式將矩陣轉置的一個好方法:
for each row i
take element <i , j, value> and store it
as element <j, i, value> of the transpose;
如果我們以索引來處理原始的陣列,除非我們以經處理了在元素<j, i, value>之前
的所有元素,我們將不能確知該將它放到轉置矩陣的哪一個位置。
如果我們將三項式連續的放在轉置矩陣中,當我們插入新的三項式時,究必須移
動元素以保持正確的順序。
12
藉著使用行索引來決定元素放在轉置矩陣中的位置,我們可避免這種資料移動。
我們應該“找出行0的所有元素,而後將它們存放在轉置矩陣的列0,找出行1
的所有元素,而後將它們存放在轉置矩陣的列1,等”。因為原始的矩陣以列序
排列,轉置矩陣中每一列的各行也會以升冪順序排列。
這個演算法應用在transpose函數(程式2.7)中。第一個陣列a為原始的陣列,而第二
個陣列b存放轉置矩陣。
13
SparseMatrix SparseMatrix::Transpose( )
{// 回傳*this的轉置矩陣
SparseMatrix b(cols , rows , terms); // b.smArray的容量為terms
if (terms > 0)
{// 非零的矩陣
int currentB = 0;
for (int c = 0 ; c < cols ; c++) // 根據行來轉置
for (int i = 0 ; i < terms ; i++)
// 尋找並移動在第c行的項
if (smArray[i].col = = c)
{
b.smArray[currentB].row = c;
b.smArray[currentB].col = smArray[i].row;
b.smArray[currentB++].value = smArray[i].value;
}
} // if (terms > 0) 結束
return b;
}
14
分析transpose:要決定此演算法的計算時間是容易的,因為所使用的巢狀for迴圈
是決定性因素。
外層的迴圈執行a[0].col次,其中a[0].col存放原始矩陣的行數。此外,內層迴圈每
做一次需時a[0].value,其中a[0].value為原始矩陣中元素的個數。因此,巢狀for迴
圈所需的全部時間為columns,elements。所以,漸進式的時間複雜度為
O(columns*elements)。
當元素個數的次方為columns. rows,我們的轉置函數之時間從O(columns*elements)變
成O(columns*columns*rows)。或許,為了節省空間,我們反而浪費了許多的時間。
實際上,藉著多使用一點儲存空間,我們可以建立一個更好的演算法。
我們可將一個用一連串的三項式來表示的矩陣,在O(columns+elements)時間內轉置
完成。這個演算法是fast_transpose,它首先決定再原始矩陣中每一行的元素個數。
15
SparseMatrix SparseMatrix::FastTranspose( )
{// 在O(terms + cols)的時間內回傳 *this的轉置矩陣
SparseMatrix b(cols , rows , terms);
if (terms > 0)
{// 非零的矩陣
int *rowSize = new int[cols];
int *rowStart = new int[cols];
// 計算rowSize[i] = b的第i列之項數
fill(rowSize, rowSize + cols, 0); // 初始化
for (int i = 0 ; i < terms ; i ++) rowSize[smArray[i].col]++;
// rowStart[i] = b的第i列之起始位置
rowStart[0] = 0;
for (int i = 1 ; i < cols ; i++) rowStart[i] = rowStart[i-1] + rowSize[i-1];
for (int i = 0 ; i < terms ; i++)
{// 從 *this複製到b
int j = rowStart[smArray[i].col];
b.smArray[j].row= smArray[i].col;
b.smArray[j].col = smArray[i].row;
b.smArray[j].value = smArray[i].value;
rowStart[smArray[i].col]++;
} // for結束
delete [] rowSize;
delete [] rowStart;
} // if結束
return b;
}
16
分析:因為迴圈內的指令僅需常數時間,此演算法所需的計算時間為O(columns+
elements)。當元素個數的次方為columns*rows,時間變為O(columns*rows)
如果我們以圖(a)之稀疏矩陣來試驗演算法,在執行第三個for迴圈以後,row_terms
和starting_pos之值為:
[0]
[1]
[2]
[3]
[4]
[5]
row_terms
2
1
2
2
0
1
starting_pos
1
3
4
6
8
8
轉置矩陣中的列i的元素個數包含於row_term[i]中。轉置矩陣中列i的起始位置存
放在starting_pos[i]。
17
矩陣相乘
定義:對於A和B,其中A為m*n,B為n*p,則矩陣相乘所得的矩陣D的
維度為m*p。其元素<i, j>為:
n 1
d ij   aik bkj
k 0
其中0<=i<m
且
0<=j<p。
要將兩個以有序串列表示的稀疏矩陣相乘。我們必須計算D各列的元素,以便將
元素儲存在適當的位置上而不必移動先前已計算的元素。要做這件事,我們選擇
A中的一列並找出B中行j的所有元素。
18
SparseMatrix SparseMatrix::Multiply(SparseMatrix b)
{// 回傳稀疏矩陣 *this與b的積
if (cols != b.rows) throw “Incompatible matrices”;
SparseMatrix bXpose = b.FastTranspose( );
SparseMatrix d (rows, b.cols, 0);
int currRowIndex = 0,
currRowBegin = 0,
currRowA = smArray[0].row;
// 設定邊界條件
if (terms = = capacity) ChangeSize1D(terms + 1);
bXpose.ChangeSize1D(bXpose.terms + 1);
smArray[terms].row = rows;
bXpose.smArray[b.terms].row = b.cols;
bXpose.smArray[b.terms].cols = -1;
int sum = 0;
while (currRowIndex < terms)
{// 產生d的第currentRowA列
int currColB = bXpose.smArray[0].row;
int currColIndex = 0;
19
while (currColIndex <= b.terms)
{// *this的第currRowA列乘上b的第currColB行
if (smArray[currRowIndex].row != currRowA)
{// currRowA的結尾
d.StoreSum(sum,currRowA,currColB);
sum = 0; // 重設sum
currRowIndex = currRowBegin;
// 前進至下一行
while (bXpose.smArray[currColIndex].row = = currColB)
currColIndex++;
currColB = bXpose.smArray[currColIndex].row;
}
else if (bXpose.smArray[currColIndex].row != currColB)
{// b中第currColB行的結尾
d.StoreSum(sum,currRowA,currColB);
sum = 0; // 重設sum
// 第currRowA列改成與下一行相乘
currRowIndex = currRowBegin;
currColB = bXpose.smArray[currColIndex].row;
}
20
else
if (smArray[currRowIndex].col <
bXpose.smArray[currColIndex].col)
currRowIndex++; // 前進此列的下一個項
else if (smArray[currRowIndex].col = =
bXpose.smArray[currColIndex].col)
{// 加到sum裡
sum += smArray[currRowIndex].value *
bXpose.smArray[currColIndex].value;
currRowIndex++;currColIndex++;
}
else currColIndex++; // currColB的下一項
} // while (currColIndex <= b.terms) 結束
while (smArray[currRowIndex].row = = currRowA)
currRowIndex++;
currRowBegin = currRowIndex;
currRowA = smArray[currRowIndex].rows;
} // while (currRowIndex < terms) 結束
return d;
}
21
2.5
陣列的表示法
多維陣列的內部表示法需要更複雜的定址公式。如果陣列宣告為
a[upper0 ][upper1 ]...[uppern1 ] ,則陣列中的元素個數可以很清楚的表示為:
n 1
 upper
i
i 0
其中為 upperi 的乘積。舉例而言,如果宣告a為a[10] [10] [10],則需要
10*10*10=1000個儲存單位來保存此陣列。
22
字串,其組成的元素為字元.因為他是ADT,我們定義字串的形式為
s  s0 ,...,sn1 ,其中
s i 為程式語言字元集中的字元.如果n=0,則S為空字
串或虛字串。
class String
{
public:
String(char *init, int m);
// 建構子:將 *this 初始化為長度為m的字串 init。
bool operator = = (String t);
// 如果 *this 所表示的字串等於 t,回傳true;否則回傳false。
bool operator!( );
// 如果 *this 是空字串,回傳true; 否則回傳false。
int Length( );
// 回傳 *this 裡的字元數。
23
String Concat(String t);
// 回傳一個字串,它的內容是字串 *this 後接著字串t。
String Substr(int i, int j);
// 如果這些位置在 *this 裡是有效的,那麼回傳 *this 裡的第i, i+1, ..., i+j-1
// 共j個字元的子字串;否則丟出一個例外。
int Find(String pat);
// 回傳pat在 *this裡的開始位置 i;
// 如果 pat 是空字串或者pat不是 *this 的子字串則回傳-1。
24
樣式比對
假設有兩個字串string和pat,其中pat是要在string中找尋的樣式。判斷pat是否在string
中最簡單的方法是使用內建函數strstr。如果我們有下列的宣告:
char pat[MAX_SIZE], string[MAX_SIZE], *t;
則可用下列指令來判斷pat是否在string中:
if (t = strstr(string, pat))
printf(“the string from strstr is :%s\n”, t);
else
printf(“the pattern was not found with strstr\n”);
雖然strstr看起來對字串樣式比對非常的適合,但是我們仍要設計自有的樣式比對函數
25
int String::Find(String pat)
{// 如果在 *this 字串裡面找不到pat,那麼傳回 -1;否則回傳pat在 *this 裡的起始
位置。
for (int start = 0; start <= Length( )-pat.Length(); star ++)
{ // 從 str [start]開始檢查有沒有相同的字元
int j;
for (j = 0; j < pat.Length( ) && str [start+j] = = pat.str[j]; j++)
if (j = = pat.Length( )) return start; // 找到相同的字串
// 在 start 這個位置沒找到匹配
}
return -1 ; // pat 為空字串或者不存在於在s中
}
26