JavaScript 数据结构与算法 之 图

简介: JavaScript 数据结构与算法 之 图

图是网络结构的抽象模型。图是一组由边连接的节点(或顶点)。
  • 基本概念:一个图 G = (V, E)由以下元素组成。

    • V:一组顶点
    • E:一组边,连接 V 中的顶点
  • 术语

    • 由一条边连接在一起的顶点称为相邻顶点
    • 一个顶点的度是其相邻顶点的数量
    • 路径是顶点 v1, v2, …, vk的一个连续序列,其中 vi和 vi+1是相邻的

      • 简单路径要求不包含重复的顶点
    • 如果图中不存在环,则称该图是无环的
    • 如果图中每两个顶点间都存在路径,则该图是连通的
    • 图可以是无向的(边没有方向)或是有向的(有向图)

      • 如果图中每两个顶点间在双向上都存在路径,则该图是强连通的
    • 图还可以是未加权的(目前为止我们看到的图都是未加权的)或是加权的
  • 图的表示

    • 邻接矩阵

    • 邻接表

    • 关联矩阵

class Graph {
  constructor(isDirected = false) {
    this.isDirected = isDirected; // 是否有向
    this.vertices = []; // 顶点
    this.adjList = new Dictionary(); // 顶点名为键,邻接顶点列表为值
  }
  addVertex(v) {
    if (!this.vertices.includes(v)) {
      this.vertices.push(v);
      this.adjList.set(v, []);
    }
  }
  addEdge(v, w) {
    if (!this.adjList.get(v)) {
      this.addVertex(v);
    }
    if (!this.adjList.get(w)) {
      this.addVertex(w);
    }
    this.adjList.get(v).push(w);
    if (!this.isDirected) {
      this.adjList.get(w).push(v);
    }
  }
  getVertices() {
    return this.vertices;
  }
  getAdjList() {
    return this.adjList;
  }
  toString() {
    let s = '';
    for(let i = 0; i < this.vertices.length; i++) {
      s += `${this.vertices[i]} -> `;
      const neighbors = this.adjList.get(this.vertices[i]);
      for(let j = 0; j < neighbors.length; j++) {
        s += `${neighbors[j]}`;
      }
      s += '\n';
    }
    return s;
  }
}

图的遍历

图遍历算法的思想是必须追踪每个第一次访问的节点,并且追踪有哪些节点还没有被完全探索。对于两种图遍历算法,都需要明确指出第一个被访问的顶点。完全探索一个顶点要求我们查看该顶点的每一条边。对于每一条边所连接的没有被访问过的顶点,将其标注为被发现的,并将其加进待访问顶点列表中。为了保证算法的效率,务必访问每个顶点至多两次。连通图中每条边和顶点都会被访问到。

广度优先搜索算法和深度优先搜索算法基本上是相同的,只有一点不同,那就是待访问顶点列表的数据结构

算法 数据结构 描述
深度优先搜索 将顶点存入栈,顶点是沿着路径被探索的,存在新的相邻顶点就去访问
广度优先搜索 队列 将顶点存入队列,最先入队列的顶点先被探索
const Colors = {
  WHITE: 0, // 没有被访问过
  GREY: 1, // 被访问过但未被探索过
  BLACK: 2 // 被访问过且被探索过
}
const initializeColor = vertices => {
  const color = {};
  for (let i = 0; i < vertices.length; i++) {
    color[vertices[i]] = Colors.WHITE;
  }
  return color;
};

const breadthFirstSearch = (graph, startVertex, cb) => {
  const vertices = graph.getVertices();
  const adjList = graph.getAdjList();
  const color = initializeColor(vertices);
  const queue = new Queue();

  queue.enqueue(startVertex);
  while(!queue.isEmpty()) {
    const u = queue.dequeue();
    const neighbors = adjList.get(u);
    color[u] = Colors.GREY;
    for (let i = 0; i < neighbors.length; i++) {
      const w = neighbors[i];
      if (color[w] === Colors.WHITE) {
        color[w] = Colors.GREY;
        queue.enqueue(w);
      }
    }
    color[u] = Colors.BLACK;
    if (cb) {
      cb(u);
    }
  }
};

