前端数据结构与算法总结<week two>

总结题目ing~
续上周~~
标题没有错,是按照本地文件夹目录结构划分的

在这里插入图片描述
在这里插入图片描述

三、LinkList 链表

3.3 反转链表

3.3.1 思路

  1. 使用栈实现
    • 考虑不需要处理的情况
    • 全部节点入栈
    • 从栈中取出元素,放到一个新的链表中
  2. 非递归实现
    • 考虑不需要处理的情况
    • 使用 current 保存下一个节点
    • head 指向 newHead
    • newHead 变成 head
    • head 变成 current
  3. 递归实现
    • 注意递归结束条件
    • 找到倒数第二个节点开始反转

3.3.2 步骤

  1. 使用栈实现

    • head 本身为 null
    • 只有 head 一个节点
    • 全部入栈
    • 选择栈中最后一个节点为链表头节点
    • 全部出栈
    • 最后节点的 next 置为 null
  2. 非递归实现

    • head 本身为 null
    • 只有 head 一个节点
    • while 循环中处理链表翻转
    • 返回 newHead
  3. 递归实现

    • 递归结束条件
    • head.next.next = head
    • head.next = null

3.3.3 代码

  1. 使用栈实现
function reverseList(head) {
   
  if (head === null) return null;
  if (head.next === null) return head;
  const stack = [];
  let current = head;
  while (current) {
   
    stack.push(current);
    current = current.next;
  }
  const newHead = stack.pop();
  let currentNode = newHead;
  while (stack.length) {
   
    const node = stack.pop();
    currentNode.next = node;
    currentNode = currentNode.next;
  }
  currentNode.next = null;
  return newHead;
}

  1. 使用非递归实现
function reverseList(head) {
   
  if (head === null || head.next === null) return head
  let newHead = null
  while (head) {
   
    let current = head.next
    head.next = newHead
    newHead = head
    head = current
  }
  return newHead
}

  1. 使用递归实现
function reverseList(head) {
   
  if (head === null || head.next === null) return head;
  const newHead = reverseList(head.next);
  head.next.next = head;
  head.next = null;
  return newHead;
}

3.4 两数相加

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]

3.4.1 思路

  • 依次遍历两个链表的节点进行相加
  • 使用 l3 存储相加后的结果
  • 往后面进位

3.4.2 步骤

  • 创建 l3 链表
  • 使用两个指针分别指向 l1,l2
  • 循环遍历 l1 ,l2
    • 两数相加 val
      • 获取进位:val / 10
      • 获取个位:val % 10
    • 将个位传入创建新节点
    • 将指针后移
  • 判断最后是否有进位
  • 返回 l3 链表节点

3.4.3 代码

function addTwoNumbers(l1, l2) {
   
  const l3 = new ListNode(0);
  let p1 = l1;
  let p2 = l2;
  let carry = 0;
  while (p1 || p2) {
   
    const v1 = p1.val;
    const v2 = p2.val;
    const val = v1 + v2 + carry;
    carry = Math.floor(val / 10);
    l3.next = new ListNode(val % 10);
    if (p1) p1 = p1.next;
    if (p2) p2 = p2.next;
  }
  if (carry) l3.next = new ListNode(carry);
  return l3.next;
}

3.5 删除排序链表中的重复元素

输入:head = [1,1,2]
输出:[1,2]

3.5.1 思路

  • 遍历链表,相等就跳下一个

3.5.2 步骤

  • 使用指针
  • 循环判断

3.5.3 代码

function deleteRepeatVal(head) {
   
  let p = head;
  while (p && p.next) {
   
    if (p.next.val === p.val) {
   
      p.next = p.next.next;
    } else {
   
      p = p.next;
    }
  }
  return head;
}

3.6 删除排序链表中的重复元素二

输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]

3.6.1 思路

  • 遍历进行删除
  • 需要用到哑节点,因为头节点有可能被删除

