2

本系列所有文章:
第一篇文章:学习数据结构与算法之栈与队列
第二篇文章:学习数据结构与算法之链表
第三篇文章:学习数据结构与算法之集合
第四篇文章:学习数据结构与算法之字典和散列表
第五篇文章:学习数据结构与算法之二叉搜索树

起因

最近要准备校招,打开某网站准备开始刷题,发现算法题根本无法动手,于是觉得这块需要恶补。(⊙v⊙)嗯,至少得先知道概念吧。于是翻出了机房里的这本《学习JavaScript数据结构与算法》开始学习程序员的基础知识。这本书用了我最熟悉的JS来实现各种数据结构和算法,而且书很薄,可以说是一本不错的入门教程。虽然我是个前端,但是计算机基础不能丢下。

栈可以理解为一种特殊的数组。遵循后进先出(LIFO)的原则,元素在栈顶添加和删除。生活中常用来比作栈的例子主要是一叠盘子或一堆书,但是我觉得不够形象,因为盘子或书可以从中间被抽走。所以我一般把栈看成弹匣,想象一下子弹被一个个推进弹匣中,比如下图:

栈

(图片来自谷歌搜索,侵删)

用JavaScript实现栈

声明一个构造函数:

function Stack () {
  // 使用数组来保存栈元素
  var items = []
}

为栈声明一些方法:

  • push(elements(s)):添加一个或多个元素到栈顶
  • pop():删除位于栈顶的元素,并返回该元素
  • peek():返回栈顶元素
  • isEmpty():当前栈为空则返回true,否则为false
  • size():返回栈的元素个数
  • clear():清空栈

实现push

用数组的push方法向数组末尾添加新元素,实现元素入栈

// 栈顶添加
this.push = function (element) {
  items.push(element)
}

实现pop

用数组的pop方法在数组末尾删除一个元素,并返回删除元素,实现元素出栈

// 栈顶删除并返回删除元素
this.pop = function () {
  return items.pop()
}

实现peek

栈顶就是数组最后一个元素,使用Array[Array.length - 1]获得

// 返回栈顶元素
this.peek = function () {
  return items[items.length - 1]
}

实现其他方法

队列里面也用了这些方法,为避免重复,就先单独拿出来了。

// 栈是否为空
this.isEmpty = function () {
  return items.length === 0
}

// 返回栈里的元素个数
this.size = function () {
  return items.length
}

// 清空栈
this.clear = function () {
  items = []
}

// 打印栈
this.print = function () {
  console.log(items.toString())
}

栈的应用

书上的例子有将十进制转二进制,这里我把后面那个十进制转任意进制的代码贴出来。

十进制转二进制原理很简单:把十进制数不断除以2直到为0,然后把每次的余数拼接到一起就是二进制数。

转其他进制也是类似的方法,只不过是把除以2换成其他数而已。代码如下:

// 把十进制转成任何进制
function BaseConverter (decNumber, base) {
  var remStack = new Stack(),
      rem,
      binaryString = '',
      digits = '0123456789ABCDEF'

  // 判断十进制数是否为0,把余数推入栈中
  while (decNumber > 0) {
    rem = Math.floor(decNumber % base)
    remStack.push(rem)
    decNumber = Math.floor(decNumber / base)
  }

  // 把栈中的元素拼接打印出来
  while (!remStack.isEmpty()) {
    binaryString += digits[remStack.pop()]
  }

  // 返回转换的二进制数
  return binaryString
}

这里的decNumber是要转换的十进制数,base是要转换的进制,remStack是上面Stack的实例,在remStack中操作栈的方法。这里的digits是对打印出来的数做一个处理,比如十六进制的数余数会大于9,那么就要用A、B、C、D、E、sF来表示10~15。

栈的学习暂时就这样了,这里贴上代码地址,有兴趣的可以看看:

栈的实现-源代码

队列

和栈很类似,只是原则不同,队列是先进先出(FIFO),又称先来先服务。队列在头部删除元素,尾部添加元素。生活中队列的例子就是排队了,这也很容易理解。

用JavaScript实现队列

同样声明构造函数:

function Queue () {
  var items = []
}

队列的方法:

  • enqueue(elements(s)):向队列尾部添加一个或多个元素
  • dequeue():移除队列头部的元素并返回
  • front():返回队列头部的元素

其他的和栈一样。

实现enqueue

用push方法推入元素

// 向队列尾部添加元素
this.enqueue = function (element) {
  items.push(element)
}

实现dequeue

用shift方法删除第一个数组元素,并返回删除的元素

// 删除队列头部的元素并返回删除元素
this.dequeue = function () {
  return items.shift()
}

实现front

直接返回第一个数组元素

// 返回队列头部的元素
this.front = function () {
  return items[0]
}

队列的应用

书上有讲优先队列和循环队列的应用,这里就简单讲一下优先队列的原理:

要实现优先队列有两种思路:一是将元素按正确的位置添加到队列中,然后元素正常在队列头部被删除;二是元素在队列尾部不按优先级正常入列,然后按优先级删除对应元素。

书上是按第一种思路实现的优先队列,这里限于篇幅就贴上代码地址:

队列的实现-源代码

接下来谈谈循环队列的应用——击鼓传花游戏

function hotPotato (nameList, num) {
  var queue = new Queue()

  // 参与者的名字入列
  for (var i = 0; i < nameList.length; i++) {
    queue.enqueue(nameList[i])
  }

  var eliminated = ''

  // 队列中的最后一个人为胜者
  while (queue.size() > 1) {
    // 按设定的击鼓次数,每个人都从队列头部出列转到队列尾部(模拟传花)
    for (var i = 0; i < num; i++) {
      queue.enqueue(queue.dequeue())
    }
    // 到了规定次数后,在队列头部的人(相当于拿到花)被淘汰
    eliminated = queue.dequeue()
    console.log(eliminated + '在击鼓传花游戏中被淘汰')
  }

  // 胜者出列并被返回
  return queue.dequeue()
}

其中nameList是参与游戏的名字列表,num是击鼓次数。

队列学习就暂时到这里,明天继续学习链表。


LiuBoJun
264 声望18 粉丝

专注于知识库产品开发,在富文本编辑器领域有丰富经验