动态数组
在其他语言中(例如Java),数组在声明的时候就需要指定长度,是一种固定长度的类型。而JavaScript的数组本身就是动态的,但是作为练习,可以先尝试实现一个动态数组:
class CustomArray {constructor (capacity = 10) {this.data = new Array(capacity)this.size = 0}getSize () {return this.size}getCapacity () {return this.data.length}get (index) {if (index < 0 || index >= this.size) throw new RangeError('Get failed. Required index > 0 and index < size')return this.data[index]}getLast () {return this.get(this.size - 1)}set (index, el) {if (index < 0 || index >= this.size) throw new RangeError('Get failed. Required index > 0 and index < size')this.data[index] = el}isEmpty () {return this.size === 0}contains (el) {for (let i = 0; i < this.size; i ++) {if (el.equals(this.data[i])) return true}return false}findIndex (el) {for (let i = 0; i < this.size; i ++) {if (el.equals(this.data[i])) return i}return -1}toString () {let str = `Array: size = ${ this.size }, capacity = ${ this.data.length }`if (this.size) {let ifor (i = 0; i < this.size; i ++) str += ('\n' + this.data[i].toString())}return str}add (index, el) {if (index < 0 || index > this.size) throw new RangeError('Add failed. Required index > 0 and index < size')if (this.size === this.data.length) this.resize(this.size * 2)let size = this.sizewhile (size > index) {this.data[size] = this.data[size - 1]size --}this.data[index] = elthis.size ++return this.size}addFirst (el) {return this.add(0, el)}addLast (el) {return this.add(this.size, el)}remove (index) {if (index < 0 || index >= this.size) throw new RangeError('Delete failed. Required index > 0 and index < size')const delEl = this.data[index]for (let i = index; i < this.size - 1; i ++) this.data[i] = this.data[i + 1]this.size --this.data[this.size] = nullif (this.size === Math.floor(this.data.length / 2)) this.resize(this.size)return delEl}removeFirst () {return this.remove(0)}removeLast () {return this.remove(this.size - 1)}removeElement (el) {const index = this.findIndex(el)if (index !== -1) this.remove(index)}resize (len) {const newData = new Array(len)for (let i = 0; i < this.size; i ++) newData[i] = this.data[i]this.data = newData}}module.exports = CustomArray
栈
栈是一种后进先出的数据结构。例如撤销操作就利用了栈这种数据结构。栈通常会有push、pop、peek等操作。
这里利用动态数组实现栈:
const CustomArray = require('./CustomArray')class Stack extends CustomArray {constructor (capacity) {if (typeof capacity === 'number') super(capacity)else super(0)}push (el) {return this.addLast(el)}pop () {return this.removeLast()}peek () {return this.getLast()}toString () {let ret = '[Stack]: [\n\t'for (let i = 0; i < this.size - 1; i ++) {ret += this.data[i].toString() + '\n\t'}ret += `${this.data[this.size - 1]}\n]`return ret}}module.exports = Stack
队列
和栈不同,队列是先进先出的数据结构,这里同样使用动态数组来实现队列:
const CustomArray = require("./CustomArray")class Queen extends CustomArray {constructor (capacity = 0) {super(capacity)}enqueue (value) {return this.addLast(value)}dequeue () {return this.removeFirst()}getFront () {return this.get(0)}toString () {}}module.exports = Queen
上述队列的实现,出队的时间复杂度都是O(n),我们可以使用循环队列的方式将时间复杂度降低为O(1),使用两个指针即可:
class LoopQueue {constructor (capacity = 1) {this.data = new Array(capacity + 1) // 额外使用一个空间来区分队列为空和队列满的情况this.size = 0this.front = 0this.tail = 0}getCapacity () {return this.data.length - 1}isEmpty () {return this.front === this.tail}getFront () {if (this.isEmpty()) throw new RangeError('cannot get front element of empty queue.')return this.data[this.front % this.data.length]}enqueue (el) {if (this.size > 0 && this.front === (this.tail + 1) % this.data.length)this.resize(this.getCapacity() * 2)this.data[this.tail] = elthis.tail = (this.tail + 1) % this.data.lengththis.size ++}dequeue () {if (this.isEmpty()) throw new RangeError('cannot dequeue element from an empty queue.')if (this.size > 0 && this.size === Math.floor(this.getCapacity() / 4))this.resize(Math.floor(this.getCapacity() / 2))const ret = this.data[this.front]this.front = (this.front + 1) % this.data.lengththis.size --return ret}resize (newCapacity) {const newData = new Array(newCapacity + 1)for (let i = 0; i < this.size; i ++) {newData[i] = this.data[(this.front + i) % this.data.length]}this.data = newDatathis.front = 0this.tail = this.size}toString () {let ret = '[Queue]: [front: 'for (let i = this.front; i != this.tail; i = (i + 1) % this.data.length) {ret += this.data[i]if ((i + 1) % this.data.length !== this.tail) ret += ', '}ret += ' tail]'return ret}}module.exports = LoopQueue
也可以不适用size记录队列长度:
/*** 版本2:不使用 size*/class LoopQueen {constructor (capacity = 1) {this.data = new Array(capacity + 1)this.front = 0this.tail = 0}getCapacity () {return Math.max(this.data.length - 1, 0)}isEmpty () {return this.front === this.tail}getFront () {if (this.isEmpty()) throw new RangeError('cannot get the front element of empty queue.')return this.data[this.front]}enqueue (el) {if (this.getCapacity() > 0 &&(this.tail + 1) % this.data.length === this.front) {this.resize(this.getCapacity() * 2)}this.data[this.tail] = elthis.tail = (this.tail + 1) % this.data.length}dequeue () {if (this.isEmpty()) throw new RangeError('cannot dequeue element from an empty queue.')const size = this.tail > this.front? this.tail - this.front: this.tail + this.data.length - this.frontif (this.getCapacity() > 0 &&size <= Math.floor(this.getCapacity() / 4)) {this.resize(Math.floor(this.getCapacity() / 2))}const ret = this.data[this.front]this.front = (this.front + 1) % this.data.lengthreturn ret}resize (newCapacity) {const newData = new Array(newCapacity + 1)const size = this.tail > this.front? this.tail - this.front: this.tail + this.data.length - this.frontfor (let i = 0; i < size; i ++) {newData[i] = this.data[(this.front + i) % this.data.length]}this.data = newDatathis.front = 0this.tail = size}toString () {let ret = '[Queue]: [front 'for (let i = this.front; i !== this.tail; i = (i + 1) % this.data.length) {ret += this.data[i]if ((i + 1) % this.data.length !== this.tail) ret += ', '}ret += ` tail] => [front: ${this.front}] => [tail: ${this.tail}] => [capacity: ${this.getCapacity()}]`return ret}}module.exports = LoopQueen
上述两种实现都会浪费一个空间,如果不想浪费一个空间可以这样实现:
/*** 版本3:不浪费一个空间,使用size来判断队列是否已满*/class LoopQueen {constructor (capacity = 1) {this.data = new Array(capacity)this.size = 0this.front = 0this.tail = 0}isEmpty () {return this.size === 0}getCapacity () {return this.data.length}getFront () {if (this.isEmpty()) throw new RangeError('cannot get the front element of empty queue.')return this.data[this.front]}enqueue (el) {if (this.getCapacity() > 0 && this.size === this.getCapacity()) this.resize(this.getCapacity() * 2)this.data[this.tail] = elthis.tail = (this.tail + 1) % this.data.lengththis.size ++}dequeue () {if (this.isEmpty()) throw new RangeError('cannot dequeue element from an empty queue.')if (this.getCapacity() > 0 && this.size <= Math.floor(this.getCapacity() / 4)) this.resize(Math.floor(this.getCapacity() / 2))const ret = this.data[this.front]this.front = (this.front + 1) % this.getCapacity()this.size --return ret}resize (newCapacity) {const newData = new Array(newCapacity)for (let i = 0; i < this.size; i ++) {newData[i] = this.data[(this.front + i) % this.data.length]}this.data = newDatathis.front = 0this.tail = this.size}toString () {let ret = '[Queue]: [front 'for (let i = 0; i < this.size; i ++) {ret += this.data[(i + this.front) % this.data.length]if (i + 1 !== this.size) ret += ', '}ret += ` tail] => [front: ${this.front}] => [tail: ${this.tail}] => [capacity: ${this.getCapacity()}]`return ret}}module.exports = LoopQueen