3.6.2 步骤

  1. 判断没有节点的情况
  2. 创建虚拟节点
  3. 循环条件为下个节点和下下个节点都存在
  4. 判断是否存在重复
    • 重复:判断相等时为了记录重复的值,内部 while 循环的 cur.next 一直找到下一个不重复的节点
    • 不重复:直接跳下一个
  5. 返回虚拟节点的下一个节点

3.6.3 代码

function deleteRepeatVal(head) {
   
  if (!head) {
   
    return head;
  }
  const dummy = new ListNode(0, head);
  let cur = dummy;
  while (cur.next && cur.next.next) {
   
    if (cur.next.val === cur.next.next.val) {
   
      const x = cur.next.val;
      while (cur.next && cur.next.val === x) {
   
        cur.next = cur.next.next;
      }
    } else {
   
      cur = cur.next;
    }
  }
  return dummy.next;
}

四、HashTable 哈希表

  • 基于数组实现的,但是相对于数组,它有很多的优势

  • 可以提供非常快速的插入-删除-查找操作O(1)

  • 速度比树还要快

  • 编码比树容易

  • 不足:

  • 数据没有顺序,不能以固定的方式遍历其中的元素

  • 通常情况下,key 是不允许重复的,不能放置相同的 key

  • 到底是什么

  • 结构就是数组,神奇的地方在于对数组下标值的一种变换

  • 变换:使用哈希函数获取到 HashCode

4.1 哈希函数

4.1.1 思路

  • 好的哈希函数

    • 快速的计算:快速获取到对应的 hashCode
    • 均匀的分布:尽可能将元素映射到不同的位置,让元素在哈希表中均匀的分布
      • 在使用常量的地方,尽量使用质数
        • 质数和其他数相乘的结果相比于其他数字更容易产生唯一性的结果,减少哈希冲突
      • 质数的使用
        • 哈希表的长度
        • N次幂的底数
    • 在哈希表中数组的长度和N次幂的底数使用质数

4.1.2 步骤

  • 定义 hashCode
  • 遍历 key 长度
  • 更新 hashCode
  • 计算 index :hashCode % max

4.1.3 代码

function hashFn(key, max) {
   
  let hashCode = 0;
  for (let i = 0; i < key.length; i++) {
   
    hashCode += key.charCodeAt(i) + hashCode * 31;
  }
  const index = hashCode % max;
  return index;
}

4.2 实现哈希表

4.2.1 思路

  • 使用链地址法
  • 实现的哈希表每个 index 对应的是一个数组(桶)
  • 数组中存放的是 key 和 value
  • 数据格式:[ [ [key,value],[key,value] ],[],[] ]

4.2.2 步骤

  • 使用类进行构建

    • 属性

      • storage:存放链地址法的链
      • length:每条链的长度(桶的大小)
      • count:记录已经存放的元素个数
    • 方法

      • 增、改

        • put

          1. 根据 key 使用哈希函数获取索引值
          2. 取出索引值对应的 桶
            • 如果没有就创造新数组
          3. 判断是否需要覆盖
            • 是:找到桶中对应的元组,更新值
            • 否:直接追加,count++
        1. 使用 key 根据哈希函数确定 index
        2. 找到对应的桶,若没有返回 undefined
        3. 遍历对应的桶
        4. 找到对应的 key,直接在桶中进行删除
        5. count–
        6. 返回被删除的 value
        • get
          1. 使用 key 根据哈希函数确定 index
          2. 找到对应的桶,若没有返回 undefined
          3. 遍历对应的桶
          4. 比较 key 对应的 value 值是否相同
            • 相同返回 value
          5. 没有找到返回 undefined

4.2.3 代码

class HashTable {
   
