堆、栈和队列是计算机科学中常见的数据结构和概念,它们在内存管理、算法设计和程序执行中扮演着不同的角色。它们各自有不同的实现方式和用途,彼此之间并没有直接的关系,但在某些场景下可以相互配合使用。
一、概念
1. 栈(Stack)
- 定义: 栈是一种后进先出(LIFO, Last In First Out)的数据结构。栈中的元素只能从一端(称为栈顶)插入或删除。
- 特点:
- 只能从栈顶添加或移除元素。
- 常见操作:
push
(压栈,插入元素)、pop
(出栈,移除元素)、peek
(查看栈顶元素)。
- 用途:
- 函数调用管理(调用栈)。
- 表达式求值(例如逆波兰表达式)。
- 撤销操作(如文本编辑器中的撤销操作)。
2. 队列(Queue)
- 定义: 队列是一种先进先出(FIFO, First In First Out)的数据结构。队列中的元素只能从一端(称为队尾)插入,从另一端(称为队头)删除。
- 特点:
- 只能从队尾添加元素,从队头移除元素。
- 常见操作:
enqueue
(入队,插入元素)、dequeue
(出队,移除元素)、peek
(查看队头元素)。
- 用途:
- 任务调度(例如操作系统中的进程调度)。
- 广度优先搜索(BFS)算法。
- 数据流处理(如缓冲区管理)。
3. 堆(Heap)
- 定义: 堆是一种树形数据结构,通常用于实现优先队列。堆分为最大堆和最小堆:
- 最大堆:每个节点的值都大于或等于其子节点的值。
- 最小堆:每个节点的值都小于或等于其子节点的值。
- 特点:
- 堆是一种完全二叉树。
- 通过堆顶元素来快速获取最大值或最小值。
- 常见操作:
insert
(插入元素)、extract
(移除堆顶元素)、heapify
(堆化操作)。
- 用途:
- 优先队列的实现。
- 堆排序。
- 图的算法(如Dijkstra算法)。
4. 总结和关系
- 栈和队列 都是线性数据结构,但它们的操作规则不同:栈是后进先出,而队列是先进先出。
- 堆 则是一种树形结构,用于快速访问优先级最高(或最低)的元素,可以看作是更复杂的队列实现。
虽然堆、栈、队列的用途和实现各异,但它们在不同的算法和数据处理场景中可能会相互配合。例如,堆可以用来实现优先队列,而栈和队列可以用来管理算法中的数据流。
二、问题
在js中,栈里面的元素在内存中是连续分布的么?
在 JavaScript 中,栈、队列和堆的数据结构并不像在某些低级语言(如 C 或 C++)中那样直接操作内存,因此它们在内存中的存储方式并不是完全连续的。
JavaScript 中的数组可以用于实现栈。虽然从逻辑上讲,栈的操作是对数组的“栈顶”进行操作,但在内存中,数组的元素并不保证是连续分布的。JavaScript 数组是对象,它们的元素可以存储在内存的不同位置,并且根据数组的增长和缩小,JavaScript 引擎可能会在后台进行内存重分配。
三、创建
栈(Stack)
栈可以使用 JavaScript 的数组来实现,因为数组自带的 push
和 pop
方法正好符合栈的“后进先出”(LIFO)特性。
class Stack {
constructor() {
this.items = [];
}
push(element) {
this.items.push(element); // 入栈
}
pop() {
return this.items.pop(); // 出栈
}
peek() {
return this.items[this.items.length - 1]; // 查看栈顶元素
}
isEmpty() {
return this.items.length === 0; // 判断栈是否为空
}
size() {
return this.items.length; // 返回栈的大小
}
}
队列(Queue)
队列也可以使用数组来实现,利用数组的 push
和 shift
方法来分别实现入队和出队操作。
class Queue {
constructor() {
this.items = [];
}
enqueue(element) {
this.items.push(element); // 入队
}
dequeue() {
return this.items.shift(); // 出队
}
front() {
return this.items[0]; // 查看队头元素
}
isEmpty() {
return this.items.length === 0; // 判断队列是否为空
}
size() {
return this.items.length; // 返回队列的大小
}
}
堆(Heap)
堆是一个完全二叉树结构,可以使用数组来表示树结构。下面是一个最小堆的简单实现:
class MinHeap {
constructor() {
this.heap = [];
}
insert(value) {
this.heap.push(value);
this.bubbleUp();
}
bubbleUp() {
let index = this.heap.length - 1;
while (index > 0) {
let parentIndex = Math.floor((index - 1) / 2);
if (this.heap[index] >= this.heap[parentIndex]) break;
[this.heap[index], this.heap[parentIndex]] = [this.heap[parentIndex], this.heap[index]];
index = parentIndex;
}
}
extractMin() {
const min = this.heap[0];
const end = this.heap.pop();
if (this.heap.length > 0) {
this.heap[0] = end;
this.sinkDown(0);
}
return min;
}
sinkDown(index) {
const length = this.heap.length;
const element = this.heap[index];
while (true) {
let leftChildIdx = 2 * index + 1;
let rightChildIdx = 2 * index + 2;
let leftChild, rightChild;
let swap = null;
if (leftChildIdx < length) {
leftChild = this.heap[leftChildIdx];
if (leftChild < element) {
swap = leftChildIdx;
}
}
if (rightChildIdx < length) {
rightChild = this.heap[rightChildIdx];
if (
(swap === null && rightChild < element) ||
(swap !== null && rightChild < leftChild)
) {
swap = rightChildIdx;
}
}
if (swap === null) break;
[this.heap[index], this.heap[swap]] = [this.heap[swap], this.heap[index]];
index = swap;
}
}
getMin() {
return this.heap[0];
}
size() {
return this.heap.length;
}
}
总结
- 栈和队列 使用数组来实现,因为它们的操作方式(
push
/pop
和push
/shift
)很符合数组的方法。 - 堆 使用数组来模拟树结构,实现优先队列。
在这些数据结构中,JavaScript 的内存管理由引擎自动处理,你无需关心元素在内存中的具体位置。