数据结构与算法之美五之手写Stack

专栏第五篇,通过两种方式实现栈。

  1. 数组
  2. 单链表

知识图谱

先呈上关于栈的知识图谱。

在这里插入图片描述

栈的 API

先将栈的公共特征提取出来成为特质。

package com.shockang.study.algorithm.archive.stack

/**
 * 将栈的共同特征抽象成特质
 *
 * @author Shockang
 */
trait Stack[E] {

  //大小
  var size: Int

  //是否为空
  def isEmpty: Boolean

  //入栈
  def push(e: E)

  //出栈
  def pop(): E

  //查看栈顶
  def peek(): E

  //清空栈
  def clear()
}

然后基于这个特质提供两种不同的实现方式。

数组实现

package com.shockang.study.algorithm.archive.stack

import java.util.StringJoiner

/**
 * 数组实现的栈
 *
 * @author Shockang
 */
class ArrayStack[E: Manifest](initSize: Int) extends Stack[E] {
  //底层用数组存储
  private var array: Array[E] = if (initSize > 0) new Array(initSize) else throw new IllegalArgumentException

  //当前数组最大容量
  private var maxSize: Int = initSize

  //当前栈大小
  override var size: Int = 0

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

  //入栈
  override def push(e: E): Unit = {
    //检查是否需要扩容
    checkAndExpand()
    array(size) = e
    size += 1
  }

  //出栈
  override def pop(): E = {
    //此时栈不能为空
    if (isEmpty) throw new NoSuchElementException
    val e: E = array(size - 1)
    array(size - 1) = null.asInstanceOf[E]
    size -= 1
    e
  }

  //查看栈顶
  override def peek(): E = {
    //此时栈不能为空
    if (isEmpty) throw new NoSuchElementException
    array(size - 1)
  }

  //清空栈
  override def clear(): Unit = {
    array = new Array(initSize)
    maxSize = initSize
    size = 0
  }

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

  //检查是否需要扩容
  private def checkAndExpand(): Unit = {
    if (size + 1 > maxSize) {
      //两倍扩容
      maxSize <<= 1
      var oldArray: Array[E] = array
      array = new Array(maxSize)
      //通过System.arraycopy迁移数据到新数组
      System.arraycopy(oldArray, 0, array, 0, size)
      //有助于 GC
      oldArray = null
    }
  }

}

单链表实现

package com.shockang.study.algorithm.archive.stack

import java.util.StringJoiner

/**
 * 单链表实现的栈
 *
 * @author Shockang
 */
class LinkedListStack[E] extends Stack[E] {

  //单链表单个结点,存储值和后继指针
  private class Node(var item: E, var next: Node)

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

  //栈大小
  override var size: Int = 0

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

  //入栈
  override def push(e: E): Unit = {
    //注意倒序添加
    head = new Node(e, head)
    size += 1
  }

  //出栈
  override def pop(): E = {
    //判空
    if (isEmpty) throw new NoSuchElementException
    val temp = head
    head = temp.next
    size -= 1
    temp.item
  }

  //查看栈顶
  override def peek(): E = {
    //判空
    if (isEmpty) throw new NoSuchElementException
    head.item
  }

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

  //方便打印
  override def toString: String = {
    val sj: StringJoiner = new StringJoiner(",")
    var node = reverse(head)
    head = node
    while (node != null) {
      sj.add(node.item.toString)
      node = node.next
    }
    //反转之后别忘了还原,好吧,这里这么搞主要还是为了复习两种反转链表的方法
    head = reverse2(head)
    "[" + sj + "]"
  }

  //为了和数组实现打印保持一致,这里利用递归将单链表反转
  private def reverse(node: Node): Node = {
    if (node == null || node.next == null) return node
    val temp = node.next
    val newHead = reverse(node.next)
    temp.next = node
    node.next = null
    newHead
  }

  //利用迭代将单链表反转
  private def reverse2(node: Node): Node = {
    var temp: Node = null
    var pre: Node = null
    var cur: Node = node
    while (cur != null) {
      temp = cur.next
      cur.next = pre
      pre = cur
      cur = temp
    }
    pre
  }
}

测试

package com.shockang.study.algorithm.archive.stack

object Main extends App {
  //var stack: Stack[Int] = new ArrayStack(5)
  var stack: Stack[Int] = new LinkedListStack()
  out(stack)
  try {
    stack.pop()
  } catch {
    case e: Exception => println(e)
  }
  stack.push(0)
  stack.push(1)
  stack.push(2)
  stack.push(3)
  stack.push(4)
  out(stack)
  stack.push(5)
  out(stack)
  println(stack.pop())
  println(stack.pop())
  out(stack)
  stack.clear()
  out(stack)

  def out(stack: Stack[Int]): Unit = {
    println("************************************************")
    try {
      println(stack.size)
      println(stack.isEmpty)
      println(stack)
      println(stack.peek())
    } catch {
      case e: Exception => println(e)
    }
    println("************************************************")
  }

}

输出

************************************************
0
true
[]
java.util.NoSuchElementException
************************************************
java.util.NoSuchElementException
************************************************
5
false
[0,1,2,3,4]
4
************************************************
************************************************
6
false
[0,1,2,3,4,5]
5
************************************************
5
4
************************************************
4
false
[0,1,2,3]
3
************************************************
************************************************
0
true
[]
java.util.NoSuchElementException
************************************************

个人网站:

https://www.shockang.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值