  storage;
  length = 7;
  count = 0;
  // 哈希函数
  hashFn(key, max) {
   
    let hashCode = 0;
    for (let i = 0; i < key.length; i++) {
   
      hashCode += key.charCodeAt(i) + hashCode * 31;
    }
    const index = hashCode % max;
    return index;
  }
  // 增、改
  put(key, value) {
   
    const index = this.hashFn(key, this, length);
    let bucket = this.storage[index];
    if (!bucket) {
   
      bucket = [];
      this.storage[index] = bucket;
    }
    let isUpdate = false;
    for (let i = 0; i < bucket.length; i++) {
   
      const tuple = bucket[i];
      if (key === tuple[0]) {
   
        tuple[1] = value;
        isUpdate = true;
      }
    }
    if (!isUpdate) {
   
      bucket.push([key, value]);
      this.count++;
    }
  }
  // 查
  get(key) {
   
    const index = this.hashFn(key, this.length);
    const bucket = this.storage[index];
    if (!bucket) {
   
      return undefined;
    }
    for (let i = 0; i < bucket.length; i++) {
   
      const tuple = bucket[i];
      if (tuple[0] === key) {
   
        return tuple[1];
      }
    }
    return undefined;
  }

  // 删
  delete(key) {
   
    const index = this.hashFn(key, this.length)
    const bucket = this.storage[index]
    if (!bucket) return undefined
    for (let i = 0; i < bucket.length; i++){
   
      const tuple = bucket[i]
      if (tuple[0] === key) {
   
        bucket.splice(i, 1)
        this.count--
        return tuple[1]
      }
    }
  }

}

4.3 扩容缩容

数据量增大会造成 bucket 越来越长,造成效率的降低

4.3.1 思路

  • 封装 resize 方法,用于设置新的长度,获取到原来所有的数据,重新放入到新的数组中
  • 新的长度最好是一个质数,能够分布得更加均匀

4.3.2 步骤

  1. 初始化数组长度

    • 判断是否为质数(只能被 1 和 它本身除没有余数)
    • 不是就++再进行判断
    • 直到找到质数
  2. 存储之前的 oldStorage,之后对哈希表进行初始化

    • storage =[]
    • count = 0
  3. 遍历之前的每一个桶

    • 没有桶直接返回

    • 遍历每一个桶中的元素

  4. 调用 put 方法将其放入到新的数组中

4.3.3 使用

  • 扩容 length*2
    • put 方法中 count++ 后发现 loadFactor 已经大于 0.75
  • 缩容 length/2
    • delete 方法中 count-- 后发现 loadFactor 已经小于 0.25 并且 this.length > 7

4.3.4 代码

  
isPrime(num) {
   
    const sqrt = Math.sqrt(num);
    for (let i = 0; i <= sqrt; i++) {
   
      if (sqrt % i === 0) return false;
    }
    return true
  }
  resize(newLength) {
   
    let newPrimeLength = newLength
    while (!this.isPrime(newLength)) {
   
      newPrimeLength++
    }
    this.length = newPrimeLength
    const oldStorage = this.storage
    this.storage = []
    this.count = 0
    oldStorage.forEach(bucket => {
   
      if(!bucket) return
      for (let i = 0; i < bucket.length; i++){
   
        const tuple = bucket[i]
        this.put([tuple[0],tuple[1]])
      }
    })
  }

4.3.5 完整代码

class HashTable {
   
  storage;
  length = 7;
  count = 0;
  // 哈希函数
  hashFn(key, max) {
   
    let hashCode = 0;
    for (let i = 0; i < key.length; i++) {
   
      hashCode += key.charCodeAt(i) + hashCode * 31;
    }
    const index = hashCode % max;
    return index;
  }
  // 增、改
  put(key, value) {
   
    const index = this.hashFn(key, this, length);
    let bucket = this.storage[index];
    if (!bucket) {
   
      bucket = [];
      this.storage[index] = bucket;
    }
    let isUpdate = false;
    for (let i = 0; i < bucket.length; i++) {
   
      const tuple = bucket[i];
      if (key === tuple[0]) {
   
        tuple[1] = value;
        isUpdate = true;
      }
    }
    if (!isUpdate) {
   
      bucket.push([key, value]);
      this.count++;
      const loadFactor = this.count / this.length;
      if (loadFactor > 0.75) {
   
        this.resize(this.length * 2);
      }
    }
  }
  // 查
  get(key) {
   
    const index = this.hashFn(key, this.length);
    const bucket = this.storage[index];
    if (!bucket) {
   
      return undefined;
    }
    for (let i = 0; i < bucket.length; i++) {
   
      const tuple = bucket[i];
      if (tuple[0] === key) {
   
        return tuple[1];
      }
    }
    return undefined;
  }

