深度和广度优先搜索:如何找出社交网络中的三度好友关系?

1 深度优先搜索(DFS)

  深度优先搜索(Depth-First-Search),简称 DFS。最直观的例子就是“走迷宫”。假设你站在迷宫的某个岔路口,然后想找到出口。你随意选择一个岔路口来走,走着走着发现走不通的时候,你就回退到上一个岔路口,重新选择一条路继续走,直到最终找到出口。这种走法就是一种深度优先搜索策略。

  这里的搜索,其实是“遍历”,所以不会打印重复的顶点。在很多回溯法的路径问题中,如果只是单纯的找一条路径,那就不需要写回visit[w] = false;但如果是一条“规定”的路径,比如剑指offer12题,可能会是"b f c e d c"这样一条路径。那就需要写回visit[w] = false

2 广度优先搜索(BFS)

  广度优先搜索(Breadth-First-Search),简称 BFS。直观地讲,它其实就是一种“地毯式”层层推进的搜索策略,即先查找离起始顶点最近的,然后是次近的,依次往外搜索。理解起来并不难,所以我画了一张示意图,你可以看下。

3 C++代码实现

#pragma once
#include <iostream>
using namespace std;

#define MAXVEX 100
#define INFINITY 65535

// 邻接矩阵
struct GraphAdiMatix{
    int numVertexes;
    int numEdges;
    // 顶点表
    int vexs[MAXVEX];
    // 邻接矩阵
    int arc[MAXVEX][MAXVEX];
};

// 建立无向网图的邻接矩阵
void createAdjMatixGraph(GraphAdiMatix * G){
    int cnt = 0;
    cout<<"输入顶点数和边数"<<endl;
    cin>>G->numVertexes>>G->numEdges;
    // 顶点初始化
    for(int i = 0; i < G->numVertexes; i++){
        G->vexs[i] = i;
    }

    // 邻接矩阵初始化
    for(int i = 0; i < G->numVertexes; i++){
        for(int j = 0; j < G->numVertexes; j++)
            G->arc[i][j] = INFINITY;
    }

    cnt = G->numEdges;
    while(cnt--){
        int x, y, w;
        cout<<"输入边(vi,vj)上的顶点序号i,j和权w:"<<endl;
        cin>>x>>y>>w;
        // 因为是无向图,所以矩阵对称
        G->arc[x][y] = G->arc[y][x] = w;
    }
}

// (Breadth First Search, BFS)广度优先遍历邻接矩阵(其实就像二叉树的按层遍历)
void BFSTraveseMatix(GraphAdiMatix G){
    bool visit[MAXVEX];
    memset(visit, false, sizeof(visit));
    // 定义辅助队列
    queue<int> queueBFS;
    for(int i = 0; i < G.numVertexes; i++){
        if(!visit[i]){
            visit[i] = true;
            // 入列
            queueBFS.push(i);
            while(!queueBFS.empty()){
                cout<<queueBFS.front()<<endl;
                // 获取队首元素并出列
                int front = queueBFS.front();
                queueBFS.pop();
                for(int i = 0; i < G.numVertexes; i++){
                    if(G.arc[front][i] != INFINITY && !visit[i]){
                        queueBFS.push(i);
                        visit[i] = true;
                    }
                }
            }
        }
    }
}

// DFS一般是递归(好像还必须得写两个函数)
void DFSMatix(GraphAdiMatix G, bool visit[], int i){
    visit[i] = true;
    cout<<i<<endl;
    for(int j = 0; j < G.numVertexes; j++){
        if(G.arc[i][j] != INFINITY && !visit[j]){
            DFSMatix(G, visit, j);
        }
    }
}

// (Depth First Search, DFS)深度优先遍历邻接矩阵(其实就像二叉树的前序遍历)
void DFSTraveseMatix(GraphAdiMatix G){
    bool visit[MAXVEX];
    memset(visit, false, sizeof(visit));
    for(int i = 0; i < G.numVertexes; i++){
        if(!visit[i]){
            DFSMatix(G, visit, i);
        }
    }
}


/**********************************************************************************/

// 边表节点(理解为链表)
typedef struct EdgeNode{
    // 邻接点域,存储该顶点对应的下标
    int adjvex;
    // 权值
    int weight;
    // 链域,指向下一个邻接点
    EdgeNode* next;
    // 构造函数
    EdgeNode(int adjvex, int weight): adjvex(adjvex), weight(weight), next(nullptr){}
}EdgeNode;

// 顶点表节点(理解为数组)
typedef struct VertexNode{
    // 顶点域,存储顶点信息,默认为数组下标值
    int data;
    // 边表头指针
    EdgeNode* firstEdge;
}VertexNode, AdjList[MAXVEX];

// 邻接表
struct GraphAdiList{
    AdjList adjlist;
    int numVertexes;
    int numEdges;
};

void createAdjGraph(GraphAdiList* G){
    int cnt;
    cout<<"输入顶点数和边数"<<endl;
    cin>>G->numVertexes>>G->numEdges;
    for(int i = 0; i < G->numVertexes; i++){
        G->adjlist[i].firstEdge = nullptr;
    }
    cnt = G->numEdges;
    EdgeNode* pNode;
    while(cnt--){
        int x, y;
        cout<<"输入边(vi,vj)上的顶点序号:"<<endl;
        cin>>x>>y;
        pNode = new EdgeNode(y, 0);
        pNode->adjvex = y;
        pNode->next = G->adjlist[x].firstEdge;
        G->adjlist[x].firstEdge = pNode;

        pNode = new EdgeNode(x, 0);
        pNode->adjvex = x;
        pNode->next = G->adjlist[y].firstEdge;
        G->adjlist[y].firstEdge = pNode;
    }
}

