數組是應用最廣泛的數據存儲結構。它被植入到大部分編程語言中。大部分數據結構都有最基本的四個操作:插入、刪除、查找、修改。對于這四種操作每一種數據結構都有相應的算法。算法和數據結構因此就是非常緊密的相聯系的。
1 數組例子 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
數組的每一個元素必須是連續,也就是中間沒有洞。就是說第一個元素和第三個元素有值,但不允許第二個元素是空的。
1 package chapter2; 2 3 public class Array { 4 public static void main(String[] args){ 5 //創建一個數組 6 long[] arr; 7 arr = new long[100]; 8 int nElems = 0; 9 int j; 10 long searchKey; 11 arr[0] = 99; 12 arr[1] = 11; 13 arr[2] = 22; 14 arr[3] = 33; 15 arr[4] = 55; 16 arr[5] = 66; 17 arr[6] = 44; 18 arr[7] = 77; 19 arr[8] = 23; 20 arr[9] = 88; 21 nElems = 10; 22 //輸出前10位數組元素 23 for(j = 0; j < nElems; j++){ 24 System.out.print(arr[j] + " "); 25 } 26 System.out.println(); 27 //查找數組的一個元素值為55的元素 28 searchKey = 55; 29 for(j = 0; j < arr.length; j++){ 30 if(arr[j] == searchKey){ 31 break; 32 } 33 } 34 if(j == nElems){ 35 System.out.println("Can't find the element"); 36 }else{ 37 System.out.println("find the element " + searchKey + " at index " + (j + 1) ); 38 } 39 //刪除指定元素 40 searchKey = 66; 41 for(j = 0; j < arr.length; j++){ 42 if(arr[j] == searchKey){ 43 break; 44 } 45 } 46 for(int k = j; k < nElems; k++){ 47 arr[k] = arr[k+1]; 48 } 49 nElems--; 50 //輸出刪除后的數組 51 for(j = 0; j < nElems; j++){ 52 System.out.print(arr[j] + " "); 53 } 54 } 55 } 56 57 輸出結果: 58 99 11 22 33 55 66 44 77 23 88 59 find the element 55 at index 5 60 99 11 22 33 55 44 77 23 88
2 將程序劃分成類 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
上面的程序包含了一個很大的方法,通過將程序劃分成類以后,并且將其中的方法模塊化,這樣程序看起來更加有條理。
1 package chapter2; 2 class ArrayMethod{ 3 private long[] arr; 4 private int nElems; 5 public ArrayMethod(int max){ 6 arr = new long[max]; 7 nElems = 0; 8 } 9 public void insert(long value){ 10 arr[nElems] = value; 11 nElems++; 12 } 13 public boolean find(long searchKey){ 14 //查找數組的一個元素值為searchKey的元素 15 int j; 16 for(j = 0; j < arr.length; j++){ 17 if(arr[j] == searchKey){ 18 break; 19 } 20 } 21 if(j == nElems){ 22 return false; 23 }else{ 24 return true; 25 } 26 } 27 //刪除指定元素 28 public boolean delete(long searchKey){ 29 int j; 30 for(j = 0; j < arr.length; j++){ 31 if(arr[j] == searchKey){ 32 break; 33 } 34 } 35 for(int k = j; k < nElems; k++){ 36 arr[k] = arr[k+1]; 37 } 38 nElems--; 39 return true; 40 } 41 //輸出數組 42 public void display(){ 43 //輸出前10位數組元素 44 for(int j = 0; j < nElems; j++){ 45 System.out.print(arr[j] + " "); 46 } 47 System.out.println(); 48 } 49 } 50 class HighArray { 51 52 public static void main(String[] args){ 53 int maxSize = 100; 54 ArrayMethod arr = new ArrayMethod(maxSize); 55 arr.insert(99); 56 arr.insert(11); 57 arr.insert(22); 58 arr.insert(33); 59 arr.insert(55); 60 arr.insert(66); 61 arr.insert(44); 62 arr.insert(77); 63 arr.insert(23); 64 arr.insert(88); 65 //輸出數組 66 arr.display(); 67 //查找值為55的元素 68 long searchKey = 55; 69 if(arr.find(searchKey)){ 70 System.out.println("find the element "); 71 }else{ 72 System.out.println("Can't find the element"); 73 } 74 //刪除指定元素 75 searchKey = 22; 76 if(arr.delete(searchKey)){ 77 System.out.println("Delete the element successfully "); 78 }else{ 79 System.out.println("Can't find the element"); 80 } 81 //輸出刪除元素后的數組 82 arr.display(); 83 } 84 }
3 有序數組 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
假設一個數組,其中的數據項按關鍵字升序排列,即最小值在下標為0的單元上,每一個單元都比前一個單元的值大。這種數組被稱為有序數組。
當向這種數組中插入數據項時,需要為插入操作找到正確的位置:剛好在稍小值的后面,稍大值的前面。然后將所有比待茶數據項的值向后移以便騰出空間。
將數據按順序排列的好處之一就是可以通過二分法查找顯著地提高查找速度。但缺點是降低了插入操作的速度。
4 線性查找 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
默認情況下是線性查找,線性查找和未經排序的數組的查找操作相似。
5 二分查找 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
當使用二分查找時就體現有序數組的好處,這種查找比線性查找快很多,尤其是對大數組來說更為顯著。
二分查找首先從要查找的范圍確定一個中位數,然后比較要找的數和中位數的大小關系,確定更小的范圍,依次遞歸,知道找到那個數。
?
?
?將數目進行放大,可以發現二分查找是極為優秀的
5.1 有序數組的二分搜索代碼 ??
二分查找是將數組數據項范圍不斷對半分隔來查找特定的數據項。方法如下所示:
1 package chapter2; 2 class ArrayMethod{ 3 private long[] arr; 4 private int nElems; 5 public ArrayMethod(int max){ 6 arr = new long[max]; 7 nElems = 0; 8 } 9 public void insert(long value){ 10 arr[nElems] = value; 11 nElems++; 12 } 13 //二分查找法 14 public int halfFind (long searchKey){ 15 int lowerBound = 0; 16 int upperBound = nElems - 1; 17 int curIn; 18 while(true){ 19 curIn = (lowerBound + upperBound)/2; 20 if(arr[curIn] == searchKey){ 21 return curIn; 22 }else if(lowerBound >= upperBound){ 23 return nElems; 24 }else{ 25 if(arr[curIn] > searchKey){ 26 upperBound = curIn - 1; 27 }else{ 28 lowerBound = curIn + 1; 29 } 30 } 31 } 32 } 33 public int size(){ 34 return nElems; 35 } 36 public boolean find(long searchKey){ 37 //查找數組的一個元素值為searchKey的元素 38 int j; 39 for(j = 0; j < arr.length; j++){ 40 if(arr[j] == searchKey){ 41 break; 42 } 43 } 44 if(j == nElems){ 45 return false; 46 }else{ 47 return true; 48 } 49 } 50 //刪除指定元素 51 public boolean delete(long searchKey){ 52 int j; 53 for(j = 0; j < arr.length; j++){ 54 if(arr[j] == searchKey){ 55 break; 56 } 57 } 58 for(int k = j; k < nElems; k++){ 59 arr[k] = arr[k+1]; 60 } 61 nElems--; 62 return true; 63 } 64 //輸出數組 65 public void display(){ 66 //輸出前10位數組元素 67 for(int j = 0; j < nElems; j++){ 68 System.out.print(arr[j] + " "); 69 } 70 System.out.println(); 71 } 72 } 73 class HighArray { 74 75 public static void main(String[] args){ 76 int maxSize = 100; 77 ArrayMethod arr = new ArrayMethod(maxSize); 78 arr.insert(99); 79 arr.insert(11); 80 arr.insert(22); 81 arr.insert(33); 82 arr.insert(55); 83 arr.insert(66); 84 arr.insert(44); 85 arr.insert(77); 86 arr.insert(23); 87 arr.insert(88); 88 //輸出數組 89 arr.display(); 90 //查找值為55的元素 91 long searchKey = 35; 92 // if(arr.find(searchKey)){ 93 // System.out.println("find the element "); 94 // }else{ 95 // System.out.println("Can't find the element"); 96 // } 97 //二分法查找 98 if(arr.halfFind(searchKey) != arr.size()){ 99 System.out.println("Find it"); 100 }else{ 101 System.out.println("Can't find it"); 102 } 103 // //刪除指定元素 104 // searchKey = 22; 105 // if(arr.delete(searchKey)){ 106 // System.out.println("Delete the element successfully "); 107 // }else{ 108 // System.out.println("Can't find the element"); 109 // } 110 //輸出刪除元素后的數組 111 arr.display(); 112 } 113 }
5.2 有序數組的優點 ? ? ? ? ? ? ?
使用有序數組最主要的好處是查找的速度比無序數組快多了。不好的方面是在插入操作中由于所有靠后的數據都需要移動以騰開空間,所以速度較慢。有序數組和無序數組中的刪除操作都很慢,這是因為數據項必須向前移動來填補已刪除數據項的洞。有序數組在查找頻繁的情況下十分有用,但若是插入和刪除較為頻繁時,則無法高效工作。
6 大O表示法 ? ? ? ? ? ? ? ? ? ??
計算機科學中評價算法效率的方法稱為大O表示法。比較算法時候通常會說"算法A比算法B快2倍",這種說法意義不大。因為數據項的變化會對排序造成一定很大影響。有可能數據項增加50%,算法A就比B快了3倍,或者可能只有一半的數據項,A和B的速度是相同的。
6.1 無序數組的插入:常數 ? ??
無序數組的插入是我們到現在為止所見過的算法中唯一一個與數組項個數無關的算法。新數據項總被放在下一個有空的地方,a[nElems],然后nElems增加。無論數組中的數據項個數N有多大,一次插入總是用相同的時間。我們可以說向一個無序數組中插入一個數據項的時間T是一個常數K
T=K
在現實情況中,插入所需的實際時間與以下這些因素有關:微處理器,編譯程序生成程序代碼的效率,等等。
6.2 線性查找:與N成正比 ? ??
在數組數據項的線性查找中,我們已經發現尋找特定數據項所需的比較平均次數為數據項總數的一半。因此設N為數據項總數,搜索時間T與N的一半成正比:
T=K*N/2
同插入一樣,若要得到方程中K的值,首先需要對某個N值的查找進行計時,然后用T來計算K。當得到K后,便可以對任意N的值來計算T。將2并入K可以得到更方便的公式,新K值等于原先的K除以2即
T=K*N
這個方程說明平均線性查找時間與數組的大小成正比。即如果一個數組增大兩倍,則所花費的查找時間也會相應地增長兩倍。
6.3 二分查找:與log(N)成正比
同樣,我們可以為二分查找指定出一個與T和N有關的公式:T=K*log2(N)
實際上,由于所有的對數和其他對數成比例(比如從底數2轉換到底數為10需乘以3.322),也可以將這個為常數的底數也并入K。由此不必指定底數:
T=K*log(N)
不要常數
大O表示法同上面的公式比較類似,但它省去了常數K。當比較算法時,并不在乎具體的微處理器或編譯器;真正需要比較的是對應不同的N值,T是如何變化的,而不是具體的數字,因此不需要常數。
大O表示法使用大寫字母O,可以認為其含義是order of(大約是)。我們可以使用大O表示法來描述線性查找使用了O(N)級時間,二分查找使用了O(logN)級時間。向一個無序數組中的插入使用了O(1),或常數級時間。
下表總結的是討論過的算法的運行時間
大O表示法的實質并不是對運行時間給出實際值,而是表達了運行時間是如何受數據項個數所影響的。除了實際安裝后真正去測量一次算法的運行時間之外,這可能是對算法進行比較的最有意義的方法了。
6.4 幾種算法時間復雜度的對比?
?
7 為什么不用數組表示一切? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
僅使用數組似乎就可以完成所有工作,為什么不用它們來進行所有數據存儲呢?我們已經見到了許多關于數組的缺點。在一個無序數組中可以很快進行插入(O(1)),但是查找卻要花費較慢的O(N)時間。在一個有序數組中可以查找得很快,用O(logN)的時間,但插入卻花費了O(N)時間。對于這兩種數組而言,由于平均半數的數據項為了填補"空洞"必須移動,所以刪除操作平均需要O(N)時間。
如果有一種數據結構進行任何如插入、刪除和查找的操作都很快(理想的O(1)或是O(logN)時間),那就好了。后面我們會介紹類似的數組,但這是以程序的復雜度為代價的。
另外數組被創建后,占用內存長度是確定的,無法進行修改,這樣的話,如果創建的長度過大,但是實際需要的很少就會造成內存浪費,如果創建的長度過小,就會造成溢出,無法彈性使用。