JavaScript data structure and algorithm study notes (below - figure structure)

10. Graph theory

1.1. Introduction to graphs

What is a graph?

  • A graph structure is a data structure somewhat similar to a tree structure ;
  • Graph theory is a branch of mathematics, and, in mathematics, a tree is a type of graph;
  • Graph theory takes graphs as the research object, and studies the mathematical theory and methods of graphs composed of vertices and edges ;
  • The main research purpose is: the connection between things , the vertex represents the thing , and the edge represents the relationship between two things ;

Figure features:

  • A set of verticesV  (Vertex) is usually used to represent a collection of vertices;
  • A set of edgesE  (Edge) is usually used to represent the set of edges;
    • An edge is a connection between vertices and vertices;
    • Edges can be directed or undirected. For example, A----B means undirected, A ---> B means directed;

Common terms for graphs:

  • Vertex: represents a node in the graph ;

  • Edge: represents the connection between the vertex and the vertex ;

  • Adjacent vertices: Vertices connected by an edge are called adjacent vertices ;

  • Degree: The degree of a vertex is the number of adjacent vertices ;

  • path:

    • Simple path: A simple path requires no duplicate vertices;
    • Loop: A path whose first vertex is the same as the last vertex is called a loop;
  • Undirected graph: All edges in the graph have no direction;

  • Directed graph: All edges in the graph are directed ;

  • Unweighted graph: The edges in the unweighted graph have no weight meaning;

  • Weighted graph: The edges in the weighted graph have certain weight meanings;

1.2. Representation of graphs

adjacency matrix

A common way to represent a graph is an adjacency matrix .

  • A two-dimensional array can be used to represent an adjacency matrix;

  • The adjacency matrix associates each node with an integer , which serves as the subscript value of the array ;

  • Use a two-dimensional array to represent the connections between vertices ;

As shown in FIG:

  • 0 in the two-dimensional array means that there is no connection, and 1 means that there is a connection;
  • For example: A[ 0 ] [ 3 ] = 1, which means there is a connection between A and C;
  • The values ​​on the diagonal of the adjacency matrix are all 0, indicating that A - A, B - B, etc. self-loops are not connected (there is no connection between itself and itself);
  • If it is an undirected graph, the adjacency matrix should be a symmetric matrix with all elements on the diagonal being 0;

Adjacency matrix problem:

  • If the graph is a sparse graph , there will be a large number of 0s in the adjacency matrix , resulting in a waste of storage space;

adjacency list

Another common way to represent graphs is the adjacency list .

  • The adjacency list consists of each vertex in the graph and a list of vertices adjacent to the vertex ;
  • This list can be stored in a variety of ways, such as: array/linked list/dictionary (hash table), etc.;

As shown in FIG:

  • It can be clearly seen in the figure that A is adjacent to B, C, and D. If you want to represent these vertices (edges) adjacent to A vertex, you can store them as the value of A in the corresponding array/link list /dictionary .
  • Afterwards, the corresponding data can be retrieved very conveniently through the key (key) A;

Adjacency list problem:

  • The adjacency list can simply obtain the out-degree , that is, the number of a vertex pointing to other vertices;
  • However, it is very difficult to calculate the in-degree of the adjacency table (the number of other vertices pointing to a vertex is called the in-degree of the vertex). At this time, it is necessary to construct an inverse adjacency list to effectively calculate the in-degree;

2. Package structure

In the implementation process, the adjacency list is used to represent the edge, and the dictionary class is used to store the adjacency list.

2.1. Add dictionary class and queue class

First, you need to introduce the dictionary class and queue class that were implemented before and will be used later:

    //封装字典类
function Dictionary(){
  //字典属性
  this.items = {}

  //字典操作方法
  //一.在字典中添加键值对
  Dictionary.prototype.set = function(key, value){
    this.items[key] = value
  }

  //二.判断字典中是否有某个key
  Dictionary.prototype.has = function(key){
    return this.items.hasOwnProperty(key)
  }

  //三.从字典中移除元素
  Dictionary.prototype.remove = function(key){
    //1.判断字典中是否有这个key
    if(!this.has(key)) return false

    //2.从字典中删除key
    delete this.items[key]
    return true
  }

  //四.根据key获取value
  Dictionary.prototype.get = function(key){
    return this.has(key) ? this.items[key] : undefined
  }

  //五.获取所有keys
  Dictionary.prototype.keys = function(){
    return Object.keys(this.items)
  }

  //六.size方法
  Dictionary.prototype.keys = function(){
    return this.keys().length
  }

  //七.clear方法
  Dictionary.prototype.clear = function(){
    this.items = {}
  }
}

   // 基于数组封装队列类
    function Queue() {
    // 属性
      this.items = []
    // 方法
    // 1.将元素加入到队列中
    Queue.prototype.enqueue = element => {
      this.items.push(element)
    }

    // 2.从队列中删除前端元素
    Queue.prototype.dequeue = () => {
      return this.items.shift()
    }

    // 3.查看前端的元素
    Queue.prototype.front = () => {
      return this.items[0]
    }

    // 4.查看队列是否为空
    Queue.prototype.isEmpty = () => {
      return this.items.length == 0;
    }

    // 5.查看队列中元素的个数
    Queue.prototype.size = () => {
      return this.items.length
    }

    // 6.toString方法
    Queue.prototype.toString = () => {
      let resultString = ''
        for (let i of this.items){
          resultString += i + ' '
        }
        return resultString
      }
    }

