260 likes | 376 Views
第二章- 陣列. 2.3 多項式抽象資料型態 陣列不僅只是陣列資料結構而已,我們也可以利用它來製作其他的抽象資料型態。例如,一種簡單而常見的資料結構:有序串列 ( ordered list ) 或線性串列 ( linear list ) 。我們可以找到許多此種資料結構的實例,例如: (1) 每週之星期 ( Sunday, Monday, … , Saturday ) (2) 一疊紙牌之點數( A,1,2, … ,k ) 在串列上可以執行許多的運算,包括: (1) 找出串列的長度n (2) 由左而右(或由右而左)讀取串列中的項目
E N D
2.3 多項式抽象資料型態 • 陣列不僅只是陣列資料結構而已,我們也可以利用它來製作其他的抽象資料型態。例如,一種簡單而常見的資料結構:有序串列( ordered list )或線性串列( linear list ) 。我們可以找到許多此種資料結構的實例,例如: (1) 每週之星期( Sunday, Monday,…, Saturday ) (2) 一疊紙牌之點數(A,1,2,…,k) • 在串列上可以執行許多的運算,包括: (1) 找出串列的長度n (2) 由左而右(或由右而左)讀取串列中的項目 (3) 從串列中擷取第i個項目,
(4) 替換串列中第i個項目之值, (5) 在串列的第i個位置插入一個新的項目, 。先前編號為i, i+1 ,…, n-1的各個項目變成i+1, i+2, …,n (6) 自串列的第i個位置刪除一個項目, 。先前編號為i+1, i+2,…, n-1的各個項目變成i, i+1, …, n-2 • 最常用的實作法是以陣列來表示一個有序串列,其中串列的元素 關聯到陣列的索引i。我們稱此為循序映射( Sequential mapping ),因為如果採用陣列標準的實作法, , 將會儲存在陣列中連續的i和i+1位置內。 • 所以,我們可以在常數時間內擷取一個項目,替換一個項目,或找出一個串列的長度。我們也可以從任何一個方向讀取串列中的項目,只要簡單的改變註標的控制項即可。 • 現在我們要對表示法做一些決定。非常合理的,第一個決定要求不同的指數
以降冪順序安排。這個要求可以簡化許多的運算。使用我們的定義和此規定,我們可以寫出和C函數非常接近的ADD函數(程式2.4) 。
這個演算法的工作方式為比較兩個多項式之各項,直到一個或兩個多項式變成空的。Switch指令執行的工作為比較和將適當的項次相加以形成新的多項式,d。如果有一個多項式變成空的,我們將非空的多項式餘下的項次複製到d。這個演算法的工作方式為比較兩個多項式之各項,直到一個或兩個多項式變成空的。Switch指令執行的工作為比較和將適當的項次相加以形成新的多項式,d。如果有一個多項式變成空的,我們將非空的多項式餘下的項次複製到d。
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兩個多項式之積。 • floatEval(floatf ); • // 求出當多項式 *this為 f時的值並且回傳它的結果。 • };
2.4 稀疏矩陣 • 簡介 • 在數學上,一個矩陣包含m列和n行的元素。 ( a ) ( b ) • 圖:兩個矩陣
一種矩陣的標準表示法為二維陣列,如定義a[MAX_ROWS][MAX_COLS]。以這種表示法,我們可以用a[i][j]快速的找到一個元素,其中i為列索引,j為行索引。一種矩陣的標準表示法為二維陣列,如定義a[MAX_ROWS][MAX_COLS]。以這種表示法,我們可以用a[i][j]快速的找到一個元素,其中i為列索引,j為行索引。 • 還是有一些問題存在標準表示法中。例如:圖(b),他包含了許多的0。我們稱此為稀疏矩陣( sparse matrix )。 • 因為稀疏矩陣很浪費空間,我們必須考慮不同的表示法。如果一個矩陣大多數都為0的元素,我們會浪費相當大的空間。因此,稀疏矩陣表示法應該只儲存不為0的元素。 • 在矩陣上執行的運算中,最少的運算集合包含了矩陣建立,相加,相乘,和轉置(transpose)。
在實做這些運算之前,必須先建立稀疏矩陣的表示法。我們可以用一個三項式的陣列來表示一個稀疏矩陣。因為我們想要轉置運算可以有效率的進行,我門必須將三項式按照列索引的升冪順序來排列。在實做這些運算之前,必須先建立稀疏矩陣的表示法。我們可以用一個三項式的陣列來表示一個稀疏矩陣。因為我們想要轉置運算可以有效率的進行,我門必須將三項式按照列索引的升冪順序來排列。 • 我們可以更進一步的要求,任一列的三項式都依照其行索引的升冪順序來儲存。此外,為了保證運算會結束,我門必須知道矩陣中的行數,列數,和矩陣中非0元素的個數。
圖2.4:以三項式儲存的稀疏矩陣及其轉置矩陣 (a) (b) • 因此a[0].row包含列數; a[0].col包含行數;而a[0].value包含全部不為零之元素個數。位置1到8儲存了代表不為0之元素的三項式。列索引在欄位row中;而元素值在欄位value中。三項式以列順序排列,各列中以行順序排列。
轉置矩陣 • 要將矩陣轉置,我們必須將行和列互換。就是說,在原始矩陣中的每一個元素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>之前的所有元素,我們將不能確知該將它放到轉置矩陣的哪一個位置。 • 如果我們將三項式連續的放在轉置矩陣中,當我們插入新的三項式時,究必須移動元素以保持正確的順序。
藉著使用行索引來決定元素放在轉置矩陣中的位置,我們可避免這種資料移動。藉著使用行索引來決定元素放在轉置矩陣中的位置,我們可避免這種資料移動。 • 我們應該“找出行0的所有元素,而後將它們存放在轉置矩陣的列0,找出行1的所有元素,而後將它們存放在轉置矩陣的列1,等”。因為原始的矩陣以列序排列,轉置矩陣中每一列的各行也會以升冪順序排列。 • 這個演算法應用在transpose函數(程式2.7)中。第一個陣列a為原始的陣列,而第二個陣列b存放轉置矩陣。
分析transpose:要決定此演算法的計算時間是容易的,因為所使用的巢狀for迴圈是決定性因素。分析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,它首先決定再原始矩陣中每一行的元素個數。
分析:因為迴圈內的指令僅需常數時間,此演算法所需的計算時間為O(columns+elements)。當元素個數的次方為columns*rows,時間變為O(columns*rows)分析:因為迴圈內的指令僅需常數時間,此演算法所需的計算時間為O(columns+elements)。當元素個數的次方為columns*rows,時間變為O(columns*rows) • 如果我們以圖(a)之稀疏矩陣來試驗演算法,在執行第三個for迴圈以後,row_terms和starting_pos之值為: • 轉置矩陣中的列i的元素個數包含於row_term[i]中。轉置矩陣中列i的起始位置存放在starting_pos[i]。
矩陣相乘 • 定義:對於A和B,其中A為m*n,B為n*p,則矩陣相乘所得的矩陣D的維度為m*p。其元素<i, j>為: 其中0<=i<m 且 0<=j<p。 • 要將兩個以有序串列表示的稀疏矩陣相乘。我們必須計算D各列的元素,以便將元素儲存在適當的位置上而不必移動先前已計算的元素。要做這件事,我們選擇A中的一列並找出B中行j的所有元素。
2.5 陣列的表示法 • 多維陣列的內部表示法需要更複雜的定址公式。如果陣列宣告為 • ,則陣列中的元素個數可以很清楚的表示為: • 其中為 的乘積。舉例而言,如果宣告a為a[10] [10] [10],則需要10*10*10=1000個儲存單位來保存此陣列。
字串,其組成的元素為字元.因為他是ADT,我們定義字串的形式為字串,其組成的元素為字元.因為他是ADT,我們定義字串的形式為 ,其中 為程式語言字元集中的字元.如果n=0,則S為空字串或虛字串。 • classString • { • public: • String(char *init, intm); • //建構子:將 *this初始化為長度為m的字串 init。 • booloperator = = (Stringt); • //如果 *this所表示的字串等於 t,回傳true;否則回傳false。 • booloperator!( ); • // 如果 *this是空字串,回傳true; 否則回傳false。 • intLength( ); • // 回傳 *this 裡的字元數。
StringConcat(Stringt); • //回傳一個字串,它的內容是字串 *this 後接著字串t。 • StringSubstr(inti, intj); • //如果這些位置在 *this 裡是有效的,那麼回傳 *this裡的第i, i+1, ..., i+j-1 • // 共j個字元的子字串;否則丟出一個例外。 • intFind(Stringpat); • //回傳pat在 *this裡的開始位置 i; • // 如果 pat是空字串或者pat不是 *this的子字串則回傳-1。
樣式比對 • 假設有兩個字串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看起來對字串樣式比對非常的適合,但是我們仍要設計自有的樣式比對函數
intString::Find(Stringpat) • {// 如果在 *this 字串裡面找不到pat,那麼傳回 -1;否則回傳pat在 *this 裡的起始位置。 • for (intstart = 0;start <= Length( )-pat.Length();star ++) • { //從 str [start]開始檢查有沒有相同的字元 • intj; • for (j = 0;j < pat.Length( )&&str [start+j] = = pat.str[j]; j++) • if (j = = pat.Length( )) returnstart; //找到相同的字串 • //在 start 這個位置沒找到匹配 • } • return-1 ; // pat為空字串或者不存在於在s中 • }