数据结构 | 数组、栈、队列(含JS实现)

数组理论基础

数组是存放连续内存空间上的相同数据类型的集合

在JavaScript中,可以在数组中保存不同数据类型的值,因此内存地址并不是连续的,而是采用hash映射的方式

但是现在的JavaScript引擎为了优化JavaScript,会分配一个连续的内存空间给相同数据类型的数组,以达到更好的遍历效果,所以,只要你数组里面存的是相同类型的值,在内存中的地址是连续的

需要注意:

  • 数组的下标都是从0开始的
  • 数组内存空间的地址是连续

所以在删除或者增加元素的时候,就难免要移动其他元素的地址

数组方法

JavaScript数组API(含手写)

栈(含手写栈类)

栈是一种遵循后进先出(LIFO)的有序集合

栈方法

  • 向栈顶添加元素——push
  • 从栈顶删除元素——pop
  • 查看栈顶元素——peek
  • 检查栈是否为空——isEmpty
  • 清空栈元素——clear

基于数组实现栈及其方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Stack {
constructor() {
this.stack = [];
}

// length可以用size代替
size() {
return this.stack.length;
}

// 向栈顶添加元素——push
push(val) {
this.stack.push(val);
return this.stack.length;
}

// 从栈顶删除元素——pop
pop() {
return this.stack.pop();
}

// 查看栈顶元素——peek
peek() {
return this.stack[this.stack.length - 1];
}

// 检查栈是否为空——isEmpty
isEmpty() {
return !this.stack.length;
}

// 清空栈元素——clear
clear() {
return this.stack = [];
}
}

应用一下吧~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 初始化stack
const stack = new Stack();

/*
实现以下效果:
[push(2), push(3), push(4), pop(), isEmpty(), peek(), pop(), pop(), isEmpty()]
得到的结果是:
[1,2,3,4,false,3,3,2,true]
*/

stack.push(2);
stack.push(3);
stack.push(4);

stack.pop();

// test1
console.log(stack.isEmpty()); // false

// test2
console.log(stack.peek()); // 3

stack.pop();
stack.pop();

// test2
console.log(stack.isEmpty()); // true

基于对象实现栈及其方法

用数组实现Stack是非常方便的,但是如果要在数组中找一个元素,最坏的情况下需要迭代数组的所有位置,也就是时间复杂度是O(n)

另外,数组是一个有序集合,为了保证元素排列有序,它会占用更多的内存空间

使用JavaScript对象存储所有栈元素的优势在于:

  • 直接获取元素,占用较少的内存空间
  • 保证所有元素按照我们需要的排序

注意:相对于数组版本,对象版本需要实现toString()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class Stack {
constructor() {
this.count = 0;
this.stack = {};
}

// length可以用size代替
size() {
return this.count;
}

// 检查栈是否为空——isEmpty
isEmpty() {
return this.count === 0;
}

// 向栈顶添加元素——push
push(val) {
this.stack[this.count] = val;
this.count++;
return this.count;
}

// 从栈顶删除元素——pop
pop() {
if (this.isEmpty()) {
return undefined;
} else {
const res = this.stack[this.stack.length -1];
this.count--;
delete this.stack[this.count];
return res;
}
}

// 查看栈顶元素——peek
peek() {
if (this.isEmpty()) {
return undefined;
}
return this.stack[this.count -1];
}

// toString() [对象特有]
toString() {
if (this.isEmpty()) {
return '';
}

let objString = String(this.stack[0]);
for (let i = 1; i < this.count; i++) {
objString = `${objString},${this.stack[i]}`;
};
return objString;
}
}

应用一下吧~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 初始化stack
const stack = new Stack();

/*
实现以下效果:
[push(2), push(3), push(4), pop(), isEmpty(), peek(), pop(), pop(), isEmpty(), push(2), push(3), push(4), toString()]
得到的结果是:
[1,2,3,4,false,3,3,2,true,1,2,3, '1,2,3']
*/

stack.push(2);
stack.push(3);
stack.push(4);

stack.pop();

// test1
console.log(stack.isEmpty()); // false

// test2
console.log(stack.peek()); // 3

stack.pop();
stack.pop();

// test2
console.log(stack.isEmpty()); // true

stack.push(2);
stack.push(3);
stack.push(4);

// test3
console.log(stack.toString()); // 2,3,4

栈应用

  • 函数调用
  • 表达式求值
  • 括号匹配
  • 浏览器的前进、后退功能

队列(含手写队列类)

队列是一种遵循先进先出(FIFO)的有序集合

队列方法

  • 向队列添加元素——enqueue
  • 从队列移除元素——dequeue
  • 查看队列头元素——peek
  • 检查队列是否为空——isEmpty
  • 清空队列——clear

基于数组实现队列及其方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Queue{
constructor() {
this.queue = [];
}

// size
size() {
return this.queue.length;
}

// 向队列添加元素——enqueue
enqueue(val) {
this.queue.push(val);
return this.queue.size();
}

// 从队列移除元素——dequeue
dequeue() {
return this.queue.shift();
}

// 查看队列头元素——peek
peek() {
return this.queue[0];
}

// 检查队列是否为空——isEmpty
isEmpty() {
return !this.queue.length;
}

// 清空队列——clear
clear() {
return this.queue = [];
}

}

