Figure C + + implementation of template, BFS and DFS

chart

There are two ways to store graphs: adjacency matrix and adjacency table.

adjacency matrix

A two-dimensional array is used to indicate whether there are edges between vertices and how much weight the edges are. For an undirected graph, the adjacency matrix is a symmetric matrix. The disadvantage is that although the adjacency matrix is easy to write, it needs to open up a two-dimensional array. If there are too many vertices, it is easy to exceed the memory limit of the problem.

Adjacency table

Put all the outgoing edges of the same vertex in a list, then N vertices will have N lists. These N lists are the adjacency table of graph G, which is recorded as Adj[N]. Each node will store the information of an edge (the number outside the bracket is the end number of the edge, and the number inside the bracket is the edge weight).

For beginners, it is easier to use the variable length array vector to represent the adjacency table.

If the adjacency table only stores the end sequence number of each edge and does not store the weight, the element type in the vector can be directly defined as int type vector < int > adj [n]; If you want to add a directed edge from node 3 to node 1, you need adj [3] push_ back(1);

If the adjacency table also needs to store edge weights, the element type of vector uses the Node structure, and the code is as follows:

struct Node{
    int w;//Edge weight
    int v;//End sequence number of edge
};

At this time, add a directed edge with a weight of 2 from node 3 to node 1. The code is as follows:

Node temp;
temp.w=2;
temp.v=1;
Adj[3].push_back(temp);

The fastest way to write is to provide a constructor for the structure and directly add temporary nodes during the addition process. The code is as follows:

struct Node{
    int w,v;
    Node(int _w,int _v):w(_w),v(_v){}
};
Adj[3].push_back(Node(2,1));

Traversal of Graphs

Generally, there are two traversal methods: depth first search DFS and breadth first search BFS.

DFS

Idea: follow a path until you can't move forward, then return to the fork of the path where there are unreachable nodes closest to the current vertex, and continue to access those branch vertices until you finish traversing the whole graph.

Implementation: set the passed vertex as accessed. When this vertex is encountered in the next recursion, it will not be processed until the vertices of the whole graph are marked as accessed. The pseudo code is as follows:

DFS(u){
    vis[u]=true;//Settings u have been accessed
    	for(from u All the vertices that can be reached by departure v)
            if vis[v]==false //If v is not accessed
                DFS(v);//Recursive access v
}
DFSTrave(G){ //Ergodic graph G	
    for(G All vertices of u){ //For all vertices of graph G
        if vis[u]=false //If u is not accessed
            DFS(u); //Access the connected block where u is located
    }
}

Adjacency matrix DFS

const int MAXV=1000;
const int INF=1000000000;
int n,G[MAXV][MAXV];//n is the number of vertices and MAXV is the maximum number of vertices
bool vis[MAXV]={false};//Initialize the access status of all nodes to no access

void DFS(int n,int depth){//u is the symbol of the currently accessed node and depth is the depth
    vis[u]=true;
    //Traverse all nodes that can be reached from u
    for(int v=0;v<n;v++){
        if(vis[v]==false&&G[u][v]!=INF){//If V is not accessed, and u can reach v
            DFS(v,depth+1);
        }
    }
}
void DFSTrave(){
    for(int u=0;u<n;u++){//For each vertex u
        if(vis[u]==false){//If the current vertex u is not accessed
            DFS(u,1);//Access the connected block where each vertex u is located, representing the first layer
        }
    }
}

Adjacency table version DFS

const int MAXV=1000;
const int INF=1000000000;
vector<int>Adj[MAXV];
int n;
bool vis[MAXV]={false};

void DFS(int u,int depth){
    vis[u]=true;
    for(int i=0;i<Adj[u].size();i++){//All outgoing points connected after traversing the current node u
        int v=Adj[u][i];
        if(vis[v]==false){
            DFS(v,depth+1);
        }
    }
}
void DFSTrave(){
    for(int u=0;u<n;u++){
        if(vis[u]==false){
            DFS(u,1);
        }
    }
}

The DFS difference between the two structures is mainly due to the different representation methods of the graph. Therefore, when finding other nodes on the next layer of a node, using the adjacency matrix needs to traverse all positions to find the points that are not accessed and connected to the point. The adjacency table only needs to find the table connected behind the current node, and does not have to traverse all nodes.

BFS

Idea: access vertices in a diffuse manner each time. Starting from the starting point of the search, continuously give priority to accessing the neighbors of the current node, that is, first visit the starting point, then visit the neighbor nodes that have not been visited by the starting point in turn, and then visit their neighbors in turn according to the order of visiting the neighbor nodes of the starting point until the solution is found or the whole space is searched.

Step: you need to establish a queue, add the initial vertex to the queue, repeatedly take out the first vertex of the queue, and queue all the reachable vertices that have not been added to the queue until the traversal ends when the queue is empty. The pseudo code is as follows:

BFS(u){
    queue q;
    inq[u]=true;
    while(q Non empty){
        for(from u All the vertices that can be reached v){
            if(inq[v]==false){
                take v Join the team
                inq[v]=true;
            }
        }
    }
}
BFSTrave(G){
    for(G All vertices of u){
        if(inq[u]==false){
            BFS(u);
        }
    }
}

Adjacency matrix BFS

int n,G[MAXN][MAXN];
bool inq[MAXN]={false};
void BFS(int u){//Traverse the connected block where u is located
    queue<int> q;//Define queue
    q.push(u);//Join the initial point u into the team
    inq[u]=true;//Setting u has been queued
    while(!q.empty()){//As long as the queue is not empty
        int u=q.front();//Take out the team head element
        q.pop();//Remove the first element from the team
        for(int v=0;v<n;v++){
            //If the adjacency point v of u has not joined the queue
            if(inq[v]==false&&G[u][v]!=INF){
                q.push(v);//Join v the team
                inq[v]=true;//Mark v as queued
            }
        }
    }
}
void BFSTrave(){
    for(int u=0;u<n;u++){
        if(inq[u]==false){
            BFS(u);
        }
    }
}

