《漫画算法-小灰的算法之旅》第1章-算法概述读书笔记
目錄
- 1. 前言
- 2. 正文
- 2.1 算法和數據結構
- 2.1.1 什么是算法?
- 2.1.2 衡量算法好壞最重要的標準有哪兩個?
- 2.1.3 什么是數據結構?
- 2.1.4 算法和數據結構有什么關系呢?
- 2.1.5 數據結構有哪些組成方式?
- 2.2 時間復雜度
- 2.2.1 如何評估算法時間上的優劣?
- 2.2.2 什么是基本操作或者說常數操作?
- 2.2.3 如何計算程序的基本操作次數?
- 2.2.4 什么是時間復雜度?
- 2.2.5 推導出時間復雜度的原則有哪些?
- 2.3 空間復雜度
- 2.3.1 什么是空間復雜度?
- 2.3.2 如何計算空間復雜度?
- 2.3.5 如何在時間復雜度和空間復雜度之間取舍?
- 3. 最后
- 參考
1. 前言
說實話,對于數據結構與算法,是存在畏懼心理的。但是,一點一點地學,總會能克服掉這種心理吧。
2. 正文
2.1 算法和數據結構
2.1.1 什么是算法?
算法,對應的英文單詞是 algorithm,最早來自數學領域。
在數學領域,算法是用于解決某一類問題的公式和思想。如等差數列的公式:
在計算機科學領域,算法的本質是一系列程序指令,用于解決特定的運算和邏輯問題。如給出一系列整數,找出最大的整數,或者給出一系列整數,按從小到大排序。
2.1.2 衡量算法好壞最重要的標準有哪兩個?
時間復雜度和空間復雜度。
考量一個算法好壞主要從算法所占用的時間和空間兩個維度去考量。
- 時間維度:是指執行當前算法所消耗的時間,通常用時間復雜度來描述;
- 空間維度:是指執行當前算法需要占用多少內存空間,通常用空間復雜度來描述。
2.1.3 什么是數據結構?
數據結構,對應的英文單詞是 data structure,是數據的組織、管理和存儲格式,使用數據結構的目的是為了更加高效地訪問和修改數據。
2.1.4 算法和數據結構有什么關系呢?
數據結構是算法的基石。如果把算法比作美麗靈動的舞者,那么數據結構就是舞者腳下廣闊而堅實的舞臺。
數據結構和算法是互不分開的。離開了算法,數據結構就顯得毫無意義;而沒有了數據結構,算法就沒有實現的條件了。在解決問題時,不同的算法會選用不同的數據結構。
2.1.5 數據結構有哪些組成方式?
- 線性結構:最簡單的基本數據結構,包括數組、鏈表,以及由它們衍生出來的棧、隊列、哈希表;
- 樹:相對復雜的基本數據結構,包括二叉樹,以及由它衍生出來的二叉堆等;
- 圖:更為復雜的基本數據結構;
- 其他數據結構:由基本數據結構變形而來,用于解決某些特定問題,如跳表、哈希鏈表、位圖等。
2.2 時間復雜度
2.2.1 如何評估算法時間上的優劣?
通過統計代碼的絕對執行時間:代碼的絕對執行時間只有在實際運行后才能得到,但是絕對執行時間會受到運行環境(機器性能的高低)和輸入規模(數據規模的大小)的影響,所以通過比較代碼的絕對執行時間來確定算法的時間復雜度有很大的局限性。
通過預估代碼的基本操作次數:這需要對一個算法流程非常熟悉,然后寫出這個算法流程中,發生了多少次基本執行操作,進而總結出基本操作次數的表達式。基本操作次數的表達式會轉化為時間復雜度指標來表示。
如果時間復雜度指標無法區分算法好壞,就需要通過實際運行代碼的方式來比較算法好壞了。
2.2.2 什么是基本操作或者說常數操作?
如果一個操作花費的時間是固定的并且和樣本的數據量沒有關系,就把這樣的操作稱為基本操作,或常數操作。
常見的基本操作有:
- 從數組里面取出一個元素;
- 加減乘除運算;
- 位運算。
常見的非基本操作有:
- 從鏈表上面取出一個元素(跟鏈表長度有關);
- 在數組上查找最小值(跟數組長度有關)。
2.2.3 如何計算程序的基本操作次數?
這里分場景來舉例說明。
場景1:
從鏈表中取出第 i 個元素,每次查找下一個節點耗時為 1 時間單位:
public static void case1() {LinkedList<Integer> list = new LinkedList<>();for (int i = 0; i < 2000; i++) {list.add(i);}// 取出第 1 個元素Integer a = list.get(0);// 取出第 999 個元素Integer c = list.get(999); }則基本操作次數表達式 T(n) = n,因為第 n 個元素需要從鏈表頭查找 n 次才可以獲取到。
場景2:
給定一個整數 n,求初始值為 1 的整數乘以多少次 2 才可以達到 n:
這里我們把循環內的休眠時間和 i = i * 2 作為一個大的常數操作,而忽略掉 i < n 這個常數操作時間。
可以知道,
如果 n = 2,則 T = 1;
如果 n = 4,則 T = 2;
如果 n = 16,則 T = 4;
總結一下,T(n) = log2^n;
場景3:
從數組中取出第 i 個元素,每次挪動指針耗時為 1 時間單位:
T(n) = 1 時間單位。
場景4:
打印 n 行 n 列的星號:
打印第 1 行,需要 1 次 int i = 1; 賦值操作;1 次 i <= n 比較;n 次 j <= n 比較;n 次 j++ 操作;n 次 System.out.print("*");,1 次System.out.println(); 換行操作 ,總共需要 3n + 3 次常數操作;
打印第 2 行,需要 1 次 i++ 操作;1 次 i <= n 比較; n 次 j <= n 比較;n 次 j++ 操作;n 次 System.out.print("*");,1 次System.out.println(); 換行操作 ,總共需要 3n + 3 次常數操作;
…
打印第 n 行,需要 1 次 i++ 操作;1 次 i <= n 比較; n 次 j <= n 比較;n 次 j++ 操作;n 次 System.out.print("*");,1 次System.out.println(); 換行操作 ,總共需要 3n + 3 次常數操作。
最后還需要 1 次 i <= n 比較;結束循環。
所以,總共需要的常數操作次數 T(n) = (3n + 3) * n + 1 = 3n^2 + 3n + 1。
2.2.4 什么是時間復雜度?
若存在函數 f(n),使得當 n 趨于無窮大時,T(n) / f(n) 的極限值為不等于零的常數,則稱 f(n) 是 T(n) 的同數量級函數。記作 T(n) = O(f(n)),稱為O(f(n)),O為算法的漸進時間復雜度,簡稱為時間復雜度。
因為漸進時間復雜度用大寫O來表示,所以也被稱為大O表示法。大O用來表示上界,表示了算法的最壞情況運行時間。
2.2.5 推導出時間復雜度的原則有哪些?
- 如果運行時間是常數量級,則用常數 1 表示;
- 只保留時間函數中的最高階項;
- 如果最高階項存在,則省去最高階項前面的系數。
場景1:T(n) = n。
不是常數量級,最高階項是 n,則轉化的時間復雜度為:T(n)=O(n);
場景2:T(n) = log2^n。
不是常數量級,最高階項是 log2^n,省略常數 2,則轉化的時間復雜度為:T(n)=O(logn);
場景3:T(n) = 1。
是常數量級,則轉化的時間復雜度為:T(n)=O(1);
場景4:T(n) = 3n^2 + 3n + 1。
不是常數量級,最高階項是 3n^2,去除系數 3,則轉化的時間復雜度為:T(n)=O(n ^ 2);
2.3 空間復雜度
2.3.1 什么是空間復雜度?
空間復雜度是對一個算法在運行過程中臨時占用存儲空間大小的量度,同樣使用大O(讀作歐,不是零)表示法。
程序占用空間大小的計算公式記作 S(n) = O(f(n)),其中 n 為問題的規模,f(n) 為算法所占存儲空間的函數。
2.3.2 如何計算空間復雜度?
這里需要分情況來說明。
場景1:常量空間
當算法分配的存儲空間大小是固定的,和輸入規模沒有直接的關系時,空間復雜度記作 O(1)。如:
public static void case1(int n) {// 給變量 i 分配的存儲空間大小是固定的,跟輸入規模 n 沒有直接的關系。int i = 0;for (; i < n; i++) {// 遍歷元素。} }場景2:線性空間
當算法分配的存儲空間是一個線性的集合(如數組或鏈表),并且集合大小和輸入規模 n 成正比時,空間復雜度記作 O(n)。如:
public static void case2(int n) {int[] arr = new int[n]; }場景3:二維空間
當算法分配的存儲空間是一個二維數組集合,并且二維數組的行和列都與輸入規模 n 成正比時,空間復雜度記作 O(n^2)。如:
public static void case3(int n) {int[][] arr = new int[n][n]; }場景4:遞歸空間
計算機在執行程序時,會專門分配一塊內存,用來存儲方法調用棧(棧是一種先進后出的數據結構,或者說后進先出的數據結構)。
方法調用棧包括進棧和出棧兩種操作。
- 進棧:當進入一個方法時,執行進棧操作,把調用的方法和參數信息(棧幀)壓入棧中。
- 出棧:當一個方法返回時,執行出棧操作,把調用的方法和參數信息(棧幀)從棧中彈出。
當 n = 1 時,會有 fibonacci 方法和 n = 1 入棧:在方法內部,滿足 n <= 1,所以直接返回,有 1 個棧幀;
當 n = 2 時,會有 fibonacci 方法和 n = 2 入棧:在方法內部,fibonacci 方法和 n = 0 入棧,fibonacci 方法和 n = 1 入棧,總共會有 3 個棧幀;
當 n = 3 時,會有 fibonacci 方法和 n = 3 入棧:在方法內部,fibonacci 方法和 n = 1 入棧,fibonacci 方法和 n = 2 入棧,總共會有 5 個棧幀;
當 n = 4 時,會有 fibonacci 方法和 n = 4 入棧:在方法內部,fibonacci 方法和 n = 2 入棧,fibonacci 方法和 n = 3 入棧,總共會有 9 個棧幀;
當 n = 5 時,會有 fibonacci 方法和 n = 5 入棧:在方法內部,fibonacci 方法和 n = 3 入棧,fibonacci 方法和 n = 4 入棧,總共會有 15 個棧幀。
當 n = 6 時,會有 fibonacci 方法和 n = 6 入棧:在方法內部,fibonacci 方法和 n = 4 入棧,fibonacci 方法和 n = 5 入棧,總共會有 25 個棧幀。
S(n) ≈ (n - 1) ^ 2 = O(n^2);
再舉一個遞歸的例子:求和
public static int sum(int n) {if (n <= 1) {return 1;}return n + sum(n - 1); }當 n = 1 時,有 sum 方法和 n = 1 入棧,方法內部:滿足 n <= 1,直接返回??偣灿?1 個棧幀;
當 n = 2 時,有 sum 方法和 n = 2 入棧,方法內部:有 sum 方法和 n = 1 入棧??偣灿?2 個棧幀;
當 n = 3 時,有 sum 方法和 n = 3 入棧,方法內部:有 sum 方法和 n = 2 入棧。總共有 3 個棧幀;
所以,S(n) = n = O(n);
2.3.5 如何在時間復雜度和空間復雜度之間取舍?
在絕大多數時候,時間復雜度更重要一些,也就是說,哪怕要多分配一些內存空間,也要提升程序的執行速度。
比如,查找一個數組中的重復數字的例子。
采用犧牲時間換空間的實現如下:
/*** i = 0 時,外部循環:1 次 int i = 0; 操作,1 次 i < arr.length; 操作,內部循環:1 次 int j = 0; 操作,1 次 j < i; 操作,共 4 次操作* i = 1 時,外部循環:1 次 i++ 操作,1 次 i < arr.length; 操作,內部循環:1 次 int j = 0; 操作,2 次 j < i; 操作,1 次比較操作,1 次 j++ 操作,共 7 次操作* i = 2 時,外部循環:1 次 i++ 操作,1 次 i < arr.length; 操作,內部循環:1 次 int j = 0; 操作,3 次 j < i; 操作,2 次比較操作,2 次 j++ 操作,共 10 次操作* i = n 時,外部循環:1 次 i++ 操作,1 次 i < arr.length; 操作,內部循環:1 次 int j = 0; 操作,n + 1 次 j < i; 操作,n 次比較操作,n 次 j++ 操作,共 3n + 4 次操作* 總共:T(n) = (3n + 4)*n / 2 = 1.5n^2 + 2n = O(n^2)*/ public static void findTheSameTwoNumber1() {int[] arr = new int[]{3, 1, 2, 5, 4, 9, 7, 2};for (int i = 0; i < arr.length; i++) {for (int j = 0; j < i; j++) {if (arr[j] == arr[i]) {System.out.println("the same number is: " + arr[j]);break;}}} } /**時間復雜度是 O(n^2),空間復雜度是 O(1)。
采用犧牲空間換時間的實現如下:
/*** i = 0 時,1 次 int i = 0; 操作,1 次 i < arr.length; 操作,1 次 hashSet.add(arr[i]) 操作,共 3 次操作* i = 1 時,1 次 i++ 操作,1 次 i < arr.length; 操作,1 次 hashSet.add(arr[i]) 操作,共 3 次操作* i = n 時,1 次 i++ 操作,1 次 i < arr.length; 操作,1 次 hashSet.add(arr[i]) 操作,共 3 次操作* 所以 T(n) = 3n = O(n);*/ public static void findTheSameTwoNumber2() {int[] arr = new int[]{3, 1, 2, 5, 4, 9, 7, 2};// 借助中間數據,一個哈希集合,需要開辟一定的內存空間來存儲有用的數據信息。HashSet<Integer> hashSet = new HashSet<>();for (int i = 0; i < arr.length; i++) {if (!hashSet.add(arr[i])) {System.out.println("the same number is: " + arr[i]);break;}} }時間復雜度是 O(n),空間復雜度是 O(1)。
3. 最后
本文主要學習時間復雜度和空間復雜度的概念以及例子。
參考
- 關于時間復雜度,你不知道的都在這里!;
- 什么是大O表示法;
- 三種表示方法:O, Ω, Θ
總結
以上是生活随笔為你收集整理的《漫画算法-小灰的算法之旅》第1章-算法概述读书笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mac下关于node版本的切换
- 下一篇: 炒币这么久,你是否从未看见过区块链世界的