如何在 c++ 中表示图形

How to represent a graph in c++

本文关键字:表示 图形 c++      更新时间:2023-10-16

我想写一个创建MST的素数和dijkstra算法。但我不知道在 c++ 中表示图形的最佳方式是什么。

我可以通过两个整数对表示一条边,例如向量 0 到 1 将是 pair(0,1);

typedef pair<int, int> Edge;

然后 prims 函数将采用由边及其权重组成的对向量。

void prims(vector<pair<Edge, int>>);

我认为这种方式不是最好的方式,谁能告诉我哪种方式最适合表示图形?

前段时间我一直在实现 Dijkstra 以查找二进制图像中的路径。我将图形表示为结构GraphNodes的向量,其中包含一个包含节点与其他节点的所有连接的Connections向量。每个连接都有其距离属性,即边的权重。以下是我使用的两个结构:

//forward declaration
struct GraphNode;
struct Connection {
    Connection() : distance(1) { };
    Connection(GraphNode* ptr, double distance) : ptr(ptr), distance(distance) { };
    bool operator==(const Connection &other) const;
    GraphNode* ptr;
    double distance;
};
struct GraphNode {
    GraphNode() : connections(8), predecessor(NULL), distance(-1) { };
    cv::Point point;
    double distance;
    GraphNode* predecessor;
    std::vector<Connection> connections;
};
bool Connection::operator==(const Connection &other) const {
    return ptr == other.ptr && distance == other.distance;
}

GraphNode 的距离属性是它当前在 Dijkstra 算法中的距离,即当前已知最短距离到起始节点的距离。一开始,这是用 -1 初始化的。

然后我像这样实现了 Dijkstra 算法:

std::vector<cv::Point> findShortestPathDijkstra(std::vector<GraphNode>& graph, int startNodeIndex, int destNodeIndex) const {
    GraphDistanceSorter sorter(graph);
    std::set<GraphNode*, GraphDistanceSorter> unusedNodes(sorter);
    for (int i = 0; i < graph.size(); ++i) {
        unusedNodes.insert(&graph[i]);
    }
    while (unusedNodes.size() > 0) {
        GraphNode* currentNode = *unusedNodes.begin();
        if (currentNode->distance == -1) {
            return std::vector<cv::Point>();
        }
        if (currentNode == &graph[destNodeIndex]) break;
        unusedNodes.erase(currentNode);
        //update distances of connected nodes
        for (Connection const& con : currentNode->connections) {
            /*here we could check if the element is really in unusedNodes (search, O(log n)), but this would
            actually take longer than calculating the new distance (O(1)), which will in this case always be greater
            than the old one, so the distance is never updated for nodes not in unusedNodes ()*/
            double newDistance = currentNode->distance + con.distance;
            if (newDistance < con.ptr->distance || con.ptr->distance == -1) {
                unusedNodes.erase(con.ptr);
                con.ptr->distance = newDistance;
                con.ptr->predecessor = currentNode;
                unusedNodes.insert(con.ptr);
            }
        }
    }
    //now trace back the path as a list of points
    std::vector<cv::Point> points;
    GraphNode* current = &graph[destNodeIndex];
    points.push_back(current->point);
    while (current != &graph[startNodeIndex]) {
        if (current->predecessor == NULL) return std::vector<cv::Point>();
        current = current->predecessor;
        points.push_back(current->point);
    }
    return points;
}

如您所见,有一个集合unusedNodes包含到目前为止所有未使用的节点。它只包含graphNodes上的指针。实际的图形表示形式在向量中。拥有集合的好处是,它总是根据某个标准进行排序。我实现了自己的排序器GraphDistanceSorter,它根据Dijkstra算法的距离标准对GraphNodes进行排序。这样,我只需要从集合中选取第一个节点,并知道它是距离最小的节点:

struct GraphDistanceSorter {
    bool operator() (const GraphNode* lhs, const GraphNode* rhs) const;
};
bool GraphDistanceSorter::operator() (const GraphNode* lhs, const GraphNode* rhs) const {
    if (lhs->distance == rhs->distance) {
        return lhs < rhs;
    } else {
        if (lhs->distance != -1 && rhs->distance != -1) {
            if (lhs->distance != rhs->distance) {
                return lhs->distance < rhs->distance;
            }
        } else if (lhs->distance != -1 && rhs->distance == -1) {
            return true;
        }
        return false;
    }
}

表示理论计算机科学中学到的图的两种主要方法是邻接矩阵和邻接列表

邻接矩阵如下图所示是一个 n*n 矩阵,a[i][j] 表示节点 i 和节点 j 之间的边,因此如果它是一个加权图,它可以是一个整数,而不是未加权图的布尔值。

邻接矩阵(图片来源:谷歌)

另一方面,邻接列表是一组链表(确切地说是n集),第i个集合恰好具有i连接到的节点。在这种情况下,您将需要一些额外的方法来保存边缘距离,例如,您可以构建自己的类 Edge