Adjacency table version

vector<int> Adj[MAXV];
int n;
bool inq[MAXV]={false};
void BFS(int u){//Traversing a single connected block
    queue<int> q;//Define queue
    q.push(u);//Queue initial nodes
    inq[u]=true;//Setting u has been queued
    while(!q.empty()){//As long as the queue is not empty
        int u=q.front();//Get team leader element
        q.pop();//Pop up queue
        for(int i=0;i<Adj[u].size();i++){//Enumerate the vertices that can be reached from u
            int v=Adj[u][i];
            if(inq[v]==false){//If v never joined the team
                q.push(v);//Join v the team
                inq[v]=true;//Set as queued
            }
        }
    }
}
void BFSTrave(){//Ergodic graph G
    for(int u=0;u<n;u++){//Enumerate all vertices
        if(inq[u]==false){//If vertex u has not been queued
            BFS(q);//Traverse the connected block where u is located
        }
    }
}

Drawing template

Graph class uses map to store node information and set to store edge information

class Graph{
public:
    unorder_map<int,Node*>nodes;
    unorder_set<Edge*>edges;
    Graph(){};
};

The Node class consists of five parts: the value of the Node, the in degree, the out degree, the next Node nexts of the current Node, and the edge edges starting from the current Node

class Node{
    public:
    int value;
    int out;
    int in;
    vector<Node*>nexts;
    vector<Edge*>edges;
    Node(int val,int inn=0,int outt=0):value(val),in(inn),out(outt){}
};

The Edge class consists of three parts: the weight, the from and to nodes of the current Edge

class Edge{
public:
    int weight;
    Node* from;
    Node* to;
    Edge(int w,Node* f,Node* t):weight(w),from(f),to(t){}
};

The generated graph function is encapsulated into classes in the form of adjacency matrix. The construction process of figure is as follows:

  1. Instantiate a Graph object, traverse the array by row, and take out the weight, from and to nodes corresponding to each row of data
  2. Judge whether the from and to nodes in the current graph exist. If they do not exist, you need to create nodes
  3. Creating edges with form and to nodes
  4. Enrich the pointing node to, edge and out degree of form node, and enrich the in degree of to node.
  5. Add the built edge to the edge in the figure
class GraphGenerator{
public:
	Graph graph;
    for(int i=0;i<matrix.size();i++){
        //Loop reading information in a two-dimensional array
        int weight=matrix[i][0];
        int from=matrix[i][1];
        int to=matrix[i][2];
        //If these two points do not appear in the figure, initialize this point
        if(graph.nodes.find(from)==graph.nodes.end()){
            graph.nodes[from]=new Node(from);
        }
        if(graph.nodes.find(to)==graph.nodes.end()){
            graph.nodes[to]=new Node(to);
        }
        //Take out these two points from the graph to enrich the information of the points
        Node* fromNode=graph.nodes[from];
        Node* toNode=graph.nodes[to];
        //Initialize an edge object by reading the edge weight and information at both ends
        Edge* newEdge=new Edge(weight,fromNode,toNode);
        //Enrich the information of fromnode, including the node, edge and exit degree
        fromNode->nexts.push_back(toNode);
        fromNode->edges.push_back(newEdge);
        fromNode->out++;
        //Enrich toNode information and increase penetration
        toNode->in++;
        //Add the generated edge to the edge collection member object of the graph object
        graph.edges.insert(newEdge);
    }
    return graph;
};

Graph search

void BFS(Node* head) {
	if (head == NULL)return;
	queue<Node*>q;
	unordered_set<Node*>s;//Use set to determine whether the node has joined the queue
	q.push(head);//Add the first node to the queue
	s.insert(head);
	while (!q.empty()) {//As long as the queue is not empty, it loops all the time
		Node* cur = q.front();//Take the current team leader element and pop it up
		cout << cur->value << " ";
		q.pop();
		for (Node* n : cur->nexts) {//Traverse all adjacent (next level) nodes of this node
			if (s.find(n) == s.end()) {//If this node has not joined the queue
				s.insert(n);//The node is marked as visited and queued
				q.push(n);
			}
		}
	}
}

void DFS(Node* head) {
	if (head == NULL)return;
	stack<Node*>st;
	unordered_set<Node*>se;
    //Put the header node on the stack and record it
	st.push(head);
	se.insert(head);
	cout << head->value << " ";
    //As long as the stack is not empty, it loops all the time
	while (!st.empty()) {
        //Pop up the stack top element as the current node
		Node* cur = st.top();
		st.pop();
        //Traverse all the next level nodes of the current node. If there is no next level node or the next level node is accessed, the last two sentences of the program will be executed continuously
        //The nodes in the stack keep popping up until the next node of the current node is found or the stack is empty.
		for (Node* n : cur->nexts) {
			if (se.find(n) == se.end()) {//If this lower level node has not been accessed
				st.push(cur);//If you continue to drill down, you need to push both the current node and the next layer node into the stack
				st.push(n);
				se.insert(n);//The tag accesses the next level node
				cout << n->value << " ";
				break;//If a next level node is found, exit the current loop and continue to search deeply
			}
		}
	}
}

Keywords: C++ Algorithm Graph Theory

Added by tallberg on Sun, 16 Jan 2022 11:44:12 +0200