用 JavaScript 来构建数据结构知识总结

文章介绍了使用JavaScript实现数据结构的基础,包括栈的后进先出(LIFO)操作,队列的先进先出(FIFO)操作,链表的构造和遍历,原型链的概念,以及如何用实例来判断对象类型。此外,还讨论了集合(Set)的去重功能,字典(Map)的键值对存储,以及树的遍历方法(如深度优先和广度优先)和堆的概念。
摘要由CSDN通过智能技术生成

1. 栈 —— 后进先出

  • JavaScript 实现栈的方式:

// 数组
class Stack {
  #items
  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
  }
}
​
// 对象
class Stack {
  constructor() {
    this.count = 0;
    this.items = {};
  }
  // 方法
  push(element) {
    this.items[this.count] = element;
    this.count++;
  }
  pop() {
    if (this.isEmpty()) {
      return undefined;
    }
    this.count--;
    const result = this.items[this.count];
    delete this.items[this.count];
    return result;
  }
  peak() {
    if (this.isEmpty()) {
      return undefined;
    }
    return this.items[this.count - 1];
  }
  size() {
    return this.count;
  }
  isEmpty() {
    return this.count === 0;
  }
  clear() {
    this.items = {};
    this.count = 0;
    // 或者利用pop()移除所有元素
    // while (!this.isEmpty()) {
    //   this.pop()
    // }
  }
  toString() {
    if (this.isEmpty()) {
      return '';
    }
    let objString =  `${this.items[0]}`;
    for (let i = 1; i < this.count; i++) {
      objString = `${objString}, ${this.items[i]}`
      // console.log(objString, i);
    }
    return objString;
  }
}
  • 应用:

// 十进制转二进制
function decimalToBinary(decNumber) {
  const remStack = new Stack()
  let num = decNumber;
  while (num > 0) {
    remStack.push(num % 2)
    num = parseInt(num / 2)
  }
  console.log(remStack.toString()); // 0,1,0,1
  let res = ''
  while (!remStack.isEmpty()) {
    res += remStack.pop()
  }
  return Number(res)
}
console.log(decimalToBinary(10)) // 1010
console.log(decimalToBinary(233)) // 11101001
console.log(decimalToBinary(1000)) // 1111101000
​
​
// 十进制转为任意进制
function baseConverter(decNumber, base) {
  const remStack = new Stack()
  const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  let num = decNumber
  let res = ''
  if (!(base >= 2 && base <= 36)) {
    return '';
  }
  while (num > 0) {
    remStack.push(num % base)
    num = parseInt(num / base)
  }
  while (!remStack.isEmpty()) {
    res += digits[remStack.pop()]
  }
  return res;
}
console.log(baseConverter(100345, 2)); // 11000011111111001
console.log(baseConverter(100345, 8));  // 303771
console.log(baseConverter(100345, 16)); // 187F9
console.log(baseConverter(100345, 35)); // 2BW0

2. 队列 —— 先进先出

  • JavaScript 封装队列

class Queue {
  #items
  #count
  #frontiest
  constructor() {
    this.#items = {}
    this.#count = 0
    this.#frontiest = 0
  }
  enqueue(element) {
    // 添加队列
    this.#items[this.#count] = element
    this.#count++
  }
  dequeue() {
    // 出队
    if (this.isEmpty()) {
      return undefined
    }
    const res = this.#items[this.#frontiest]
    delete this.#items[this.#frontiest]
    this.#frontiest++
    return res;
  }
  peek() {
    return this.#items[this.#frontiest]
  }
  isEmpty() {
    return this.#count - this.#frontiest === 0
  }
  size() {
    return this.#count - this.#frontiest
  }
  toString() {
    if (this.isEmpty()) {
      return ''
    }
    let objString = this.#items[this.#frontiest]
    for (let i = this.#frontiest + 1; i < this.#count; i++) {
      objString += `, ${this.#items[i]}`
    }
    return objString
  }
}

3. 链表 —— 用next 指针连在一起

  • JavaScript 中实现链表

const a = {val: 'a'}
const b = {val: 'b'}
const c = {val: 'c'}
const d = {val: 'd'}
​
a.next = b;
b.next = c;
c.next = d;
d.next = null;
  • 遍历链表

