計算機基礎學習筆記 | 數據結構基礎

學習資料

  • 極客時間:數據結構與算法之美
  • 《小灰的漫畫算法之旅》

基礎

  • 數據結構:數據的組織、管理、存儲格式,其目的是爲了高效的訪問和修改數據
  • 算法:一系列程序指令,用於處理特定的運算和邏輯問題

十種常用數據結構

  • 數組
  • 鏈表
  • 隊列
  • 散列表
  • 二叉樹
  • 跳錶
  • Trie 樹

十種常用的算法

  • 遞歸
  • 排序
  • 二分查找
  • 搜索
  • 哈希算法
  • 貪心算法
  • 分治算法
  • 回溯算法
  • 動態規劃
  • 字符串匹配算法

時間複雜度

網圖,侵權請聯繫刪除
在這裏插入圖片描述
大O表達法,用來大概表示需要進行的時間

  • 忽略低階、常量、係數三部分並不左右增長趨勢
    幾個方法:
  • 之關係循環次數最多的一段代碼
  • 加法法則:總複雜度等於量級最大的那段代碼的複雜度
  • 乘法法則:嵌套代碼的複雜度等於嵌套內外代碼複雜度的乘積

常見時間複雜度:

  • 多項式量級
  • 非多項式量級(O(2^n) 和 O(n!) )

多項式時間複雜度

  • O(1)
int i = 1;
int j = 2;
int sum = i + j;
  • O(logn)
    i = 1;
    while(i <= n){
        i = i * 2;
    }

2^x = n,則 x = log2(n),忽略底數O(logn)

  • O(m + n)、O( m * n)

由多個數據規模來決定時間複雜度:不能確定m、n的值,則爲 O(m + n)


int cal(int m, int n) {
  int sum_1 = 0;
  int i = 1;
  for (; i < m; ++i) {
    sum_1 = sum_1 + i;
  }

  int sum_2 = 0;
  int j = 1;
  for (; j < n; ++j) {
    sum_2 = sum_2 + j;
  }

  return sum_1 + sum_2;
}

空間複雜度

常見爲O(1)、O(n)、O(n^2)

  • 常量空間O(1):算法的存儲空間大小固定,和輸入規模無直接關係
  • 線性空間O(n):線性集合(數組),且集合大小和輸入規模n成正比
  • 二維空間O(n^2):二維數組
  • 遞歸空間:與遞歸深度成正比

基礎數據結構

數組 array

在內存中順序存儲(佔用一片連續的內存地址);每個元素有着自己的下標,可以通過下標查找元素;

  • 數組插入、刪除的時間複雜度:O(n)
  • 數組查找、更新的時間複雜度:O(1)
  • 優勢:查找效率高,只需要給出下標
  • 劣勢:插入、刪除效率低,需要移動大量元素
讀取元素

因爲數組在內存中順序存儲,所以可以直接通過下標讀取到對應的數組元素 ,這種讀取元素的方式叫做隨機讀取

 int[] array = new int[]{3,1,2,5,4,9,7,2}; 
 // 輸出數組中下標爲3的元素 
 System.out.println(array[3]);
更新元素

直接通過下標賦值

 int[] array = new int[]{3,1,2,5,4,9,7,2}; 
 // 給數組下標爲5的元素賦值 
 array[5] = 10; 
 // 輸出數組中下標爲5的元素 
 System.out.println(array[5])
插入元素

存在三種情況

  • 尾部插入
  • 中間插入
  • 超範圍插入

尾部插入

直接插入到尾部空閒位置,等同於更新元素

中間插入

先把插入元素以及後面的元素向後移動,再將要插入的元素放到對應的數據位置

超範圍插入

需要進行數組擴容:創建一個新的數組,再將舊數組的元素複製過去

刪除元素

將元素逐個向左移位

鏈表 (linked list)

鏈表是一種在物理上非連續、非順序的數據結構,由若干節點(node)所組成,在內存中的存儲方式是隨機存儲;
單向鏈表的每一節點又包含兩部分,一部分存放數據data,一部分是指向下一個節點的指針next;第一個節點成爲頭節點,最後一個節點稱爲尾節點
雙向列表不僅擁有data、next部分,還存放指向前置節點的prev指針

查找節點