如下
class Edge
{
     int destination, length;
     Edge* next = 0;
}

并将其用于您的链接列表。我习惯于std::vector<std::pair<int, int>> a[N]定义一个对列表,a[i][j].first将是点头 i 的第 j 个邻居,并a[i][j].second它们之间的边缘长度。对于无向图,您也可以将 i 添加到 j 邻居。因此,这也是表示图形的一种灵活方式。

邻接列表(图片来源:谷歌相册)

所以现在让我们谈谈复杂性,我会尽量保持简单:

我们有 n 个列表,每个列表都有 #(边缘离开节点 i)所以总数是这个数字的总和,即边 E 的总数。这意味着位置复杂度为 O(E),与邻接矩阵中的 O(N^2) 相比,稀疏图中最多为 5 * n。(我们需要一个 E 的线性因子来表示它)。现在让我们考虑访问点头 x 的所有邻居:在邻接矩阵中,我们将遍历整条第 x 行,如果它不是 0,则有一个边是 O(N)。在邻接列表中,它又是 x 的邻居的数量,尽管可以达到 O(N)。但是,如果我们访问所有节点的所有邻居(在更新 dis 数组时在 Dijkstra 中就是这种情况),您将需要在邻接列表中访问 n 个元素 n 次,这也是 O(N^2) 时间复杂度,而在邻接列表中,它恰好是邻居数量的总和 - 再次 E. 这意味着我们还需要 O(E) 来访问所有边的所有邻居。并且所有边通常在输入中给出 O(E) 将作为计算时间传递,但 O(N^2) 对于 N <= 10^6 的约束来说,将是一个高复杂度。最后,我将为您介绍我通常使用邻接列表(向量作为列表)实现图的不同变体:

#include<iostream>
#include<vector>
int main(){
    const int N = 5;
    int n, e;
    std::vector<std::pair<int, int>> graph[N], inverse[N];
    std::vector<int> unweighted[N], undirectedUnweighted[N];
    std::cin >> n >> e;
    for(int i = 0; i < e; i++)
    {
        int x, y, z;//z is length of edge
        std::cin >> x >> y >> z;
        //substitute 1 from x, y if they starts from 1
        graph[x].push_back(std::make_pair(y, z));
        inverse[y].push_back(std::make_pair(x, z));
        unweighted[x].push_back(y);
        undirectedUnweighted[x].push_back(y);
        undirectedUnweighted[y].push_back(x);
    }
    return 0;
}
表示

图形的简单形式(查找顶点的邻居和度数)


#include<iostream>
/** Representing graphs in c++ programming language */
using namespace std;
int main() {
    cout << "33[1;33mNote: if there are no neighbourhood between vertices write '-' symbol!33[0mn"<<endl;
    int number_of_vertices;
    cout<<"33[1;32mPlease enter number of vertices: 33[0m";
    cin>>number_of_vertices;
    int max_num_of_neighbours;
    cout<<"33[1;32mPlease enter maximum number of neighbours: 33[0m";
    cin>>max_num_of_neighbours;
    char array[number_of_vertices][max_num_of_neighbours];
    char vertices[number_of_vertices];
    cout<<"33[1;33mPlease sign vertices with lowercase alphabet letters: 33[0m"<<endl;
    for(int i = 0; i < number_of_vertices; i ++) {
        cout<<(i+1)<<" = ";
        cin>>vertices[i];
    }
    for(int i = 0; i < number_of_vertices; cout<<endl, i ++) {
        cout<<"33[1;32mPlease enter neighbours for 33[0m"<<vertices[i]<<" --> ";
        for(int j = 0; j < max_num_of_neighbours; j ++) {
            cin>>array[i][j];
        }
    }

    for(int i = 0; i < number_of_vertices; cout<<endl, i ++) {
        cout<<"33[1;34mNeighbours for 33[0m"<<"33[1;35m"<<vertices[i]<<"33[0m"<<" --> ";
        int deg = 0;
        for(int j = 0; j < max_num_of_neighbours; j ++) {
            if(array[i][j] != '-') {
                deg ++;
            }
            if(array[i][j] == '-') {
                cout<<"33[1;31m"<<array[i][j]<<"33[0m"<<"t";
            } else {
                cout<<"33[1;32m"<<array[i][j]<<"33[0m"<<"t";
            }
        }
        cout<<"33[1;36m"<<"deg["<<"33[0m"<<"33[1;35m"<<vertices[i]<<"33[0m"<<"33[1;36m"<<"] = "<<"33[0m"<<deg;
    }
    cout<<endl<<"33[1;33mRemember that '33[1;31m-33[0m33[1;33m' shows when two vertices aren't adjacent!33[0m"<<endl;

}

为了添加我使用的交互性 如何将彩色文本输出到 Linux 终端? 用于更改文本颜色