数据结构与算法之美六之手写Queue

数据结构与算法之美六之手写Queue

Scroll Down

专栏第六篇,手写 3 种简单的队列

  1. 数组实现顺序队列
  2. 单链表实现链式队列
  3. 数组实现循环队列

知识图谱

首先呈上和队列相关的知识图谱。

在这里插入图片描述

队列的 API

将队列的共同特征抽象成一个特质

package com.shockang.study.algorithm.archive.queue

/**
 * 手写队列
 *
 * @author Shockang
 */
trait Queue[E] {

  //队列大小
  var size: Int

  //是否为空
  def isEmpty: Boolean

  //清空队列
  def clear()

  //入队
  def enqueue(e: E)

  //出队
  def dequeue(): E
}

数组实现顺序队列

package com.shockang.study.algorithm.archive.queue

import java.util.StringJoiner

/**
 * 数组实现的顺序队列
 *
 * @author Shockang
 */
class ArrayQueue[E: Manifest](initSize: Int) extends Queue[E] {

  //底层存储数组
  private var array: Array[E] = new Array(initSize)

  //队列开头的指针,这样可以避免每次出队的数组迁移,会导致开头的存储空间浪费,但是在动态扩容/缩容的时候可以填充满开头,以空间换时间
  private var start: Int = _

  //队列大小
  override var size: Int = _

  //队列是否为空
  override def isEmpty: Boolean = size == 0

  //清空队列
  override def clear(): Unit = {
    array = new Array(initSize)
    size = 0
    start = 0
  }

  //入队
  override def enqueue(e: E): Unit = {
    checkAndExpand()
    array(start + size) = e
    size += 1
  }

  //出队
  override def dequeue(): E = {
    checkAndShrink()
    val e = array(start)
    //有助于 GC
    array(start) = null.asInstanceOf[E]
    size -= 1
    start += 1
    e
  }

  //方便打印
  override def toString: String = {
    val sj = new StringJoiner(",")
    for (i <- start until start + size) sj.add(array(i).toString)
    "[" + sj + "]"
  }

  //检查并扩容
  private def checkAndExpand(): Unit = {
    if (start + size + 1 > array.length) change()
  }

  //检查并缩容
  private def checkAndShrink(): Unit = {
    if (isEmpty) throw new NoSuchElementException
    if (size - 1 < 0.1 * array.length) change()
  }

  //将数组长度变为当前队列长度的两倍
  private def change(): Unit = {
    val newSize = size << 1
    val newArray: Array[E] = new Array(newSize)
    //注意从 start 开始复制数组
    System.arraycopy(array, start, newArray, 0, size)
    array = newArray
    start = 0
  }

}

单链表实现链式队列

package com.shockang.study.algorithm.archive.queue

import java.util.StringJoiner

/**
 * 单链表实现的链式队列
 *
 * @author Shockang
 */
class LinkedListQueue[E] extends Queue[E] {

  //单链表单个结点
  private class Node(var item: E, var next: Node)

  //单链表头结点
  private var head: Node = _

  //单链表尾结点
  private var tail: Node = _

  //队列大小
  override var size: Int = _

  //队列是否为空
  override def isEmpty: Boolean = size == 0

  //清空队列
  override def clear(): Unit = {
    head = null
    size = 0
  }

  //入队
  override def enqueue(e: E): Unit = {
    val next = new Node(e, null)
    if (isEmpty) head = next
    else tail.next = next
    tail = next
    size += 1
  }

  //出队
  override def dequeue(): E = {
    if (isEmpty) throw new NoSuchElementException
    val e = head.item
    head = head.next
    size -= 1
    if (isEmpty) tail = head
    e
  }

  //方便打印
  override def toString: String = {
    val sj = new StringJoiner(",")
    var node = head
    while (node != null) {
      sj.add(node.item.toString)
      node = node.next
    }
    "[" + sj + "]"
  }
}

数组实现循环队列

package com.shockang.study.algorithm.archive.queue

import java.util.StringJoiner

/**
 * 数组实现的循环队列
 */
class CircularQueue[E: Manifest](initSize: Int) extends Queue[E] {
  //底层存储数组
  private var array: Array[E] = new Array(initSize)
  //队列头指针
  private var head: Int = 0
  //队列尾的后继指针
  private var tail: Int = 0
  //队列大小
  override var size: Int = _

