动态数组

在其他语言中(例如Java),数组在声明的时候就需要指定长度,是一种固定长度的类型。而JavaScript的数组本身就是动态的,但是作为练习,可以先尝试实现一个动态数组:

  1. class CustomArray {
  2. constructor (capacity = 10) {
  3. this.data = new Array(capacity)
  4. this.size = 0
  5. }
  6. getSize () {
  7. return this.size
  8. }
  9. getCapacity () {
  10. return this.data.length
  11. }
  12. get (index) {
  13. if (index < 0 || index >= this.size) throw new RangeError('Get failed. Required index > 0 and index < size')
  14. return this.data[index]
  15. }
  16. getLast () {
  17. return this.get(this.size - 1)
  18. }
  19. set (index, el) {
  20. if (index < 0 || index >= this.size) throw new RangeError('Get failed. Required index > 0 and index < size')
  21. this.data[index] = el
  22. }
  23. isEmpty () {
  24. return this.size === 0
  25. }
  26. contains (el) {
  27. for (let i = 0; i < this.size; i ++) {
  28. if (el.equals(this.data[i])) return true
  29. }
  30. return false
  31. }
  32. findIndex (el) {
  33. for (let i = 0; i < this.size; i ++) {
  34. if (el.equals(this.data[i])) return i
  35. }
  36. return -1
  37. }
  38. toString () {
  39. let str = `Array: size = ${ this.size }, capacity = ${ this.data.length }`
  40. if (this.size) {
  41. let i
  42. for (i = 0; i < this.size; i ++) str += ('\n' + this.data[i].toString())
  43. }
  44. return str
  45. }
  46. add (index, el) {
  47. if (index < 0 || index > this.size) throw new RangeError('Add failed. Required index > 0 and index < size')
  48. if (this.size === this.data.length) this.resize(this.size * 2)
  49. let size = this.size
  50. while (size > index) {
  51. this.data[size] = this.data[size - 1]
  52. size --
  53. }
  54. this.data[index] = el
  55. this.size ++
  56. return this.size
  57. }
  58. addFirst (el) {
  59. return this.add(0, el)
  60. }
  61. addLast (el) {
  62. return this.add(this.size, el)
  63. }
  64. remove (index) {
  65. if (index < 0 || index >= this.size) throw new RangeError('Delete failed. Required index > 0 and index < size')
  66. const delEl = this.data[index]
  67. for (let i = index; i < this.size - 1; i ++) this.data[i] = this.data[i + 1]
  68. this.size --
  69. this.data[this.size] = null
  70. if (this.size === Math.floor(this.data.length / 2)) this.resize(this.size)
  71. return delEl
  72. }
  73. removeFirst () {
  74. return this.remove(0)
  75. }
  76. removeLast () {
  77. return this.remove(this.size - 1)
  78. }
  79. removeElement (el) {
  80. const index = this.findIndex(el)
  81. if (index !== -1) this.remove(index)
  82. }
  83. resize (len) {
  84. const newData = new Array(len)
  85. for (let i = 0; i < this.size; i ++) newData[i] = this.data[i]
  86. this.data = newData
  87. }
  88. }
  89. module.exports = CustomArray

栈是一种后进先出的数据结构。例如撤销操作就利用了栈这种数据结构。栈通常会有pushpoppeek等操作。
这里利用动态数组实现栈:

  1. const CustomArray = require('./CustomArray')
  2. class Stack extends CustomArray {
  3. constructor (capacity) {
  4. if (typeof capacity === 'number') super(capacity)
  5. else super(0)
  6. }
  7. push (el) {
  8. return this.addLast(el)
  9. }
  10. pop () {
  11. return this.removeLast()
  12. }
  13. peek () {
  14. return this.getLast()
  15. }
  16. toString () {
  17. let ret = '[Stack]: [\n\t'
  18. for (let i = 0; i < this.size - 1; i ++) {
  19. ret += this.data[i].toString() + '\n\t'
  20. }
  21. ret += `${this.data[this.size - 1]}\n]`
  22. return ret
  23. }
  24. }
  25. module.exports = Stack

队列

和栈不同,队列是先进先出的数据结构,这里同样使用动态数组来实现队列:

  1. const CustomArray = require("./CustomArray")
  2. class Queen extends CustomArray {
  3. constructor (capacity = 0) {
  4. super(capacity)
  5. }
  6. enqueue (value) {
  7. return this.addLast(value)
  8. }
  9. dequeue () {
  10. return this.removeFirst()
  11. }
  12. getFront () {
  13. return this.get(0)
  14. }
  15. toString () {}
  16. }
  17. module.exports = Queen

上述队列的实现,出队的时间复杂度都是O(n),我们可以使用循环队列的方式将时间复杂度降低为O(1),使用两个指针即可:

  1. class LoopQueue {
  2. constructor (capacity = 1) {
  3. this.data = new Array(capacity + 1) // 额外使用一个空间来区分队列为空和队列满的情况
  4. this.size = 0
  5. this.front = 0
  6. this.tail = 0
  7. }
  8. getCapacity () {
  9. return this.data.length - 1
  10. }
  11. isEmpty () {
  12. return this.front === this.tail
  13. }
  14. getFront () {
  15. if (this.isEmpty()) throw new RangeError('cannot get front element of empty queue.')
  16. return this.data[this.front % this.data.length]
  17. }
  18. enqueue (el) {
  19. if (this.size > 0 && this.front === (this.tail + 1) % this.data.length)
  20. this.resize(this.getCapacity() * 2)
  21. this.data[this.tail] = el
  22. this.tail = (this.tail + 1) % this.data.length
  23. this.size ++
  24. }
  25. dequeue () {
  26. if (this.isEmpty()) throw new RangeError('cannot dequeue element from an empty queue.')
  27. if (this.size > 0 && this.size === Math.floor(this.getCapacity() / 4))
  28. this.resize(Math.floor(this.getCapacity() / 2))
  29. const ret = this.data[this.front]
  30. this.front = (this.front + 1) % this.data.length
  31. this.size --
  32. return ret
  33. }
  34. resize (newCapacity) {
  35. const newData = new Array(newCapacity + 1)
  36. for (let i = 0; i < this.size; i ++) {
  37. newData[i] = this.data[(this.front + i) % this.data.length]
  38. }
  39. this.data = newData
  40. this.front = 0
  41. this.tail = this.size
  42. }
  43. toString () {
  44. let ret = '[Queue]: [front: '
  45. for (let i = this.front; i != this.tail; i = (i + 1) % this.data.length) {
  46. ret += this.data[i]
  47. if ((i + 1) % this.data.length !== this.tail) ret += ', '
  48. }
  49. ret += ' tail]'
  50. return ret
  51. }
  52. }
  53. module.exports = LoopQueue

