前言: 最近因为学习数据结构与算法,所以就写把自己学到的东西分享出来,以及自己的一些感悟。
这篇文章主要讲解
Javascript中一些常见的数据结构及其实现。
Stack (栈)
栈是一种后进先出(Last In First Out/LIFO)的数据结构。
栈的实现:
实现原理: 主要是通过对 array 不同的操作方式,来达到我们想要的结果
class Stack {
constructor() {
this.items = []
}
is_empty() {
// 判断栈是否为空
return this.items.length === 0
}
push(item) {
// 添加到栈的顶部
this.items.append(item)
}
pop() {
// 从栈的顶部删除
return this.items.pop()
}
peek() {
// 查找栈顶的元素
return this.items[this.items.length - 1]
}
size() {
// 返回栈的大小
return this.items.length
}
}
栈的应用:
题目: 简单的符号匹配。如((())) ==> true,([()]) ==> true,(([)) ==> false
/**
* 利用栈的后进先出特性,先将左半边的符号入栈,当匹配到后半边的时候,
* 依次出栈,依次匹配,如果能匹配上,并且最后栈为空,说明是完全匹配。
*/
function parChecker(str) {
let s = new Stack()
let index = 0
while (index < str.length) {
let sym = str[index]
if (sym === '([{') {
s.push(sym)
} else {
if (s.is_empty()) {
return false
} else {
let top = s.pop()
if (!matches(top, sym)) {
return false
}
}
}
index += 1
}
if (s.is_empty()) {
return true
} else {
return false
}
}
function matches(open, close) {
let opens = '([{'
let closes = ')}'
return opens.index(open) === closes.index(close)
}
console.log(parChecker('({[()]})')) // true
console.log(parChecker('({[)})'))
Queue (队列)
队列是一种先进先出(First In First Out/FIFO)的数据结构。
队列的实现
实现原理:和栈一样都是通过array来构建的,不同在于:栈是从末尾删除,队列是从头部删除
class Queue {
constructor() {
this.items = []
}
is_empty() {
return this.items.length === 0
}
size() {
return this.items.length
}
// 入队
enqueue(data) {
this.items.push(data)
}
// 出队,删除的是头部元素
dequeue() {
return this.items.shift()
}
}
队列的应用:
著名的约瑟夫环问题。
/*
比如有5个人玩游戏([1, 2, 3, 4, 5]),规定7个一轮回,直至剩下最后一个人。
(拿炸金花来说,发牌一般是发给下一个人)
第一次: 2, 3, 4, 5, 1, 2, 3。 最后一张牌是发给3,所以3淘汰。[1, 2, 4, 5]
第二次: 因为3淘汰了,所以4成为发牌人。 5, 1, 2, 4, 5, 1, 2。最后是2,所以2淘汰。[1, 4, 5]
第三次: 因为2淘汰了,所以4成为发牌人。5, 1, 4, 5, 1, 4,5。最后是5,所以5淘汰 [1, 4]
第四次: 因为5淘汰了,所以1成为发牌人。4,1,4,1, 4, 1, 4,所以4淘汰,最后只剩下1.
对于这个问题,我们可以用队列来解决。(后面我们还会说到通过单向循环链表来解决,不过略比队列要复杂点)
思路是: 通过不断地入队和出队,将每次循环需要删除的元素‘移动’到队列的最前端(也就是入队最早的),来达到删除他的目的
*/
function yueSeFu(list, num) {
let q = new Queue()
for (let i = 0; i < list.length; i++) {
q.enqueue(list[i])
}
while (q.size() > 1) {
for (let j = 0; j < num; j++) {
q.enqueue(q.dequeue())
}
q.dequeue()
}
return q.dequeue()
}
console.log(yueSeFu([1, 2, 3, 4, 5], 7)) // 1
双端队列
在队列的基础上,既能左边先进先出,也能又边先进先出。(也称为双端队列)
双端队列的实现
实现原理: 通过对list的不同操作来达到我们想要的效果
class Deque {
constructor() {
this.items = []
}
is_empty() {
return this.items.length === 0
}
size() {
return this.items.length
}
// 在后面添加
addRear(data) {
this.items.push(data)
}
// 在前面添加
addFront(data) {
this.items.unshift(data)
}
// 在后面删除
removeRear() {
return this.items.pop()
}
// 在前面删除
removeFront() {
return this.items.shift()
}
}
双端队列的应用:
回文字符串检查。 如'abcba' ==> true / 'abcd' ==> false
/**
通过双端队列就能很好的检查一个字符串是否是回文字符串。
思路:前面删除的字符串和后面删除的字符串是否一样
*/
function palChecker(str) {
let deque = new Deque()
for (let i = 0; i < str.length; i++) {
deque.addRear(str[i])
}
while (deque.size() > 1) {
let rear = deque.removeRear()
let front = deque.removeFront()
if (rear !== front) {
return false
}
}
return true
}
console.log('abcba') // true
console.log('abcd') // false
结尾:写了一遍,自己又加深了一边记忆。每种数据结构都有他们对应的应用场景,只有很好的理解他们,才能以最简单的方式正确的解决最复杂的问题。
如果文章有不对的地方,欢迎指正。By the way, 最近在学Python。如果想在python中实现这些数据结构,代码基本上是一样的。只是在Python中操作List的方法略有不同而已。
下次会更新链表(单向链表,单向循环链表,双向链表,双向循环链表)。谢谢!