  //队列是否为空,这里有两种判断方法:1.size == 0;2.head == tail
  override def isEmpty: Boolean = size == 0

  //清空队列
  override def clear(): Unit = {
    array = new Array(initSize)
    head = 0
    tail = 0
    size = 0
  }

  //入队
  override def enqueue(e: E): Unit = {
    checkAndExpand()
    //到队尾了就从头入队,循环队列
    if (tail == array.length) tail = 0
    array(tail) = e
    tail += 1
    size += 1
  }

  //出队
  override def dequeue(): E = {
    checkAndShrink()
    val e = array(head)
    if (head == array.length - 1) head = 0
    else head += 1
    size -= 1
    e
  }

  //方便打印
  override def toString: String = {
    val sj = new StringJoiner(",")
    if (head < tail) {
      for (i <- head until tail) sj.add(array(i).toString)
    } else if (head > tail) {
      for (i <- head until array.length) sj.add(array(i).toString)
      for (i <- 0 until tail) sj.add(array(i).toString)
    }
    "[" + sj + "]"
  }

  //检查并扩容
  private def checkAndExpand(): Unit = {
    if (size + 1 > array.length) change()
  }

  //检查并缩容
  private def checkAndShrink(): Unit = {
    if (isEmpty) throw new NoSuchElementException
    if (size - 1 < 0.1 * array.length) change()
  }

  //当前数组大小变成队列长度的两倍
  private def change(): Unit = {
    val newArray: Array[E] = new Array(if (size == 0) 1 else size << 1)
    //数据迁移
    var index: Int = 0
    //这里需要分类讨论
    if (head < tail) {
      for (i <- head until tail) {
        newArray(index) = array(i)
        index += 1
      }
    } else if (head > tail) {
      //先迁移队头到数组结尾的
      for (i <- head until array.length) {
        newArray(index) = array(i)
        index += 1
      }
      //再迁移数组开头到队尾的
      for (i <- 0 until tail) {
        newArray(index) = array(i)
        index += 1
      }
    }
    head = 0
    tail = size
    array = newArray
  }
}

测试

package com.shockang.study.algorithm.archive.queue

object Main extends App {
  //val queue: Queue[Int] = new ArrayQueue[Int](10)
  //val queue: Queue[Int] = new LinkedListQueue[Int]()
  val queue: Queue[Int] = new CircularQueue[Int](5)
  out()
  try {
    queue.dequeue()
  } catch {
    case e: Exception => println(e)
  }
  queue.enqueue(0)
  queue.enqueue(1)
  queue.enqueue(2)
  queue.enqueue(3)
  queue.enqueue(4)
  queue.enqueue(5)
  queue.enqueue(6)
  queue.enqueue(7)
  queue.enqueue(8)
  queue.enqueue(9)
  out()
  queue.enqueue(10)
  out()
  println(queue.dequeue())
  out()
  println(queue.dequeue())
  println(queue.dequeue())
  println(queue.dequeue())
  println(queue.dequeue())
  println(queue.dequeue())
  println(queue.dequeue())
  println(queue.dequeue())
  println(queue.dequeue())
  println(queue.dequeue())
  out()
  println(queue.dequeue())
  out()
  try {
    println(queue.dequeue())
  } catch {
    case e: Exception => println(e)
  }
  queue.clear()
  out()

  def out(): Unit = {
    println("************************************************")
    try {
      println(queue.size)
      println(queue.isEmpty)
      println(queue)
    } catch {
      case e: Exception => println(e)
    }
    println("************************************************")
  }
}

输出

************************************************
0
true
[]
************************************************
java.util.NoSuchElementException
************************************************
10
false
[0,1,2,3,4,5,6,7,8,9]
************************************************
************************************************
11
false
[0,1,2,3,4,5,6,7,8,9,10]
************************************************
0
************************************************
10
false
[1,2,3,4,5,6,7,8,9,10]
************************************************
1
2
3
4
5
6
7
8
9
************************************************
1
false
[10]
************************************************
10
************************************************
0
true
[]
************************************************
java.util.NoSuchElementException
************************************************
0
true
[]
************************************************

个人博客:

CSDN