只能根據頭節點開始向後一個一個節點逐一查找,時間複雜度:最壞的情況是O(n)

更新節點

如果不考慮查找節點的過程,鏈表更新直接替換新數據即可,時間複雜度O(n)

插入節點
  • 尾部插入:把最後一個節點的next指針指向新插入的節點即可
  • 頭部插入:1、把新節點的next指向原頭結點;2、把新節點變爲鏈表的頭節點
  • 中間插入:1、新節點的next指向插入位置的節點;2、插入位置的前一個節點指向新節點
刪除節點
  • 尾部刪除:尾節點直接指向空
  • 頭部刪除:將頭節點指向原頭節點的next指針
  • 中間刪除:將要刪除節點的前置節點指向要刪除節點的下一個節點

數組和鏈表的對比

查找 更新 插入 刪除
數組 O(1) O(1) O(n) O(n)
鏈表 O(n) O(1) O(1) O(1)
  • 數組適合讀取操作多、寫操作少的場景
  • 鏈表適合插入、刪除多的情況
  • 數組和鏈表都屬於“物理結構”,是存在的存儲結構;與之相對應的是邏輯結構,是抽象、依賴物理結構存在的

邏輯結構

  • 隊列 (就像一個不封底的兵乓球桶,)
  • 散列表

  • 就像一個封底的乒乓球桶,先放進去的後拿出來,即“先進後出”
  • 可以用數組或者鏈表實現
  • 入棧(push):只允許棧頂一側入棧,時間複雜度:O(1)
  • 出棧(pop):只允許棧頂元素出棧,時間複雜度:O(1)

隊列

  • 就像隧道,通過隧道的車輛只能從一邊出、一邊入,並且先駛入的先出來,不能“超車”,也不能“逆行”
  • 可以用數組或者鏈表實現
  • 入隊(enqueue):只允許在隊尾位置放入元素
  • 出隊(dequeue):只允許在隊頭一側移除元素
  • 循環隊列:使數組形式存在的隊列,在不斷的出隊入隊中維持隊列容量的恆定;具體操作:當隊列滿的時候,隊尾指針指向數組的首位,直到(隊尾指針+1)%數組長度 = 隊頭下標表示隊列真的存滿了

散列表(哈希表)

  • 存在 鍵-值的映射關係(Key-Vaule),時間複雜度接近於O(1)
  • 本質上也是數組,通過哈希函數將Key轉換成對應的下標
  • 通過 開放尋址法鏈表法來解決哈希衝突
寫操作
  • 通過哈希函數將key值轉換爲下標
  • 如果下標無元素,則將元素填充到該下標;如果該下標下已經有元素了(哈希衝突),則使用開發尋址法(尋找下一個空檔位置)或者鏈表法(將原元素的next下標指向要添加的元素)
寫操作
  • 通過哈希函數,將key轉化爲數組下標
  • 通過這個下標找到對應的元素,再通過鏈表一個個比對key值是否相等
擴容
  • 創建一個長度爲原數組兩倍的新的空數組
  • 遍歷所有元素,重新Hash後,添加到新數組中

應用

  • 棧的應用:遞歸、回溯歷史(回退棧)
  • 隊列的應用:對歷史的“回放”

例如在多線程中,爭奪公平鎖的等待隊列,就是按照訪問順序來決定線程在隊列中的
次序的。

  • 雙端隊列:可以在隊頭的一端入隊或出隊,也可以從隊尾的一端入隊或出隊
  • 優先隊列:優先級高的節點先出隊
  • 散列表代表:HashMap

  1. 有且僅有一個特定的稱爲根的節點。
  2. 當n>1時,其餘節點可分爲m(m>0)個互不相交的有限集,每一個集合本身又是一 個樹,並稱爲根的子樹

在這裏插入圖片描述
在這裏插入圖片描述

  • 節點1是根節點(root),節點5、6、7、8是樹的末端,沒有“孩子”,被 稱爲葉子節點(leaf)。圖中的虛線部分,是根節點1的其中一個子樹。
  • 節點4的上一級節點,是節點4的父節點(parent);從節點4衍生出來的 節點,是節點4的孩子節點(child);和節點4同級,由同一個父節點衍生出來的節點, 是節點4的兄弟節點(sibling)
  • 樹的最大層級數,被稱爲樹的高度或深度。顯然,上圖這個樹的高度是4。

