前言:在平時開發中數組幾乎是最基本也是最常用的數據類型,相比鏈表、二叉樹等又簡單很多,所以在學習數據和算法時用數組來作為一個起點再合適不過了。本篇博文的所有代碼已上傳 github ,對應工程的 array 模塊,下載地址:https://github.com/lgliuwei/DataStructureStudy,項目工程為 IntelliJ IDEA 環境,童鞋不妨下載下來,參照着代碼看博文豈不是效果更好~
首先介紹一下工程的目錄結構和作用,本工程的各個模塊之間以 Module 形式划分,每個 Module 大致對應一個專題(例如:鏈表對應工程中的 link),其中 libuitils 作為工具模塊為其他的模塊提供基礎的工具類,例如輸出語句的打印、字符串處理等等。
一、數組
俗話說磨刀不誤砍柴工,為了后續的方便,先做一些准備工作,首先創建一個抽象類 BaseArray,包含的幾個關鍵方法如下:
·initArrayByRandom(int size) :使用隨機數生成一個數組。
·initArrayByRandomNoRepeat(int size):不重復的隨機數生成一個數組。
·swap(int aIndex, int bIndex):交換數組中兩個下標的值。
詳細代碼如下:
1 /** 2 * 數組基類 3 * Created by liuwei on 17/7/21. 4 */ 5 public abstract class BaseArray { 6 protected int[] mArray; 7 protected int mSize; 8 protected int mMaxSize; 9 public BaseArray(int maxSize){ 10 mMaxSize = maxSize; 11 mArray = new int[mMaxSize]; 12 mSize = 0; 13 } 14 public abstract int insert(int e) throws ArrayIndexOutOfBoundsException; 15 public abstract int delete(int e); 16 /** 17 * 隨機數創建數組 18 * @param size 19 * @return 20 */ 21 public void initArrayByRandom(int size) throws ArrayIndexOutOfBoundsException { 22 if (size > mMaxSize) { 23 throw new ArrayIndexOutOfBoundsException("size不能大於數組的maxSize"); 24 } else { 25 mSize = size; 26 for (int i = 0; i < size; i++) { 27 mArray[i] = getRandomInt(size); 28 } 29 } 30 } 31 /** 32 * 隨機數創建數組(無重復) 33 * @param size 34 * @return 35 */ 36 public void initArrayByRandomNoRepeat(int size) throws ArrayIndexOutOfBoundsException { 37 if (size > mMaxSize) { 38 throw new ArrayIndexOutOfBoundsException("size不能大於數組的maxSize"); 39 } else { 40 mSize = size; 41 int n = 0; 42 boolean noRepeat; 43 while (n < mSize) { 44 noRepeat = true; 45 int temp = getRandomInt(mSize * 10); 46 for (int i = 0; i < n; i++) { 47 if (temp == mArray[i]) { 48 noRepeat = false; 49 break; 50 } 51 } 52 if (noRepeat) { 53 mArray[n] = temp; 54 n++; 55 } 56 } 57 58 } 59 } 60 public void initArray(int[] array) { 61 mSize = array.length; 62 for (int i = 0; i < mSize; i++) { 63 mArray[i] = array[i]; 64 } 65 } 66 public int size(){ 67 return mSize; 68 } 69 /** 70 * 獲取一個隨機整數 71 * @return 72 */ 73 public int getRandomInt(int bounder){ 74 return new Random().nextInt(bounder); 75 } 76 public void display(){ 77 for (int i = 0; i < mSize; i++) { 78 print(mArray[i] + ", "); 79 } 80 println(""); 81 } 82 protected void swap(int aIndex, int bIndex) { 83 int temp = mArray[aIndex]; 84 mArray[aIndex] = mArray[bIndex]; 85 mArray[bIndex] = temp; 86 } 87 protected void print(Object o){ 88 Logger.print(o); 89 } 90 protected void println(Object o){ 91 Logger.println(o); 92 } 93 }
看到這個類比較長也不要害怕,它里面只是包含一些工具性質的方法,目的是為我們提供方便,使我們在后續的二分查找和排序中可以更加專注於算法之中。
接着通過繼承 BaseArray 創建一個有序數組類 OrderedArray ,普通的插入對於數組來說再簡單不過了,直接往對應的下標中賦值即可,就不多說了,這里為創建的實體數組添加一個有序插入(正序)的方法,初步想了一下有序插入大致需要三步:
1、從數組的0下標開始往后找,直到發現大於帶插入的值時停下,記錄下標。
2、從數組的最后一個下標開始依次后移一位,直到第一步中記錄的下標。
3、將帶插入的值賦給第一步中紀律的下標。
詳細代碼如下:
1 /** 2 * 有序插入 3 */ 4 @Override 5 public int insert(int e) throws ArrayIndexOutOfBoundsException { 6 if (mSize == mMaxSize) { 7 throw new ArrayIndexOutOfBoundsException("數組已經滿了"); 8 } 9 int i; 10 for (i = 0; i < mSize; i++) { 11 if (e < mArray[i]) break; 12 } 13 for (int j = mSize; j > i; j--) { 14 mArray[j] = mArray[j-1]; 15 } 16 mArray[i] = e; 17 mSize++; 18 return i; 19 }
二、線性查找
如果我們想從一個有序數組中查找一個元素有兩種方法,線性查找和二分查找,線性查找就是最最常規的方法,從數組的0下標開始依次往后查找,直到找到要查找的元素,則查找成功,如果在數組中不存在帶查找的元素,因為是有序數組,我們只需找到比待查元素大時即可退出。
線性查找詳細代碼如下:
1 /** 2 * 線性查找 3 * @param e 4 * @return 5 */ 6 public int findByLiner(int e) { 7 for(int i = 0; i < mSize; i++) { 8 if (e == mArray[i]) { 9 return i; 10 } else if (mSize > (i + 1) &&e > mArray[i] && e < mArray[i + 1]) { 11 return -1; 12 } 13 } 14 return -1; 15 }
線性查找比較簡單,這里不過過多分析,很容易我們就能看出來線性查找的平均比較次數是數組元素個數的一半,所花費的時間與元素個數(假設是N)的一半成正比,在算法中描述時間復雜度是我們通常忽略常數,習慣性用大O表示法,所以線性查找的時間復雜度表示為:O(N)。
三、二分查找
二分查找類似於我們朋友聚會喝酒時玩的猜字游戲,游戲中,通常會給出一個范圍例如0-100,然后由一方從中默默挑出一個字讓你來猜,你猜的時候他會告訴你是否猜中,或者比他挑的字大或小。為了盡快的猜中,我們會選擇首先從中間開始猜,根據對方的提示我們來選擇偏大的一半還是偏小的一半然后再從新范圍的一半開始猜,這樣很快就能猜中答案。
具體的算法思路如下(假設數組下標范圍是0-N):
1、首先定義兩個下標邊界變量lowBounder=0,highBounder=N-1
2、讓當前下標為lowBounder和highBounder的中間與待查找的元素比較:
·如果相等,則查找成功。
·如果小於待查找元素,則將lowBounder賦值為當前下標+1。
·如果大於帶查找元素,則將hightBounder賦值為當前下標-1。
·如果此過程發現lowBounder大於highBounder,則表示未找到。
3、循環執行第二步。
詳細代碼如下:
1 /** 2 * 二分查找 3 * @param e 4 * @return 5 */ 6 public int findByHalf(int e) { 7 int lowIndex = 0; 8 int highIndex = mSize - 1; 9 int currentIndex; 10 while(true){ 11 currentIndex = (lowIndex + highIndex) / 2; 12 if (e == mArray[currentIndex]) { 13 return currentIndex; 14 } else if (lowIndex >= highIndex) { 15 return -1; 16 } else if (e > mArray[currentIndex]) { 17 lowIndex = currentIndex + 1; 18 } else { 19 highIndex = currentIndex - 1; 20 } 21 } 22 }
單從思路我們就可以分析出二分查找的平均查找時間要比線性查找快的多。
它的時間復雜度為:O(logN)。