530 likes | 621 Views
第二章 陣列. 資料結構與演算法. 徐熊健. 目錄. 2.1 陣列的宣告與使用 2.2 陣列的運算 2.3 陣列在記憶體中的位置. 陣列的抽象定義是一組具有相同資料型態 ( data type) 的元素,所組成的有序集合 ( ordered set), 通常儲存在一塊連續的記憶體上 。 陣列的應用技巧可開出善用陣列資料結構之門。 陣列在實體記憶空間中配置的關係可引領各位瞭解陣列資料結構如何存放在電腦中,以及程式語言、編譯器如何運作,方使陣列得以有效率地為吾人所用。. 2.1 陣列的宣告與使用.
E N D
第二章 陣列 資料結構與演算法 徐熊健
目錄 • 2.1 陣列的宣告與使用 • 2.2 陣列的運算 • 2.3 陣列在記憶體中的位置
陣列的抽象定義是一組具有相同資料型態 (data type)的元素,所組成的有序集合 (ordered set),通常儲存在一塊連續的記憶體上。 • 陣列的應用技巧可開出善用陣列資料結構之門。 • 陣列在實體記憶空間中配置的關係可引領各位瞭解陣列資料結構如何存放在電腦中,以及程式語言、編譯器如何運作,方使陣列得以有效率地為吾人所用。
2.1 陣列的宣告與使用 • 任何資料結構的引用,皆需透過程式語言先行宣告,陣列的宣告必須包含三項要素: • (1)陣列的名稱; • (2)陣列的大小; • (3)陣列中元素的資料型態。
list[0] 1 list[1] 2 list[2] 3 list[3] 4 list[4] 5 //list為一整數陣列,共有5個元素。 程式2-1陣列內元素值的指定(assignment) 1 main() 2 { int list[5]; 3 int i; 4 for (i=0 ; i<5 ; i++) list[i]=i+1; 5 } • 陣列list 的邏輯圖示
利用常數定義 • 程式2-2陣列元素的指定與陣列對應元素相加 1 #define size=5 2 main() 3 { int A[size],B[size],C[size]; 4 int i; 5 for (i=0; i<size; i++) 6 { A[i]=i+1; 7 B[size-i-1]= A[i]; 8 } 9 for (i=0; i<size; i++) 10 C[i]= A[i]*B[i]; 11 } • 陣列A, B和C 的邏輯圖示 A[0] 1 B[0] 5 C[0] 5 A[1] 2 B[1] 4 C[1] 8 A[2] 3 B[2] 3 C[2] 9 A[3] 4 B[3] 2 C[3] 8 A[4] 5 B[4] 1 C[4] 5 (a) (b) (c) 註標是可以先行計算的
2.2 陣列的運算 • 2.2.1 挑選排序法 • 2.2.2 二元搜尋法 • 2.2.3 矩陣的運算 • 2.2.4 魔術方塊
2.2.1 挑選排序法 程序swap的宣告在第1~6行。所以在第14行欲執行交換,可呼叫swap! • 程式2-3挑選排序法 1 void swap(int *x, int *y) 2 { int temp; 3 temp = *x; 4 *x = *y; 5 *y = temp; 6 } 7 void SelectionSort(int data[], int n) 8 { int i, j; 9 int min, temp; 10 for (i=0; i<n-1; i++) 11 { min = i; 12 for (j=i+1; j<n; j++) 13 if (data[j]<data[min]) min = j; 14 swap(&data[i], &data[min]); 15 } 16 } SelectionSort(data, n);
參數傳遞的說明 宣告 以*x, *y記載傳入參數data[i], data[min]的位址 void swap(int *x, int *y) swap(&data[i], &data[min]); 將data[i], data[min]的位址傳入程序swap中 (a) 傳位法 以data[]記載傳入參數陣列data的位址、n記載傳入參數n值 void SelectionSort(int data[],int n) SelectionSort(data, n); 將陣列data的位址,n的值傳入程序SelectionSort中 (b) 傳位與傳值法 呼叫 宣告 呼叫 在這個例子中,n用傳位法傳遞、甚至 data 和 n 都用全域變數(global variable),也不致出錯;但用傳值法可增加程式的可讀性,對程序的可再使用也較明確。
挑選排序法的執行過程 每次挑出欲排序資料中最小者 • 欲對下列 6 筆資料:9、8、4、7、2、3,進行挑選排序,結果如下: 原資料 i=0 i=1 i=2 i=3 i=4 排序結果 data[0] 9 22 2 2 2 2 data[1] 8 8 3 3 3 3 3 data[2] 4 4 4 4 4 4 4 data[3] 7 7 7 7 7 7 7 data[4] 2 9 9 9 9 8 8 data[5] 3 3 8 8 8 9 9 :當時最小元素的位置x :x 為已排序完成的資料 藉著迴圈和陣列,適當更動迴圈控制變數做為陣列的註標,即可透過比較資料大小、調換資料所在位置,完成排序的目的。
2.2.2 二元搜尋法 • 直覺的線性搜尋法 (linear seach) — 這個方法是從第一筆資料開始,依序一一比對,直到找到所要的資料,或找不到為止。 O(n) • 但若資料已經過排序(由小到大),則可以用二元搜尋法 (binary search),來提高搜尋的速率,其基本想法如下: .檢查數列中間之資料是否為所求?若是即找到; 否則再看: .若中間資料大於 x,對前半部數列進行二元搜尋 .若中間資料小於 x,對後半部數列進行二元搜尋
演算法2-1線性搜尋法: Input: data[0], data[1], ..., data[n-1] Output:資料 x 所在的位置(註標)或 -1(x 不在陣列data中) 1 for (i=0; i<n; i++) 2 if (x==data[i]) return(i); 3 retrun(-1); 自i=0起,核對data[i]是否與x相等: 若相等: 則演算法傳出位置i後停止; 若不相等: 則換下一筆,繼續比對; 若陣列所有資料皆無 x : 則傳回-1,知會呼叫程序:x不在陣列data中。
演算法2-2二元搜尋法: Input:已由小至大排序的陣列 data 及欲搜尋的資料 x Output:若 x 在data陣列中,則輸出 x 所在位置之註標,否則輸出-1 1 lower=0; upper=n-1; 2 while (lower<=upper) 3 { mid = (lower+upper)/2; 4 if (data[mid] == x) 5 return(mid); 6 else 7 If (data[mid] < x) 8 lower = mid+1; 9 else 10 upper = mid-1; 11 } 12 return -1; 檢查data[mid]與x是否相等,若二者相等,則表示在位置mid中已找到x了,遂傳回mid; 若data[mid+1]~data[upper]之間,遂把lower調至mid+1 ; 執行時表示data[mid]大於x,即x只可能在data[lower]~data[mid-1]之間,於是把upper調成mid-1。
int binary_search(int data[], int x) { int lower=0, upper=n-1; while (lower<=upper) { mid = (lower+upper)/2; if (data[mid] == x) return(mid); else If (data[mid] < x) lower = mid+1; else upper = mid-1; } return -1; }
二元搜尋 vs. 線性搜尋 • 即在最差的情況下 (x 根本不在陣列data中),也只需O(log n)次的比對,即可完成搜尋。所以二元搜尋法的時間複雜度為O(log n),比起線性搜尋法(時間複雜度為O(n)),二元搜尋法的效率比較好。 • 切記:二元搜尋法實施之前,請確定欲搜尋的數列已經過排序。 • 若搜尋需求有k 次,則排序與二元搜尋法的搭配,共需時間O(nlogn+klogn),而循序搜尋法需時O(kn),
程式2-4二元搜尋法—遞迴呼叫 1 int BinarySearch(int data[], int x, int left, int right) 2 { int middle; 3 if (left <= right) 4 { mid = (left+right)/2; 5 if (data[mid] == x) return(mid); 6 if (data[mid] < x) 8 BinarySearch(data[], x, mid+1, right); 9 else 10 BinarySearch(data[], x, left, mid-1); 11 } 12 return –1; 13 }
2.2.3 矩陣的運算 • 矩陣 (或行列式) 是一種數學上的重要結構,恰可用二維陣列表示之。 • 若矩陣A是一個34的整數矩陣,我們可以C語言宣告矩陣A int A[3][4]; 此二維整數陣列A共有3列4行,而A[i][j]即對應了矩陣A之元素aij。
矩陣轉置 B = AT A B[j][i] A[i][j] B[i][j] A[j][i]
矩陣轉置的演算法 演算法2-3矩陣轉置 輸入:mn 矩陣A 輸出:矩陣B,B=AT 1 將二維矩陣A的元素載入二維陣列A中:A[i][j]=aij 2 for (i=0; i<m; i++) 3 for (j=0; j<n; j++) 4 B[i][j]=A[j][i]; 演算法的時間複雜度為O(mn)。
矩陣相加的演算法 演算法2-4矩陣相加 輸入:mn矩陣A 輸出:矩陣C,C=A+B 1 將二維矩陣A,B的元素載入二維陣列A,B中: A[i][j]=aij;B[i][j]=bij 2 for (i=0; i<m; i++) 3 for (j=0; j<n; j++) 4 C[i][j]=A[i][j]+B[i][j]; 演算法的時間複雜度為O(mn)。
2-5 矩陣相乘演算法 輸入:mn矩陣A,np矩陣B 輸出:矩陣C,C=AB • 將二維矩陣A,B的元素載入二維陣列A,B中: A[i][j]=aij; B[i][j]=bij; 2 for (i=0; i<m; i++) 3 for (j=0; j<p; j++) 4 { C[i][j] = 0; 5 for (k=0; k<n; k++) 6 C[i][j] += A[i][k]*B[k][j]; 7 } 演算法的時間複雜度為O(mnp)
2.2.4 魔術方塊 • 當 n 是奇數時,H.Coxeter提出產生魔術方塊的規則: • 1 填在第一列中間; • 將方塊想成上下、左右、對角相接的圓球,往左上角填入下一個數字;若左上角已遭其它數字佔用,則填在目前方格的下方。 • 左上角、右上角、左下角、右下角皆可 • 33 • 55
程式2-5魔術方塊 1 #define MaxSize = 21; 2 int square[MaxSize][MaxSize]; 3 void MagicSquare(int n) 4 { int i, j, k, l, data; 5 if ((n>MaxSize)||(n<1)) 6 { cerr<<”輸入方塊過大,不予處理。”<<endl; return; } 7 else if ((n%2)==0) 8 { cerr<<”輸入方塊邊長為偶數,不予處理。”<<endl; return; } 9 for (i=0; i<n; i++) for (j=0; j<n; j++) square[i][j] = 0; 10 i = 0; j = (n-1)/2; 11 square[i][j] = 1; 12 data = 2;
程式2-5魔術方塊 (續) 13 while (data <= n*n) 14 { k = (i-1<0) ? n-1 : i-1; 15 l = (j-1<0) ? n-1 : j-1; 16 if (square[k][l] > 0) i = (i+1)%n; 17 else { i = k; j = l; } 18 square[i][j] = data++; 19 } 20 cout<<n<<””<<n<<”的魔術方塊”<<endl; 21 for (i=0; i<n; i++) 22 { for (j=0; j<n; j++) 23 out<<squarepi][j]<<” ”; 24 cout<<endl; 25 } 26 }
魔術方塊問題、演算法的時間複雜度 • 13~19行包含了一個while迴圈,迴圈控制變數為data,data會從2起逐漸加1至n2;足見其時間複雜度為O(n2); • 這個問題須將魔術方塊的n2個方格皆填上數字,至少須Ω(n2)的時間 • 程式2-5已具解此問題的最佳演算法,時間複雜度為Θ(n2)。
Magic Squares of n = 4 34 34
Other square of n = 4 A magic square on the Sagrada Família church façadein Barcelona
More Magic Squares • http://en.wikipedia.org/wiki/Magic_square • http://zh.wikipedia.org/zh-tw/%E5%B9%BB%E6%96%B9 • http://home.educities.edu.tw/listeve/Htm/magicsquare/magicsquare-1.htm
More Options void MagicSquare2(int n) { int i, j, k, l, data; int ** square = new int * [n]; for (i=0; i<n; i++) { square[i] = new int [n]; } for (i=0; i<n; i++) for (j=0; j<n; j++) square[i][j]=0; if (radioButton1->Checked || radioButton3->Checked) { i = 0; j = (n-1)/2; } else {i = n-1; j = (n-1)/2; } square[i][j] = 1; data = 2;
while (data <= n*n) { if (radioButton1->Checked) {k = (i-1<0) ? n-1 : i-1; l = (j-1<0) ? n-1 : j-1; if (square[k][l]>0) i = (i+1) % n; else {i = k; j = l;} } else if (radioButton2->Checked) {k = (i+1==n) ? 0 : i+1; l = (j-1<0) ? n-1 : j-1; if (square[k][l]>0) i = (i-1 < 0) ? n-1 : i-1; else { i = k;j = l;} } else if (radioButton3->Checked) {k = (i-1<0) ? n-1 : i-1; l = (j+1==n) ? 0 : j+1; if (square[k][l]>0) i = (i+1) % n; else { i = k; j = l;} } else if (radioButton4->Checked) {k = (i+1==n) ? 0 : i+1; l = (j+1==n) ? 0 : j+1; if (square[k][l]>0) i = (i-1 < 0) ? n-1 : i-1; else { i = k; j = l;} } square[i][j]=data++; }
print_square(square, n); for (i=0; i<n; i++) { delete [] square[i]; } delete [] square; }
String^ nsquare = Convert::ToString(n*n); String^ data_str = ""; int len = nsquare->Length; int block_len = len+2; int blank_len = 0; int row_sum = 0; int col_sum = 0; richTextBox2->AppendText("--- Magic Square "+Convert::ToString(n)+"x"+Convert::ToString(n)+" ---\n"); for (i=0; i<n; i++) { s = ""; for (j=0; j<n; j++) { data_str = Convert::ToString(square[i][j]); blank_len = block_len - data_str->Length; for (k=0; k<blank_len; k++) s += " "; s += data_str; } richTextBox2->AppendText(s+"\n"); }
2.3 陣列在記憶體中的位置 • 2.3.1 一維陣列 • 2.3.2 二維陣列 • 2.3.3 三維陣列與高維陣列
2.3.1 一維陣列 • 若一維陣列 A 在記憶體中的起點為 ,而且任一元素佔用 l 位元組,若有 n 個元素,則陣列A 在記憶體中的配置位置,可描繪如下。其中A[i]在記憶體中的位址為 +il: A[i]在記憶體中的位址為 +il 陣列的存取時間與元素存放的位置無關,亦即編譯器可在相同的時間內,決定任何位置陣列元素的位址,其存入(或取出)的時間皆為一樣。
C 中的陣列 • 在C語言中利用「位址」亦可對陣列中的元素做存取動作;亦即 A[i]=0 與 A+i=0 是同義的; • 變數 A 一旦宣告為陣列,則單獨引用A即指陣列之起始位置,而 A+I 即為 A[i] 的位址; • 在此不必再考量陣列元素的長度,因為C編譯器會在各位宣告陣列A的型態時,主動記載其佔用的位元組數,以個人電腦為例:整數 int 佔 2(或4,視編譯器而定)個位元組,即 sizeof(int)會等於 2(或4),而 float 為4(或更多)個位元組,即 sizeof(float) 等於 4(或更多)。
2.3.2 二維陣列 • 當二維陣列空間轉為一維記憶體空間時,可採以列為優先 (row major)或採以行為優先 (column major)的配置方式。 • 以列為優先的次序,指的是先依列由小到大的順序,再依行由小到大的順序存放資料,即第0列所有資料依行序存妥後,再將第1列所有資料依行序存妥…,依此類推。 • 行為優先的次序,則先依行由小到大的順序,再依列由小到大的順序存放陣列的資料。 現今大多的程式語言皆依以列為優先的原則設計。
u1u2二維陣列A與A[i][j]的位置關係 A[i][j] 的位置為 +(iu2+j)l
2.3.2 三維陣列中元素的定址關係 u1 … 2 1 u1 u3 0:u3-1 0:u1-1 u2 0:u2-1 A[u1-1][0][0]~A[u1-1][u2-1][u3-1] k 0:k-1 A[1][0][0]~A[1][u2-1][u3-1] j i A[0][0][0]~A[0][u2-1][u3-1] 0:j-1 0:i-1
A[i][j][k]的位址計算 • A[i][j][k] 之前共有 iu2u3+ju3+k個A中的元素。 • 當陣列A的起始位址為 、每個元素大小為l 位元組時,A[i][j][k] 的位址為+(iu2u3+ju3+k)l
i1w1 i2w2 i3w3 in-1wn-1 +()l inwn A[i1][i2]…[in] 在n維陣列的位址 • A[i1][i2]…[in]之前會之元素個數: i1u2u3 … un + i2u3u4 … un + i3u4u5 … un ... + in-1un + in =
+()l +l 簡化陣列元素在記憶體位址的計算 • 在電腦的內部運作中,編譯器可先行找出陣列宣告的上下限值:u1, u2, …, un,然後利用n-2 個乘法,求出上式的w1, w2, … , wn-1 (wn=1,不必求),先行存下。 • 計算A[i1][i2]…[in]的位址時,即可利用上式,以n 個乘法和n+1個加法求得其位址值。 • 也因而陣列是一個隨機(random access) 存取的結構,其元素的存取與存放的位置無關。 令 wj=
Dynamic Allocation of Arrays in C int * data; n = ... data = new int (n); // data = (int *)malloc(n*sizeof(int)); // listBox1->Items->Add((int)data); for (i=0; i<n; i++) { data[i] = ... } free(data);
Dynamic Allocation of 2D Arrays in C int ** data2; n = ...; m = ...; data2 = new int * [n]; //data2 = (int **) malloc(n*sizeof (void *)); //listBox3->Items->Add((int)data2); for (i=0; i<n; i++) { data2[i] = new int [m]; //data2[i] = (int *) malloc(m*sizeof(int *)); } for (i=0; i<n; i++) { for (j=0; j<n; j++) { data2[i][j] = ... } } for (i=0; i<n; i++) { free(data2[i]); // delete [] data2[i]; } free(data2); // delete [] data2; m data2 data2[0] data2[1] int * data2[i] n data2[i][j]