【问题标题】:Store the all the neighbors of the vertexes, find a vertex's neighbors quickly存储顶点的所有邻居,快速找到一个顶点的邻居
【发布时间】:2016-10-07 02:26:55
【问题描述】:

假设我将N 顶点标记为从0N-1。每个顶点最多有3 邻居和至少1 邻居。假设邻居信息最初存储在一个名为pairs 的向量中,其中pairs[2*i]pairs[2*i+1] 是一对相邻顶点。

现在我需要快速查找vertex[i] 的邻居是什么,存储这些信息的最佳方式是什么?


我想出的方法是:

  • 声明一个名为neighbors[3*N]的向量,以便neighbors[3*i+0]neighbors[3*i+1]neighbors[3*i+2]存储三个可能的邻居。

  • 为什么说可能,因为每个顶点最多有三个邻居。

  • 所以我将向量neighbors的所有元素初始化为N,这意味着这不是一个有效的邻居,因为顶点的标签是从0N-1

代码实现如下:

void get_neighbors(const std::vector<int>& pairs, 
        std::vector<int>& neighbors)   {
    int N = neighbors.size()/3;
    int M = pairs.size()/2;
    //init all the vertexes' neighbours to nothing
    for (int i=0; i<3*N; ++i)   {
        neighbors[i] = N;
    }

    //loop through all the vertexes, and store their neighbors
    for(int i=0; i<N; ++i)  {
        int j = 0;
        //loop through pairs, and find out what neighbors vertex[i] has
        for (int k=0; k < M; ++k) {
            if(pairs[2*k]==i)    {
                neighbors[3*i+j]=pairs[2*k+1];
                ++j;
            }
            else if(pairs[2*k+1]==i)    {
                neighbors[3*i+j]=pairs[2*k];
                ++j;
            }
        }
    }
}

我对我的方法感到不舒服的地方:

  1. 声明一个向量neighbors(3*N)太多了,因为它的许多元素将是无用的N

  2. 如果我想查找vertex[i]的邻居,每次我都需要测试neighbors[3*i]==Nneighbors[3*i+1]==Nneighbors[3*i+2]==N

【问题讨论】:

  • 对从ab的每条边使用vector&lt;vector&lt;int&gt; &gt; graph (N);,即ba的邻居,反之亦然使用:graph[a].push_back (b);这将声明一个@987654351的向量@元素。 因为只有 3 个邻居,所以要获得邻居总是需要恒定的时间。
  • @PRP 你有时间写这个作为答案并提供一些关于graph 的参考吗?我不熟悉符号&gt; graph(N)?您的方法看起来很干净,但是与我的方法相比,查找时间会是多少?
  • vector&lt;vector&lt;int&gt;&gt; 将比单个向量 neighbours 有更多的开销;如果 3*N 个元素的开销太大,那么 N vector&lt;int&gt;s 肯定是太多的开销,因为它会更多的开销。
  • @buzhidao 我在回答中分析了 4 个场景。请选择你认为最好的那个。 在我看来,结构数组解决方案是最好的,我也解释了原因。
  • @PRP 感谢您的详细回答,我会好好检查一下。

标签: c++ algorithm vector


【解决方案1】:

您正在使用的数据结构是Graph。但是图是一个更一般的概念,其中一个节点可以有任意数量的邻居。

由于您的问题是一个更严格的问题,即每个节点最多有 3 个邻居。 我在 cmets 中建议的解决方案是一种通用的解决方案,可用于所有图表,并且也适用于您的情况。

声明一个图表:

vector<vector<int> > graph (N);

如果从ab 有一条边,请执行以下操作:

graph[a].push_back (b);

检索节点a的邻居:

for (int i=0;i<graph[a].size();i++)
    // Do whatever you want with the neighbour. The variable graph[a][i] holds the neighbour number

现在解决最多 3 个邻居的问题。 我认为最好的解决方案是:

struct node
{
    int data; // Some data related to the node. This is optional.
    int neigh_1;
    int neigh_2;
    int neigh_3;
}

并将图制作为节点数组:

node[N] graph;

