栈是一种受到限制和线性表,只能够在表尾添加元素,并且只能够获取和删除表尾元素。表尾成为栈的栈顶,表头成为栈的栈底。向栈添加一个元素成为进栈、入栈或压栈,取出元素称为出栈或退栈。
完全可以把栈理解成为一个功能受到限制的链表:只能够向链表的尾部追加元素,并且只能够获取和删除链表的尾部元素。链表的尾部称为栈顶,链表的头部成为栈底。入栈即向链表尾部添加一个元素,出栈即移除尾部元素。
假如依次向栈压入三个元素: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
复制代码
可以发现,同样的接口和功能的线性表,在内部实现方式不同的情况下,差别非常的大,所以在实现一个功能的时候,需要考虑数据结构的特性,选择最优的方案。