const depthFirstSearch = (graph, cb) => {
  const vertices = graph.getVertices();
  const adjList = graph.getAdjList();
  const color = initializeColor(vertices);

  for (let i = 0; i < vertices.length; i++) {
    if (color[vertices[i]] === Colors.WHITE) {
      depthFirstSearchVisit(vertices[i], color, adjList, cb);
    }
  }
};
const depthFirstSearchVisit = (u, color, adjList, cb) => {
  color[u] = Colors.GREY;
  if (cb) {
    cb(u);
  }
  const neighbors = adjList.get(u);
  for(let i = 0; i < neighbors.length; i++) {
    const w = neighbors[i];
    if (color[w] === Colors.WHITE) {
      depthFirstSearchVisit(w, color, adjList, cb);
    }
  }
  color[u] = Colors.BLACK;
};
  • 使用BFS寻找最短路径
const BFS = (graph, startVertex) => {
  const vertices = graph.getVertices();
  const adjList = graph.getAdjList();
  const color = initializeColor(vertices);
  const queue = new Queue();
  const distances = {}; // 从v到u的距离distances[u]
  const predecessors = {}; // 前溯点predecessors[u],用来推导从v到其他每个顶点u的最短距离
  queue.enqueue(startVertex);

  for (let i = 0; i < vertices.length; i++) {
    distances[vertices[i]] = 0;
    predecessors[vertices[i]] = null;
  }

  while (!queue.isEmpty()) {
    const u = queue.dequeue();
    const neighbors = adjList.get(u);
    color[u] = Colors.GREY;
    for (let i = 0; i < neighbors.length; i++) {
      const w = neighbors[i];
      if (color[w] === Colors.WHITE) {
        color[w] = Colors.GREY;
        distances[w] = distances[w] + 1;
        predecessors[w] = u;
      }
    }
    color[u] = Colors.BLACK;
  }
  return {
    distances,
    predecessors
  };
};
  • 改进的DFS
const DFS = graph => {
  const vertices = graph.getVertices();
  const adjList = graph.getAdjList();
  const color = initializeColor(vertices);
  const d = {}; // 顶点u的发现时间d[u]
  const f = {}; // 当顶点u被标注为黑色时,u的完成探索时间f[u]
  const p = {}; // 顶点u的前溯点p[u]
  const time = { count: 0 };
  for (let i = 0; i < vertices.length; i++) {
    f[vertices[i]] = 0;
    d[vertices[i]] = 0;
    p[vertices[i]] = null;
  }
  for (let i = 0; i < vertices.length; i++) {
    if (color[vertices[i]] === Colors.WHITE) {
      DFSVisit(vertices[i], color, d, f, p, time, adjList);
    }
  }
  return {
    discovery: d,
    finished: f,
    predecessors: p,
  };
};
const DFSVisit = (u, color, d, f, p, time, adjList) => {
  color[u] = Colors.GREY;
  d[u] = ++time.count;
  const neighbors = adjList.get(u);
  for(let i = 0; i < neighbors.length; i++) {
    const w = neighbors[i];
    if (color[w] === Colors.WHITE) {
      p[w] = u;
      DFSVisit(w, color, d, f, p, time, adjList);
    }
  }
  color[u] = Colors.BLACK;
  f[u] = ++time.count;
};

最短路径算法

  • Dijkstra算法
Dijkstra 算法是一种计算从单个源到所有其他源的最短路径的贪心算法

const graph = [
  [0, 2, 4, 0, 0, 0],
  [0, 0, 2, 4, 2, 0],
  [0, 0, 0, 0, 3, 0],
  [0, 0, 0, 0, 0, 2],
  [0, 0, 0, 3, 0, 2],
  [0, 0, 0, 0, 0, 0]
];
const INF = Number.MAX_SAFE_INTEGER; // 无穷大
const minDistance = (dist, visited) => {
  let min = INF;
  let minIndex = -1;
  for (let v = 0; v < dist.length; v++) {
    if (visited[v] === false && dist[v] <= min) {
      min = dist[v];
      minIndex = v;
    }
  }
  return minIndex;
};
const dijkstra = (graph, src) => {
  const dist = [];
  const visited = [];
  const { length } = graph;
  for (let i = 0; i < length; i++) {
    dist[i] = INF;
    visited[i] = false;
  }
  dist[src] = 0;
  for (let i = 0; i < length - 1; i++) {
    const u = minDistance(dist, visited);
    visited[u] = true;
    for(let v = 0; v < length; v++) {
      if (!visited[v] &&
        graph[u][v] !== 0 &&
        dist[u] !== INF &&
        dist[u] + graph[u][v] < dist[v]) {
        dist[v] = dist[u] + graph[u][v];
      }
    }
  }
  return dist;
};
  • Floyd-Warshell
