數據結構8-棧和隊列

棧是一種受到限制和線性表,只可以在表尾添加元素,而且只可以獲取和刪除表尾元素。表尾成爲棧的棧頂,表頭成爲棧的棧底。向棧添加一個元素成爲進棧、入棧或壓棧,取出元素稱爲出棧或退棧。數組

徹底能夠把棧理解成爲一個功能受到限制的鏈表:只可以向鏈表的尾部追加元素,而且只可以獲取和刪除鏈表的尾部元素。鏈表的尾部稱爲棧頂,鏈表的頭部成爲棧底。入棧即向鏈表尾部添加一個元素,出棧即移除尾部元素。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");
    }
}
複製代碼

接下來

有了棧和隊列的概念,後面就能夠爲二叉樹的操做作好準備了。

相關文章
相關標籤/搜索