1. 程式人生 > >Java資料結構與演算法初級篇之陣列、集合和散列表

Java資料結構與演算法初級篇之陣列、集合和散列表

 原始碼下載地址:https://download.csdn.net/download/geduo_83/10913510

之前沒有寫過關於資料結構的文章,那麼今天我們將在本文中介紹最基礎、最簡單的資料結構。
陣列,作為資料結構中最基礎的一個儲存方式,是我們學習一切資料結構、演算法的基石。大部分的資料結構可以用陣列來實現。接下來我們會介紹陣列的概念、儲存結構、特點和使用場景。
集合算是升級版的陣列,在本文當中我們會介紹集合的概念、特點、實現以及的它的適用場景。
散列表又叫雜湊表,在很多語言中都是在陣列的基礎上實現的,當然散列表也有其他的實現形式,本文我們會介紹散列表的基本概念、特點、實現方式等。

1. 前言 2

2. 陣列 3

    2.1 概念 3

    2.2 特點 3

    2.3 儲存結構 3

    2.4 使用場景 3

    2.5 常見演算法 4

        2.5.1 氣泡排序 4

        2.5.2 選擇排序 6

        2.5.3 桶排序 7

        2.5.4 陣列中是否有重複元素 9

        2.5.5 刪除陣列中的重複元素 10

        2.5.6 兩數求和 12

        2.5.7 求從左上到右下的路徑數 15

        2.5.8 求左上到右下的路徑最小值 17

3. 集合 18

    3.1 概念 18

    3.2 特點 19

        1.它的長度是可變的 19

        2.他會浪費一定記憶體空間 19

        3.資料的拷貝會浪費一定的時間 19

    3.3 適用場景 19

    3.4 常見的演算法 19

        3.4.1 自定義實現一個集合 19

        3.4.2 刪除集合中的偶數 21

    3.5 效能分析 23

4. 散列表 24

5. 小結 26

 

1. 前言

之前沒有寫過關於資料結構的文章,那麼今天我們將在本文中介紹最基礎、最簡單的資料結構。

陣列,作為資料結構中最基礎的一個儲存方式,是我們學習一切資料結構、演算法的基石。大部分的資料結構可以用陣列來實現。接下來我們會介紹陣列的概念、儲存結構、特點和使用場景。

集合算是升級版的陣列,在本文當中我們會介紹集合的概念、特點、實現以及的它的適用場景。

散列表又叫雜湊表,在很多語言中都是在陣列的基礎上實現的,當然散列表也有其他的實現形式,本文我們會介紹散列表的基本概念、特點、實現方式等。

2. 陣列

2.1 概念

陣列是記憶體中有序的元素序列

2.2 特點

定長、按順序訪問、索引快、插入刪除慢

2.3 儲存結構

2.4 使用場景

陣列其實是非常簡單的一個數據結構,用起來也比較簡單,他是其他所有資料結構的基礎,所以只有掌握好陣列,才能學習好其他的資料結構和演算法。

什麼時候使用陣列,通過資料的特點我們就可以想到,資料的長度是固定的,所以不會出現長度變化的業務上比較適合使用陣列

如果我們在app開發中非常常見的選單按鈕,它的個數一般都是固定的不會發生變化,如下圖這個介面有首頁、報表、訊息、我的,我們就可以用陣列來儲存。

2.5 常見演算法

2.5.1 氣泡排序

package A陣列.A001氣泡排序;