  // 删
  delete(key) {
   
    const index = this.hashFn(key, this.length);
    const bucket = this.storage[index];
    if (!bucket) return undefined;
    for (let i = 0; i < bucket.length; i++) {
   
      const tuple = bucket[i];
      if (tuple[0] === key) {
   
        bucket.splice(i, 1);
        this.count--;
        const loadFactor = this.count / this.length;
        if (loadFactor < 0.25 && this.length > 7) {
   
          this.resize(Math.floor(this.length / 2));
        }
        return tuple[1];
      }
    }
  }
  isPrime(num) {
   
    const sqrt = Math.sqrt(num);
    for (let i = 0; i <= sqrt; i++) {
   
      if (sqrt % i === 0) return false;
    }
    return true;
  }
  resize(newLength) {
   
    let newPrimeLength = newLength;
    while (!this.isPrime(newLength)) {
   
      newPrimeLength++;
    }
    this.length = newPrimeLength;
    const oldStorage = this.storage;
    this.storage = [];
    this.count = 0;
    oldStorage.forEach((bucket) => {
   
      if (!bucket) return;
      for (let i = 0; i < bucket.length; i++) {
   
        const tuple = bucket[i];
        this.put([tuple[0], tuple[1]]);
      }
    });
  }
}

五、Tree 树

5.1 实现二叉搜索树 BSTree

5.1.1 思路

  • 通过类来封装
    • 封装节点 TreeNode
    • 封装树 BSTree
  • 实现常用操作
      • insert
      • remove
        • 搜索该节点是否存在
          • 不存在
          • 存在
            • 叶子节点
            • 只有一个子节点
            • 有两个子节点
              • 找到后继结点
      • search
      • min
      • max
    • 遍历
      • 前中后
      • 层序:利用队列完成

5.1.2 步骤

  1. 封装树的节点
    • 包括 value 值,左子节点,右子节点
  2. 封装树 BSTree
    • 初始化为根节点
  3. insert
    • 创建新节点
    • 判断根节点是否为空
    • 封装插入节点的函数
      • 判断插入左边还是右边
      • 找到对应插入的位置
        • 孩子节点为空
      • 否则递归查找
  4. 遍历
      • 根左右
      • 左根右
      • 左右根
    • 层序
      • 创建队列
      • 根节点入队
      • 访问出队元素
        • 将其左右子节点入队
  5. 最值
    • getMaxValue
      • 一直往右边找
    • getMinValue
      • 一直往左边找
  6. 搜索
    • 拿到根节点
    • 与 value 值比较
      • 大于则去左边
      • 小于则去右边
    • 返回 boolean
  7. 删除
    • 封装 searchNode 方法,找到该节点 current,如果不存在直接返回 false,删除失败
      • 拿到根节点
      • 设置父节点
        • 新增属性:parent
    • 定义替代节点
    • 综合考虑三种情况
      • 删除节点是叶子节点
      • 删除节点只有一个子节点
        • 左子节点
          • 将其设置为替代节点
        • 右子节点
          • 将其设置为替代节点
      • 删除节点有两个子节点
        • 找到后继节点(右边最小)右子树的最左边的节点
          • 封装 getSuccessor 方法
            • 获取右子树,找后继
              • 找到最左边
              • 特殊情况:刚好是右子节点,一条右边的链,避免改左节点的右子节点的 parent 的情况
              • 左边直接修改父节点
          • 将后继节点设置为替代节点
            • 替代节点为根节点
            • 替代节点为根节点的左子节点
            • 替代节点为根节点的右子节点

