グラフの実装をC++ですばやく作成することについて疑問に思っていました。データ構造が操作しやすく、グラフアルゴリズム(BFS、DFS、クラスカル、ダイクストラなど)を使用する必要があります。アルゴリズムOlympiadにこの実装が必要なので、データ構造をより簡単に記述できます。
そのようなDSを提案できますか(主な構造体またはクラスとそれらに何が含まれるか)。隣接リストと隣接行列が主な可能性であることは知っていますが、より詳細なcodeサンプルを意味します。
たとえば、私はこれについてDS前回DFSのグラフを実装しなければならなかったときのことを考えました:
struct Edge {
int start;
int end;
struct Edge* nextEdge;
}
次に、i番目のノードで始まるエッジを表すEdgeリスト(struct Edge)をi番目の場所に含むサイズnの配列を使用しました。
しかし、このグラフでDFSを実行しようとすると、約10のwhileループで50行のコードを記述する必要がありました。
どのような「良い」実装がありますか?
それは本当に実装する必要のあるアルゴリズムに依存し、特効薬はありません(そしてそれは驚くべきことではありません...プログラミングに関する一般的なルールは一般的なルールがないということです;-))。
ポインタを持つノード/エッジ構造を使用して有向マルチグラフを表すことがよくあります...より具体的には:
struct Node
{
... payload ...
Link *first_in, *last_in, *first_out, *last_out;
};
struct Link
{
... payload ...
Node *from, *to;
Link *prev_same_from, *next_same_from,
*prev_same_to, *next_same_to;
};
言い換えると、各ノードには、着信リンクの二重リンクリストと発信リンクの二重リンクリストがあります。各リンクはfrom
ノードとto
ノードを認識しており、同時に2つの異なる二重リンクリストにあります。同じfrom
ノードから出てくるすべてのリンクのリストと同じto
ノードに到達するすべてのリンクのリスト。
ポインタprev_same_from
およびnext_same_from
は、同じノードからfromから出てくるすべてのリンクのチェーンをたどるときに使用されます。ポインタprev_same_to
およびnext_same_to
は、同じノードを指すへのすべてのリンクのチェーンを管理するときに代わりに使用されます。
ポインタをいじるのはたくさんあります(したがって、ポインタが好きでない限り、これを忘れるだけです)が、クエリと更新の操作は効率的です。たとえば、ノードまたはリンクの追加はO(1)、リンクの削除はO(1)であり、ノードxの削除はO(deg(x))です。
もちろん、問題、ペイロードサイズ、グラフサイズ、グラフ密度に応じて、このアプローチは(メモリに加えて、ノードごとに4つのポインタとリンクごとに6つのポインタがあるので)過剰な負荷をかけたり、過度にメモリを要求したりする可能性があります。
同様の構造の完全な実装は here にあります。
以下は、隣接リストとしてのC++のグラフデータ構造の実装です。
頂点の表現にはSTLベクトルを使用し、エッジと宛先頂点を表すにはSTLペアを使用しました。
#include <iostream>
#include <vector>
#include <map>
#include <string>
using namespace std;
struct vertex {
typedef pair<int, vertex*> ve;
vector<ve> adj; //cost of Edge, destination vertex
string name;
vertex(string s) : name(s) {}
};
class graph
{
public:
typedef map<string, vertex *> vmap;
vmap work;
void addvertex(const string&);
void addedge(const string& from, const string& to, double cost);
};
void graph::addvertex(const string &name)
{
vmap::iterator itr = work.find(name);
if (itr == work.end())
{
vertex *v;
v = new vertex(name);
work[name] = v;
return;
}
cout << "\nVertex already exists!";
}
void graph::addedge(const string& from, const string& to, double cost)
{
vertex *f = (work.find(from)->second);
vertex *t = (work.find(to)->second);
pair<int, vertex *> Edge = make_pair(cost, t);
f->adj.Push_back(Edge);
}
この質問は古くからありますが、どういうわけか、私はそれを私の心から取り除くことができないようです。
すべてのソリューションはグラフの実装を提供しますが、それらはすべて非常に冗長です。彼らは単にエレガントではありません。
本当に必要なのは、独自のグラフクラスを作成する代わりに、ある点が別の点に接続されていることを伝える方法です-そのため、std::map
およびstd::unordered_map
完全に正常に動作します。単純に、ノードとエッジのリストの間のマップとしてグラフを定義します。 Edgeに追加のデータが必要ない場合は、エンドノードのリストで問題ありません。
したがって、C++の簡潔なグラフは、次のように実装できます。
using graph = std::map<int, std::vector<int>>;
または、追加のデータが必要な場合は、
struct Edge {
int nodes[2];
float cost; // add more if you need it
};
using graph = std::map<int, std::vector<Edge>>;
これで、グラフ構造が言語の残りの部分にうまく組み込まれ、新しい不格好なインターフェースを覚える必要はありません。古い不格好なインターフェースでも問題ありません。
ベンチマークはありませんが、こちらの他の提案よりも優れていると感じています。
注意:int
sはインデックスではなく、識別子です。
インデックス(ポインタではない)の隣接リストを使用することを好みます
typedef std::vector< Vertex > Vertices;
typedef std::set <int> Neighbours;
struct Vertex {
private:
int data;
public:
Neighbours neighbours;
Vertex( int d ): data(d) {}
Vertex( ): data(-1) {}
bool operator<( const Vertex& ref ) const {
return ( ref.data < data );
}
bool operator==( const Vertex& ref ) const {
return ( ref.data == data );
}
};
class Graph
{
private :
Vertices vertices;
}
void Graph::addEdgeIndices ( int index1, int index2 ) {
vertices[ index1 ].neighbours.insert( index2 );
}
Vertices::iterator Graph::findVertexIndex( int val, bool& res )
{
std::vector<Vertex>::iterator it;
Vertex v(val);
it = std::find( vertices.begin(), vertices.end(), v );
if (it != vertices.end()){
res = true;
return it;
} else {
res = false;
return vertices.end();
}
}
void Graph::addEdge ( int n1, int n2 ) {
bool foundNet1 = false, foundNet2 = false;
Vertices::iterator vit1 = findVertexIndex( n1, foundNet1 );
int node1Index = -1, node2Index = -1;
if ( !foundNet1 ) {
Vertex v1( n1 );
vertices.Push_back( v1 );
node1Index = vertices.size() - 1;
} else {
node1Index = vit1 - vertices.begin();
}
Vertices::iterator vit2 = findVertexIndex( n2, foundNet2);
if ( !foundNet2 ) {
Vertex v2( n2 );
vertices.Push_back( v2 );
node2Index = vertices.size() - 1;
} else {
node2Index = vit2 - vertices.begin();
}
assert( ( node1Index > -1 ) && ( node1Index < vertices.size()));
assert( ( node2Index > -1 ) && ( node2Index < vertices.size()));
addEdgeIndices( node1Index, node2Index );
}
グラフアルゴリズムのみをテストすればよく、他の場所ではグラフアルゴリズムを使用しないと仮定すると、さらに単純な表現が可能です。これは、次に示すように、頂点から隣接リストへのマップとして使用できます。
#include<bits/stdc++.h>
using namespace std;
/* implement the graph as a map from the integer index as a key to the adjacency list
* of the graph implemented as a vector being the value of each individual key. The
* program will be given a matrix of numbers, the first element of each row will
* represent the head of the adjacency list and the rest of the elements will be the
* list of that element in the graph.
*/
typedef map<int, vector<int> > graphType;
int main(){
graphType graph;
int vertices = 0;
cout << "Please enter the number of vertices in the graph :- " << endl;
cin >> vertices;
if(vertices <= 0){
cout << "The number of vertices in the graph can't be less than or equal to 0." << endl;
exit(0);
}
cout << "Please enter the elements of the graph, as an adjacency list, one row after another. " << endl;
for(int i = 0; i <= vertices; i++){
vector<int> adjList; //the vector corresponding to the adjacency list of each vertex
int key = -1, listValue = -1;
string listString;
getline(cin, listString);
if(i != 0){
istringstream iss(listString);
iss >> key;
iss >> listValue;
if(listValue != -1){
adjList.Push_back(listValue);
for(; iss >> listValue; ){
adjList.Push_back(listValue);
}
graph.insert(graphType::value_type(key, adjList));
}
else
graph.insert(graphType::value_type(key, adjList));
}
}
//print the elements of the graph
cout << "The graph that you entered :- " << endl;
for(graphType::const_iterator iterator = graph.begin(); iterator != graph.end(); ++iterator){
cout << "Key : " << iterator->first << ", values : ";
vector<int>::const_iterator vectBegIter = iterator->second.begin();
vector<int>::const_iterator vectEndIter = iterator->second.end();
for(; vectBegIter != vectEndIter; ++vectBegIter){
cout << *(vectBegIter) << ", ";
}
cout << endl;
}
}
以下はグラフの基本的な実装です。注:私は次の頂点につながっている頂点を使用します。また、各頂点には、隣接するノードを指すリストがあります。
#include <iostream>
using namespace std;
// 1 ->2
// 1->4
// 2 ->3
// 4->3
// 4 -> 5
// Adjacency list
// 1->2->3-null
// 2->3->null
//4->5->null;
// Structure of a vertex
struct vertex {
int i;
struct node *list;
struct vertex *next;
};
typedef struct vertex * VPTR;
// Struct of adjacency list
struct node {
struct vertex * n;
struct node *next;
};
typedef struct node * NODEPTR;
class Graph {
public:
// list of nodes chained together
VPTR V;
Graph() {
V = NULL;
}
void addEdge(int, int);
VPTR addVertex(int);
VPTR existVertex(int i);
void listVertex();
};
// If vertex exist, it returns its pointer else returns NULL
VPTR Graph::existVertex(int i) {
VPTR temp = V;
while(temp != NULL) {
if(temp->i == i) {
return temp;
}
temp = temp->next;
}
return NULL;
}
// Add a new vertex to the end of the vertex list
VPTR Graph::addVertex(int i) {
VPTR temp = new(struct vertex);
temp->list = NULL;
temp->i = i;
temp->next = NULL;
VPTR *curr = &V;
while(*curr) {
curr = &(*curr)->next;
}
*curr = temp;
return temp;
}
// Add a node from vertex i to j.
// first check if i and j exists. If not first add the vertex
// and then add entry of j into adjacency list of i
void Graph::addEdge(int i, int j) {
VPTR v_i = existVertex(i);
VPTR v_j = existVertex(j);
if(v_i == NULL) {
v_i = addVertex(i);
}
if(v_j == NULL) {
v_j = addVertex(j);
}
NODEPTR *temp = &(v_i->list);
while(*temp) {
temp = &(*temp)->next;
}
*temp = new(struct node);
(*temp)->n = v_j;
(*temp)->next = NULL;
}
// List all the vertex.
void Graph::listVertex() {
VPTR temp = V;
while(temp) {
cout <<temp->i <<" ";
temp = temp->next;
}
cout <<"\n";
}
// Client program
int main() {
Graph G;
G.addEdge(1, 2);
G.listVertex();
}
上記のコードを使用すると、DFS/BFSなどに拡張できます。