极客时间 算法训练营 第一周总结
學(xué)習(xí)總結(jié)
學(xué)習(xí)內(nèi)容
課程內(nèi)容
- 第三課
- 數(shù)組
- 鏈表
- 跳表
- 第四課
- 棧
- 隊(duì)列
知識(shí)點(diǎn)總結(jié)
數(shù)組
數(shù)組用一塊連續(xù)的內(nèi)存空間,來(lái)存儲(chǔ)相同類型的一組數(shù)據(jù)。
支持隨機(jī)訪問(wèn),時(shí)間復(fù)雜度 O(1)
插入、刪除操作比較低效,為了滿足連續(xù)空間需要進(jìn)行數(shù)據(jù)的搬移,平均情況時(shí)間復(fù)雜度為O(n)
鏈表
鏈表內(nèi)存空間可以不連續(xù)
鏈表類型有:單鏈表、雙向鏈表、循環(huán)鏈表、雙向循環(huán)鏈表等
更適合插入、刪除操作頻繁的場(chǎng)景,時(shí)間復(fù)雜度 O(1)
但是訪問(wèn)時(shí)需要遍歷鏈表 ,平均情況時(shí)間復(fù)雜度為O(n)
某些情況下雙向鏈表的訪問(wèn)比單鏈表更高效,如指定訪問(wèn)某個(gè)節(jié)點(diǎn)前面的節(jié)點(diǎn)
為了提高訪問(wèn)效率,用空間換時(shí)間的設(shè)計(jì)思路出現(xiàn)跳表
跳表
通過(guò)空間換時(shí)間,構(gòu)建多級(jí)索引來(lái)提高查詢的效率,實(shí)現(xiàn)了基于鏈表的“二分查找”
是一種動(dòng)態(tài)數(shù)據(jù)結(jié)構(gòu),支持快速的插入、刪除、查找操作,時(shí)間復(fù)雜度為O(nlogn)
棧
棧是一種受限的數(shù)據(jù)結(jié)構(gòu),只支持入棧和出棧操作,其入棧、出棧時(shí)間復(fù)雜度為O(1)。特點(diǎn)是先入后出。
隊(duì)列
特點(diǎn)是先入先出,常見(jiàn) 有 優(yōu)先隊(duì)列,雙端隊(duì)列
關(guān)于迭代和遞歸的一些總結(jié)
個(gè)人理解如果不同意見(jiàn)歡迎探討,如果錯(cuò)誤歡迎指正
迭代和遞歸都是要找到其重復(fù)處理單元,不同的地方是
迭代:思路是從前向后,大多數(shù)情況下使用哨兵節(jié)點(diǎn)能簡(jiǎn)化處理流程
遞歸:思路是從后向前,僅關(guān)注當(dāng)前層和下一層,重復(fù)操作就不要向下考慮太多不然容易暈
遞歸
先找重復(fù)處理單元,再找遞歸終止條件,解決了這兩個(gè)問(wèn)題就很好處理了
就鏈表處理,我將遞歸相關(guān)題目歸納為以下幾點(diǎn):
- 先找重復(fù)處理單元
- 遞歸終止條件
- 遞歸前處理(遞歸到下一層前處理):
- 遞歸(遞歸到下一層):
- 遞歸后處理(下一層遞歸返回后處理)
具體可以參看下面的例子
迭代
同樣先找重復(fù)處理單元,再找迭代終止條件
就鏈表處理,我將迭代相關(guān)題目歸納為以下幾點(diǎn):
- 先找重復(fù)處理單元
- 迭代終止條件
- 迭代前處理
- 迭代處理
- 迭代后處理
具體可以參看下面的例子
例子
例子 24. 兩兩交換鏈表中的節(jié)點(diǎn)
遞歸實(shí)現(xiàn)
#@author:leacoder #@des: 遞歸實(shí)現(xiàn) 兩兩交換鏈表中的節(jié)點(diǎn)'''重復(fù)處理單元: 兩兩交換 鏈表可以分為 待處理 + 當(dāng)前層正處理(兩兩交換的兩個(gè)節(jié)點(diǎn)) + 下層遞歸已處理下一層遞歸返回已處理頭結(jié)點(diǎn) 當(dāng)前層就知道: 需要交換的 a、b 兩節(jié)點(diǎn) 以及 后續(xù)已處理頭節(jié)點(diǎn)遞歸終止條件: head.next 為 None 可以將 head 為 None 的特殊情況一起做判斷 if not head or not head.next:return head遞歸前處理(遞歸到下一層前處理): 這里兩兩交換鏈表中的節(jié)點(diǎn) , 下一次遞歸從 下 兩個(gè)的結(jié)點(diǎn)開始 題 25. K 個(gè)一組翻轉(zhuǎn)鏈表 則另一種處理 可以記錄下 當(dāng)前的 next next = head.next遞歸(遞歸到下一層): 下兩位,使用遞歸前處理的 next p = swapPairs(next.next) 題 25. K 個(gè)一組翻轉(zhuǎn)鏈表 則 會(huì)有不同 遞歸后處理(下層遞歸返回后處理):1->2->3->4 2->1->4->3''' class Solution:def swapPairs(self, head: ListNode) -> ListNode:# 遞歸終止條件:if not head or not head.next:return head# 遞歸前處理(遞歸到下一層前處理):# 當(dāng)前層 需交換節(jié)點(diǎn) 為 head (a) 、head.next (b)next = head.next# 遞歸(遞歸到下一層):resultmp = self.swapPairs(next.next) # 下移兩位 返回值為兩兩交換后 兩個(gè)節(jié)點(diǎn)中的前一個(gè)節(jié)點(diǎn)# 遞歸后處理(下層遞歸返回后處理):head.next = resultmp # 兩兩交換next.next = head # 兩兩交換''' 語(yǔ)法糖head.next, next.next = self.swapPairs(next.next), head'''return next # 返回 兩兩交換后 兩個(gè)節(jié)點(diǎn)中的前一個(gè)節(jié)點(diǎn)迭代實(shí)現(xiàn)
#@author:leacoder #@des: 迭代實(shí)現(xiàn) 兩兩交換鏈表中的節(jié)點(diǎn)''' 利用哨兵簡(jiǎn)化操作1->2->3->42->1->4->3重復(fù)處理單元: 兩兩交換 鏈表可以分為 已處理 + 正處理(兩兩交換的兩個(gè)節(jié)點(diǎn)) + 后續(xù)迭代待處理迭代終止條件: 兩兩交換,迭代處理這兩個(gè)節(jié)點(diǎn),那么后續(xù)節(jié)點(diǎn)不足兩個(gè)時(shí)跳出迭代 prev.next ,prev.next.next 為 None 迭代前處理: 利用哨兵簡(jiǎn)化操作,添加哨兵節(jié)點(diǎn) prev 最終結(jié)果 以 2->1->4->3 為例 其頭節(jié)點(diǎn)為 2 由于兩兩交換 prev 也會(huì)跟隨下移,先 用 retult = prev 記錄prev下移前的值用于最終結(jié)果返回迭代處理:1->2->3->4 為例 將需要翻轉(zhuǎn)操作的兩個(gè)節(jié)點(diǎn)記錄(要斷開連接,先記錄) a = prev.next (1) b = prev.next.next (2) 交換兩節(jié)點(diǎn) prev 節(jié)點(diǎn)下移,迭代開始新一輪處理 (由于兩兩交換,跳過(guò)兩個(gè)節(jié)點(diǎn))迭代后處理: 無(wú)特殊處理'''class Solution:def swapPairs(self, head: ListNode) -> ListNode:# 迭代前處理:prev = ListNode(None) # 哨兵prev.next = headretult = prev # 記錄 第一次被操作前的prev 用于返回結(jié)果# 迭代終止條件 prev.next ,prev.next.next 為 None while prev.next and prev.next.next: # 迭代處理:# 以 1 2 反轉(zhuǎn)為例,prev為哨兵a = prev.next # 由于 要操作 prev.next (1) 和 prev.next.next (2) 先記錄下來(lái)b = prev.next.next # 記錄 要交換的兩節(jié)點(diǎn)prev.next = b # (2)a.next = b.next # (3)b.next = a # (1)prev = a # (1) # 向下移動(dòng)2位''' 使用語(yǔ)法糖prev.next,a.next,b.next= b,b.next,a # 2個(gè)節(jié)點(diǎn)交換,注意哨兵的next改變了prev = a # 向下移動(dòng)2位'''# 迭代后處理 無(wú)return retult.next例子 25. K 個(gè)一組翻轉(zhuǎn)鏈表
遞歸實(shí)現(xiàn)
#@author:leacoder #@des: 遞歸實(shí)現(xiàn) k個(gè)一組翻轉(zhuǎn)鏈表'''重復(fù)處理單元: k個(gè)一組翻轉(zhuǎn) 鏈表可以分為 : 待處理 + 當(dāng)前層正處理(k個(gè)一組鏈表) + 下層遞歸已處理下一層遞歸返回已處理頭結(jié)點(diǎn) 當(dāng)前層就知道: 需要翻轉(zhuǎn)k個(gè)一組鏈表的鏈表頭、鏈表尾 鏈表頭 head 參數(shù) 鏈表尾 遍歷 k 個(gè)節(jié)點(diǎn)遞歸終止條件: 后續(xù)迭代待處理剩余節(jié)點(diǎn)不足 k 個(gè)遞歸前處理(遞歸到下一層前處理): k個(gè)一組翻轉(zhuǎn)鏈表,下一次遞歸從 下 k 個(gè)節(jié)點(diǎn)開始 記錄 翻轉(zhuǎn)前的 鏈表頭 鏈表尾遞歸(遞歸到下一層): 下k位,使用遞歸前處理的 鏈表尾.next遞歸后處理(下層遞歸返回后處理): 將下一層遞歸返回結(jié)果加入到原鏈表 K 個(gè)一組鏈表翻轉(zhuǎn),''' class Solution:def reverseKGroup(self, head: ListNode, k: int) -> ListNode:# 用于記錄 翻轉(zhuǎn)前的 鏈表頭 鏈表尾end = headcount = 0# 遞歸終止條件while end and count!= k:end = end.nextcount += 1if count == k: # 遞歸終止條件# 遞歸前處理(遞歸到下一層前處理) # 記錄 翻轉(zhuǎn)前的 鏈表頭 head 鏈表尾 end 在前面已做# 遞歸(遞歸到下一層):resultmp = self.reverseKGroup(end,k) # 下層 已處理頭結(jié)點(diǎn) # 遞歸后處理(下層遞歸返回后處理)while count: # K 個(gè)一組鏈表翻轉(zhuǎn),tmp = head.nexthead.next = resultmp # 將下一層遞歸返回結(jié)果加入到原鏈表resultmp = headhead = tmpcount -= 1head = resultmp # 翻轉(zhuǎn)后鏈表頭return head迭代實(shí)現(xiàn)
#@author:leacoder #@des: 迭代實(shí)現(xiàn) k個(gè)一組翻轉(zhuǎn)鏈表''' 利用哨兵簡(jiǎn)化操作 prev = ListNode(None) # 哨兵 prev.next = head prev 每次迭代下移(下移位數(shù)為k)重復(fù)處理單元: k個(gè)一組翻轉(zhuǎn) 鏈表可以分為 :已處理 + 正處理(k個(gè)一組鏈表) + 后續(xù)迭代待處理 正處理(k個(gè)一組鏈表)需要知道 鏈表頭和鏈表尾 鏈表頭 可以通過(guò) prev 的 next 獲取 鏈表尾 需要遍歷k個(gè)節(jié)點(diǎn)獲取迭代終止條件: 后續(xù)迭代待處理剩余節(jié)點(diǎn)不足 k 個(gè)迭代前處理: result 記錄 prev 下移前的值 用于 結(jié)果返回 end 參數(shù) 記錄尾節(jié)點(diǎn) prev、end 每次迭代指向同一節(jié)點(diǎn)迭代處理: 遍歷k個(gè)節(jié)點(diǎn)獲取 鏈表尾 構(gòu)建 k個(gè)一組鏈表 并進(jìn)行翻轉(zhuǎn) reverse() reverse() 翻轉(zhuǎn)函數(shù)返回 翻轉(zhuǎn)后新鏈表頭 將翻轉(zhuǎn)后新鏈接入原鏈表 prev 下移 end 下移迭代后處理: 無(wú)'''class Solution:def reverseKGroup(self, head: ListNode, k: int) -> ListNode:# 迭代前處理:prev = ListNode(None) # 哨兵prev.next = headresult = prevend = prev # end 記錄 k個(gè)一組鏈表 鏈表尾 prev.next 記錄 k個(gè)一組鏈表 鏈表頭# 迭代終止條件:while(end.next): # 剩余節(jié)點(diǎn)不足 k 個(gè)for i in range(k): # 遍歷k個(gè)節(jié)點(diǎn)獲取 鏈表尾 并 判斷 剩余節(jié)點(diǎn) 是否不足 k 個(gè)if not end: # 不足 k 個(gè)breakend = end.nextif not end: # 不足 k 個(gè)break'''start = prev.next # k個(gè)一組鏈表 鏈表頭next = end.next # 記錄 原 end.next end.next = None # k個(gè)一組鏈表 鏈表尾'''# 構(gòu)建 k個(gè)一組的鏈表start, next, end.next = prev.next, end.next, None# 翻轉(zhuǎn) k個(gè)一組的鏈表prev.next = self.reverse(start) # 返回 翻轉(zhuǎn)后新鏈表頭 接入原鏈表start.next = next # 接入原鏈表 start 為 翻轉(zhuǎn)后新鏈表尾prev = startend = startreturn result.nextdef reverse(self, head: ListNode):prev = Nonecurr = headwhile(curr):'''tmp = curr.nextcurr.next = prevprev = currcurr = tmp'''curr.next, prev, curr = prev, curr, curr.nextreturn prev課后作業(yè) 改寫Deque的代碼
課程中示例代碼-Deque
代碼
import java.util.LinkedList; import java.util.Deque;public class DequeExample {public static void main(String[] args) {Deque<String> deque = new LinkedList<String>();deque.push("a");deque.push("b");deque.push("c");System.out.println(deque);String str = deque.peek();System.out.println(str);System.out.println(deque);while (deque.size() > 0) {System.out.println(deque.pop());}System.out.println(deque);} }輸出:
[c, b, a] c [c, b, a] c b a []接口說(shuō)明
push
public void push?(E e)
Pushes an element onto the stack represented by this list. In other words, inserts the element at the front of this list. This method is equivalent to addFirst(E).將元素壓入此列表表示的堆棧中。換句話說(shuō),將元素插入此列表的前面。
此方法等效于addFirst(E)
pop
public E pop()
Pops an element from the stack represented by this list. In other words, removes and returns the first element of this list. This method is equivalent to removeFirst().從此列表表示的堆棧中彈出一個(gè)元素。換句話說(shuō),刪除并返回此列表的第一個(gè)元素。
此方法等效于equivalent to removeFirst().
peek
public E peek()
Retrieves, but does not remove, the head (first element) of this list.檢索但不刪除此列表的頭(第一個(gè)元素)。
addFirst 改寫 Deque 代碼
代碼
import java.util.LinkedList; import java.util.Deque;public class DequeExample {public static void main(String[] args) {Deque<String> deque = new LinkedList<String>();deque.addFirst("a"); // 使用 addFirst 替換 pushdeque.addFirst("b");deque.addFirst("c");System.out.println(deque);String str = deque.peek();System.out.println(str);System.out.println(deque);while (deque.size() > 0) {System.out.println(deque.removeFirst()); // 使用 removeFirst 替換 pop}System.out.println(deque);} }輸出:
[c, b, a] c [c, b, a] c b a []接口說(shuō)明
addFirst
void addFirst?(E e)
Inserts the specified element at the front of this deque if it is possible to do so immediately without violating capacity restrictions, throwing an IllegalStateException if no space is currently available. When using a capacity-restricted deque, it is generally preferable to use method offerFirst(E).如果可以在不違反容量限制的情況下立即執(zhí)行此操作,則將指定的元素插入此雙端隊(duì)列的前面,如果當(dāng)前沒(méi)有可用空間,則拋出IllegalStateException。使用容量受限的雙端隊(duì)列時(shí),通常最好使用方法offerFirst(E)。
removeFirst
E removeFirst()
Retrieves and removes the first element of this deque. This method differs from pollFirst only in that it throws an exception if this deque is empty.檢索并刪除此雙端隊(duì)列的第一個(gè)元素。此方法與pollFirst的不同之處僅在于,如果此雙端隊(duì)列為空,則它將引發(fā)異常。
Queue和PriorityQueu源碼分析
什么是 Queue
數(shù)據(jù)結(jié)構(gòu)中的隊(duì)列,先進(jìn)先出式的數(shù)據(jù)結(jié)構(gòu),所有新元素都插入隊(duì)列的末尾,移除元素都移除隊(duì)列的頭部。主要注意的時(shí),Java中的Queue是一個(gè)接口。
Queue 源碼分析
public interface Queue<E> extends Collection<E> {boolean add(E e); //往隊(duì)列插入元素,如果出現(xiàn)異常會(huì)拋出異常boolean offer(E e); //往隊(duì)列插入元素,如果出現(xiàn)異常則返回falseE remove(); //移除隊(duì)列元素,如果出現(xiàn)異常會(huì)拋出異常E poll(); //移除隊(duì)列元素,如果出現(xiàn)異常則返回nullE element(); //獲取隊(duì)列頭部元素,如果出現(xiàn)異常會(huì)拋出異常E peek(); //獲取隊(duì)列頭部元素,如果出現(xiàn)異常則返回null }上面六個(gè)函數(shù)總體上分為兩類:安全的會(huì)進(jìn)行容量檢查的(add,remove,element),如果隊(duì)列沒(méi)有值,則取元素會(huì)拋出IlleaglStatementException異常。不安全的不進(jìn)行容量控制的(offer,poll,peek )。
AbstractQueue 是Queue 的抽象實(shí)現(xiàn)類, AbstractQueue 也繼承自 AbstractCollection 。AbstractQueue 實(shí)現(xiàn)的方法不多,主要就 add、remove、element 三個(gè)方法的操作失敗拋出了異常。
什么是 PriorityQueue
PriorityQueue 優(yōu)先級(jí)隊(duì)列, ,不同于普通的遵循FIFO(先進(jìn)先出)規(guī)則的隊(duì)列,每次都選出優(yōu)先級(jí)最高的元素出隊(duì),優(yōu)先隊(duì)列里實(shí)際是維護(hù)了這樣的一個(gè)堆,通過(guò)堆使得每次取出的元素總是最小的(用戶可以自定義比較方法,相當(dāng)于用戶設(shè)定優(yōu)先級(jí))。 PriorityQueue 是一個(gè)小頂堆,是非線程安全的;PriorityQueue不是有序的,只有堆頂存儲(chǔ)著最小的元素;從數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu)看, PriorityQueue 一個(gè)數(shù)組;從邏輯結(jié)構(gòu)看, PriorityQueue 是一棵平衡二叉樹。
什么是 PriorityQueue
PriorityQueue 優(yōu)先級(jí)隊(duì)列, ,不同于普通的遵循FIFO(先進(jìn)先出)規(guī)則的隊(duì)列,每次都選出優(yōu)先級(jí)最高的元素出隊(duì),優(yōu)先隊(duì)列里實(shí)際是維護(hù)了這樣的一個(gè)堆,通過(guò)堆使得每次取出的元素總是最小的(用戶可以自定義比較方法,相當(dāng)于用戶設(shè)定優(yōu)先級(jí))。 PriorityQueue 是一個(gè)小頂堆,是非線程安全的;PriorityQueue不是有序的,只有堆頂存儲(chǔ)著最小的元素;從數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu)看, PriorityQueue 一個(gè)數(shù)組;從邏輯結(jié)構(gòu)看, PriorityQueue 是一棵平衡二叉樹。
PriorityQueue 源碼分析
繼承關(guān)系
public class PriorityQueue<E> extends AbstractQueue<E>implements java.io.Serializable{// 略}PriorityQueue 繼承了AbstractQueue 類,而AbstractQueue 類實(shí)現(xiàn)了Queue接口。
主要屬性
// 默認(rèn)容量 private static final int DEFAULT_INITIAL_CAPACITY = 11; // 存儲(chǔ)元素的數(shù)組,Object類型的 transient Object[] queue; // 元素個(gè)數(shù) int size; // 比較器 private final Comparator<? super E> comparator; // 修改次數(shù) transient int modCount;transient Object[] queue
源碼注釋
優(yōu)先級(jí)隊(duì)列是一個(gè)平衡的二叉樹堆,queue[n]的兩個(gè)子節(jié)點(diǎn)queue[2n+1] and queue[2(n+1)]。 優(yōu)先級(jí)隊(duì)列 按照 comparator 進(jìn)行排序或者 按自然順序排序。 如果 comparator 為null ,堆中每一個(gè)節(jié)點(diǎn) n 以及 n的后代d, n<=d。假設(shè)隊(duì)列為非空,則具有最小值的元素位于queue [0]中。
常用構(gòu)造函數(shù)
- 1、創(chuàng)建一個(gè)優(yōu)先隊(duì)列對(duì)象,默認(rèn)大小,隊(duì)列中的元素按照自然順序排序。
- 2、創(chuàng)建一個(gè)指定大小的優(yōu)先隊(duì)列對(duì)象,隊(duì)列中的元素按照自然順序排序。
- 3、創(chuàng)建一個(gè)默認(rèn)大小(11)的優(yōu)先隊(duì)列對(duì)象,隊(duì)列中的元素按照指定比較器進(jìn)行排序。
- 4、根據(jù)指定的大小和比較器來(lái)創(chuàng)建一個(gè)優(yōu)先隊(duì)列對(duì)象。
add(e)/offer(e)方法介紹
add方法
插入一個(gè)元素到優(yōu)先隊(duì)列中
add方法的源代碼如下:
public boolean add(E e) {return offer(e); }從源碼中可以看出add方法直接調(diào)用了offer方法。
offer(e)方法
public boolean offer(E e) {if (e == null) //檢查是否為null, 如果為null 則拋空指針異常throw new NullPointerException();modCount++;int i = size;if (i >= queue.length) // 檢查容量是否足夠, 不足進(jìn)行擴(kuò)容grow(i + 1); // 擴(kuò)容函數(shù)siftUp(i, e); // 在數(shù)組的末尾插入新的元素,向上調(diào)整,使之保持為二叉堆的特性。size = i + 1;return true; }1)首先檢查要添加的元素e是否為null,如果為null,則拋空指針異常,如果不為null,則進(jìn)行2
2)判斷數(shù)組是否已滿,如果已滿,則進(jìn)行擴(kuò)容,否則將元素加入到數(shù)組末尾即可。
由于這個(gè)數(shù)組表示的是一個(gè)“二叉堆”,因此還需要進(jìn)行相應(yīng)的調(diào)整操作,使得這個(gè)數(shù)組在添加元素之后依然保持的是二叉堆的特性。
grow(int minCapacity)方法
//增加數(shù)組的容量 private void grow(int minCapacity) {int oldCapacity = queue.length;// Double size if small; else grow by 50%//如果比較小,則擴(kuò)容為原來(lái)的2倍,否則只擴(kuò)容為原來(lái)的1.5倍int newCapacity = oldCapacity + ((oldCapacity < 64) ?(oldCapacity + 2) :(oldCapacity >> 1));// overflow-conscious codeif (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);queue = Arrays.copyOf(queue, newCapacity); }hugeCapacity(int minCapacity) 方法
private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflowthrow new OutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE; }注:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
public static final int MAX_VALUE = 0x7fffffff;
當(dāng) 大于 MAX_ARRAY_SIZE 時(shí)擴(kuò)容記性了特殊處理
siftUp(int k, E x)方法
將元素x插入到queue[k]位置上,并進(jìn)行調(diào)整使之具有二叉堆特性
private void siftUp(int k, E x) {if (comparator != null)siftUpUsingComparator(k, x); // 使用比較器 comparatorelsesiftUpComparable(k, x); }siftUpUsingComparator和siftUpComparable源碼如下
從 位置 k 向數(shù)組起始位置 調(diào)整使之具有二叉堆特性,自下而上的堆化,
也就是 二叉堆插入元素思想操作:
可參見(jiàn) 拜托,面試別再問(wèn)我堆(排序)了!
siftUpComparable(int k, E x)方法
@SuppressWarnings("unchecked") private void siftUpComparable(int k, E x) {Comparable<? super E> key = (Comparable<? super E>) x;while (k > 0) {int parent = (k - 1) >>> 1; // 找 parent 父節(jié)點(diǎn)Object e = queue[parent];if (key.compareTo((E) e) >= 0) // 比較 按 自然順序 排列break;queue[k] = e; k = parent; }queue[k] = key; }siftUpUsingComparator(int k, E x)方法
@SuppressWarnings("unchecked") private void siftUpUsingComparator(int k, E x) {while (k > 0) {int parent = (k - 1) >>> 1;Object e = queue[parent];if (comparator.compare(x, (E) e) >= 0) // 使用比較器 comparatorbreak;queue[k] = e;k = parent;}queue[k] = x; }poll()方法介紹
取出優(yōu)先隊(duì)列中的第一個(gè)元素
public E poll() {if (size == 0) // 優(yōu)先隊(duì)列大小判斷return null;int s = --size;modCount++;E result = (E) queue[0]; // 取出第一個(gè)元素E x = (E) queue[s]; // 取出最后一個(gè)元素queue[s] = null;if (s != 0)siftDown(0, x); return result; }poll方法:取出queue[0]元素,然后將queue[size-1]插入到queue[0],然后自上而下堆化來(lái)保持二叉堆的特性。
siftDown(int k, E x)方法
//將元素x存儲(chǔ)在queue[k],并進(jìn)行相應(yīng)的調(diào)整 private void siftDown(int k, E x) {if (comparator != null)siftDownUsingComparator(k, x);elsesiftDownComparable(k, x); }siftDownComparable(int k, E x)方法
private void siftDownComparable(int k, E x) {Comparable<? super E> key = (Comparable<? super E>)x;int half = size >>> 1; // loop while a non-leafwhile (k < half) {int child = (k << 1) + 1; // assume left child is leastObject c = queue[child];int right = child + 1;if (right < size &&((Comparable<? super E>) c).compareTo((E) queue[right]) > 0) //兩個(gè)兒子節(jié)點(diǎn)較小的那一個(gè)c = queue[child = right];if (key.compareTo((E) c) <= 0)break;queue[k] = c;k = child;}queue[k] = key; }siftDownUsingComparator(int k, E x)方法
private void siftDownUsingComparator(int k, E x) {int half = size >>> 1;while (k < half) {int child = (k << 1) + 1;Object c = queue[child];int right = child + 1;if (right < size &&comparator.compare((E) c, (E) queue[right]) > 0)c = queue[child = right];if (comparator.compare(x, (E) c) <= 0)break;queue[k] = c;k = child;}queue[k] = x; }remove(Object o) 方法介紹
移除指定元素
public boolean remove(Object o) {int i = indexOf(o); // 判斷位置if (i == -1) //沒(méi)有在數(shù)組中找到return false;else {removeAt(i); //進(jìn)行刪除并調(diào)整return true;} }indexOf(Object o)方法
private int indexOf(Object o) {if (o != null) {for (int i = 0; i < size; i++) // 數(shù)組遍歷過(guò)程if (o.equals(queue[i]))return i;}return -1; }找到對(duì)象o在數(shù)組中出現(xiàn)的第一個(gè)索引
emoveAt(int i)方法
E removeAt(int i) {// assert i >= 0 && i < size;modCount++;int s = --size;if (s == i) // removed last elementqueue[i] = null;else {E moved = (E) queue[s]; // 取出最后一個(gè)元素queue[s] = null;siftDown(i, moved); //從i向下調(diào)整堆if (queue[i] == moved) { // 如果發(fā)現(xiàn)調(diào)整后 moved 還在 i 位置沒(méi)有下沉,向上調(diào)整看是否上浮siftUp(i, moved);if (queue[i] != moved)return moved;}}return null; }堆插入元素與刪除堆頂元素操作
以下面這個(gè)對(duì)為例
用數(shù)組來(lái)存儲(chǔ)為:
插入元素
往堆中插入一個(gè)元素后,需要繼續(xù)滿足堆的兩個(gè)特性,即:
(1)堆是一顆完全二叉樹;
(2)堆中某個(gè)節(jié)點(diǎn)的值總是不大于(或不小于)其父節(jié)點(diǎn)的值。
把元素插入到最后一層最后一個(gè)節(jié)點(diǎn)往后一位的位置,也就是在數(shù)組的末尾插入新的元素
,插入之后可能不再滿足條件(2),這時(shí)候我們需要調(diào)整,自下而上的堆化。
上面那個(gè)堆插入元素2,我們把它放在9后面,這時(shí)不滿足條件(2)了,我們就需要堆化。(這是一個(gè)小頂堆)
從插入元素的過(guò)程,我們知道每次與 n/(2x)n/(2^x)n/(2x) 的位置進(jìn)行比較,所以,插入元素的時(shí)間復(fù)雜度為 O(logn)O(log n)O(logn)。
刪除堆頂元素
刪除了堆頂元素后,要使得還滿足堆的兩個(gè)特性。把最后一個(gè)元素移到根節(jié)點(diǎn)的位置,這時(shí)候就滿足條件(1),之后就需要堆化了使它滿足條件(2)
從刪除元素的過(guò)程,我們知道把最后一個(gè)元素拿到根節(jié)點(diǎn)后,每次與 2n2n2n 和 (2n+1)(2n+1)(2n+1) 位置的元素比較,取其小者,所以,刪除元素的時(shí)間復(fù)雜度也為 O(logn)O(log n)O(logn) 。
回到 remove(Object o) 方法 ,不是刪除堆頂元而是刪除指定元素,源碼中會(huì)先找Object o在數(shù)組中位置 i,再取出最后一個(gè)元素從i自上而下堆化,如果最后一個(gè)元素沒(méi)有下沉,會(huì)從i自下而上堆化
總結(jié)
以上是生活随笔為你收集整理的极客时间 算法训练营 第一周总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 批量添加手机联系人:csv/excel转
- 下一篇: 王者荣耀服务器不稳定,王者荣耀延迟460