5.1.3 代码

class TreeNode {
   
  value;
  left = null;
  right = null;
  constructor(value) {
   
    this.value = value;
  }
}
class BSTree {
   
  root = null;
  parent = null;
  get isLeft() {
   
    return !!(this.parent && this.parent.left === this);
  }
  get isRight() {
   
    return !!(this.parent && this.parent.right === this);
  }
  insert(value) {
   
    const newNode = new TreeNode(value);
    if (!this.root) {
   
      this.root = newNode;
    } else {
   
      this.insertNode(this.root, newNode);
    }
  }

  insertNode(root, newNode) {
   
    if (newNode.value <= root.value) {
   
      if (root.left === null) {
   
        root.left = newNode;
      } else {
   
        this.insertNode(root.left, newNode);
      }
    } else {
   
      if (root.right === null) {
   
        root.right = newNode;
      } else {
   
        this.insertNode(root.right, newNode);
      }
    }
  }

  // 遍历
  preOrderTraverse() {
   
    this.preOrderTraverseNode(root);
  }
  preOrderTraverseNode(node) {
   
    if (node) {
   
      console.log(node.value);
      this.preOrderTraverseNode(node.left);
      this.preOrderTraverseNode(node.right);
    }
  }
  inOrderTraverse() {
   
    this.inOrderTraverseNode(root);
  }
  inOrderTraverseNode(node) {
   
    if (node) {
   
      this.inOrderTraverseNode(node.left);
      console.log(node.value);
      this.inOrderTraverseNode(node.right);
    }
  }
  postOrderTraverse() {
   
    this.postOrderTraverseNode(root);
  }
  postOrderTraverseNode(node) {
   
    if (node) {
   
      this.postOrderTraverseNode(node.left);
      this.postOrderTraverseNode(node.right);
      console.log(node.value);
    }
  }
  levelOrderTraverse() {
   
    if (!this.root) return;
    const queue = [];
    queue.push(this.root);
    while (queue.length) {
   
      const node = queue.shift();
      if (node.left) queue.push(node.left);
      if (node.right) queue.push(node.right);
    }
  }
  getMaxValue() {
   
    let current = this.root;
    while (current && current.right) {
   
      current = current.right;
    }
    return current.value;
  }
  getMinValue() {
   
    let current = this.root;
    while (current && current.left) {
   
      current = current.left;
    }
    return current.value;
  }

  search(value) {
   
    let current = this.root;
    if (current.value === value) {
   
      return true;
    } else if (current.value > value) {
   
      current = current.left;
    } else {
   
      current = current.right;
    }
    return false;
  }
  searchNode(value) {
   
    let current = this.root;
    let parent = null;
    while (current) {
   
      if (current.value === value) return current;
      parent = current;
      if (current.value < value) current = current.right;
      if (current.value > value) current = current.left;
      if (current) current.parent = current;
    }
    return null;
  }

  remove(value) {
   
    const current = this.searchNode(value);
    if (!current) return false;
    let replaceNode = null;
    if (current.left === null && current.right === null) {
   
      replaceNode = null;
    } else if (current.right === null) {
   
      replaceNode = current.left;
    } else if (current.left === null) {
   
      replaceNode = current.right;
    } else {
   
      const successor = this.getSuccessor(current);
      replaceNode = successor;
    }
    if (current === this.root) {
   
      this.root = replaceNode;
    } else if (current.isLeft) {
   
      current.parent.left = replaceNode;
    } else {
   
      current.parent.right = replaceNode;
    }
    return true;
  }
  getSuccessor(delNode) {
   
    let current = delNode.right;
    let successor = null;
    while (current) {
   
      successor = current;
      current = current.left;
      if (current) current.parent = successor;
    }
    if (successor !== delNode.right) {
   
      successor.parent.left = successor.right;
      successor.right = delNode.right;
    }
    successor.left = delNode.left;
    return successor;
  }
}

