Skip to the content.

1. Stacks and queues-SPL

今天我们将学习两个用于特定功能的集合(collections):

image-20230717151327837

2. Stacks

2.1 Concepts

栈:基于添加元素和以相反顺序检索它们的原则的集合。

基本栈操作:

既然Vector拥有stack的所有功能,那我们为什么还需要stack呢,因为stack是一种用于某些专门用途的数据结构。

并且stack本身也比vector更加轻量,使用的内存更少,并且核心操作函数(peek()pop()push())的时间复杂度都是O(1),因此当用于某些特定用途时,stack明显更为合适。

2.2 Stacks in computer science

编程语言和编译器:

匹配相关的成对事物:

复杂算法:

2.3 Stack-SPL

下图是SPL中的stack的大部分方法,当然我们也可以把stack打印到cout或者output stream中:

image-20230717155036130

同样是介绍在SPL中使用的方法:

#include "stack.h" // SPL

stack<int> s;     // {0}    bottom -> top
s.push(42);       // {42}
s.push(-3);       // {42,-3}
s.push(17);       // {42,-3,17}

cout << s.pop() << endl;   // 17   (s is {42,-3})
cout << s.peek() << endl;  // -3   (s is {42,-3})
cout << s.pop() << endl;   // -3   (s is {42})

此外,如果你想在stack中像其他语言一样可以存储超类型object元素,yeah,但你为此必须得使用pointer(指针),我们会在接下来的几周里学到pointer;如果你想创建一个stack of objects,你可以像在其他语言中直接创建一个stack of objects,这意味着会复制每个object元素;你也可以创建一个stack of pointers,让pointer元素指向object对象所在的地址,这样就可以避免object对象的复制。

2.4 Stack limitations/idioms

不能用索引(S[i])的方式访问一个stack。每次只能从stack中弹出一个元素。

我们通常用while(!s.empty()){}的方式去遍历一个栈,每次访问一个元素,都会导致这个元素被删除(因为只有用s.pop()函数才能访问到下面的元素),所以如果不希望元素被删除,那么就不用stack

2.5 Stack implementation

Stack内部一般是用Array或者Vector实现的。

当然stack也可以用linked list实现。

当Stack用Vector实现时,它的Insert操作的平均时间复杂度是O(1):在它需要扩容之前,都是O(1)是显而易见的;当进行第n+1次时,就需要扩容,显然这次操作是O(n),但是如果与前面的所有操作进行复杂度均摊,那么会发现每一次操作平均要2次移动,所以平均下来仍然是常数阶的时间复杂度,即O(1)。

3. Queues

队列:按照添加顺序检索元素。

基本队列操作:

3.1 Queues in computer science

操作系统:

编程:

现实世界的例子:

3.2 The Queue class-SPL

同样是介绍SPL库中的queue:

image-20230718145552404

#include "queue.h" // SPL

Queue<int> q;  // {}  front -> back
q.enqueue(42); // {42}
q.enqueue(-3); // {42,-3}
q.enqueue(17); // {42,-3,17}
cout << q.dequeue() << endl; // 42 ( q is {-3,17})
cout << q.peek() << endl;    // -3 ( q is {-3,17})
cout << q.dequeue() << endl; // -3 ( q is {17})

3.3 Queues idioms(常用遍历方式)

像stack一样,必须把元素从queue中取出来后才能访问他们:

// process ( and destroy ) an entire queue
while (!q.isEmpty()) {
	do something with q.dequeue();
}

// 这种方法可以在最后保持queue原来样子的基础上遍历queue
// 即运行完q.dequeue()后,马上再运行一次q.enqueue()
// 这样运行完后queue还是原来的样子
// 但是不能直接去获取size的值,在程序开始前获取一次,使用到结束即可
int size = q.size();
for (int i = 0; i < size; i++) {
    do something with q.dequeue();
    (including possibly re-adding it to the queue)
}

3.4 Mixing stacks and queues

我们经常为了达成某种效果而把stacks和queues混在一起使用,如:逆序queue中的元素。

#include "stack.h" // SPL
#include "queue.h" // SPL

Queue<int> q;
q.enqueue(1);
q.enqueue(2);
q.enqueue(3); // {1,2,3}

Stack<int> s;

while (!q.isEmpty()) {   // transfer queue to stack
	s.push(q.dequeue()); // q={}  s={1,2,3}
}

while (!s.isEmpty()) {
    q.enqueue(s.pop());  // q={3,2,1}  s={}
}
cout << q << endl;       // {3,2,1}

3.5 deques(双向的queue)

双向queue,头部和尾部都可以进行enqueue或dequeue的操作。结合了stack和queue的功能,很少用。但是在SPL中仍然定义了它:#include "deque.h"

双端队列(deque):一种双端开口的队列。

基本双端队列操作:

image-20230718153530392

image-20230718153614504