数据结构之图

一、浅谈
图的内容非常的多,包括各种概念和算法。想要一次性全部拿下是非常难的,尤其是里面的算法,比如最小生成树算法这些,就很像KMP算法,属于一时懂,但后来就很容易忘光的算法。关于图内容的总结,需要较长时间,所以,这篇博文会动态更新。

二、图的种类
把图的全部分类总结如下:
有向图:顶点间的连线是有方向的
无向图:顶点间的连线是无向的
完全图:任意两顶点间都存在连线(有向时则需要来回两条,无向时则只需一条)
连通图:任意两顶点间都存在通路
强连通图:任意两顶点间都存在直接的通路
弱连通图:任意两顶点间都存在通路,但是不一定是直接的通路
AOV网:有向无环图(DAG)中,用顶点表示活动,用有向边表示活动之间的先后顺序的网络(拓扑排序)
AOE网:在AOV网的边上加上权值表示完成该活动所需时间的网络

三、图的操作
(1) 建立、插入和删除
建立一个图,需要记录顶点和边。记录顶点的方式,可以是顺序存储(数组)和链式存储(链表)。
边的记录方式,就比较多了。第一种,可以是直接用邻接矩阵,即表征两个点直接是否有边存在。
第二种,可以用邻接表,就是用一个存链表的数组,数组存的是每个顶点连接的顶点组成的链表的头节点,
第三种,可以用十字链表法,比较复杂,总之就是可以同时表示入度和出度的改进邻接表。

//链表
struct ListNode {
    int val;
    ListNode* next;
    ListNode* tail;
    ListNode():val(0),next(nullptr), tail(nullptr) {}
    ListNode(int n) :val(n), next(nullptr), tail(this) {}
};
//图
struct Gragh {
    //顶点---顺序存储
    vector<int>points;
    //边---邻接表法
    vector<ListNode*>edges;
    //构造
    Gragh(vector<vector<int>>connects) {
        for (int i = 0; i < connects.size(); ++i) {
            points.push_back(connects[i][0]);
            ListNode* cur = new ListNode(connects[i][0]);
            while (i < connects.size() - 1 && connects[i].size()>1&&connects[i][0] == connects[i+1][0]) {
                cur->tail->next = new ListNode(connects[i][1]);
                cur->tail = cur->tail->next;
                ++i;
            }
            if (i < connects.size() - 1 && connects[i].size()>1) {
                cur->tail->next = new ListNode(connects[i][1]);
                cur->tail = cur->tail->next;
            }
            edges.push_back(cur);
        }
    }
    //插入
    void insert(vector<int>connect) {
        int i = 0;
        for (i; i < points.size(); ++i) {
            if (points[i] == connect[0]) {
                edges[i]->tail->next = new ListNode(connect[1]);
                edges[i]->tail = edges[i]->tail->next;
                break;
            }
        }
        if (i == points.size()) {
            points.push_back(connect[0]);
            ListNode* cur = new ListNode(connect[0]);
            if (connect.size() > 1) {
                cur->tail->next = new ListNode(connect[1]);
                cur->tail = cur->tail->next;    
            }
            edges.push_back(cur);
        }
    }
    //删除
    void deletes(int num) {
        for (int i = 0; i < points.size(); ++i) {
            if (points[i] == num) {
                points.erase(points.begin() + i);
                edges.erase(edges.begin() + i);
                break;
            }
        }
        for (int i = 0; i < edges.size(); ++i) {
            ListNode* cur = edges[i];
            while (cur->next) {
                if (cur->next->val == num) {
                    ListNode* tmp = cur->next;
                    cur->next = tmp->next;
                    delete tmp;
                }
                cur = cur->next;
            }
        }
    }
    //显示
    void show() {
        for (int i = 0; i < points.size(); ++i) {
            cout << points[i] << ":  ";
            if (edges[i]) {
                ListNode* tmp = edges[i]->next;
                while (tmp) {
                    cout << tmp->val;
                    if (tmp->next)    cout << " -> ";
                    tmp = tmp->next;
                }
            }
            cout << endl;
        }
    }
    //获取顶点数
    int getpoints() {
        return points.size();
    }
};

(2) 遍历
遍历分为两种,一种是DFS遍历,一种是BFS遍历
DFS遍历就是“见异思迁,遇到新的顶点就跑到新的顶点”---表现为类似树的前序遍历
BFS遍历就是“一心一意,把该顶点所有信息遍历完再进入下一个”---表现为类似树的层序遍历

