lec3 List Stack Queue(2)

关于链表,栈,队列。

列表抽象数据类型(Stack ADT)

59fe5fb7893b64832c60cca9b5e3ad4

栈的重点在于应用,其他一带而过即可。

Stack (栈)

栈(Stack)是一种数据结构,允许我们只在其一端进行插入(Push)和删除(Pop)操作。这个端口被称为栈的顶部Top)。栈遵循 LIFO(Last In, First Out,后进先出)原则,即最后插入的元素最先被删除。

image-20241119141031186

栈的基本操作:

  1. Push:将元素插入到栈的顶部。
  2. Pop:删除并返回栈顶元素(也称为 TopAndPop)。
  3. IsEmpty:测试栈是否为空。
  4. Peek(或Top):查看栈顶元素,但不移除它(有时也包括在栈的实现中)。

栈的实现方式:

栈可以通过不同的数据结构来实现,常见的实现方式有 链表实现数组实现

Stack ADT(抽象数据类型)

栈(Stack)是一个具有 后进先出(LIFO, Last In, First Out)特性的线性数据结构,通常提供三种基本操作:插入(Push)、删除(Pop)和查看栈顶元素(Top)。为了确保栈的操作具有一致性和可扩展性,可以使用抽象数据类型(ADT)来定义栈的接口。

栈的抽象数据类型通过接口定义了栈应当支持的操作,包括对栈的插入、删除以及查看栈顶元素等功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// The Stack ADT
template <typename E>
class Stack {
private:
void operator=(const Stack&) {} // 防止赋值操作
Stack(const Stack&) {} // 防止拷贝构造

public:
// Push: 将元素插入到栈顶
virtual bool push(const E& it) = 0;

// Pop: 移除栈顶元素并返回
virtual E pop() = 0;

// TopValue: 返回栈顶元素的副本
virtual const E& topValue() const = 0;
};

数组实现栈(Array-Based Stack):

在数组实现的栈中,栈的大小是固定的,因此栈必须在初始化时指定容量。栈的操作比较简单,所有的元素都存储在一个数组中,栈顶的元素通过一个索引进行标记。

数组栈的特性:

  • 固定大小:栈的最大大小在创建时定义,后续无法动态扩展(除非进行特殊处理,如扩容)。
  • 简化的数组列表:栈操作通常是在数组的尾部进行,类似于一个队列的尾端插入和删除。

AStack 类的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
template <typename E> 
class Astack: public Stack<E> {
private:
int maxSize; // 栈的最大大小
int top; // 栈顶元素的索引(空闲位置)
E *listArray; // 存储栈元素的数组

public:
// 构造函数,默认大小为 defaultSize
Astack(int size = defaultSize) {
maxSize = size;
top = 0;
listArray = new E[size];
}

// 析构函数,释放数组内存
~Astack() {
delete[] listArray;
}

// 将栈重置为空
void clear() {
top = 0; // 重新初始化栈
}

// Push 操作:将元素插入栈顶
void push(const E& it) {
Assert(top != maxSize, "Stack is full"); // 确保栈不满
listArray[top++] = it; // 插入元素并更新栈顶指针
}

// Pop 操作:删除并返回栈顶元素
E pop() {
Assert(top != 0, "Stack is empty"); // 确保栈不为空
return listArray[--top]; // 更新栈顶指针并返回栈顶元素
}

// TopValue 操作:返回栈顶元素
const E& topValue() const {
Assert(top != 0, "Stack is empty"); // 确保栈不为空
return listArray[top - 1]; // 返回栈顶元素
}
};

栈操作的工作原理:

  1. Push 操作:
    • 在执行 Push 操作时,首先检查栈是否已满(通过 topmaxSize 比较),如果栈满则抛出异常。
    • 然后,将元素 it 放入当前栈顶的位置,并更新栈顶指针 toptop++)。
  2. Pop 操作:
    • 在执行 Pop 操作时,首先检查栈是否为空(通过 top 与 0 比较),如果栈空则抛出异常。
    • 然后,更新栈顶指针 toptop--),并返回被删除的栈顶元素。
  3. TopValue 操作:
    • TopValue 操作返回栈顶元素的引用,但不删除栈顶元素。
    • 如果栈为空,调用该方法将抛出异常。
  4. Clear 操作:
    • Clear 操作将栈重新初始化,即将栈顶指针 top 重置为 0,表示栈为空。