2.2. Create graph class

First create the graph class Graph, and add basic attributes, and then implement the common methods of the graph class:

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

2.3. Add vertices and edges

as the picture shows:

 Create an array object vertexes to store the vertices of the graph; create a dictionary object edges to store the edges of the graph, where the key is the vertex, and the value is an array that stores the adjacent vertices of the key vertex.

Code:

      //添加方法
      //一.添加顶点
      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)//取出字典对象edges中存储边的数组,并添加关联顶点
        this.edges.get(v2).push(v1)//表示的是无向表,故要添加互相指向的两条边
      }

2.4. Convert to string output

Add the toString method to the graph class Graph to output each vertex in the graph in the form of an adjacency list.

Code:

      //三.实现toString方法:转换为邻接表形式
      Graph.prototype.toString = function (){
        //1.定义字符串,保存最终结果
        let resultString = ""

        //2.遍历所有的顶点以及顶点对应的边
        for (let i = 0; i < this.vertexes.length; i++) {//遍历所有顶点
          resultString += this.vertexes[i] + '-->'
          let vEdges = this.edges.get(this.vertexes[i])
          for (let j = 0; j < vEdges.length; j++) {//遍历字典中每个顶点对应的数组
            resultString += vEdges[j] + '  ';
          }
          resultString += '\n'
        }
        return resultString
      }

2.5. Graph traversal

Graph traversal ideas:

  • The traversal idea of ​​the graph is the same as that of the tree, which means that all vertices in the graph need to be visited once, and there cannot be repeated visits (the toString method above will repeat visits);

Two algorithms for traversing a graph:

  • Breadth-First Search (Breadth-First Search, referred to as BFS );
  • Depth-First Search (Depth-First Search, DFS for short );
  • Both traversal algorithms need to specify the first vertex to be visited ;

In order to record whether the vertices have been visited, three colors are used to indicate their state

  • White : Indicates that the vertex has not been visited;
  • Gray : Indicates that the vertex has been visited, but its adjacent vertices have not been completely visited;
  • Black : Indicates that the vertex has been visited, and all its adjacent vertices have been visited;

First, encapsulate the initializeColor method to initialize all vertices in the graph to white. The code implementation is as follows:

      //四.初始化状态颜色
      Graph.prototype.initializeColor = function(){
        let colors = []
        for (let i = 0; i < this.vertexes.length; i++) {
           colors[this.vertexes[i]] = 'white';
        }
        return colors
      }

The idea of ​​breadth-first search algorithm:

  • The breadth-first search algorithm will traverse the graph from the specified first vertex, and visit all its adjacent vertices first, just like visiting one layer of the graph at a time;
  • It can also be said to traverse each vertex in the graph first wide and then deep ;

Implementation idea:

The breadth-first search algorithm can be simply implemented based on the queue :

  • First create a queue Q (tail in, head out);
  • Call the encapsulated initializeColor method to initialize all vertices to white;
  • Designate the first vertex A, mark A as gray (visited node), and put A into the queue Q;
  • Loop through the elements in the queue, as long as the queue Q is not empty, do the following:
    • First remove the gray A from the head of Q;
    • After A is taken out, all adjacent vertices of A that have not been visited (white) are sequentially added to the queue from the tail of the queue Q, and turned gray. In this way, the gray adjacent vertices will not be added to the queue repeatedly;
    • After all the adjacent nodes of A are added to Q, A turns black and is removed from Q in the next cycle;

Code:

      //五.实现广度搜索(BFS)
      //传入指定的第一个顶点和处理结果的函数
      Graph.prototype.bfs = function(initV, handler){
        //1.初始化颜色
        let colors = this.initializeColor()

        //2.创建队列
        let que = new Queue()

        //3.将顶点加入到队列中
        que.enqueue(initV)

        //4.循环从队列中取出元素,队列为空才停止
        while(!que.isEmpty()){
          //4.1.从队列首部取出一个顶点
          let v = que.dequeue()

          //4.2.从字典对象edges中获取和该顶点相邻的其他顶点组成的数组
          let vNeighbours = this.edges.get(v)

          //4.3.将v的颜色变为灰色
          colors[v] = 'gray'

          //4.4.遍历v所有相邻的顶点vNeighbours,并且加入队列中
          for (let i = 0; i < vNeighbours.length; i++) {
            const a = vNeighbours[i];
            //判断相邻顶点是否被探测过,被探测过则不加入队列中;并且加入队列后变为灰色,表示被探测过
            if (colors[a] == 'white') {
              colors[a] = 'gray'
              que.enqueue(a)
            }
          }

          //4.5.处理顶点v
          handler(v)

          //4.6.顶点v所有白色的相邻顶点都加入队列后,将顶点v设置为黑色。此时黑色顶点v位于队列最前面,进入下一次while循环时会被取出
          colors[v] = 'black'
        }
      }