// 创建一个指针
let p = a;
while (p) {
    console.log(p.val)
    p = p.next; // 移动指针
}
  • 链表插值

const e = {val: 'e'}
c.next = e;
e.next = d;
  • 删除

// 知晓被删除元素前一个元素,将前一个元素的next,指向被删除元素后一个元素
c.next = d;
​
// 不知晓被删元素的上一个元素,把被删元素的下一个元素值复制给被删的元素,再将指针指向下下一个元素
cur.val = cur.next.val;
cur.next = cur.next.next;
  • js 中的原型链

    • 原型链的本质是链表

    • 原型链上的节点是各种原型对象,如:Function.prototype、Object.prototype

    • 原型链通过 __proto__ 属性连接各种原型对象

    • 如果 A 沿着原型链能找到 B.prototype,那么 A instanceof B 为 true

    • 如果在 A 对象上没有找到 x 属性,那么会沿着原型链找 x 属性,直到 null

    1. obj -> Object.prototype -> null

    2. func -> Function.prototype -> Object.prototype -> null

    3. arr -> Array.prototype -> Object.prototype -> null

    • 面试题:instanceof 的原理,并用代码实现

    const instanceOf = (A, B) => {
        let p = A;
        while (p) {
            if (p === B.protopype) {
                return true
            }
            p = p.__proto__;
        }
        return false;
    }

4. 集合 —— Set 无序且唯一

  • JS 中用 new Set() 实现

  • 常用操作:

    • 去重

    const arr = [1, 2, 2, 3]
    const res = [...new Set(arr)]
    • 判断存在

    const set = new Set([1, 3, 4])
    const res = set.has(2) // false
    • 求交集

    const arr1 = [1, 2, 3, 3]
    const arr2 = [2, 2, 4, 4]
    const res = [...new Set(arr1)].filter(n => new Set(arr2).has(n));
    const res = [...new Set(arr1)].filter(n => arr2.includes(n))
    • 添加 set.add(1)

    • 删除 set.delete(5)

    • 获取集合大小 set.size()

    • 集合转数组

      • [...new Set([1, 2])]

      • Array.from(new Set([1, 2]))

    • 求差集 [...mySet].filter(v => !mySet2.has(v))

    • 求交集 [...myset].filter(v => myset2.has(v))

5. 字典 —— Map 以键值对的形式存储

  • JS 中的实现 new Map()

  • 基本操作

    • m.set('a', 'aa')

    • m.delete('a')

    • m.set('a', 'hahahaha')

    • m.get('a')

6. 树 —— 一种分层数据的抽象模型

  • JS 中创建树:Object 或 Array

  • 常用操作

    • 深度优先遍历——先访问根节点;对根节点的children进行深度优先遍历

    const dfs = (root) => {
        console.log(root.val)
        if (root.children) {
            root.children.forEach(dfs)
        }
    }
    • 广度优先遍历——新建队列,把根节点入队;把队头出队访问;把队头的children挨个入队;重复上述

    const bfs = (root) => {
        const q = [root]
        while (q.length) {
            const n = q.shift()
            console.log(n.val)
            n.children.forEach(v => {
                q.push(v)
            })
        }
    }
    • 先中后序遍历

      • 先序遍历 —— 根左右

      // 递归版
      const preorder = (root) => {
          if (!root) return;
          console.log(root.val)
          if (root.left) preorder(root.left)
          if (root.right) preorder(root.right)
      }
      // 非递归版——利用栈的先进后出,先将根节点入栈,再出栈,将左节点和右节点入栈
      const preorder = (root) => {
          if (!root) return
          const stack = [root]
          while (stack.length) {
              const n = stack.pop()
              console.log(n.val)
              if (n.right) stack.push(n.right)
              if (n.left) stack.push(n.left)
          }
      }
      • 中序遍历 —— 左根右

      // 递归版
      const inorder = (root) => {
          if (!root) return;
          if (root.left) preorder(root.left)
          console.log(root.val)
          if (root.right) preorder(root.right)
      }
      // 非递归版
      const inorder = (root) => {
          if (!root) return
          const stack = []
          let p = root;
          while (stack.length || p) {
              // 把左节点压入栈中
              while (p) {
                  stack.push(p)
                  p = p.left
              }
              // 将左节点出战访问
              const n = stack.pop()
              console.log(n.val)
              // 将指针指向右节点,继续中序遍历
              p = n.right
          }
      }
      • 后序遍历 —— 左右根

      // 递归版
      const postorder = (root) => {
          if (!root) return;
          if (root.left) preorder(root.left)
          if (root.right) preorder(root.right)
          console.log(root.val)
      }
      // 非递归版——先进行先序遍历,压入栈中,再依次出栈
      const postorder = (root) => {
          if (!root) return
          const stack = [root]
          const res = []
          while (stack.length) {
              const n = stack.pop()
              res.push(n.val)
              if (n.left) stack.push(n.left)
              if (n.right) stack.push(n.right)
          }
          while (res.length) {
              console.log(res.pop())
          }
      }