// 求图中顶点的出度
int outDegree(GraphAdiList G, int i){
    EdgeNode* pNode;
    int outD = 0;
    pNode = G.adjlist[i].firstEdge;
    while(pNode != nullptr){
        outD++;
        pNode = pNode->next;
    }
    return outD;
}

// (Breadth First Search, BFS)遍历邻接表(其实就像二叉树的按层遍历)
void BFSTravese(GraphAdiList G){
    // 定义访问数组
    bool visit[MAXVEX];
    memset(visit, false, sizeof(visit));
    // 定义顶点
    EdgeNode* pNode;
    // 定义辅助队列
    queue<int> queueBFS;
    for(int i = 0; i < G.numVertexes; i++){
        if(!visit[i]){
            cout<<"这个地方可能只会执行一次,然后全部在下面那个while执行了,因为第一个链表就包含了所有的元素."<<endl;
            visit[i] = true;
            // 入列
            queueBFS.push(i);
            while(!queueBFS.empty()){
                // 打印并取出队首元素
                int front = queueBFS.front();
                cout<<front<<endl;
                // 出列队首元素
                queueBFS.pop();
                // 找到当前顶点边表头指针
                pNode = G.adjlist[front].firstEdge;
                while(pNode != nullptr){
                    // 此节点没被访问过
                    if(!visit[pNode->adjvex]){
                        // 将此顶点入列
                        queueBFS.push(pNode->adjvex);
                        visit[pNode->adjvex] = true;
                    }
                    pNode = pNode->next;
                }
            }
        }
    }
}

void DFS(GraphAdiList G, bool visit[], int i){
    visit[i] = true;
    cout<<i<<endl;
    EdgeNode* pNode = G.adjlist[i].firstEdge;
    while(pNode != nullptr){
        if(!visit[pNode->adjvex])
            DFS(G, visit, pNode->adjvex);

        pNode = pNode->next;
    }
}

// (Depth First Search, DFS)深度优先遍历邻接表(其实就像二叉树的前序遍历)
void DFSTravese(GraphAdiList G){
    bool visit[MAXVEX];
    memset(visit, false, sizeof(visit));
    for(int i = 0; i < G.numVertexes; i++){
        if(!visit[i])
            DFS(G, visit, i);
    }
}

int main(int argc, char* argv[]){
/******** 邻接表测试 **********/

    GraphAdiList G;
    createAdjGraph(&G);
    BFSTravese(G);
    DFSTravese(G);

/******************************/

/******** 邻接矩阵测试 **********/

    //GraphAdiMatix G;
    //createAdjMatixGraph(&G);
    //BFSTraveseMatix(G);
    //DFSTraveseMatix(G);

/******************************/
    return 0;

}

4 Java代码实现(目前只有邻接表的BFS)

import java.util.LinkedList;
import java.util.Queue;

/**
 * @author ZR
 * @Classname Graph
 * @Description TODO
 * @Date 2020/8/7 15:04
 */
public class Graph {
    // 顶点的个数
    private int v;
    // 邻接表
    private LinkedList<Integer> adj[];


    public Graph(int v){
        this.v = v;
        this.adj = new LinkedList[v];
        for(int i = 0; i < v; i++){
            adj[i] = new LinkedList<>();
        }
    }

    public void BFSTravese(Graph G){
        boolean visit[] = new boolean[Perfect.MAXVEX];
        for(int i = 0; i < visit.length; i++){
            visit[i] = false;
        }
        // 定义链表
        LinkedList<Integer> pNode;
        // 定义辅助队列
        Queue<Integer> queue = new LinkedList<>();
        for(int i = 0; i < G.v; i++){
            if(!visit[i]){
                visit[i] = true;
                queue.add(i);
                while(!queue.isEmpty()){
                    // 取出队首元素并出列
                    int front = queue.poll();
                    System.err.println(front);
                    pNode = G.adj[front];
                    for(Integer integer: pNode){
                        if(!visit[integer]) {
                            visit[integer] = true;
                            queue.add(integer);
                        }
                    }
                }
            }
        }
    }
}

5 解答开篇

  了解了深度优先搜索和广度优先搜索的原理之后,开篇的问题是不是变得很简单了呢?我们现在来一起看下,如何找出社交网络中某个用户的三度好友关系?

  上一节我们讲过,社交网络可以用图来表示。这个问题就非常适合用图的广度优先搜索算法来解决,因为广度优先搜索是层层往外推进的。首先,遍历与起始顶点最近的一层顶点,也就是用户的一度好友,然后再遍历与用户距离的边数为 2 的顶点,也就是二度好友关系,以及与用户距离的边数为 3 的顶点,也就是三度好友关系。

  我们只需要稍加改造一下广度优先搜索代码,用一个数组来记录每个顶点与起始顶点的距离,非常容易就可以找出三度好友关系。

原文地址:https://www.cnblogs.com/flyingrun/p/13453955.html