也可以不适用size记录队列长度:

  1. /**
  2. * 版本2:不使用 size
  3. */
  4. class LoopQueen {
  5. constructor (capacity = 1) {
  6. this.data = new Array(capacity + 1)
  7. this.front = 0
  8. this.tail = 0
  9. }
  10. getCapacity () {
  11. return Math.max(this.data.length - 1, 0)
  12. }
  13. isEmpty () {
  14. return this.front === this.tail
  15. }
  16. getFront () {
  17. if (this.isEmpty()) throw new RangeError('cannot get the front element of empty queue.')
  18. return this.data[this.front]
  19. }
  20. enqueue (el) {
  21. if (
  22. this.getCapacity() > 0 &&
  23. (this.tail + 1) % this.data.length === this.front
  24. ) {
  25. this.resize(this.getCapacity() * 2)
  26. }
  27. this.data[this.tail] = el
  28. this.tail = (this.tail + 1) % this.data.length
  29. }
  30. dequeue () {
  31. if (this.isEmpty()) throw new RangeError('cannot dequeue element from an empty queue.')
  32. const size = this.tail > this.front
  33. ? this.tail - this.front
  34. : this.tail + this.data.length - this.front
  35. if (
  36. this.getCapacity() > 0 &&
  37. size <= Math.floor(this.getCapacity() / 4)
  38. ) {
  39. this.resize(Math.floor(this.getCapacity() / 2))
  40. }
  41. const ret = this.data[this.front]
  42. this.front = (this.front + 1) % this.data.length
  43. return ret
  44. }
  45. resize (newCapacity) {
  46. const newData = new Array(newCapacity + 1)
  47. const size = this.tail > this.front
  48. ? this.tail - this.front
  49. : this.tail + this.data.length - this.front
  50. for (let i = 0; i < size; i ++) {
  51. newData[i] = this.data[(this.front + i) % this.data.length]
  52. }
  53. this.data = newData
  54. this.front = 0
  55. this.tail = size
  56. }
  57. toString () {
  58. let ret = '[Queue]: [front '
  59. for (let i = this.front; i !== this.tail; i = (i + 1) % this.data.length) {
  60. ret += this.data[i]
  61. if ((i + 1) % this.data.length !== this.tail) ret += ', '
  62. }
  63. ret += ` tail] => [front: ${this.front}] => [tail: ${this.tail}] => [capacity: ${this.getCapacity()}]`
  64. return ret
  65. }
  66. }
  67. module.exports = LoopQueen

上述两种实现都会浪费一个空间,如果不想浪费一个空间可以这样实现:

  1. /**
  2. * 版本3:不浪费一个空间,使用size来判断队列是否已满
  3. */
  4. class LoopQueen {
  5. constructor (capacity = 1) {
  6. this.data = new Array(capacity)
  7. this.size = 0
  8. this.front = 0
  9. this.tail = 0
  10. }
  11. isEmpty () {
  12. return this.size === 0
  13. }
  14. getCapacity () {
  15. return this.data.length
  16. }
  17. getFront () {
  18. if (this.isEmpty()) throw new RangeError('cannot get the front element of empty queue.')
  19. return this.data[this.front]
  20. }
  21. enqueue (el) {
  22. if (this.getCapacity() > 0 && this.size === this.getCapacity()) this.resize(this.getCapacity() * 2)
  23. this.data[this.tail] = el
  24. this.tail = (this.tail + 1) % this.data.length
  25. this.size ++
  26. }
  27. dequeue () {
  28. if (this.isEmpty()) throw new RangeError('cannot dequeue element from an empty queue.')
  29. if (this.getCapacity() > 0 && this.size <= Math.floor(this.getCapacity() / 4)) this.resize(Math.floor(this.getCapacity() / 2))
  30. const ret = this.data[this.front]
  31. this.front = (this.front + 1) % this.getCapacity()
  32. this.size --
  33. return ret
  34. }
  35. resize (newCapacity) {
  36. const newData = new Array(newCapacity)
  37. for (let i = 0; i < this.size; i ++) {
  38. newData[i] = this.data[(this.front + i) % this.data.length]
  39. }
  40. this.data = newData
  41. this.front = 0
  42. this.tail = this.size
  43. }
  44. toString () {
  45. let ret = '[Queue]: [front '
  46. for (let i = 0; i < this.size; i ++) {
  47. ret += this.data[(i + this.front) % this.data.length]
  48. if (i + 1 !== this.size) ret += ', '
  49. }
  50. ret += ` tail] => [front: ${this.front}] => [tail: ${this.tail}] => [capacity: ${this.getCapacity()}]`
  51. return ret
  52. }
  53. }
  54. module.exports = LoopQueen