六、Graph 图

6.1 实现图

6.1.1 思路

  • 属性
    • 顶点 verteces
    • 边 adjList
  • 方法
    • 添加顶点 addVertex
    • 添加边 addEdge(v1,v2)

6.1.2 步骤

  • 创建图类
  • 顶点集使用数组
  • 每个顶点对应顶点元素(数组)
    • 使用 Map
  • addVertex
    • 顶点集 push 新顶点
    • Map set 顶点,空数组
  • addEdge
    • Map 中分别找到两个顶点对应的顶点集再 push

6.1.3 代码

class Graph {
   
  verteces = [];
  adjList = new Map();
  addVertex(vertex) {
   
    this.verteces.push(vertex)
    this.adjList.set(vertex,[])
  }
  addEdge(v1, v2) {
   
    this.adjList.get(v1).push(v2)
    this.adjList.get(v2).push(v1)
  }
}

6.2 深度优先遍历 dfs

6.2.1 思路

  • 利用栈先进后出,反过来遍历放进去,这样就得到最先访问的顶点

6.2.2 步骤

  1. 判断有无顶点
  2. 创建栈加入第一个顶点
  3. 创建 visited 记录是否已经访问过
  4. 循环条件:栈不为空
    • 拿到顶点
    • 进行打印输出
    • 拿到邻居
      • 倒过来放进栈和 set 中

6.2.3 代码

  dfs() {
   
    if (this.verteces.length === 0) return;
    const stack = [];
    stack.push(this.verteces[0]);
    const visited = new Set();
    visited.add(this.verteces[0]);
    while (stack.length) {
   
      const vertex = stack.pop();
      console.log(vertex);
      const neighbors = this.adjList.get(vertex);
      if (!neighbors) continue;
      for (let i = neighbors.length - 1; i >= 0; i--) {
   
        stack.push(neighbors[i]);
        visited.add(neighbors[i]);
      }
    }
  }

6.3 广度优先遍历

6.3.1 思路

  • 基于队列先进先出,先访问一个顶点所有的相邻点

6.3.2 步骤

  1. 判断有无顶点
  2. 创建队列加入第一个顶点
  3. 创建 visited 记录是否已经访问过
  4. 循环条件:队列不为空
    • 拿到队头
    • 打印输出
    • 拿到相邻节点
      • 遍历放入队列和 set

6.3.1 代码

class Graph {
   
  verteces = [];
  adjList = new Map();
  addVertex(vertex) {
   
    this.verteces.push(vertex);
    this.adjList.set(vertex, []);
  }
  addEdge(v1, v2) {
   
    this.adjList.get(v1).push(v2);
    this.adjList.get(v2).push(v1);
  }
  bfs() {
   
    if (this.verteces.length === 0) return;
    const queue = [];
    queue.push(this.verteces[0]);
    const visited = new Set();
    visited.add(this.verteces[0]);
    while (queue.length) {
   
      const vertex = queue.shift();
      console.log(vertex);
      const neighbors = this.adjList.get(vertex);
      if (!neighbors) continue;
      for (let i = 0; i < neighbors.length; i++) {
   
        if (!visited.has(neighbors[i])) {
   
          queue.push(neighbors[i]);
          visited.add(neighbors[i]);
        }
      }
    }
  }
}

6.4 太平洋大西洋水流问题

6.4.1 思路

  • 逆流而上,顺着一个顶点进行深度优先遍历,看能否到达海洋,并进行标注

6.4.2 步骤

  1. 判断矩阵是否存在

  2. 获取矩阵的行数和列数

  3. 构建两个矩阵,初始化为 false

  4. 遍历周围四个方向的坐标

    • 遍历过标记为 true

    • 满足限制条件

    • 深度遍历

  5. 上左逆流而上,下右逆流而上

  6. 收集能流到两个大洋的坐标

