算法 系列 队列

基础数据结构...

Posted by lichao modified on June 17, 2020

队列 API 如下:

1
2
3
4
5
6
7
8
9
10
class MyQueue {
  /** 添加元素到队尾 */
  public void push(int x);
  /** 删除队头的元素并返回 */
  public int pop();
  /** 返回队头元素 */
  public int peek();
  /** 判断队列是否为空 */
  public boolean empty();
}

⽤栈实现队列

使⽤两个栈 s1, s2 就能实现⼀个队列的功能(这样放置栈可能更容易理解):

algorithm

1
2
3
4
5
6
7
8
class MyQueue {
  private Stack<Integer> s1, s2;
  public MyQueue() {
    s1 = new Stack<>();
    s2 = new Stack<>();
    }
  // ...
}

当调⽤ push 让元素⼊队时,只要把元素压⼊ s1 即可,⽐如说 push 进 3 个元素分别是 1,2,3,那么底层结构就是这样: algorithm

1
2
3
4
/** 添加元素到队尾 */
public void push(int x) {
  s1.push(x);
}

那么如果这时候使⽤ peek 查看队头的元素怎么办呢?按道理队头元素应该是 1,但是在 s11 被压在栈底,现在就要轮到 s2 起到⼀个中转的作⽤了:当 s2 为空时,可以把 s1 的所有元素取出再添加进 s2 ,这时候 s2 中元素就是先进先出顺序了。

algorithm

1
2
3
4
5
6
7
8
/** 返回队头元素 */
public int peek() {
  if (s2.isEmpty())
    // 把 s1 元素压⼊ s2
    while (!s1.isEmpty())
      s2.push(s1.pop());
  return s2.peek();
}

同理,对于 pop 操作,只要操作 s2 就可以了。

1
2
3
4
5
6
/** 删除队头的元素并返回 */
public int pop() {
  // 先调⽤ peek 保证 s2 ⾮空
  peek();
  return s2.pop();
}

最后,如何判断队列是否为空呢?如果两个栈都为空的话,就说明队列为空:

1
2
3
4
/** 判断队列是否为空 */
public boolean empty() {
  return s1.isEmpty() && s2.isEmpty();
}

⾄此,就⽤栈结构实现了⼀个队列,核⼼思想是利⽤两个栈互相配合。

值得⼀提的是,这⼏个操作的时间复杂度是多少呢?有点意思的是 peek 操作,调⽤它时可能触发 while 循环,这样的话时间复杂度是 O(N),但是⼤部分情况下 while 循环不会被触发,时间复杂度是 O(1)。由于 pop 操作调⽤了 peek ,它的时间复杂度和 peek 相同。像这种情况,可以说它们的最坏时间复杂度是 O(N),因为包含 while 循环,可能需要从 s1s2 搬移元素。