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


    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 的顶点,也就是三度好友关系。

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

  • 相关阅读:
    CArray类模板封装示例
    环形队列CQueue类模板示例
    CStack栈模板封装示例
    TSIC506驱动程序
    proteus虚拟串口实现
    js学习笔记(一)
    提升网页性能
    JS性能优化笔记搜索整理
    @font-face制作Web Icon
    原生js动画效果(源码解析)
  • 原文地址:https://www.cnblogs.com/flyingrun/p/13453955.html
Copyright © 2020-2023  润新知