6.4.3 代码

function oean(height) {
   
  if (!height[0] || !height) return [];
  const m = height.length;
  const n = height[0].length;
  const flow1 = Array.from({
    length: m }, () => new Array(n).fill(false));
  const flow2 = Array.from({
    length: m }, () => new Array(n).fill(false));
  const dfs = (r, c, flow) => {
   
    flow[r][c] = true[([r, c + 1], [r, c - 1], [r - 1, c], [r + 1, c])].forEach(([nr, nc]) => {
   
      if (nr >= 0 && nc >= 0 && nr < m && nc < n && !flow[nr][nc] && height[nr][nc] >= flow[r][c]) {
   
        dfs(nr, nc, flow);
      }
    });
  };
  for (let r = 0; r < m; r++) {
   
    dfs(r, 0, flow1);
    dfs(r, m - 1, flow2);
  }
  for (let c = 0; c < n; c++) {
   
    dfs(0, c, flow1);
    dfs(n - 1, c, flow2);
  }
  const res = [];
  for (let i = 0; i < m; i++) {
   
    for (let j = 0; j < n; j++) {
   
      if (flow1[i][j] && flow2[i][j]) {
   
        res.push([i, j]);
      }
    }
  }
  return res;
}

6.5 克隆图

6.5.1 思路

  • 克隆顶点(遍历所有节点)
  • 克隆边

6.5.2 步骤

  • 思路一:深度优先遍历
  1. 没有节点,直接结束
  2. 记录是否访问过
  3. 从节点处开始深度优先遍历
    • 创建相同的顶点并存储到 Map(映射节点和对应的克隆节点)
    • 遍历顶点对应边集合(邻居)
      • 如果没有深度遍历过,对其进行深度遍历
    • 推入到新创建的节点对应的集合中
  4. 返回克隆的图
  • 思路二:利用队列(广度优先遍历)
  1. 没有节点,直接结束
  2. 记录是否访问过
  3. 将所有的节点入队
    • 获取克隆节点:visited.get(n)
    • 将所有邻居节点加入
  4. 返回克隆的图

6.5.3 代码

function cloneGraph1(node) {
   
  if (!node) return;
  const visited = new Map();
  const dfs = (n) => {
   
    const nCopy = new Node(n.val);
    visited.set(n, nCopy);
    (n.neighbors || []).forEach((neighbor) => {
   
      if (!visited.has(neighbor)) {
   
        dfs(neighbor);
      }
      nCopy.neighbors.push(visited.get(neighbor));
    });
  };
  dfs(node);
  return visited.get(node);
}

function cloneGraph2(node) {
   
  if (!node) return;
  const visited = new Map();
  visited.set(node, new Node(node.val));
  const queue = [node];
  while (queue.length) {
   
    const n = queue.shift();
    (n.neighbors || []).forEach((neighbor) => {
   
      if (!visited.has(neighbor)) {
   
        queue.push(neighbor);
        visited.set(neighbor, new Node(neighbor.val));
      }
      visited.get(n).neighbors.push(visited.get(neighbor));
    });
  }
  return visited.get(node);
}

七、Heap 堆

7.1 实现最大堆

7.1.1 思路

  • 封装类
  • 属性
    • data
    • length
  • 方法
    • swap
    • insert
    • extract
    • peek
    • size
    • isEmpty
    • buildHeap

7.1.2 步骤

  1. insert 插入方法
    • 插入到数组最后的位置
    • length ++
    • 上滤操作:heapify_up(循环条件为不为根节点)
      • 找到父节点
      • 不断比较看能否和父节点交换位置
  2. extract 提取元素(堆顶元素)
    • 判断元素个数
      • 0
      • 1
    • 弹出堆顶
    • length –
    • 进行下滤操作 heapify_down(循环条件为有左子节点)
      • 拿到左右子节点
      • 拿到两者中较大的一个
      • 交换位置
  3. buildHeap 原地建堆
    • 放在 constructor 中
    • 使用传入的 arr 的值:数组/长度
    • 第一个非叶子节点进行下滤操作

