为了OFFER,我加深学习队列,现在还一脸懵逼
@Author:Runsen
@Date:2020/9/8
現在大四基本是重刷數據結構和算法,因為筆試真的太重要了。 我又重溫了爭大佬專欄的隊列,又鞏固了下。而且我發現留言區大佬的筆記很多,下面很多都是來自大佬總結的。
文章目錄
- 一、如何理解“隊列”?
- 二、順序隊列和鏈式隊列
- 三、循環隊列
- 四、阻塞隊列和并發隊列
- 五、線程池沒有空閑線程時,新的任務請求線程資源時,線程池該如何處理?各種處理策略又是如何實現的呢?
- 【思考】
- Leetcode 225. 用隊列實現棧
- Leetcode劍指 Offer 59 - I. 滑動窗口的最大值
- 703 數據流中的第K大元素
【大佬筆記】
一、如何理解“隊列”?
1、隊列是一種操作受限的線性表數據結構。
2、隊列最大的特點就是先進先出。
3、最基本的操作:入隊 enqueue(),放一個數據到隊列尾部;出隊 dequeue(),從隊列頭部取一個元素。
二、順序隊列和鏈式隊列
1、用數組實現的隊列叫順序隊列,用鏈表實現的隊列叫鏈式隊列。
2、隊列需要兩個指針:一個是 head 指針,指向隊頭;一個是 tail 指針,指向隊尾。
3、隨著不停地進行入隊、出隊操作,head 和 tail 都會持續往后移動。當 tail 移動到最右邊,即使數組中還有空閑空間,也無法繼續往隊列中添加數據了。
實際上,我們在出隊時可以不用搬移數據。如果沒有空閑空間了,只需要在入隊時,再集中觸發一次數據的搬移操作。出隊函數 dequeue() 保持不變,我們稍加改造一下入隊函數 enqueue() 的實現,當隊列的 tail 指針移動到數組的最右邊后,如果有新的數據入隊,我們可以將 head 到 tail 之間的數據,整體搬移到數組中 0 到 tail-head 的位置。
4、基于鏈表的實現,同樣需要兩個指針:head 指針和 tail 指針。分別指向鏈表的第一個結點和最后一個結點。入隊時,tail->next= new_node, tail = tail->next;出隊時,head = head->next。
三、循環隊列
1、循環隊列,原本數組是有頭有尾的,是一條直線。把首尾相連,扳成了一個環。
2、在數組實現隊列的時候,會有數據搬移操作,要想解決數據搬移的問題,需要像環一樣的循環隊列。
3、要想寫出沒有 bug 的循環隊列的實現代碼,最關鍵的是,確定好隊空和隊滿的判定條件。
1)隊列為空的判斷條件仍然是 head == tail。
2)當隊滿時,(tail+1)%n=head。 tail 指向的位置實際上是沒有存儲數據的。所以,循環隊列會浪費一個數組的存儲空間。
四、阻塞隊列和并發隊列
1、阻塞隊列
1)阻塞隊列就是在隊列基礎上增加了阻塞操作。
2)在隊列為空的時候,從隊頭取數據會被阻塞。因為此時還沒有數據可取,直到隊列中有了數據才能返回;如果隊列已經滿了,那么插入數據的操作就會被阻塞,直到隊列中有空閑位置后再插入數據,然后再返回。
3)基于阻塞隊列實現的“生產者 - 消費者模型”,可以有效地協調生產和消費的速度。
2、并發隊列
1)線程安全的隊列,叫作并發隊列。
2)最簡單直接的實現方式是直接在 enqueue()、dequeue() 方法上加鎖,但是鎖粒度大并發度會比較低,同一時刻僅允許一個存或者取操作。
3)實際上,基于數組的循環隊列,利用 CAS 原子操作,可以實現非常高效的并發隊列。這也是循環隊列比鏈式隊列應用更加廣泛的原因。
CAS是Compare And Set的縮寫,是以一種無鎖的方式實現并發控制。
五、線程池沒有空閑線程時,新的任務請求線程資源時,線程池該如何處理?各種處理策略又是如何實現的呢?
一般有兩種處理策略。第一種是非阻塞的處理方式,直接拒絕任務請求;另一種是阻塞的處理方式,將請求排隊,等到有空閑線程時,取出排隊的請求繼續處理。
1、基于鏈表的實現方式,可以實現一個支持無限排隊的無界隊列(unbounded queue),但是可能會導致過多的請求排隊等待,請求處理的響應時間過長。所以,針對響應時間比較敏感的系統,基于鏈表實現的無限排隊的線程池是不合適的。
2、基于數組實現的有界隊列(bounded queue),隊列的大小有限,所以線程池中排隊的請求超過隊列大小時,接下來的請求就會被拒絕,這種方式對響應時間敏感的系統來說,就相對更加合理。不過,設置一個合理的隊列大小,也是非常有講究的。隊列太大導致等待的請求太多,隊列太小會導致無法充分利用系統資源、發揮最大性能。
(除了前面講到隊列應用在線程池請求排隊的場景之外,隊列可以應用在任何有限資源池中,用于排隊請求,比如數據庫連接池等。實際上,對于大部分資源有限的場景,當沒有空閑資源時,基本上都可以通過“隊列”這種數據結構來實現請求排隊。)
【思考】
除了線程池這種池結構會用到隊列排隊請求,你還知道有哪些類似的池結構或者場景中會用到隊列的排隊請求呢?
一、1、像windows操作系統的消息隊列,略高級一些帶有優先級。還有qt中的信號與槽函數機制,使用connect鏈接,其中的參數就是設置為把窗口界面消息放到消息隊列,然后一次取出。比如優先級消息,窗口系統關閉,優先級高,則就直接執行關閉操作。
2、sockets網絡連接隊列。
3、數據庫連接隊列。
4、一種集群操作,很多客戶端像服務端請求資源,處理高并發大量請求。把這些請求放到隊列中。
5、分布式應用中的消息隊列,也是一種隊列結構。
今天講到并發隊列,關于如何實現無鎖并發隊列,網上有非常多的討論。對這個問題,你怎么看呢?
二、考慮使用CAS實現無鎖隊列,則在入隊前,獲取tail位置,入隊時比較tail是否發生變化,如果否,則允許入隊,反之,本次入隊失敗。出隊則是獲取head位置,進行cas。
看了下上面的大佬的筆記,似懂非懂、變成飯桶。還是刷下隊列相關的題,解決下我完全不會的心情。
Leetcode 225. 用隊列實現棧
from collections import deque class MyStack:def __init__(self):"""Initialize your data structure here."""self.q1 = deque()self.q2 = deque()def push(self, x: int) -> None:"""Push element x onto stack."""self.q1.append(x)def pop(self) -> int:"""Removes the element on top of the stack and returns that element."""while len(self.q1) > 1:self.q2.append(self.q1.popleft())x = self.q1.popleft()self.q1,self.q2 = self.q2,self.q1return xdef top(self) -> int:"""Get the top element."""res = self.q1.pop()self.q1.append(res)return resdef empty(self) -> bool:"""Returns whether the stack is empty."""if len(self.q1) == 0:return Trueelse:return FalseLeetcode劍指 Offer 59 - I. 滑動窗口的最大值
''' @Author: Runsen @WeChat:RunsenLiu @微信公眾號: Python之王 @CSDN: https://blog.csdn.net/weixin_44510615 @Github: https://github.com/MaoliRUNsen @Date: 2020/9/8輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 輸出: [3,3,5,5,6,7] 解釋:滑動窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 31 [3 -1 -3] 5 3 6 7 31 3 [-1 -3 5] 3 6 7 51 3 -1 [-3 5 3] 6 7 51 3 -1 -3 [5 3 6] 7 61 3 -1 -3 5 [3 6 7] 7'''def maxSlidingWindow(nums,k):if not nums: return []window ,res = [],[]for i,x in enumerate(nums):# 如果存在窗口 而且窗口的第一個數 不在這個范圍,就出去if i >= k and window[0] <= i-k:window.pop(0)# 每次進入窗口的和最后一個比較,如果大了,最后一個直接刪除while window and nums[window[-1]] <= x:window.pop()# 無論是不是刪除最后一個,都要加入x到窗口中window.append(i)# 如果出了窗口,就把窗口的頭加入到res中if i >= k-1:res.append(nums[window[0]])print(window)return resprint(maxSlidingWindow(nums = [1,3,-1,-3,5,3,6,7], k = 3))703 數據流中的第K大元素
''' @Author: Runsen @WeChat:RunsenLiu @微信公眾號: Python之王 @CSDN: https://blog.csdn.net/weixin_44510615 @Github: https://github.com/MaoliRUNsen @Date: 2020/9/10 703. 數據流中的第K大元素 設計一個找到數據流中第K大元素的類(class)。注意是排序后的第K大元素,不是第K個不同的元素。 你的 KthLargest 類需要一個同時接收整數 k 和整數數組nums 的構造器,它包含數據流中的初始元素。每次調用 KthLargest.add,返回當前數據流中第K大的元素。示例: int k = 3; int[] arr = [4,5,8,2]; KthLargest kthLargest = new KthLargest(3, arr); kthLargest.add(3); // returns 4 kthLargest.add(5); // returns 5 kthLargest.add(10); // returns 5 kthLargest.add(9); // returns 8 kthLargest.add(4); // returns 8 說明: 你可以假設 nums 的長度≥ k-1 且k ≥ 1。 ''' import heapq class KthLargest:def __init__(self, k: int, nums):# 建立第k大的大self.pool = heapq.nlargest(k, nums)self.k = kdef add(self, val: int) -> int:if len(self.pool) < self.k:# 彈出并返回heap的最小的元素,保持堆的不變性。heapq.heappush(self.pool,val)else:# 把val放入堆中,然后彈出并返回heap中最小的元素。heapq.heappushpop(self.pool,val)return self.pool[0]總結
以上是生活随笔為你收集整理的为了OFFER,我加深学习队列,现在还一脸懵逼的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 做脱毛之后如何护理?
- 下一篇: 为了OFFER,继续深入学习树和二叉树