棧是一種受到限制和線性表,只可以在表尾添加元素,而且只可以獲取和刪除表尾元素。表尾成爲棧的棧頂,表頭成爲棧的棧底。向棧添加一個元素成爲進棧、入棧或壓棧,取出元素稱爲出棧或退棧。數組
徹底能夠把棧理解成爲一個功能受到限制的鏈表:只可以向鏈表的尾部追加元素,而且只可以獲取和刪除鏈表的尾部元素。鏈表的尾部稱爲棧頂,鏈表的頭部成爲棧底。入棧即向鏈表尾部添加一個元素,出棧即移除尾部元素。bash
假如依次向棧壓入三個元素:Rose、Jack、Whip,入棧操做以下圖:數據結構
而出棧即每次都去棧頂元素,如上圖出棧的順序應該是:Whip、Jack、Rose,恰好和入棧順序相反。post
既然棧的功能徹底就是一個功能受限的線性表,咱們就能夠封裝一個線性表實現一個自定義棧。以前咱們已經自定義實現了5種對外接口和功能徹底一致的線性表結構:動態數組、單向鏈表、單向循環鏈表、雙向鏈表、雙向循環鏈表。測試
既然棧只須要對線性表的尾部進行添加和刪除操做,動態數組、雙向鏈表、雙向循環鏈表對尾部的操做時間複雜度都是O(1),單向鏈表、單向循環鏈表對尾部的操做時間複雜度都是O(n)。棧也不須要使用到雙向循環鏈表的特性,使用雙向鏈表和動態數組都是合適的。ui
@interface JKRStack<ObjectType> : NSObject
/// 返回棧的元素個數
- (NSUInteger)count;
/// 入棧
- (void)push:(nullable ObjectType)anObject;
/// 出棧
- (ObjectType)pop;
/// 獲取棧頂元素
- (ObjectType)peek;
@end
複製代碼
棧內部封裝一個線性表,這個線性表使用懶加載,棧的類型這裏定義成全部線性表的父類,因爲以前封裝的全部線性表結構和功能都是同樣的,後面能夠方便的修改懶加載返回的子類類型,來測試不一樣類型的線性表實現的時間複雜度對比。atom
@interface JKRStack ()
@property (nonatomic, strong) JKRBaseList *array;
@end
- (JKRBaseList *)array {
if (!_array) {
_array = [JKRLinkedList new];
}
return _array;
}
複製代碼
棧的元素個數spa
- (NSUInteger)count {
return self.array.count;
}
複製代碼
入棧3d
- (void)push:(id)anObject {
[self.array addObject:anObject];
}
複製代碼
出棧code
- (id)pop {
[self rangeCheck];
id object = self.array.lastObject;
[self.array removeLastObject];
return object;
}
複製代碼
獲取棧頂
- (id)peek {
[self rangeCheck];
return self.array.lastObject;
}
複製代碼
邊界檢查
- (void)rangeCheck {
if (self.array.count == 0) {
NSAssert(NO, @"stack is empty");
}
}
複製代碼
將 0 - 9999 分別入棧,而後依次彈出棧頂直到棧爲空:
[JKRTimeTool teskCodeWithBlock:^{
JKRStack *stack = [JKRStack new];
for (NSUInteger i = 0; i < 10000; i++) {
[stack push:[NSNumber numberWithInteger:i]];
}
while (stack.count) {
NSLog(@"%@",[stack pop]);
}
}];
複製代碼
打印結果:
9999
9998
...
1
0
耗時: 0.170 s
複製代碼
修改線性表的懶加載返回的對象,對比不一樣線性表的時間:
- (JKRBaseList *)array {
if (!_array) {
_array = [JKRLinkedList new];
// _array = [JKRLinkedCircleList new];
// _array = [JKRSingleLinkedList new];
// _array = [JKRSingleCircleLinkedList new];
// _array = [JKRArrayList new];
}
return _array;
}
複製代碼
進行 100000次 入棧和出棧,時間對比對好比下:
// JKRLinkedList(雙向鏈表)
耗時: 0.009 s
// JKRLinkedCircleList(雙向循環鏈表)
耗時: 0.011 s
// JKRSingleLinkedList(單向鏈表)
耗時: 2.880 s
// JKRSingleCircleLinkedList(單向循序鏈表)
耗時: 2.888 s
// JKRArrayList(動態數組)
耗時: 0.004 s
複製代碼
能夠發現,一樣的接口和功能的線性表,在內部實現方式不一樣的狀況下,差異很是的大,因此在實現一個功能的時候,須要考慮數據結構的特性,選擇最優的方案。
隊列和棧很是類似,也是功能受限的線性表,只可以在尾部添加元素,而且只可以從頭部獲取和刪除元素。從尾部添加元素稱爲入隊,從頭部取出元素稱爲出隊。
基於隊列的性質,隊列知足先進先出的原則,像一羣人排隊進入關口同樣,先入隊的人在隊頭最早出去,後入隊的人在隊尾,要等前面的人都出去了才能輪到他出去。
既然隊列須要在隊尾進行添加,隊頭進行取元素和刪除,須要同時對頭部和尾部進行操做,使用雙向鏈表是最合適的。
@interface JKRQueue<ObjectType> : NSObject
/// 隊列的元素個數
- (NSUInteger)count;
/// 入隊
- (void)enQueue:(nullable ObjectType)anObject;
/// 出隊
- (ObjectType)deQueue;
/// 獲取隊頭
- (ObjectType)front;
@end
複製代碼
隊列內部須要封裝一個線性表,這裏使用雙向鏈表:
@interface JKRQueue ()
@property (nonatomic, strong) JKRBaseList *array;
@end
@implementation JKRQueue
- (JKRBaseList *)array {
if (!_array) {
_array = [JKRLinkedList new];
}
return _array;
}
@end
複製代碼
隊列的元素個數
- (NSUInteger)count {
return self.array.count;
}
複製代碼
入隊
- (void)enQueue:(id)anObject {
[self.array addObject:anObject];
}
複製代碼
出隊
- (id)deQueue {
[self rangeCheck];
id object = self.array.firstObject;
[self.array removeFirstObject];
return object;
}
複製代碼
獲取隊頭
- (id)front {
[self rangeCheck];
return self.array.firstObject;
}
複製代碼
邊界檢查
- (void)rangeCheck {
if (self.array.count == 0) {
NSAssert(NO, @"queue is empty");
}
}
複製代碼
有了棧和隊列的概念,後面就能夠爲二叉樹的操做作好準備了。