7. 图 —— 网络结构

  • 图是网络结构的抽象模型,是一组由连接的节点

  • 图可以表示任何二元关系,如道路、航班等

  • JS 中可以用 Object 和 Array构建图

  • 图的表示方法:

    • 邻接矩阵

    • 邻接表

    • 关联矩阵

  • 图的深度优先遍历

    • 访问根节点

    • 对根节点的没访问过的相邻节点挨个进行深度优先遍历

    const graph = {
        0: [1, 2],
        1: [2],
        2: [0, 3],
        3: [3]
    }
    const visited = new Set()
    const dfs = (n) => {
        console.log(n);
        visited.add(n)
        graph[n].forEach(v => {
            if (!visited.has(v)) {
                dfs(v)
            }
        })
    }
    dfs(2)

  • 图的广度优先遍历

    • 新建队列,把根节点入队

    • 把对头出队访问

    • 把队头的没访问过的相邻节点入队

    • 重复,直到队列为空

    const graph = {
        0: [1, 2],
        1: [2],
        2: [0, 3],
        3: [3] 
    }
    const bfs = (n) => {
        const visited = new Set()
        visited.add(n)
        const q = [n]
        while (q.length) {
            const node = q.shift()
            console.log(n)
            graph[n].forEach(c => {
                if (!visited.has(c)) {
                    q.push(c);
                    visited.add(c)
                }
            })
        }
    }

8. 堆 —— 特殊的完全二叉树

堆是完全二叉树,分为大根堆(每一棵子树的最大值都是头节点)、小根堆(每一棵子树的最大值都是头节点)。

重点:

  1. 堆结构就是用数组实现的完全二叉树结构

  2. 完全二叉树中如果每棵子树的最值都在顶部就是大根堆

  3. 完全二叉树中如果每棵子树的最值都在顶部就是小根堆

  4. 优先级队列结构,就是堆结构

  • 数组和堆的对应 当前为i

    • 左边子节点为 i * 2 + 1

    • 右边子节点为 i * 2 + 2

    • 父节点为 (i - 1) / 2

  • 连续给你数字,使这些数字变成大根堆 heapInsert() --- O(logN)

function heap_insert(arr, index) {
  let fa = Math.trunc((index - 1) / 2);
  while (arr[index] > arr[fa]) {
    swap(arr, index, fa);
    index = fa;
    fa = Math.trunc((index - 1) / 2);
  }
}
  • 在大根堆中,拿掉最大值,使剩下的依然保持大根堆 heapify() --- O(logN)

function heapify(arr, index, heapSize) {
  // 左孩子
  let left = index * 2 + 1;
  while (left < heapSize) { // 判断是否有孩子,左孩子是否越界
    // 两个孩子谁大, 谁把下标给maxVal  得先判断右孩子是否存在
    let maxVal = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
    // 父亲和较大孩子的值谁大, 谁把下标给maxVal
    maxVal = arr[index] > arr[maxVal] ? index : maxVal;
    // 父值等于最大值, 直接break
    if (maxVal == index) break;
    swap(arr, index, maxVal);
    index = maxVal;
    left = index * 2 + 1;
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值