二叉樹

  • 是樹的一種特殊的形式
  • 每個節點最多(0、1、2)有兩個子節點(左孩子、右孩子)
  • 滿二叉樹定義:一個二叉樹的所有非葉子節點都存在左右孩子,並且所有葉子節點都在同一層級上, 那麼這個樹就是滿二叉樹
  • 完全二叉樹:對一個有n個節點的二叉樹,按層級順序編號,則所有節點的編號爲從1到n。如果這 個樹所有節點和同樣深度的滿二叉樹的編號爲從1到n的節點位置相同,則這個二叉樹爲完 全二叉樹。

滿二叉樹和完全二叉樹的區別:滿二叉樹要求所有分支都是滿的;而完全 二叉樹只需保證最後一個節點之前的節點都齊全即可

鏈表實現

  • 存儲數據的data變量
  • 指向左孩子的left指針
  • 指向右孩子的right指針

數組實現
在這裏插入圖片描述
當子孩子沒有數據時數組相應的位置會空出來,可以方便計算節點位置

  • 當一個父節點下標是parent,則左孩子下標爲:2 * parent + 1;右孩子下標爲:2 * parent + 2
  • 如果一個左孩子的下標是leftChild,則父節點下標位 (leftChild - 1)/ 2
應用
  • 二叉查找樹
  • 也叫二叉排序樹,特點:
  • 如果左子樹不爲空,則左子樹上所有節點的值均小於根節點的值
  • 如果右子樹不爲空,則右子樹上所有節點的值均大於根節點的值
  • 左、右子樹也都是二叉查找樹
    自平衡:
    特殊情況下,會導致“失衡”,解決方法:自平衡(紅黑樹、AVL樹、樹堆)
二叉樹的遍歷
  • 前序遍歷:輸出順序 根節點 -> 左子樹 -> 右子樹
  • 中序遍歷:輸出順序 左子樹 -> 根節點-> 右子樹
  • 後序遍歷:輸出順序 左子樹 -> 右子樹 -> 根節點
  • 層序遍歷(廣度優先遍歷,一層層遍歷)
 /**
     * 按前序遍歷的順序構建二叉樹
     * @param inputList
     * @return
     */
    public static TreeNode createBinaryTree(LinkedList<Integer> inputList){

        if (inputList == null || inputList.isEmpty()) return null;

        TreeNode node = null;
        Integer data = inputList.removeFirst();
        if (data != null){
            node = new TreeNode(data);
            node.leftNode = createBinaryTree(inputList);
            node.rightNode = createBinaryTree(inputList);
        }


        return node;

    }

    /**
     * 二叉樹的前序遍歷
     * @param node
     */
    public static void preOrderTraveral(TreeNode node){

        if (node == null) return;

        System.out.print(node.data);
        preOrderTraveral(node.leftNode);
        preOrderTraveral(node.rightNode);

    }

    /**
     * 二叉樹的中序遍歷
     * @param node
     */
    public static void inOrderTraveral(TreeNode node){
        if (node == null) return;

        inOrderTraveral(node.leftNode);
        System.out.print(node.data);
        inOrderTraveral(node.rightNode);
    }

    /**
     * 二叉樹的後序遍歷
     * @param node
     */
    public static void postOrderTraveral(TreeNode node){
        if (node == null) return;

        postOrderTraveral(node.leftNode);
        postOrderTraveral(node.rightNode);
        System.out.print(node.data);
    }

二叉堆

本質上是一種完全二叉樹,有兩種類型:1. 最大堆 2.最小堆
最大堆:任何一個父節點的值,都大於或等於它左、右孩子節點 的值。
最小堆:的任何一個父節點的值,都小於或等於它左、右孩子節點的值。
兩類操作:“上浮”和下沉
操作:

  • 刪除:是單一節點的下沉,時間複雜度O(logn)
  • 插入:是單一節點的上浮,時間複雜度O(logn)
  • 構建:需要所有非葉子節點依次下沉,時間複雜度O(n)