//DFS遍历
void dfs(int num,Gragh g, unordered_set<int>& visited) {
    for (int i = 0; i < g.points.size(); ++i) {
        if (num == g.points[i]) {
            if (visited.find(num) == visited.end()) {
                cout << num << " -> ";
                visited.insert(num);
            }
            ListNode* cur = g.edges[i]->next;
            while (cur) {
                dfs(cur->val, g, visited);
                cur = cur->next;
            }
            break;
        }
    }
}
//BFS遍历
void bfs( Gragh g, unordered_set<int>& visited) {
    for (int i = 0; i < g.points.size(); ++i) {
        if (visited.find(g.points[i]) == visited.end()) {
            cout << g.points[i] << " -> ";
            visited.insert(g.points[i]);
        }
        ListNode* cur = g.edges[i]->next;
        while (cur) {
            if (visited.find(cur->val) == visited.end()) {
                cout << cur->val << " -> ";
                visited.insert(cur->val);
            }
            cur = cur->next;
        }    
    }
    cout << endl;
}

三、图的相关算法
(1) 最短路径算法

//建立AOE网---重新定义图结构,用邻接矩阵存储边信息
struct AOE {
    vector<int>points;
    vector<vector<int>>edges;
    AOE(vector<vector<int>>connects) {
        for (int i = 0; i < connects.size(); ++i) {
            points.push_back(connects[i][0]);
            while (i < connects.size() - 1 && connects[i].size()>1 && connects[i][0] == connects[i + 1][0])
                ++i;
        }
        edges = vector<vector<int>>(points.size(), vector<int>(points.size()));
        for (int i = 0; i < connects.size(); ++i) {
            for (int k = 0; k < points.size(); ++k) {
                if (points[k] == connects[i][0]) {
                    for (int j = 0; j < points.size(); ++j) {
                        if (connects[i].size()>2&&points[j] == connects[i][1]) {
                            edges[k][j] = connects[i][2];
                            break;
                        }
                    }
                    break;
                }
            }
        }
    }
    void show() {
        for (int i = 0; i < points.size(); ++i) {
            cout << points[i] << ":   ";
            for (int j = 0; j < points.size(); ++j)
                cout <<setiosflags(ios::left)<< setw(10) << edges[i][j] << "   ";
            cout << endl;
        }
    }

};

1) 弗洛伊德最短路径算法

//1.弗洛伊德算法
void floyd(AOE aoe){
    const int n = aoe.points.size();
    for (int i = 0; i < n; ++i) {
        for(int j=0;j<n;++j){
            if (i != j &&aoe.edges[i][j] == 0)
                aoe.edges[i][j] = INT32_MAX / 2;
        }
    }
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            for (int k = 0; k < n; ++k) {
                if (aoe.edges[i][j] >(aoe.edges[i][k] + aoe.edges[k][j]))
                    aoe.edges[i][j] = aoe.edges[i][k] + aoe.edges[k][j];
            }
        }
    }
    for (int i = 0; i < n; ++i) {
        cout <<aoe.points[i] << ":   ";
        for (int j = 0; j < n; ++j)
            cout << setiosflags(ios::left) << setw(10)<<aoe.edges[i][j] << "   ";
        cout << endl;
    }
}

1) 迪杰斯特拉最短路径算法

//2.Dijkstra算法
void Dijkstra(AOE aoe,int num) {
    const int n = aoe.points.size();
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (i != j && aoe.edges[i][j] == 0)
                aoe.edges[i][j] = INT32_MAX / 2;
        }
    }
    vector<vector<int>>S;
    vector<vector<int>>U(n-1,vector<int>(2));
    for(int i=0;i<n;++i){
        if (aoe.points[i] == num) {
            //初始化S,U
            S.push_back({ num,aoe.edges[i][i] });
            for (int j = 1; j < n; ++j) 
                U[j - 1] = { aoe.points[j],aoe.edges[i][j] };
            //更新S和U
            while (!U.empty()) {
                //更新S
                sort(U.begin(), U.end(), [](vector<int>& a, vector<int>& b) {
                    return a[1] < b[1];
                });
                S.push_back(U[0]); 
                //更新U
                for (int u = 1; u < U.size(); ++u) {
                    for (int j = 0; j < n; ++j) {
                        if (U[0][0] == aoe.points[j]) {
                            for (int k = 1; k < n; ++k) {
                                if (aoe.points[k] == U[u][0]) {
                                    if (U[0][1] + aoe.edges[j][k] < U[u][1])
                                        U[u][1] = U[0][1] + aoe.edges[j][k];
                                    break;
                                }
                            }
                            break;
                        }
                    }    
                }
                U.erase(U.begin());
            }
            break;
        }
    }
    //显示结果
    for (int i = 0; i < S.size(); ++i) 
        cout << num << " 到 " << S[i][0] 
             << " 的最小距离为: " << S[i][1] << endl;        
}

(2)最小生成树算法

全部评论

相关推荐

10-29 15:38
门头沟学院 Java
榕城小榕树:难道你简历里写了配送路径优化算法?
点赞 评论 收藏
分享
10-06 12:46
门头沟学院 Java
跨考小白:定时任务启动
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务