基于对象实现队列及其方法

基于对象实现队列比数组要难一点,因为对象是根据key来记录索引的,所以需要lowsetCount来记录队头元素的索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Queue{
constructor() {
this.queue = [];
this.count = 0;
this.lowsetCount = 0;
}

// size
size() {
return this.count - this.lowsetCount;
}

// 向队列添加元素——enqueue
enqueue(val) {
this.queue[this.count] = val;
this.count++;
}

// 从队列移除元素——dequeue
dequeue() {
if (this.isEmpty()) {
return undefined;
}

const res = this.queue[this.lowsetCount];
delete this.queue[this.lowsetCount];
this.lowsetCount++;
return res;
}

// 查看队列头元素——peek
peek() {
if (this.isEmpty()) {
return undefined;
}

return this.queue[this.lowsetCount];
}

// 检查队列是否为空——isEmpty
isEmpty() {
return !this.size();
}

// 清空队列——clear
clear() {
this.queue = {};
this.count = 0;
this.lowsetCount = 0;
}

// toString()
toString() {
if (this.isEmpty()) {
return '';
}

let objString = String(this.queue[this.lowsetCount]);
for (let i = this.lowsetCount+1; i < this.count; i++) {
objString = `${objString},${this.queue[i]}`;
};
return objString;
}
}

应用一下吧~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 初始化队列
const queue = new Queue();

/*
实现下列效果:
[enqueue(1), enqueue(2), enqueue(3), isEmpty() ,peek(), dequeue(), dequeue(), enqueue(4), toString()]
打印
[1,2,3,false,1,1,2,4, '3,4']
*/

queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);

console.log(queue.isEmpty()); // false

console.log(queue.peek()); // 1

queue.dequeue();
queue.dequeue();

queue.enqueue(4);

console.log(queue.toString()); // 3,4

队列应用

  • 排队
  • 异步任务:微任务队列和宏任务队列
  • 打印队列

双端队列(含手写双端队列类)

双端队列支持同时从后面或者前面添加或者移除元素

双端队列方法

  • 从队头添加元素——addFront
  • 从队尾添加元素——addBack
  • 从队头移除元素——removeFront
  • 从队尾移除元素——removeBack
  • 查看队头元素——peekFont
  • 查看队尾元素——peekBack
  • 检查队列是否为空——isEmpty
  • 清空队列——clear

基于对象实现队列及其方法

用数组实现较为简单,这里就写了,直接用对象开写!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class Deque{
constructor() {
this.deque = {};
this.count = 0;
this.lowsetCount = 0;
}

// size
size() {
return this.count - this.lowsetCount;
}

// 从队头添加元素——addFront
addFront(val) {
if (this.isEmpty()) { // 情况1: 如果队列是空的
this.addBack(val);
} else if (this.lowsetCount > 0) { // 情况2:如果已经有删除过的情况,即 lowsetCount > 0,顶替lowsetCount-1的位置即可
this.deque[this.lowsetCount - 1] = val;
} else if (this.lowsetCount === 0) { // 情况3:为0的情况
for (let i = this.count; i > 0; i++) {
this.deque[this.count] = this.deque[this.count -1];
};
this.count++;
this.lowsetCount = 0;
this.deque[0] = val;
}
}

// 从队尾添加元素——addBack
addBack(val) {
this.deque[this.count] = val;
this.count++;
}

// 从队头移除元素——removeFront
removeFront() {
let res = this.deque[this.lowsetCount];
delete this.deque[this.lowsetCount];
this.lowsetCount++;
return res;
}

// 从队尾移除元素——removeBack
removeBack() {
this.count--;
let res = this.deque[this.count];
delete this.deque[this.count];
return res;
}

// 查看队头元素——peekFont
peekFront() {
return this.deque[this.lowsetCount];
}

// 查看队尾元素——peekBack
peekBack() {
return this.deque[this.count - 1];
}

// 检查队列是否为空——isEmpty
isEmpty() {
return this.count - this.lowsetCount === 0;
}

// 清空队列——clear
clear() {
this.queue = {};
this.count = 0;
this.lowsetCount = 0;
}

// toString()
toString() {
if (this.isEmpty()) {
return '';
}

let objString = String(this.deque[this.lowsetCount]);
for (let i = this.lowsetCount+1; i < this.count; i++) {
objString = `${objString},${this.deque[i]}`;
};
return objString;
}
}

应用一下吧~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const deque = new Deque();

console.log(deque.isEmpty()); // true

deque.addBack(1);
deque.addBack(2);

console.log(deque.toString()); // '1,2'

deque.addBack(3);

console.log(deque.toString()); // '1,2,3'

console.log(deque.size()); // 3
console.log(deque.isEmpty()); // false

deque.removeFront();
console.log(deque.toString()); // '2,3'

deque.removeBack();
console.log(deque.toString()); // '2'

双端队列应用

  • 浏览器撤销和回退操作
  • 判断回文子串

参考

代码随想录_数组基础