时间复杂度分析:

  • Push 操作:O(1),将元素插入栈顶是常数时间操作。
  • Pop 操作:O(1),移除栈顶元素是常数时间操作。
  • TopValue 操作:O(1),查看栈顶元素是常数时间操作。
  • Clear 操作:O(1),重置栈顶指针为 0,也是常数时间操作。

Linked Stack(链式栈)

链式栈(Linked Stack)是栈的一种实现方式,与数组实现的栈不同,链式栈基于链表的结构来动态管理栈的元素。链式栈的优势在于其动态性,能够随着元素的增加或减少灵活调整栈的大小,而不需要提前指定栈的容量。

链式栈(Linked Stack)的基本结构和操作:

链式栈的元素通常是通过链表节点(Link)来表示的,栈的每个节点包含两个部分:

  1. 元素数据(element):存储栈中的数据。
  2. 指向下一个节点的指针(next):指向链表中的下一个节点。

栈的顶端由 top 指针表示,初始时 topNULL,表示栈为空。

LStack 类的实现:

下面是链式栈的实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 链式栈的实现
template <typename E>
class LStack: public Stack<E> {
private:
Link<E>* top; // 指向栈顶元素的指针
int size; // 栈中元素的数量

public:
// 构造函数,初始化栈为空
LStack(int sz = defaultSize) {
top = NULL;
size = 0;
}

// 析构函数,清除栈中的所有元素
~LStack() {
clear();
}

// 清除栈的所有元素
void clear() {
while (top != NULL) { // 删除所有链表节点
Link<E>* temp = top;
top = top->next;
delete temp;
}
size = 0;
}

// Push 操作:将元素压入栈顶
void push(const E& it) {
top = new Link<E>(it, top); // 创建新的节点,并让它指向当前栈顶
size++; // 更新栈大小
}

// Pop 操作:移除栈顶元素并返回
E pop() {
Assert(top != NULL, "Stack is empty"); // 确保栈不为空
E it = top->element; // 获取栈顶元素
Link<E>* ltemp = top->next; // 获取栈顶元素的下一个节点
delete top; // 删除栈顶节点
top = ltemp; // 更新栈顶指针
size--; // 更新栈大小
return it; // 返回栈顶元素
}

// TopValue 操作:返回栈顶元素的副本
const E& topValue() const {
Assert(top != NULL, "Stack is empty"); // 确保栈不为空
return top->element; // 返回栈顶元素
}
};

栈操作的实现细节:

  1. 构造函数(LStack):

    • 初始化时,栈为空,top 指针指向 NULLsize 初始化为 0。
  2. 清空栈(clear):

    • 通过循环遍历栈中的每个节点,逐个删除。
    • 通过 top 指针逐步移动到下一个节点,释放当前节点的内存。
  3. Push 操作(push):

    • 在链式栈中,push 操作通过创建一个新节点,将栈顶指针指向新节点,并将新节点的 next 指针指向原来的栈顶(即原来的节点)。
    • 新节点成为栈的栈顶,栈大小 size 增加。
    1
    top = new Link<E>(it, top);
  4. Pop 操作(pop):

    • pop 操作会首先获取栈顶元素的值,然后将栈顶指针 top 更新为指向下一个节点(即删除当前栈顶节点)。
    • 被删除的节点会被释放内存,并返回栈顶元素的值。
    1
    2
    3
    4
    E it = top->element;
    Link<E>* ltemp = top->next;
    delete top;
    top = ltemp;
  5. TopValue 操作(topValue):

    • 返回栈顶元素的副本,但不会修改栈顶指针。
    • 如果栈为空,调用该方法会抛出异常。
    1
    return top->element;

时间复杂度分析:

  • Push 操作:O(1),每次将新节点插入栈顶。
  • Pop 操作:O(1),每次删除栈顶节点。
  • TopValue 操作:O(1),直接返回栈顶元素。
  • Clear 操作:O(n),需要遍历整个链表并删除所有节点。

数组栈与链栈的比较:实现、时间复杂度与空间复杂度

时间和空间复杂度总结:

操作 数组栈 链栈
Push O(1) O(1)
Pop O(1) O(1)
TopValue O(1) O(1)
Clear O(1) O(n)
空间复杂度 O(n) O(n) + O(n)(指针开销)