const floydWarshell = graph => {
  const dist = [];
  const { length } = graph;
  for (let i = 0; i < length; i++) {
    dist[i] = [];
    for (let j = 0; j < length; j++) {
      if (i === j) {
        dist[i][j] = 0;
      } else if (!isFinite(graph[i][j])) {
        dist[i][j] = Infinity;
      } else {
        dist[i][j] = graph[i][j];
      }
    }
  }
  for (let k = 0; k < length; k++) {
    for (let i = 0; i < length; i++) {
      for (let j = 0; j < length; j++) {
        if (dist[i][k] + dist[k][j] < dist[i][j])) {
          dist[i][j] = dist[i][k] + dist[k][j];
        }
      }
    }
  }
  return dist;
}
相关文章
|
1月前
|
JavaScript 前端开发
js实现数据的双向绑定
js实现数据的双向绑定
30 2
|
25天前
|
JavaScript 算法 前端开发
采招网JS逆向:基于AES解密网络数据
采招网JS逆向:基于AES解密网络数据
37 0
|
22天前
|
JavaScript 算法 前端开发
JS算法必备之String常用操作方法
这篇文章详细介绍了JavaScript中字符串的基本操作,包括创建字符串、访问特定字符、字符串的拼接、位置查找、大小写转换、模式匹配、以及字符串的迭代和格式化等方法。
JS算法必备之String常用操作方法
|
22天前
|
JavaScript 算法 前端开发
JS算法必备之Array常用操作方法
这篇文章详细介绍了JavaScript中数组的创建、检测、转换、排序、操作方法以及迭代方法等,提供了数组操作的全面指南。
JS算法必备之Array常用操作方法
|
1月前
|
数据采集 存储 JavaScript
基于Python 爬书旗网小说数据并可视化,通过js逆向对抗网站反爬,想爬啥就爬啥
本文介绍了如何使用Python编写网络爬虫程序爬取书旗网上的小说数据,并通过逆向工程对抗网站的反爬机制,最后对采集的数据进行可视化分析。
基于Python 爬书旗网小说数据并可视化,通过js逆向对抗网站反爬,想爬啥就爬啥
|
28天前
|
JSON JavaScript 数据格式
js实现更新数据
js实现更新数据
36 1
|
1月前
|
JavaScript 算法 前端开发
"揭秘Vue.js的高效渲染秘诀:深度解析Diff算法如何让前端开发快人一步"
【8月更文挑战第20天】Vue.js是一款备受欢迎的前端框架,以其声明式的响应式数据绑定和组件化开发著称。在Vue中,Diff算法是核心之一,它高效计算虚拟DOM更新时所需的最小实际DOM变更,确保界面快速准确更新。算法通过比较新旧虚拟DOM树的同层级节点,递归检查子节点,并利用`key`属性优化列表更新。虽然存在局限性,如难以处理跨层级节点移动,但Diff算法仍是Vue高效更新机制的关键,帮助开发者构建高性能Web应用。
39 1
|
1月前
|
前端开发 JavaScript 安全
JavaScript——数字超过精度导致数据有误
JavaScript——数字超过精度导致数据有误
28 2
|
1月前
|
JavaScript 前端开发
JavaScript中通过按回车键进行数据的录入
这篇文章提供了一个JavaScript示例代码,演示了如何通过监听回车键(keyCode为13)在网页上实现数据的录入和触发一个警告框提示"正在登录验证......"。
JavaScript中通过按回车键进行数据的录入
|
1月前
|
JavaScript 数据处理
如何使用 Vue.js 将数据对象的值放入另一个数据对象中?
如何使用 Vue.js 将数据对象的值放入另一个数据对象中?