寫在前面某廠筆試題目:請基於學習過的佇列知識,重新設計乙個「迴圈佇列」。要求:在平常工作或面試中,都會涉及到資料結構。在某些情況下,系統提供的資料結構無法滿足特定的需求,此時,擴充套件或重寫適合自己需求的資料結構就顯得相當重要。而如何設計高效、簡潔的資料結構,就成了考察程式設計師功底的乙個重要依據。
各位大佬,拿到這個面試題或者需求該怎麼分析呢?
首先,線性結構,首選的肯定是陣列,在不同語言中也可以選擇不同的線性儲存結構,比如c++的 vector容器。
接著,fifo,如何實現先進先出?以及隊尾和隊頭之間連在一起形成環?在分析到這裡的時候,我當時已經是一臉懵,但是仔細一想,如果設定兩個標誌指標,乙個指向頭,乙個指向尾,不就行了。
再接著分析,滿足時間複雜度o(1),即滿足線性時間,一般是入隊和出隊不能有遍歷的情況。空間複雜度o(n),用陣列就可以直接實現。
以上分析後,拿起鍵盤,噼里啪啦一頓亂敲,初版形成了,請看下個章節。
#include
using
namespace std;
class
circularqueue
~circularqueue()
}// 入隊
bool
enqueue
(int value)
if(capacity ==0)
if((rear >= length || capacity >= length)
&& front ==0)
if(rear >= length && capacity
p[rear]
= value;
capacity++
; rear++
;return
true;}
// 出隊
bool
dequeue()
front++
; capacity--;if
(front >= rear)
return
true;}
// 獲取隊頭元素
intfront()
return p[front];}
// 獲取隊尾元素
intrear()
return p[rear-1]
;}// 判斷佇列是否為空
bool
isempty()
return
false;}
// 判斷佇列是否已滿
bool
isfull()
return
false;}
private
:// 總長度
int length;
// 儲存陣列物件
int* p =
null
;// 隊頭索引
int front =0;
// 隊尾索引
int rear =0;
// 陣列容量
int capacity =0;
};
以上是第乙個版本的完整**,基本實現了時間複雜度o(1)和空間複雜度o(n)。測試**如下。
int
main()
通過測試發現,如果遇到隊滿,出隊後,隊頭前面有空餘位置,繼續入隊時,入隊失敗。即無法完成 迴圈佇列的需求。接著優化分析,請看以下章節。
通過第一版本的實現,效果並不理想,下面通過示例圖分析整個過程。
當隊列為空時,頭結點 front和 尾結點 rear 都指向同一位置 ,如下圖
由上兩圖可以得出迴圈佇列空隊的判斷條件。
開始入隊,頭結點 front保持在0號位置 ,尾結點隨著入隊元素變動,如圖尾結點 rear 在2號位置。形成乙個連續的佇列。
接著繼續入隊,隊滿時出隊,頭結點 front 開始移動,如圖頭結點在2號位置,尾結點 rear在4號位置,佇列狀態如下:
上述情況下,如果繼續入隊,一般普通的佇列會提示隊滿,因為尾結點已經在佇列末尾了。但是實際情況是隊頭還有兩個空位置,這就浪費了空間。作為迴圈佇列,是完全可以避免空間浪費的,如下圖:
那麼,什麼情況下才無法繼續入隊呢?隊滿的狀態是什麼呢?下圖所示:
由此可見當 front == rear + 1時,隊滿了,那如b所示的情況應該怎麼表示呢?請大家思考。
但是,如何控制隊頭和隊尾標誌,靈活的實現入隊出隊的移動呢?
通過觀察,對於乙個固定大小的陣列,任何位置都可以是隊首,如果知道佇列長度,就可以根據下面公式計算出隊尾位置:
r ea
r=(f
ront
+cou
nt−1
)%ca
paci
tyrear=(front+count−1)\%capacity
rear=(
fron
t+co
unt−
1)%c
apac
ity其中 capacity
是陣列長度,count
是佇列長度,front
和 rear
分別是隊首和隊尾的索引。
按照以上思路,重新優化演算法,實現請參考下個章節。
奉上優化後的**:
#include
using
namespace std;
class
mycircularqueue
~mycircularqueue()
// 入隊
bool
enqueue
(int value)
arr[rear]
= value;
rear =
(rear +1)
% capacity;
return
true;}
// 出隊
bool
dequeue()
front =
(front +1)
% capacity;
return
true;}
// 獲取隊頭元素
intfront()
return arr[front];}
// 獲取隊尾元素
intrear()
return arr[
(rear -
1+ capacity)
% capacity];}
// 判斷是否為空
bool
isempty()
// 判斷是否已滿
bool
isfull()
};
有位前輩曾說過:「設計資料結構的關鍵是如何設計屬性,好的設計屬性數量更少「。那麼為什麼會越少越好呢?原因如下:
屬性數量少說明屬性之間冗餘更低,依賴少。
屬性冗餘度越低,操作邏輯越簡單,發生錯誤的可能性更低,出錯率低。
屬性數量少,使用的空間也少,操作效能更高,空間複雜度更低。
但是,凡事不可能是絕對的,一定的冗餘可以降低操作的時間複雜度,達到時間複雜度和空間複雜度的相對平衡。根據以上原則,設計迴圈佇列資料結構時,使用了4個屬性,下面列舉每個屬性,並解釋其含義。
另外,涉及到的計算總結如下:
通過整個環節的不停折騰,終於將迴圈佇列的問題敲定。鑑於水平有限,分析後的最優版本肯定還存在優化的空間,希望各位大神批評指正,一起進步。
資料結構 佇列 迴圈佇列
在佇列的陣列實現中,我們很容易發現數在出隊後,陣列的前面部分會有剩餘空間沒有被使用,所以我們為了最大程度的利用固定長度的陣列,我們採用迴圈佇列的儲存方式,這種方式的最大問題在於resize的時候比較麻煩,所以我們不考慮resize的情況。基本結構如下,這裡front指向第乙個元素的位置,rear指向...
資料結構 佇列 迴圈佇列
資料結構 佇列 迴圈佇列 順序儲存 犧牲乙個空間單元來判段佇列滿狀態。q.front q.rear 1 initsize date 2017 4 16 include define elemtype char define initsize 100 typedef structsqqueue voi...
資料結構 迴圈佇列
所謂順序儲存結構就是用一組位址連續的儲存單元依次存放從隊頭到隊尾的元素。宣告兩個指標rear front分別用來指示隊尾元素的下一位置和隊頭元素的位置。初始化時rear front 0 插入新的元素時尾指標加1,元素出佇列時隊頭指標加1。不過這樣做有個問題,不論是入隊還是出隊,隊頭或隊尾指標都是加1...