JS版数据结构

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)之间有几个主要区别

  1. 对象的属性数量是没有限制的,可以非常大,而Map的大小可以通过size属性获取,而且更适合存储大量键值对的数据结构。
  2. Map对象会根据插入顺序维护键值对的顺序,而对象的属性遍历顺序并不是严格按照插入顺序的。
  3. 对于大型数据集,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.二叉树与平衡二叉树

  1. 二叉树是一种树形数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。这些子节点必须遵循特定的顺序。二叉树有许多变种,包括二叉搜索树、完全二叉树、平衡二叉树等。以下是一个简单的二叉树的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. 平衡二叉树是一种特殊的二叉树,其左右子树的高度之差不超过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]
  • 31
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值