数据结构与算法(二):栈 和 队列

栈 和 队列 也是常见的数据结构。Java 里面的方法栈,消息队列 是 栈 和 队列 数据结构的实际应用。

概念

(stack) 是一种线性数据结构。可以把栈想像成只有一端开口的圆筒容器。栈的元素只能先进后出(FILO:first int last out),最早进入的元素存放的位置叫 栈底(bottom),最后进入的元素的位置叫作 栈顶(top)。

基本操作

的基本操作是 入栈出栈

  1. 入栈

    入栈操作(push) 是把新元素放入栈中,只能从栈顶一侧放入元素,新元素的位置将成为新的 栈顶。

  2. 出栈

    出栈操作(pop) 是把元素从栈中弹出,只有栈顶的元素才允许出栈,出栈元素的前一个元素的位置将成为新的 栈顶。

入栈出栈 只会影响最后一个元素,不涉及其它元数的整体移动,所以无论是数组还是以链表实现,入栈、出栈的时间复杂度都是 Ο(1)

队列

概念

队列(queue) 是一种线性结构。可以把队列想像成单行隧道。队列容器有两个端口,一个是出口端,叫作 队头(front),一个是入口端,叫作 队尾(rear)。队列中的元素只能先入先出(FIFO:first in first out)。

队列 即可用数组实现,也可用链表来实现。用数组实现,为了入队操作的方便,最后入队元素的下一个位置规定为队尾位置。

基本操作

  1. 入队

    入队(enqueue) 是把新元素放入队列中,只允许在队尾的位置放入元素,新元素的下一个位置将成为新的队尾。

  2. 出队

    出队(dequeue) 是把元素移出队列,只允许在队头一侧移出元素,出队元素的后一个元素将成为新的队头。

数组实现

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public class ArrayQueue {
/**
* 最大容量
*/
private int maxSize;
/**
* 数组
*/
private Object[] array;
/**
* 头标记
*/
private int front;
/**
* 尾索引
*/
private int rear;

//构建函数
public ArrayQueue(int maxSize) {
this.maxSize = maxSize;
array = new Object[this.maxSize];
front = -1;
rear = -1;
}

/**
* 判空
*
* @return
*/
public boolean isEmpty() {
return front == rear;
}

/**
* 判满
*
* @return
*/
public boolean isFull() {
return rear == maxSize - 1;
}

/**
* 队尾插入
*
* @param e
*/
public void add(int e) {
if (isFull()) {
throw new BusinessException("the array queue is full!");
}
rear++;
array[rear] = e;
}

/**
* 队头弹出
*
* @return
*/
public Object getHead() {
if (isEmpty()) {
throw new RuntimeException("the array queue is empty!");
}
Object result = array[0];
// 弹出头,后面元素全部前移
// System.arraycopy(array, 1, array, 0, array.length - 1);
for (int i = 0; i < array.length - 1; i++) {
array[i] = array[i + 1];
}
// 最后元素置空
array[rear] = null;
// 最后元素索引前移一位
rear--;
return result;
}
}

循环队列

队列元素不断出队,队头空间失去作用浪费掉,队列的容量会越来越小。可以用数组实现的队列可采用循环队列(loop queue) 的方式来维持队列容量的恒定。

在数组不做扩容的前提下,让新元素入队并确定新的队尾位置,利用已出队元素留下的空间,让队尾指针重新指回数组的首((准确的描述是让队尾指针指向队头元素出队空出的位置)。

循环队列在特理存储上,队尾的位置也可以在队头之前。当再有元素入队时,将其放入数组的首位,队尾指针继承后移即可。

一直到 (队尾下标 + 1) % 数组长度 = 队头下标,代表此队列直的已经满了。

注意:队尾指针指向的位置永远空出 1 位,所以队列最大容量比数组长度小 1 。

循环队列 不但充分利用了数组的空间,还避免了数组元素整体移动的麻烦,入队和出队的时间复杂度也是 Ο(1) 。

循环队列实现

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.datastructure.linkedlist;

public class LoopQueue {

private int[] array;
private int front;
private int rear;

public LoopQueue(int size) {
this.array = new int[size];
}

/**
* 入队
*
* @param element 入队的元素
*/
public void enQueue(int element) throws Exception {

if ((rear + 1) % array.length == front) {
throw new Exception("队列已满");
}
//入队元素
array[rear] = element;
//队尾指针指向出队元素的位置
rear = (rear + 1) % array.length;

}

/**
* 出队
*
* @return
*/
public int deQueue() throws Exception {
if (rear == front) {
throw new Exception("队列已空");
}
//队头元素出队
int deQueueElement = array[front];
//队头指针后移
front = (front + 1) % array.length;
return deQueueElement;
}

/**
* 输出队列
*/
public void output() {
//如果队头指针与队尾指针不相等,则队列仍有元素
for (int i = front; i != rear; i = (i + 1) % array.length) {
//输出队头到队尾之间的元素
System.out.println(array[i]);
}
}

public static void main(String[] args) throws Exception {
LoopQueue loopQueue = new LoopQueue(6);
//注意:入队必须留有一个空位作为队尾指针指向的位置
loopQueue.enQueue(1);
loopQueue.enQueue(3);
loopQueue.enQueue(7);
loopQueue.enQueue(2);
loopQueue.enQueue(4);

loopQueue.output();
System.out.println("-----------------------");

loopQueue.deQueue();
loopQueue.output();

loopQueue.deQueue();
loopQueue.deQueue();
loopQueue.deQueue();
loopQueue.output();
}
}

双端队列

双端队列数据结构,集合了栈和队列的优点,允许两端都可以进行入队或出队操作,其元素的逻辑仍是线性结构。

双端队列的实现可参考 Java 实现 数据结构-双端队列

优先队列

优先队列 遵循的是谁的优先级高,谁先出队。

优先队列不属于线性数据结构的范畴,通常采用堆数据结构来实现。

栈和队列的应用

栈的应用

栈的是先入后出的顺序,通常用于对 “历史” 回溯,即逆流而上追溯 “历史”。

例如 实现递归的逻辑,就可以用栈来代替,因为栈可以回溯方法的调用链。

队列的应用

队列有入队端口和出队端口,出队顺序是先进先出,返队列的入队顺序依次访问。

应用场景如消息队列;在多线程中,争夺锁的等待队列。

数据结构与算法(二):栈 和 队列

http://blog.gxitsky.com/2019/07/19/DataStructure-02-stack-queue/

作者

光星

发布于

2019-07-19

更新于

2022-06-17

许可协议

评论