数据结构学习笔记-图

224 阅读2分钟

图的概念

图用来表示顶点与顶点间的关系

相关概念

  • 顶点:事物通常用 V(Vertex)表示顶点的集合。
  • 边:两个事物间的关系通常用 E(Edge)表示边的集合。
  • 相邻顶点:一条边连接的顶点。
  • 度:一个顶点连接的边的条数。
  • 路径:路径是顶点 v1v2…,vn 的一个连续序列。
  • 根据边的有无方向分为:无向图 和 有向图。

图的表示

顶点表示

顶点的表示抽象成数字或者字母表示。

边的表示

1.png

  • 邻接矩阵:两个顶点间的边用1表示,无边连接的两个顶点间用0表示。当为带权图时,可以为权重。
  • 当图为稀疏图时(顶点很多,边较少)。要用大量的0 表示两个顶点之间无边连接,已造成空间浪费。

字典封装

function Dictionary() {
  // 字典属性
  this.items = {};
  // 操作方法
  // 添加key和value
  Dictionary.prototype.set = function (key, value) {
    this.items[key] = value;
  };
  // 判断字典中是否有这个key  hasOwnProperty检测一个属性是否是对象的自有属性
  Dictionary.prototype.has = function (key) {
    return this.items.hasOwnProperty(key);
  };
  // 从字典中删除元素
  Dictionary.prototype.remove = function (key) {
    // 检查有没有这个元素
    if (!this.has(key)) {
      return false;
    }
    delete this.items[key];
    return true;
  };
  //根据key找value
  Dictionary.prototype.get = function (key) {
    return this.has(key) ? this.items[key] : undefined;
  };
  // 获取所有的key
  Dictionary.prototype.keys = function () {
    return Object.keys(this.items);
  };
  // 获取所有的value
  Dictionary.prototype.values = function () {
    return Object.values(this.items);
  };
  // size()
  Dictionary.prototype.size = function () {
    return Object.keys(this.items).length;
  };
  // clear()
  Dictionary.prototype.clear = function () {
    this.items = {};
  };
}

图封装

 function Graph() {
    // 属性: 顶点(数组) / 边(字典)
    this.vertexes = []; // 顶点
    this.edges = new Dictionary(); // 边
    }

添加顶点

Graph.prototype.addVertex = function (v) {
  this.vertexes.push(v);
  this.edges.set(v, []);
};

添加边

Graph.prototype.addEdge = function (v1, v2) {
  this.edges.get(v1).push(v2); // 取出v1对应的数组  帮把v2加进去   v1->v2有边
  this.edges.get(v2).push(v1); //  v2->v1也有边
};

toString()

Graph.prototype.toString = function () {
  let res = "";
  // 遍历顶点
  for (let i = 0; i < this.vertexes.length; i++) {
    res += this.vertexes[i] + "->";
    // 取出顶点相对应的数组 ,并遍历
    let itemsEdges = this.edges.get(this.vertexes[i]);
    for (let j = 0; j < itemsEdges.length; j++) {
      res += itemsEdges[j] + " ";
    }
    res += "\n";
  }
  return res;
};
/*
 记录顶点是否被访问过,三种颜色表示
 白色  该顶点还没有被访问过
 灰色  该顶点被访问过但是未被探索
 黑色  该顶点欸访问过且被探索过
*/

初始化颜色 initColor()

 Graph.prototype.initColor = function () {
  let colors = [];
  for (let i = 0; i < this.vertexes.length; i++) {
    colors[this.vertexes[i]] = "white";
  }
  return colors;
};

BFS和DFS

BFS 基于队列 ,入队列的顶点先被探索

DFS 基于栈或使用递归,通过将顶点存入栈中,顶点是沿着路径被探索的,存在新的相邻顶点就去访问

广度优先搜索
/* 
 广度优先算法会从指定的第一个顶点开始遍历图,先访问其所有的相邻,即先宽后深的访问顶点
 广度优先的搜索实现
 1 创建一个队列Q
 2 将v标注为被发现的(灰色),并将v加入队列Q
 3 如果Q是非空,执行下面的步骤
  3.1 将v从Q中取出队列
  3.2 将v标注为被发现的灰色
  3.3 将v所有的未被访问过的邻接点(白色),加入队列中
  3.4 将v标志为黑色

图-广度优先搜索.png

 Graph.prototype.bfs = function (initV, handler) {
  // 初始化颜色
  let colors = this.initColor();
  // 创建队列
  let list = [];
  // 加入队列
  list.push(initV);
  // 循环从队列中取出元素
  while (list.length) {
    // 从队列取出一个顶点
    let v = list.shift();
    // 获取顶点相邻的另外节点
    let vList = this.edges.get(v); // 加入顶点为A  则相邻为B C D
    // 将v的颜色设置成灰色
    colors[v] = "gray";
    // 遍历v的相邻顶点
    for (let i = 0; i < vList.length; i++) {
      let e = vList[i];
      // 如图,存在C和D的相邻顶点G, 顶点G会被多次访问到 ,所以只有白色才添加
      if (colors[e] == "white") {
        colors[e] = "gray";
        list.push(e);
      }
    }
    // 访问顶点
    handler(v);
    // 将顶点改为黑色
    colors[v] = "black";
  }
};
深度优先搜索
/* 
  深度优先搜索  如图 图-深度优先搜索
  深度优先搜索算法将会从第一个指定的顶点开始遍历图,沿着路径知道这条路径最后被访问了
  接着原路退回并探索下一条路径

  深度优先搜索算法的实现
  递归,函数栈的调用
*/

图-深度优先搜索.png

Graph.prototype.dfs = function (initV, handler) {
  // 初始化颜色
  let colors = this.initColor();
  //  从某个顶点开始依次递归开始访问
  this.dfsVisited(initV, colors, handler);
};
Graph.prototype.dfsVisited = function (v, colors, handler) {
  //  将颜色设置为灰色
  colors[v] = "gray";
  // 处理顶点v
  handler(v);
  // 访问v的邻接点
  let vList = this.edges.get(v);
  for (let i = 0; i < vList.length; i++) {
    if (colors[vList[i]] == "white") {
      this.dfsVisited(vList[i], colors, handler);
    }
  }
  colors[v] = "black";
};

测试

let graph = new Graph();

      // ============================================================
      // 添加顶点
      let newVertexes = ["A", "B", "C", "D", "E", "F", "G", "H", "I"];
      newVertexes.forEach((v) => {
        graph.addVertex(v);
      });

      // 添加边
      graph.addEdge("A", "B");
      graph.addEdge("A", "C");
      graph.addEdge("A", "D");
      graph.addEdge("C", "D");
      graph.addEdge("C", "G");
      graph.addEdge("D", "G");
      graph.addEdge("D", "H");
      graph.addEdge("B", "E");
      graph.addEdge("B", "F");
      graph.addEdge("E", "I");

      let res = graph.toString();
      console.log(res);
      // res
      /*
        A->B C D 
        B->A E F 
        C->A D G 
        D->A C G H 
        E->B I 
        F->B 
        G->C D 
        H->D 
        I->E  
      */
      // ====================================================
      //  广度优先搜索
      let result = "";
      graph.bfs(graph.vertexes[0], function (v) {
        result += v + " ";
      });
      console.log("result", result); // A B C D E F G H I  

      // =====================================================

      //  深度优先搜索
      let result2 = "";
      graph.dfs(graph.vertexes[0], function (v) {
        result2 += v + " ";
      });
      console.log("result2", result2); // A B E I F C D G H