1.栈
- 栈是一种常见的数据结构,它遵循“后进先出”的原则。这意味着最后添加到栈中的元素会被最先移除。
//创建一个空栈:
let stack = [];
//向栈中添加元素(入栈):
stack.push(element);
//从栈中移除元素(出栈):
let removedElement = stack.pop();
//访问栈顶元素:
let topElement = stack[stack.length - 1];
2.队列
- 队列是另一种常见的数据结构,它遵循“先进先出”的原则。这意味着最先添加到队列中的元素会被最先移除。
//创建一个空队列:
let queue = [];
//向队列中添加元素(入队):
queue.push(element);
//从队列中移除元素(出队)
let removedElement = queue.shift();
//访问队列头部元素:
let frontElement = queue[0];
3.优先队列
- 优先队列是一种特殊类型的队列,其中每个元素都关联有一个优先级。与普通队列不同,优先队列中的元素不是按照它们进入队列的顺序进行处理,而是按照它们的优先级进行处理。具有更高优先级的元素会先被处理,而具有相同优先级的元素则按照它们进入队列的顺序进行处理。
class PriorityQueue {
constructor() {
this.queue = [];
}
// 添加元素到队列中 priority为优先等级
enqueue(element, priority) {
let item = { element, priority };
let added = false;
for (let i = 0; i < this.queue.length; i++) {
if (priority < this.queue[i].priority) {
this.queue.splice(i, 0, item);
added = true;
break;
}
}
if (!added) {
this.queue.push(item);
}
}
// 移除并返回队列中优先级最高的元素
dequeue() {
return this.queue.shift();
}
// 返回队列中优先级最高的元素
front() {
return this.queue[0];
}
// 检查队列是否为空
isEmpty() {
return this.queue.length === 0;
}
// 返回队列的大小
size() {
return this.queue.length;
}
// 清空队列
clear() {
this.queue = [];
}
}
4.链表
- 链表是一种常见的线性数据结构,它由一系列节点组成,每个节点包含了数据项以及指向下一个节点的引用,在JavaScript中,我们可以使用对象来表示链表中的节点,每个节点包含一个值和一个指向下一个节点的引用。
/ 定义链表节点类
class Node {
constructor(data) {
this.data = data; // 节点的值
this.next = null; // 指向下一个节点的引用,默认为null,表示链表的末尾
}
}
// 定义链表类
class LinkedList {
constructor() {
this.head = null; // 链表的头节点,默认为null,表示空链表
}
// 在链表末尾添加一个新节点
append(data) {
const newNode = new Node(data);
if (!this.head) {
this.head = newNode; // 如果链表为空,则新节点即为头节点
} else {
let current = this.head;
while (current.next) {
current = current.next; // 找到链表的末尾
}
current.next = newNode; // 将新节点连接到链表的末尾
}
}
// 在指定位置插入一个新节点
insert(position, data) {
if (position < 0 || position > this.size()) {
return false; // 插入位置无效
}
const newNode = new Node(data);
if (position === 0) {
newNode.next = this.head;
this.head = newNode; // 如果插入位置为0,则新节点成为头节点
} else {
let index = 0;
let current = this.head;
let previous = null;
while (index < position) {
previous = current;
current = current.next;
index++;
}
newNode.next = current;
previous.next = newNode; // 将新节点插入到指定位置
}
return true;
}
// 删除指定位置的节点
removeAt(position) {
if (position < 0 || position >= this.size()) {
return null; // 删除位置无效
}
let current = this.head;
if (position === 0) {
this.head = current.next;
} else {
let index = 0;
let previous = null;
while (index < position) {
previous = current;
current = current.next;
index++;
}
previous.next = current.next; // 将当前节点从链表中移除
}
return current.data;
}
// 返回链表的大小
size() {
let count = 0;
let current = this.head;
while (current) {
count++;
current = current.next;
}
return count;
}
}
5.双向链表
- 双向链表是一种链表数据结构,每个节点除了包含一个值外,还包含指向前一个节点和后一个节点的引用。这使得在双向链表中可以双向遍历,从而可以更高效地实现一些操作,如在任意位置插入或删除节点。
// 定义双向链表节点类
class Node {
constructor(data) {
this.data = data; // 节点的值
this.prev = null; // 指向前一个节点的引用,默认为null,表示链表的头节点
this.next = null; // 指向后一个节点的引用,默认为null,表示链表的末尾
}
}
// 定义双向链表类
class DoublyLinkedList {
constructor() {
this.head = null; // 链表的头节点,默认为null,表示空链表
this.tail = null; // 链表的尾节点,默认为null,表示空链表
}
// 在链表末尾添加一个新节点
append(data) {
const newNode = new Node(data);
if (!this.head) {
this.head = newNode;
this.tail = newNode; // 如果链表为空,则新节点既是头节点也是尾节点
} else {
newNode.prev = this.tail;
this.tail.next = newNode;
this.tail = newNode; // 将新节点连接到链表的末尾,并更新尾节点的引用
}
}
// 在指定位置插入一个新节点
insert(position, data) {
if (position < 0 || position > this.size()) {
return false; // 插入位置无效
}
const newNode = new Node(data);
if (position === 0) {
newNode.next = this.head;
if (!this.head) {
this.tail = newNode; // 如果链表为空,则新节点既是头节点也是尾节点
} else {
this.head.prev = newNode;
}
this.head = newNode; // 如果插入位置为0,则新节点成为头节点
} else if (position === this.size()) {
newNode.prev = this.tail;
this.tail.next = newNode;
this.tail = newNode; // 如果插入位置为链表末尾,则新节点成为尾节点
} else {
let index = 0;
let current = this.head;
while (index < position) {
current = current.next;
index++;
}
newNode.prev = current.prev;
newNode.next = current;
current.prev.next = newNode;
current.prev = newNode; // 将新节点插入到指定位置
}
return true;
}
// 删除指定位置的节点
removeAt(position) {
if (position < 0 || position >= this.size()) {
return null; // 删除位置无效
}
let current = this.head;
if (position === 0) {
this.head = current.next;
if (!this.head) {
this.tail = null; // 如果删除后链表为空,则更新尾节点的引用
} else {
this.head.prev = null;
}
} else if (position === this.size() - 1) {
current = this.tail;
this.tail = current.prev;
this.tail.next = null; // 如果删除位置为链表末尾,则更新尾节点的引用
} else {
let index = 0;
while (index < position) {
current = current.next;
index++;
}
current.prev.next = current.next;
current.next.prev = current.prev; // 将当前节点从链表中移除
}
return current.data;
}
// 返回链表的大小
size() {
let count = 0;
let current = this.head;
while (current) {
count++;
current = current.next;
}
return count;
}
}
6.集合
- 在计算机科学中,集合是一种数据结构,用于存储无序且唯一的元素。JavaScript语言内置了Set对象,它是ES6(ECMAScript 2015)标准引入的一种新的数据结构,用来存储一组唯一的值。这里自定义一个Set方便大家理解
class Set {
constructor() {
this.items = {}; // 使用对象存储集合的元素
}
// 向集合中添加元素,如果元素已存在则不添加
add(element) {
if (!this.has(element)) {
this.items[element] = element;
return true; // 添加成功
}
return false; // 元素已存在
}
// 从集合中移除元素,如果元素不存在则不做任何操作
delete(element) {
if (this.has(element)) {
delete this.items[element];
return true; // 删除成功
}
return false; // 元素不存在
}
// 检查集合中是否包含某个元素
has(element) {
return Object.prototype.hasOwnProperty.call(this.items, element);
}
// 清空集合中的所有元素
clear() {
this.items = {};
}
// 返回集合中的元素个数
size() {
return Object.keys(this.items).length;
}
// 返回集合中的所有元素组成的数组
values() {
return Object.values(this.items);
}
}
7.字典
- 在JavaScript中,字典类型通常指的是一种键值对的数据结构,也称为关联数组或映射。字典类型允许将键与值相关联,并且可以通过键来快速查找对应的值。在JavaScript中,字典类型可以使用对象(Object)或者Map来实现,但是对象的键值只能是字符串或Symbol类型
JavaScript中的Map与对象(Object)之间有几个主要区别
- 对象的属性数量是没有限制的,可以非常大,而Map的大小可以通过size属性获取,而且更适合存储大量键值对的数据结构。
- Map对象会根据插入顺序维护键值对的顺序,而对象的属性遍历顺序并不是严格按照插入顺序的。
- 对于大型数据集,Map通常在插入和删除元素时具有更好的性能,因为它是为此目的而设计的数据结构,而对象的性能可能会受到属性遍历顺序的影响。
const dictionary = new Map();
// 向Map中添加键值对
dictionary.set("apple", "苹果");
dictionary.set("banana", "香蕉");
dictionary.set("orange", "橙子");
// 获取Map中的值
console.log(dictionary.get("apple")); // 输出: 苹果
console.log(dictionary.get("banana")); // 输出: 香蕉
// 删除Map中的键值对
dictionary.delete("orange");
8.散列
- 在JavaScript中,我们通常会使用散列函数来实现散列表。散列表是一种数据结构,它使用散列函数来计算键的索引,然后将键值对存储在对应索引的位置上。这样一来,当我们需要查找或操作某个键对应的值时,可以通过散列函数计算出相应的索引,然后在散列表中快速定位到对应的位置。
// 散列函数:简单的将字符串转换为数字的方式
function hash(key, size) {
let hashValue = 0;
for (let i = 0; i < key.length; i++) {
hashValue += key.charCodeAt(i);
}
return hashValue % size; // 取余确保散列值在散列表的范围内
}
// 散列表:存储键值对的数据结构
class HashTable {
constructor(size) {
this.size = size;
this.table = new Array(size);
}
// 插入键值对
insert(key, value) {
const index = hash(key, this.size);
if (!this.table[index]) {
this.table[index] = [];
}
this.table[index].push({ key, value });
}
// 根据键查找值
search(key) {
const index = hash(key, this.size);
if (!this.table[index]) {
return null;
}
for (const pair of this.table[index]) {
if (pair.key === key) {
return pair.value;
}
}
return null;
}
}
// 创建一个散列表
const hashTable = new HashTable(10);
// 向散列表中插入键值对
hashTable.insert("apple", "苹果");
hashTable.insert("banana", "香蕉");
hashTable.insert("orange", "橙子");
// 根据键查找值
console.log(hashTable.search("apple")); // 输出: 苹果
console.log(hashTable.search("banana")); // 输出: 香蕉
console.log(hashTable.search("grape")); // 输出: null,找不到对应的值
9.二叉树与平衡二叉树
- 二叉树是一种树形数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。这些子节点必须遵循特定的顺序。二叉树有许多变种,包括二叉搜索树、完全二叉树、平衡二叉树等。以下是一个简单的二叉树的JavaScript示例:
// 二叉树节点
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
// 二叉树
class BinaryTree {
constructor() {
this.root = null;
}
// 插入节点
insert(value) {
const newNode = new TreeNode(value);
if (!this.root) {
this.root = newNode;
} else {
this.insertNode(this.root, newNode);
}
}
insertNode(node, newNode) {
if (newNode.value < node.value) {
if (!node.left) {
node.left = newNode;
} else {
this.insertNode(node.left, newNode);
}
} else {
if (!node.right) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode);
}
}
}
}
// 创建一个二叉树
const binaryTree = new BinaryTree();
binaryTree.insert(10);
binaryTree.insert(5);
binaryTree.insert(15);
binaryTree.insert(3);
binaryTree.insert(7);
console.log(binaryTree.root);
- 平衡二叉树是一种特殊的二叉树,其左右子树的高度之差不超过1,且左右子树都是平衡二叉树。这样可以确保树的高度近似于 log(n),其中 n 是树中节点的数量,使得插入、删除和搜索等操作的时间复杂度保持较低水平。以下是一个简单的平衡二叉树的JavaScript示例:
/ 平衡二叉树节点
class AVLNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
this.height = 1;
}
}
// 平衡二叉树
class AVLTree {
constructor() {
this.root = null;
}
// 获取节点高度
getHeight(node) {
if (!node) return 0;
return node.height;
}
// 获取节点的平衡因子
getBalanceFactor(node) {
if (!node) return 0;
return this.getHeight(node.left) - this.getHeight(node.right);
}
// 左旋转
rotateLeft(node) {
const newRoot = node.right;
node.right = newRoot.left;
newRoot.left = node;
node.height = Math.max(this.getHeight(node.left), this.getHeight(node.right)) + 1;
newRoot.height = Math.max(this.getHeight(newRoot.left), this.getHeight(newRoot.right)) + 1;
return newRoot;
}
// 右旋转
rotateRight(node) {
const newRoot = node.left;
node.left = newRoot.right;
newRoot.right = node;
node.height = Math.max(this.getHeight(node.left), this.getHeight(node.right)) + 1;
newRoot.height = Math.max(this.getHeight(newRoot.left), this.getHeight(newRoot.right)) + 1;
return newRoot;
}
// 插入节点
insert(value) {
this.root = this.insertNode(this.root, value);
}
insertNode(node, value) {
if (!node) return new AVLNode(value);
if (value < node.value) {
node.left = this.insertNode(node.left, value);
} else if (value > node.value) {
node.right = this.insertNode(node.right, value);
} else {
return node; // 已经存在相同值的节点
}
// 更新节点高度
node.height = Math.max(this.getHeight(node.left), this.getHeight(node.right)) + 1;
// 平衡树
const balanceFactor = this.getBalanceFactor(node);
if (balanceFactor > 1) {
if (value < node.left.value) {
return this.rotateRight(node);
} else {
node.left = this.rotateLeft(node.left);
return this.rotateRight(node);
}
}
if (balanceFactor < -1) {
if (value > node.right.value) {
return this.rotateLeft(node);
} else {
node.right = this.rotateRight(node.right);
return this.rotateLeft(node);
}
}
return node;
}
}
// 创建一个平衡二叉树
const avlTree = new AVLTree();
avlTree.insert(10);
avlTree.insert(5);
avlTree.insert(15);
avlTree.insert(3);
avlTree.insert(7);
console.log(avlTree.root);
10.红黑树
- 红黑树是一种自平衡的二叉查找树,它在每个节点上增加了一个额外的属性来存储颜色,通常是红色或黑色。这种树具有一组约束条件,通过这些条件可以保证树的平衡性。
红黑树的特点包括:
1.每个节点要么是红色,要么是黑色。
2.根节点是黑色的。
3.每个叶子节点(NIL节点,即空节点)都是黑色的。
4.如果一个节点是红色的,则它的两个子节点都是黑色的(红黑树中不存在两个相连的红色节点)
5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
6.新插入的节点默认为红色,然后通过调整确保树的性质不被破坏。
// 红黑树节点
class RBNode {
constructor(value, color) {
this.value = value;
this.left = null;
this.right = null;
this.color = color; // 红色或黑色
}
}
// 红黑树
class RedBlackTree {
constructor() {
this.root = null;
this.RED = 'red';
this.BLACK = 'black';
}
// 左旋转
rotateLeft(node) {
const newRoot = node.right;
node.right = newRoot.left;
newRoot.left = node;
return newRoot;
}
// 右旋转
rotateRight(node) {
const newRoot = node.left;
node.left = newRoot.right;
newRoot.right = node;
return newRoot;
}
// 插入节点
insert(value) {
this.root = this.insertNode(this.root, value);
// 根节点始终是黑色的
if (this.root && this.root.color === this.RED) {
this.root.color = this.BLACK;
}
}
insertNode(node, value) {
if (!node) {
return new RBNode(value, this.RED); // 新插入的节点默认为红色
}
// 插入值比当前节点小,往左子树插入
if (value < node.value) {
node.left = this.insertNode(node.left, value);
}
// 插入值比当前节点大,往右子树插入
else if (value > node.value) {
node.right = this.insertNode(node.right, value);
}
// 调整树的平衡性
if (this.isRed(node.right) && !this.isRed(node.left)) {
node = this.rotateLeft(node);
}
if (this.isRed(node.left) && this.isRed(node.left.left)) {
node = this.rotateRight(node);
}
if (this.isRed(node.left) && this.isRed(node.right)) {
this.flipColors(node);
}
return node;
}
// 判断节点是否为红色
isRed(node) {
if (!node) return false;
return node.color === this.RED;
}
// 颜色翻转
flipColors(node) {
node.color = this.RED;
node.left.color = this.BLACK;
node.right.color = this.BLACK;
}
}
// 创建一个红黑树
const rbTree = new RedBlackTree();
rbTree.insert(10);
rbTree.insert(5);
rbTree.insert(15);
rbTree.insert(3);
rbTree.insert(7);
console.log(rbTree.root);
11.二叉堆,最小堆,最大堆
- 二叉堆是一种基于完全二叉树结构的数据结构,通常用数组来实现。它分为两种类型:最大堆和最小堆。在最大堆中,父节点的值大于或等于其子节点的值;在最小堆中,父节点的值小于或等于其子节点的值。
二叉堆有两个基本操作:插入(insert)和删除(delete)。主要特点包括:
1.完全二叉树结构:二叉堆是一种完全二叉树,意味着除了最底层节点可能不是满的,其他层都是满的,并且最底层的节点都尽可能地靠左排列。
2.父子节点之间的关系:在最大堆中,父节点的值总是大于或等于其子节点的值;在最小堆中,父节点的值总是小于或等于其子节点的值。
3.插入操作:新元素通常被添加到二叉堆的末尾,然后通过“上浮”操作将其调整到合适的位置,以满足堆的性质。
4.删除操作:通常删除堆顶元素,然后将最后一个元素移动到堆顶,并通过“下沉”操作将其调整到合适的位置,以满足堆的性质。
class MinHeap {
constructor() {
this.heap = [];
}
// 获取父节点索引
getParentIndex(index) {
return Math.floor((index - 1) / 2);
}
// 获取左子节点索引
getLeftChildIndex(index) {
return index * 2 + 1;
}
// 获取右子节点索引
getRightChildIndex(index) {
return index * 2 + 2;
}
// 上浮操作
siftUp(index) {
let parentIndex = this.getParentIndex(index);
while (index > 0 && this.heap[parentIndex] > this.heap[index]) {
// 交换当前节点和父节点的值
[this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];
index = parentIndex;
parentIndex = this.getParentIndex(index);
}
}
// 下沉操作
siftDown(index) {
let minIndex = index;
const leftChildIndex = this.getLeftChildIndex(index);
const rightChildIndex = this.getRightChildIndex(index);
// 找到当前节点、左子节点和右子节点中的最小值索引
if (leftChildIndex < this.heap.length && this.heap[leftChildIndex] < this.heap[minIndex]) {
minIndex = leftChildIndex;
}
if (rightChildIndex < this.heap.length && this.heap[rightChildIndex] < this.heap[minIndex]) {
minIndex = rightChildIndex;
}
// 如果最小值不是当前节点,则交换当前节点和最小值节点的值,并继续向下比较
if (minIndex !== index) {
[this.heap[index], this.heap[minIndex]] = [this.heap[minIndex], this.heap[index]];
this.siftDown(minIndex);
}
}
// 插入元素
insert(value) {
this.heap.push(value);
this.siftUp(this.heap.length - 1);
}
// 删除堆顶元素
deleteMin() {
if (this.heap.length === 0) {
return null;
}
if (this.heap.length === 1) {
return this.heap.pop();
}
const minValue = this.heap[0];
this.heap[0] = this.heap.pop();
this.siftDown(0);
return minValue;
}
// 获取堆顶元素
peek() {
return this.heap.length > 0 ? this.heap[0] : null;
}
}
// 创建一个最小堆
const minHeap = new MinHeap();
minHeap.insert(4);
minHeap.insert(8);
minHeap.insert(2);
minHeap.insert(6);
minHeap.insert(5);
console.log(minHeap.heap); // 输出:[2, 4, 5, 8, 6]
console.log(minHeap.deleteMin()); // 输出:2
console.log(minHeap.heap); // 输出:[4, 6, 5, 8]
class MaxHeap {
constructor() {
this.heap = [];
}
// 获取父节点索引
getParentIndex(index) {
return Math.floor((index - 1) / 2);
}
// 获取左子节点索引
getLeftChildIndex(index) {
return index * 2 + 1;
}
// 获取右子节点索引
getRightChildIndex(index) {
return index * 2 + 2;
}
// 上浮操作
siftUp(index) {
let parentIndex = this.getParentIndex(index);
while (index > 0 && this.heap[parentIndex] < this.heap[index]) {
// 交换当前节点和父节点的值
[this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];
index = parentIndex;
parentIndex = this.getParentIndex(index);
}
}
// 下沉操作
siftDown(index) {
let maxIndex = index;
const leftChildIndex = this.getLeftChildIndex(index);
const rightChildIndex = this.getRightChildIndex(index);
// 找到当前节点、左子节点和右子节点中的最大值索引
if (leftChildIndex < this.heap.length && this.heap[leftChildIndex] > this.heap[maxIndex]) {
maxIndex = leftChildIndex;
}
if (rightChildIndex < this.heap.length && this.heap[rightChildIndex] > this.heap[maxIndex]) {
maxIndex = rightChildIndex;
}
// 如果最大值不是当前节点,则交换当前节点和最大值节点的值,并继续向下比较
if (maxIndex !== index) {
[this.heap[index], this.heap[maxIndex]] = [this.heap[maxIndex], this.heap[index]];
this.siftDown(maxIndex);
}
}
// 插入元素
insert(value) {
this.heap.push(value);
this.siftUp(this.heap.length - 1);
}
// 删除堆顶元素
deleteMax() {
if (this.heap.length === 0) {
return null;
}
if (this.heap.length === 1) {
return this.heap.pop();
}
const maxValue = this.heap[0];
this.heap[0] = this.heap.pop();
this.siftDown(0);
return maxValue;
}
// 获取堆顶元素
peek() {
return this.heap.length > 0 ? this.heap[0] : null;
}
}
// 创建一个最大堆
const maxHeap = new MaxHeap();
maxHeap.insert(4);
maxHeap.insert(8);
maxHeap.insert(2);
maxHeap.insert(6);
maxHeap.insert(5);
console.log(maxHeap.heap); // 输出:[8, 6, 5, 4, 2]
console.log(maxHeap.deleteMax()); // 输出:8
console.log(maxHeap.heap); // 输出:[6, 4, 5, 2]