應用:

  • 實現優先隊列
  • 堆排序
     /**
     * 堆的上浮操作
     * @param array 插入新數據後未調整的堆
     */
    public static void upAdjust(int[] array){
        int childIndex = array.length - 1;
        int parentIndex = (childIndex - 1)/2; // 找到父節點

        int temp = array[childIndex]; // temp 保存插入的葉子節點值,用於最後的賦值

        while (childIndex > 0 && temp < array[parentIndex]){
            array[childIndex] = array[parentIndex];
            childIndex = parentIndex;
            parentIndex = (childIndex - 1)/2;
        }

        array[childIndex] = temp;

    }


    /**
     * 堆的下沉操作
     * @param array 待調整的堆
     * @param parentIndex 要“下沉”的父節點
     * @param length 堆的有效長度
     */
    public static void downAdjust(int[] array,int parentIndex,int length){

        int temp = array[parentIndex];
        int childIndex = 2 * parentIndex + 1; // 找到左孩子

        while (childIndex < length){

            // 如果存在右孩子,且右孩子比左孩子小,將指針指向右孩子
            if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]){
                childIndex++;
            }

            // 如果父節點小於兩個子孩子的值,則跳出
            if (temp < array[childIndex]) break;

            array[parentIndex] = array[childIndex];
            parentIndex = childIndex;
            childIndex = 2 * parentIndex + 1;

        }

        array[parentIndex] = temp;
    }


    public static void buildHeap(int[] array){

        // 從最後一個非葉子節點開始,依次做“下沉”調整
        for (int i = (array.length - 2)/2;i >= 0;i--){
            downAdjust(array,i,array.length);
        }

    }
二叉堆的應用:優先隊列

隊列遵循先進先出(FIFO)原則,優先隊列不再遵循先進先出的原則,而是分爲兩種情況:

  • 最大優先隊列,無論入隊順序如何,都是當前最大的元素優先出隊
  • 最小優先隊列,無論入隊順序如何,都是當前最小的元素優先出

特性:
入隊:在數組末插入新節點,讓新節點“上浮”到合適的位置,時間複雜度:O(logn)
出隊:將堆頂的元素出棧,再將最後一個元素移到對頂,再進行“下沉”操作,時間複雜度:O(logn)

  private int[] array;
    private int size; // 當前隊列大小


    public PriorityQueue() {

        // 初始長度爲 32
        array = new int[32];

    }


    /**
     * 入隊
     * @param val
     */
    public void enqueue(int val){
        if (size > array.length) resize();

        array[size++] = val;
        HeapHelper.upAdjust(array,size); // 上浮調整,傳入有效長度
    }

    /**
     * 出隊
     * @return
     * @throws Exception
     */
    public int dequeue() throws Exception {
        if (size <= 0) throw new Exception("no more data");
        int head = array[0];
        array[0] = array[--size];
        HeapHelper.downAdjust(array,0,size);// 0:要下沉的節點,這裏是第一個,size:有效長度
        return head;

    }

樹知識點小節

這裏直接搬書裏的

  • 什麼是樹
    樹是n個節點的有限集,有且僅有一個特定的稱爲根的節點。當n>1時,其餘節點可 分爲m個互不相交的有限集,每一個集合本身又是一個樹,並稱爲根的子樹。
  • 什麼是二叉樹
    二叉樹是樹的一種特殊形式,每一個節點最多有兩個孩子節點。二叉樹包含完全二叉 樹和滿二叉樹兩種特殊形式。
  • 二叉樹的遍歷方式有幾種
    根據遍歷節點之間的關係,可以分爲前序遍歷、中序遍歷、後序遍歷、層序遍歷這4 種方式;從更宏觀的角度劃分,可以劃分爲深度優先遍歷和廣度優先遍歷兩大類。
  • 什麼是二叉堆
    二叉堆是一種特殊的完全二叉樹,分爲最大堆和最小堆。
    在最大堆中,任何一個父節點的值,都大於或等於它左、右孩子節點的值。
    在最小堆中,任何一個父節點的值,都小於或等於它左、右孩子節點的值。
  • 什麼是優先隊列
    優先隊列分爲最大優先隊列和最小優先隊列。
    在最大優先隊列中,無論入隊順序如何,當前最大的元素都會優先出隊,這是基於最 大堆實現的。
    在最小優先隊列中,無論入隊順序如何,當前最小的元素都會優先出隊,這是基於最 小堆實現的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章