数据结构9-栈

栈是一种受到限制和线性表,只能够在表尾添加元素,并且只能够获取和删除表尾元素。表尾成为栈的栈顶,表头成为栈的栈底。向栈添加一个元素成为进栈、入栈或压栈,取出元素称为出栈或退栈。

完全可以把栈理解成为一个功能受到限制的链表:只能够向链表的尾部追加元素,并且只能够获取和删除链表的尾部元素。链表的尾部称为栈顶,链表的头部成为栈底。入栈即向链表尾部添加一个元素,出栈即移除尾部元素。

假如依次向栈压入三个元素:Rose、Jack、Whip,入栈操作如下图:

而出栈即每次都去栈顶元素,如上图出栈的顺序应该是:Whip、Jack、Rose,刚好和入栈顺序相反。

既然栈的功能完全就是一个功能受限的线性表,我们就可以封装一个线性表实现一个自定义栈。之前我们已经自定义实现了5种对外接口和功能完全一致的线性表结构:动态数组单向链表单向循环链表双向链表双向循环链表

既然栈只需要对线性表的尾部进行添加和删除操作,动态数组双向链表双向循环链表对尾部的操作时间复杂度都是O(1),单向链表单向循环链表对尾部的操作时间复杂度都是O(n)。栈也不需要使用到双向循环链表的特性,使用双向链表动态数组都是合适的。

栈的接口定义

@interface JKRStack<ObjectType> : NSObject

/// 返回栈的元素个数
- (NSUInteger)count;
/// 入栈
- (void)push:(nullable ObjectType)anObject;
/// 出栈
- (ObjectType)pop;
/// 获取栈顶元素
- (ObjectType)peek;

@end
复制代码

栈的内部成员

栈内部封装一个线性表,这个线性表使用懒加载,栈的类型这里定义成所有线性表的父类,由于之前封装的所有线性表结构和功能都是一样的,后面可以方便的修改懒加载返回的子类类型,来测试不同类型的线性表实现的时间复杂度对比。

@interface JKRStack ()

@property (nonatomic, strong) JKRBaseList *array;

@end

- (JKRBaseList *)array {
    if (!_array) {
         _array = [JKRLinkedList new];
    }
    return _array;
}
复制代码

栈的接口实现

栈的元素个数

- (NSUInteger)count {
    return self.array.count;
}
复制代码

入栈

- (void)push:(id)anObject {
    [self.array addObject:anObject];
}
复制代码

出栈

- (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
复制代码

可以发现,同样的接口和功能的线性表,在内部实现方式不同的情况下,差别非常的大,所以在实现一个功能的时候,需要考虑数据结构的特性,选择最优的方案。

猜你喜欢

转载自blog.csdn.net/weixin_34406086/article/details/91399659