Detailed process:

The following is the traversal process when the first specified vertex is A:

  • As shown in figure a, put the white vertices B, C, and D that are adjacent to A and have not been visited in the dictionary edges into the queue que and turn gray, then turn A into black and remove it from the queue ;
  • Next, as shown in Figure b, the white vertices E and F that are adjacent to B and have not been visited in the dictionary edges are put into the queue que and turned gray, and then B is turned black and removed from the queue ;

  • As shown in figure c, put the unvisited white vertex G (A, D is also adjacent to but has become gray, so it is not added to the queue) taken out of the dictionary edges and adjacent to C into the queue que and turns gray, then turns C black and dequeues;
  • Next, as shown in Figure d, the unvisited white vertex H adjacent to D taken out from the dictionary edges is put into the queue que and turned gray, and then D is turned black and removed from the queue.

This cycle does not stop until the element in the queue is 0, that is, all vertices are blackened and removed from the queue. At this time, all vertices in the graph have been traversed.

It can be seen that the order of breadth-first search traverses all vertices without repetition .

The idea of ​​depth-first search algorithm:

  • The depth-first search algorithm will traverse the graph from the specified first vertex, and traverse along a path until the last vertex of the path is visited;
  • Then go back along the original path and explore the next path, that is, traverse each vertex in the graph deep and then wide ;

 

Implementation idea:

  • You can use the stack structure to implement the depth-first search algorithm;
  • The traversal order of the depth-first search algorithm is similar to the pre-order traversal in the binary search tree, and can also be implemented using recursion (the essence of recursion is the call of the function stack ).

Implement the depth-first search algorithm based on recursion: define the dfs method to call the recursive method dfsVisit, and define the dfsVisit method to recursively visit each vertex in the graph.

In the dfs method:

  • First, call the initializeColor method to initialize all vertices to white;
  • Then, call the dfsVisit method to traverse the vertices of the graph;

In the dfsVisit method:

  • First, mark the incoming specified node v in gray ;
  • Next, process the vertex V;
  • Then, visit the adjacent vertices of V;
  • Finally, mark the vertex v as black;

Code:

      //六.实现深度搜索(DFS)
      Graph.prototype.dfs = function(initV, handler){
        //1.初始化顶点颜色
        let colors = this.initializeColor()

        //2.从某个顶点开始依次递归访问
        this.dfsVisit(initV, colors, handler)
      }

      //为了方便递归调用,封装访问顶点的函数,传入三个参数分别表示:指定的第一个顶点、颜色、处理函数
      Graph.prototype.dfsVisit = function(v, colors, handler){
        //1.将颜色设置为灰色
        colors[v] = 'gray'

        //2.处理v顶点
        handler(v)

        //3.访问V的相邻顶点
        let vNeighbours = this.edges.get(v)
        for (let i = 0; i < vNeighbours.length; i++) {
          let a = vNeighbours[i];
          //判断相邻顶点是否为白色,若为白色,递归调用函数继续访问
          if (colors[a] == 'white') {
            this.dfsVisit(a, colors, handler)
          }
          
        }

        //4.将v设置为黑色
        colors[v] = 'black'
      }

Detailed process:

Here we mainly explain the third step in the code: accessing the adjacent vertices of the specified vertex.

  • Taking the specified vertex A as an example, first take out the array consisting of the adjacent vertices of vertex A from the dictionary object edges storing the vertex and its corresponding adjacent vertices:


  • Step 1 : Vertex A turns gray, and then enters the first for loop, traversing white adjacent vertices of A: B, C, D; in the first loop of the for loop (executing B), vertex B satisfies : colors == "white", trigger recursion, call this method again;
  • Step 2 : Vertex B turns gray, and then enters the second for loop, traversing white adjacent vertices of B: E, F; in the first cycle of the for loop (executing E), vertex E satisfies: colors == "white", trigger recursion, call the method again;
  • Step 3 : Vertex E turns gray, and then enters the third for loop, traversing white adjacent vertices of E: I; in the first cycle of the for loop (executing I), vertex I satisfies: colors == "white", trigger recursion, call the method again;
  • Step 4 : Vertex I turns gray, and then enters the fourth for loop. Since the adjacent vertex E of vertex I does not satisfy: colors == "white", stop the recursive call.
  • Step 5 : After the recursion ends, return all the way up, first return to the third for loop and continue to execute the second, third... loops, the execution process of each loop is the same as above, .... And so on until all the vertices of the graph are visited.
  • The following figure shows the complete process of traversing each vertex in the graph:

  • Discovery indicates that the vertex has been visited, and the status turns gray ;
  • Exploration indicates that both the vertex and all adjacent vertices of the vertex have been visited, and the status becomes black ;
  • Since the processing function handler is called after the vertex turns gray, the output order of the handler method is the order in which the vertices are found: A, B, E, I, F, C, D, G, H.

 

Guess you like

Origin blog.csdn.net/m0_65835778/article/details/126552605