/**
 * Description: <氣泡排序><br>
 * Author: 門心叼龍<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] arg) {
    int[] arr = {1, 3, 10, 1, 34, 5, 21};
    sortBubbling(arr);
    int i = 0;
    while (i < arr.length) {
      System.out.println(arr[i]);
      i++;
    }
  }

  // 氣泡排序:兩個迴圈,通過兩兩相比,進行排序
  private static void sortBubbling(int[] arr) {
    // 第一輪確定最後一個,第二輪確定倒數第二個...
    for (int i = 0; i < arr.length - 1; i++) {
      for (int j = 0; j < arr.length - i - 1; j++) {
        // 兩兩相比,就像魚吐水泡一樣...
        if (arr[j] > arr[j + 1]) {
          int temp = arr[j + 1];
          arr[j + 1] = arr[j];
          arr[j] = temp;
        }
      }
    }
  }
}

2.5.2 選擇排序

package A陣列.A002選擇排序;

/**
 * Description: <選擇排序><br>
 * Author: 門心叼龍<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] arg) {
    int[] arr = {1, 3, 10, 1, 34, 5, 21};
    sortChange(arr);
    int i = 0;
    while (i < arr.length) {
      System.out.println(arr[i]);
      i++;
    }
  }

  // 選擇排序,選擇第一個元素和剩下的n-1個比較
  private static void sortChange(int[] arr) {
    // 第一輪確定第一個元素,第二輪確定第二個元素
    for (int i = 0; i < arr.length; i++) {
      for (int j = i + 1; j < arr.length; j++) {
        // 選擇第一i個元素和剩餘的元素進行比較
        if (arr[i] > arr[j]) {
          int temp = arr[i];
          arr[i] = arr[j];
          arr[j] = temp;
        }
      }
    }
  }
}

2.5.3 桶排序

package A陣列.A003桶排序;

/**
 * Description: <桶排序><br>
 * Author: 門心叼龍<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] arg) {
    int[] arr = {1, 3, 10, 1, 34, 5, 21};
    sortBucket(arr);
    // int i = 0;
    // while (i < arr.length) {
    // System.out.println(arr[i]);
    // i++;
    // }
  }

  // 桶排序,宣告一個以 最大元素+1 為長度的陣列,遍歷原陣列,桶陣列計數
  private static void sortBucket(int[] arr) {
    int[] arr1 = new int[34 + 1];
    for (int i = 0; i < arr.length; i++) {
      arr1[arr[i]]++;
    }
    for (int i = 0; i < arr1.length; i++) {
      int count = arr1[i];
      while (count > 0) {
        System.out.println(i);
        count--;
      }
    }
  }
}

2.5.4 陣列中是否有重複元素

package A陣列.A004陣列是否有重複元素;

import java.util.HashSet;

/**
 * Description: <陣列是否有重複元素><br>
 * Author: 門心叼龍<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] arg) {
    int[] arr = {11, 3, 10, 11, 34, 5, 21};
    System.out.println(checkRepeat(arr));
  }

  // 查詢一個數組裡面有沒有重複元素
  private static boolean checkRepeat(int[] arr) {
    // 1.宣告一個散列表表
    // 2.遍歷這個陣列
    // 3.對遍歷的元素依次進行判斷,如果散列表裡面沒有就往散列表裡面塞,有就直接退出了
    HashSet<Integer> hashSet = new HashSet<>();
    for (int i = 0; i < arr.length; i++) {
      if (hashSet.contains(arr[i])) {
        return true;
      } else {
        hashSet.add(arr[i]);
      }
    }
    return false;
  }
}

2.5.5 刪除陣列中的重複元素

package A陣列.A005刪除陣列重複元素;

import java.util.Arrays;

/**
 * Description: <刪除陣列重複元素><br>
 * Author: 門心叼龍<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAgorithm {
  public static void main(String[] arg) {
    int[] arr = {1, 3, 10, 1, 34, 5, 21};
    arr = removeRepeat(arr);
    int i = 0;
    while (i < arr.length) {
      System.out.println(arr[i]);
      i++;
    }
  }

  // 查詢一個數組裡面有沒有重複元素,如果有則刪除重複元素
  private static int[] removeRepeat(int[] arr) {
    // 取出第一個元素和剩餘的其他元素進行對比
    // 一旦發現相等,則後面的元素都往前移動一個,移動完畢陣列
    loop: for (int i = 0; i < arr.length; i++) {
      for (int k = i + 1; k < arr.length; k++) {
        // 如果相等則後面的元素同意往前移動
        if (arr[i] == arr[k]) {
          int head = k;
          while (head < arr.length - 1) {
            arr[head] = arr[head + 1];
            head++;
          }
          // 對陣列進行壓縮處理
          arr = Arrays.copyOf(arr, arr.length - 1);
          i = 0;
          // 壓縮完畢,重頭開始執行
          continue loop;
        }
      }
    }
    return arr;
  }
}

2.5.6 兩數求和

package A陣列.A006兩數求和;

import java.util.Arrays;

/**
 * Description: <><br>
 * Author: 門心叼龍<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] args) {
    int[] arr = {1, 23, 12, 2, 11, 22};
    int[] res = get(arr, 33);
    System.out.println(Arrays.toString(res));
  }
  static class Data implements Comparable<Data>{
    int index;
    int data;

    public Data(int index, int data) {
      this.index = index;
      this.data = data;
    }

    @Override
    public int compareTo(Data o) {
      return data - o.data;
    }

    @Override
    public String toString() {
      return "Data{" + "index=" + index + ", data=" + data + '}';
    }
  }
  // 指定一個目標數,查詢其中兩個數之和等於目標數的下標
  public static int[] get(int[] arr, int sum) {
    int[] result = {0, 0};
    // 1.首先對原陣列排序
    Data[] arr1 = new Data[arr.length];
    int i = 0;
    while( i < arr.length){
      arr1[i] = new Data(i,arr[i]);
      i++;
    }

    Arrays.sort(arr1);
    System.out.println(Arrays.toString(arr1));
    // 2.宣告兩個指標,head,tail
    int head = 0;
    int tail = arr.length - 1;
    while (head < tail) {
      if ((arr1[head].data + arr1[tail].data) == sum) {
        result[0] = arr1[head].index;
        result[1] = arr1[tail].index;
        return result;
      } else if ((arr1[head].data + arr1[tail].data) < sum) {
        head++;
      } else {
        tail--;
      }
    }
    return null;
  }
}

2.5.7 求從左上到右下的路徑數

package A陣列.A007左上到右下路徑數;

/**
 * Description: <在一個m*n的矩形方格中, 機器人在最左上的格子裡,一次只能挪動一步,只能往右往下移動 目標地點在最右下方的方格里,問共有幾條路徑 ><br>
 * Author: 門心叼龍<br>
 * Date: 2018/11/23<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] args) {

    int path = getPath(3, 3);
    System.out.println(path);
    int[][] arr = new int[2][3];
    System.out.println("length:"+arr.length);
  }

  // 動態規劃問題
  public static int getPath(int m, int n) {
    int[][] arr = new int[m][n];
    // 將第一行都賦值為1
    for (int i = 0; i < n; i++) {
      arr[0][i] = 1;
    }
    // 將第一列都賦值為1
    for (int i = 0; i < m; i++) {
      arr[i][0] = 1;
    }
    // 給其他單元格賦值
    for (int i = 1; i < m; i++) {
      for (int j = 1; j < n; j++) {
        arr[i][j] = arr[i - 1][j] + arr[i][j - 1];
      }
    }
    return arr[m - 1][n - 1];
  }
}

2.5.8 求左上到右下的路徑最小值

package A陣列.A008左上到右下路徑中的最小值;

/**
 * Description: <在一個m*n的矩形方格中, 機器人在最左上的格子裡,一次只能挪動一步,只能往右往下移動 目標地點在最右下方的方格里,問共有幾條路徑,求路徑中的最小值><br>
 * Author: 門心叼龍<br>
 * Date: 2018/11/23<br>
 * Version: V1.0.2<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] args) {
    int[][] grid = new int[][] {{1, 1, 8}, {3, 1, 4}, {6, 2, 1}};
    int minvalue = getMinvalue(grid);
    System.out.println(minvalue);
  }

  private static int getMinvalue(int[][] grid) {
    int[][] result = new int[grid.length][grid[0].length];
    // 給result[0][0]賦值
    result[0][0] = grid[0][0];
    // 給第一行賦值
    for (int i = 1; i < result[0].length; i++) {
      result[0][i] = result[0][i - 1] + grid[0][i];
    }
    // 給第一列賦值
    for (int i = 1; i < result.length; i++) {
      result[i][0] = result[i - 1][0] + grid[i][0];
    }
    // 給其他元素賦值
    for (int i = 1; i < result.length; i++) {
      for (int j = 1; j < result[0].length; j++) {
        result[i][j] = Math.min(result[i - 1][j], result[i][j - 1]) + grid[i][j];
      }
    }
    return result[result.length - 1][result[0].length - 1];
  }
}

3. 集合

3.1 概念

大家知道資料的致命缺點就是長度固定,如果我們要儲存的資料長度不固定,該怎麼辦?這時候就要用集合了,其實集合也是基於陣列實現的,不過它是一個變長陣列,想放入多少就可以放入多少。集合就是一個變長陣列或者叫做動態陣列。

3.2 特點

1.它的長度是可變的

2.他會浪費一定記憶體空間

3.資料的拷貝會浪費一定的時間

 3.3 適用場景

集合的適用場景非常多,如果部落格的文章列表、評論列表等,只要有列表就有集合的身影。

3.4 常見的演算法

3.4.1 自定義實現一個集合

現在我們不妨畫個流程圖方便大家理解集合的工作原理:

程式碼實現如下:

package B集合.A001自定義實現一個ArrayList;

import java.util.Arrays;

/**
 * Description: <自定義實現一個ArrayList><br>
 * Author: 門心叼龍<br>
 * Date: 2018/11/19<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MyArrayList {
  private int[] arr;
  private int initsize = 5;
  private int size = 0;

  public MyArrayList() {
    arr = new int[initsize];
  }

  public int get(int index) {
    return arr[index];
  }

  public boolean add(int value) {
    // 說明此時陣列已經滿了,要擴容了
    if (size == arr.length) {
      System.out.println("陣列要擴容了...");
      arr = Arrays.copyOf(arr, size * 2);
    }
    arr[size++] = value;
    return true;
  }

  public boolean remove(int value) {
    if (arr.length > 0) {
      loop: for (int i = 0; i < arr.length; i++) {
        if (arr[i] == value) {
          int temp = i;
          while (temp < arr.length - 1) {
            arr[temp] = arr[temp + 1];
            temp++;
          }
          arr[--size] = 0;
          break loop;
        }
      }
    }
    return true;
  }

  public int size() {
    return size;
  }
}

3.4.2 刪除集合中的偶數

package B集合.A002刪除集合中的偶數;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * Description: <刪除集合中的偶數><br>
 * Author: 門心叼龍<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] arg) {
    ArrayList<Integer> list = new ArrayList() {
      {
        add(1);
        add(2);
        add(3);
        add(4);
      }
    };
    removeEvenNumber(list);
    int i = 0;
    while (i < list.size()) {
      System.out.println(list.get(i));
      i++;
    }
  }

  // 刪除集合中的偶數元素
  private static void removeEvenNumber(ArrayList<Integer> myArrayList) {
    Iterator<Integer> iterator = myArrayList.iterator();
    while (iterator.hasNext()) {
      Integer next = iterator.next();
      if (next % 2 == 0) {
        iterator.remove();
      }
    }
  }
}

3.5 效能分析

在演算法中,每種演算法的效能指標一般都有兩個,即時間複雜度和空間複雜度。

時間複雜度:它定量的描述了該演算法的執行時間。常常用大寫的O表示。

空間複雜度:是對一個演算法在執行過程中臨時佔用的儲存空間大小的度量。

雖然集合這個變長陣列比普通的陣列高階一些,但是本質上它還是基於陣列實現的,所以它和陣列的效能差不多。

對於陣列的操作,並不像我們看到的那麼直觀,計算機需要根據我們具體操作的位置,從頭到尾一個一個地尋找到指定的位置,所以我們在陣列中增加元素、修改元素、獲取元素等操作的時間複雜度都為O(n)。

變長資料也有效能損耗的問題,如果插入的元素髮現其中固定的陣列的長度不夠,則需要建立一個新的更長的陣列,還要拷貝元素到新的陣列,這都會造成效能損耗。

4. 散列表

4.1 概念

前面我們講了陣列和集合,他們都有一個共同的特點,他們在記憶體中的儲存順序是有序的,如果資料量很大我們需要在陣列或者集合中查詢一個元素,或者在陣列或集合的頭部新增或者刪除一個元素,它的效能就會大大降低。

此時散列表就應運而生了,散列表是一種以空間換時間的資料結構。

讓我們想一下,若在手機的通訊錄中查詢一個人,那我們應該不會從第1個人一直的查詢下去,因為這樣實在是太慢了。我們其實是這樣做的:首先看這個人的名字的首字母是什麼,比如姓趙,那麼我們會點選字母z,列表裡以字母z開始的人名都會迅速的查找出來,就很快的查詢到我們要查詢的那個人。

還有我們在查字典的時候,需要查詢一個單詞,肯定不會從頭翻到尾,而是通過這字的首字母,查詢到對應的那一頁,這樣可以速度的跳到那個字所在的頁面。

其實這裡就用到了散列表的思想。

散列表又叫雜湊表,能通過給定的關鍵字的值直接訪問到具體對應的值的一個數據結構。也就是說,通過關鍵字對映到一個表中的位置來直接訪問記錄,以加速訪問的速度。

而這個關鍵字就是我通常所說的key,把對應的記錄稱為value,所以可以說也是通過這個key訪問一個對映表來得到value的值,而這個對映表就是所說的散列表,也叫雜湊表。

4.2 雜湊演算法

剛才我們說到,通過關鍵字對映到一個表中的位置來直接訪問記錄,這個對映到表中的位置就是通過雜湊演算法來實現的,目前這個雜湊演算法的實現的方法比較多,主要有以下一種:

1.直接定址法

2.數字分析法

3.平方取中法

4.隨機數法

5.除留取餘法

4.3 雜湊衝突

會有這樣一種情況,有多個不同的Key通過雜湊函式可能會得到相同的地址,這樣就會造成對資料的覆蓋、丟失。那麼解決雜湊衝突的處理方式也有很多種,主要有以下幾種方法:

1.開放地址法

2.再雜湊法

3.連結地址法

4.4 儲存結構

一個好的散列表設計,除了需要選擇一個性能較好的雜湊函式,還要選擇一個好的衝突解決方式。這裡我們選擇除留取餘法作為雜湊演算法,選擇連結地址法作為衝突的處理方式。

4.5 特點

散列表有兩種用法,一種是key的值和Value的值一樣,一般我們稱這種資料結構為Set集合;如果Key的值和Value的值不一樣,我們稱這種情況為Map。

1.增啥改查的速度都很快

2.無序

4.6 適用場景

1.資料快取

2.快速查詢

4.7 效能分析

散列表的訪問,如果沒有碰撞,那麼我們完全可以認為對元素的訪問的時間複雜度為O(1)

但是實際上不可能沒有碰撞,Java中使用連結串列來解決,而碰撞之後的訪問需要遍歷連結串列,所以時間的複雜度將變為O(L),其中L為連結串列的長度。

5. 小結

陣列,作為資料結構中最為基礎的、常用的一個結構。而集合與散列表他們都是在陣列的基礎上進行稍微高階點的擴充套件的資料結構,通過對比這三種資料結構,我們更加清楚他們之間的區別和應用場景了,對陣列的應用有了更加深入的理解。

原始碼下載地址:https://download.csdn.net/download/geduo_83/10913510

問題反饋

在使用中有任何問題,歡迎反饋給我,可以用以下聯絡方式跟我交流

關於作者

  var geduo_83 = {
    nickName  : "門心叼龍",
    site : "http://www.weibo.com/geduo83"
  }