第五章 數組:爲什麼很多編程語言中數組都從0開始編號?
1.數組的定義
數組是一種線性表數據結構,他用一組連續的內存空間,來存儲相同類型的數據
2.數組爲什麼可以根據下標隨機訪問數組元素
(這裏要注意不根據下標是不能隨機訪問的啊)
假設一個長度爲10的int型數組,會分配一塊連續內存空間 1000~1039,其中,內存塊首地址是1000
我們可以通過以下公式快速獲取到指定下標的元素
a[i]_address = base_address + i * data_type_size
base_address 就是首地址 1000, i爲我們要獲取的下標,.data_type_size爲每個元素的字節大小,這裏int爲4個字節
比如我們要找第1個元素,爲1000+1*4 = 1004
這裏其實可以解釋爲什麼數組從0開始
因爲如果從1開始 上面的公式就會變成
a[i]_address = base_address + (i -1)* data_type_size
CPU在執行指令的時候 還需要多執行一步減1的操作,雖然影響不大,但數組做爲一個底層的數據結構,應該儘量做到優化
3.數組的插入,刪除
數組在插入和刪除的時候會需要移動插入或刪除目標位置後面的元素.這樣最好情況下時間複雜度爲O(1),最壞爲O(n)
深入思考
但是我們換個角度看,如果換個角度,如果數組不要求保證元素的順序性,比如在插入元素到指定下標i的時候,我們可以直接找到下標爲i的原有元素然後將他放在數組最後,然後將要插入的元素直接放在空出來的i的位置.這樣時間複雜度將變O(1)
同樣的當我們要對數組進行多次刪除時.我們可以先不進行刪除,只是記錄下要刪除的下標,等到數組容量不夠時,我們一次性進行刪除.
這就有點類似我們JVM中的標記清除.
第六章 鏈表(上) 利用鏈表實現;LRU緩存淘汰機制
一.鏈表的分類
1.單向鏈表
內存塊稱爲鏈表的“結點”,第一個結點叫作頭結點,最後一個結點叫作尾結點,記錄下個結點地址的指針叫作後繼指針 next
鏈表的優勢在於插入刪除操作簡單.只需要控制前後節點的改變即可時間複雜度爲O(1)
2.循環鏈表
循環鏈表是一種特殊的單鏈表,循環鏈表的尾結點指針是指向鏈表的頭結點
3.雙向鏈表
支持兩個方向,每個結點不止有一個後繼指針 next 指向後面的結點,還有一個前驅指針 prev 指向前面的結點。
雙向鏈表相比於單向鏈表的優勢在於,他記錄了前指針,也就是可以快速的找到前面的節點,單向鏈表雖然刪除操作快,但是前提要知道前和後節點,在已知當前節點要刪除或插入位置時,還得遍歷鏈表找到後繼節點爲目標節點的節點,也就是前節點,這個操作時間複雜度是O(N)
4.雙向循環鏈表
二.鏈表和數組的區別
兩者都是線性表結構.不同點在於數組是一組連續的內存空間,而鏈表是一組分散的內存空間,假設內存中剩下100M 但這100M不是連續的內存空間,數組就無法存入,而鏈表可以存入,同時數組需要制定初始空間,設置過大會浪費內存,設置過小就需要設置新的更大空間的數組,然後將原有數組拷貝到新的數組,這個操作非常耗時,即使arrayList可以動態擴容,但是我們如果我們儲存的數據大小達到1G,這個時候數組空間不足,自動擴容爲1.5G,此時還需要複製拷貝,可以想象下時間耗費是多大.
數組可以根據下標快速隨機訪問 時間複雜度是O(1),但是鏈表必須遍歷查詢時間複雜度是O(n),反之數組插入刪除的時間複雜度是O(n),鏈表的時間複雜度爲O(1).
三.使用鏈表實現LUA緩存淘汰算法
LUA常見淘汰策略有3種,先進先出,最少使用,最近最少使用.
我們維護一個有序單鏈表,越靠近鏈表尾部的結點是越早之前訪問的。當有一個新的數據被訪問時,我們從鏈表頭開始順序遍歷鏈表。
1. 如果此數據之前已經被緩存在鏈表中了,我們遍歷得到這個數據對應的結點,並將其從原來的位置刪除,然後再插入到鏈表的頭部。
2. 如果此數據沒有在緩存鏈表中,又可以分爲兩種情況:
-
如果此時緩存未滿,則將此結點直接插入到鏈表的頭部;
-
如果此時緩存已滿,則鏈表尾結點刪除,將新的數據結點插入鏈表的頭部。
這樣我們就用鏈表實現了一個 LRU 緩存
第七章 鏈表(下):如何輕鬆寫出正確的鏈表代碼?
*
-
單鏈表反轉
-
鏈表中環的檢測
-
兩個有序的鏈表合併
-
刪除鏈表倒數第 n 個結點
-
求鏈表的中間結點