7.1.3 代码

class Heap {
   
  data = [];
  length = 0;
  swap(i, j) {
   
    const temp = this.data[i];
    this.data[i] = this.data[j];
    this.data[j] = temp;
  }
  insert(value) {
   
    this.data.push(value);
    this.length++;
    this.heapify_up();
  }
  heapify_up() {
   
    const index = this.data.length - 1;
    while (index > 0) {
   
      let parentIndex = Math.floor((index - 1) / 2);
      if (this.data[parentIndex] >= this.data[index]) {
   
        break;
      }
      this.swap(index, parentIndex);
      index = parentIndex;
    }
  }
  extract() {
   
    if (this.length === 0) return undefined;
    if (this.length === 1) return this.data.pop();
    const topValue = this.data[0];
    this.data[0] = this.data.pop()
    this.length--;
    this.heapify_down(0);
    return topValue;
  }
  heapify_down(index) {
   
    while (2 * index + 1 < this.length) {
   
      let leftindex = 2 * index + 1;
      let rightIndex = 2 * index + 2;
      let largerIndex = leftindex;
      if (rightIndex <= this.length && this.data[leftindex] < this.data[rightIndex]) {
   
        largerIndex = rightIndex;
      }
      if (this.data[index] >= this.data[largerIndex]) break;
      this.swap(largerIndex, index);
      index = largerIndex;
    }
  }
  buildHeap(arr) {
   
    this.data = arr;
    this.length = arr.length;
    const start = Math.floor((this.length - 1) / 2);
    for (let i = start; start >= 0; start--) {
   
      this.heapify_down(i);
    }
  }
  peek() {
   
    return this.data[0];
  }
  size() {
   
    return this.length;
  }
  isEmpty() {
   
    return this.length === 0;
  }
}

十一、查找算法

11.1 顺序查找

11.1.1 思路

  • 遍历数组进行判断

11.1.2 步骤

  • for 循环进行遍历
  • 判断是否等于目标值

11.1.3 代码

function search(numbers, num) {
   
  for (let i = 0; i < numbers.length; i++){
   
    const item = numbers[i]
    if(item === num) return i
  }
  return -1
}

11.2 二分查找

11.2.1 思路

  • 每次查找将数组进行二分

11.2.2 步骤

  • 定义左边、右边的索引
  • while 循环查找
    • 找到中间的元素
    • 判断与目标值的关系
      • 等于:直接返回
      • 大于:left = mid + 1,在右边进行查找
      • 小于:right = mid -1,在左边进行查找

11.2.3 代码

function bsSearch(numbers, num) {
   
  let left = 0;
  let right = numbers.length - 1;
  while (left <= right) {
   
    let mid = Math.floor((left + right) / 2);
    if (numbers[mid] === num) {
   
      return mid;
    } else if (numbers[mid] > num) {
   
      right = mid - 1;
    } else {
   
      left = mid + 1;
    }
  }
  return -1;
}

相关推荐

  1. 数据结构算法>效率分析专项总结

    2024-01-19 22:04:02       31 阅读
  2. 数据结构常见算法总结

    2024-01-19 22:04:02       30 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-19 22:04:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-19 22:04:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-19 22:04:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-19 22:04:02       18 阅读

热门阅读

  1. MySQL 8.0中已过时的选项和变量

    2024-01-19 22:04:02       21 阅读
  2. 鸿蒙使用第三方SO库

    2024-01-19 22:04:02       41 阅读
  3. C++中的引用及指针变量

    2024-01-19 22:04:02       30 阅读
  4. 使用延迟队列处理超时订单

    2024-01-19 22:04:02       35 阅读
  5. 细胞基因完整矩阵转10xGenomics稀疏矩阵文件

    2024-01-19 22:04:02       30 阅读