这也将占用 3*N 内存。但这是比您所做的更好的方法。让我解释一下:

当你说给我节点 x 的邻居时:

您的方法将访问内存位置 x、3*x、3*x + 4、3*x + 8

我的方法将访问内存位置 x, x+4, x+8, x+16

等等!当我们只想要它的邻居时,为什么我们要访问x?这是一个有效的问题,但如果您需要在节点处理一些值或将一些信息存储到节点本地,那么我们必须访问x

注意:我建议的方法不会比您的方法更差。但对于一般情况,它肯定会表现得更好。

为什么我的方法更好?

我的方法将产生较小的cache misses,因为我有更好的空间locality of reference

如果您不关心缓存的工作,简单来说,我的方法将访问彼此靠近的位置,因此很有可能已经在更快的内存缓存中,这将导致访问速度更快。

还有人可能会争辩说我们应该只在需要时才为邻居声明内存。基本上vector 解决方案就是这样做的。但是在这种情况下它不会很好地执行,因为即使声明一个指针也需要 8 个字节。但是有了这个空间,你可以存储 2 个整数的信息,即关于 2 个邻居的信息。

因此动态分配内存不会节省任何空间,因为最大邻居数为 3。

【讨论】:

  • for (auto ele : graph[a]),抱歉,我从未见过这段代码,是for 循环吗?你似乎说这会得到节点a 的相邻元素的数量。因此,如果我想使用邻居,我应该使用graph[a][i] where 0&lt;=i&lt;ele
  • @buzhidao C++11/14 基于范围的循环
  • @buzhidao 我已将循环从 C++11/14 样式更改为正常循环。请检查。如果你觉得它有用,请接受并投票赞成这个答案
  • 我认为你的方法有问题,我使用节点x 的邻居的方法只会访问3*x+03*x+13*x+2,它们是相邻的三个。无需访问x,因为x仅表示顶点编号。
  • @buzhidao 是的。那是正确的。看到该节点也有一个名为data 的字段。在一般情况下,您可能还想对数据做一些事情,或者存储 LCA、重量或其他东西。那么我提出的方法会更好。我说的是一般场景。
【解决方案2】:

您可以像这样简单地定义一个包含节点值和邻居索引的结构

#include <vector>
#include <iostream>

struct vertex{
    int data;
    std::vector<int> neighbours;
};

下面给出了为每个节点添加 2 个邻居的示例程序。每个节点都有一个其指数平方的值。

int main(){
    vertex v[5];

    for (int i=0;i<5;i++){
        v[i].data = i*i;
    }

    for (int i=0;i<5;i++){
        v[i].neighbours.push_back((i+1) % 5);
        v[i].neighbours.push_back((i+2) % 5);
    }

    for (int i=0;i<5;i++){
         std::cout << "vector " << i << " has value " << v[i].data << std::endl;
        for (int j=0;j<v[i].neighbours.size();j++){
            int nodeNum = v[i].neighbours[j];
            std::cout << "vector " << i << " has neighbours " << v[i].neighbours[j] << " with data " << v[nodeNum].data << std::endl;
        }
        std::cout << std::endl ;
    }
    return 0;
}

这将输出你关注

vector 0 has value 0
vector 0 has neighbours 1 with data 1
vector 0 has neighbours 2 with data 4

vector 1 has value 1
vector 1 has neighbours 2 with data 4
vector 1 has neighbours 3 with data 9

vector 2 has value 4
vector 2 has neighbours 3 with data 9
vector 2 has neighbours 4 with data 16

vector 3 has value 9
vector 3 has neighbours 4 with data 16
vector 3 has neighbours 0 with data 0

vector 4 has value 16
vector 4 has neighbours 0 with data 0
vector 4 has neighbours 1 with data 1

因为我们已经为邻居使用了向量。我们可以为我们的节点分配任意数量的邻居。这是一般情况。并且也适用于您的要求。希望这会有所帮助

【讨论】:

  • 这是一个很好的治疗!
  • 谢谢。希望它有所帮助:)
  • 这正是我要找的,我主要担心的是我的方法有太多无用的值......
猜你喜欢
  • 2021-12-30
  • 2019-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多