首页 > 解决方案 > 以有效的方式在一组边中查找现有圆

问题描述

概括

给定一组边(从 3 到 1,000,000),有效地组装闭环(我称它们为圆)。我将它作为一个更大的 Python 项目的一部分运行,所以我希望最好的解决方案将用 C++/CUDA 和 python 绑定编写(这是我尝试过的)。


问题

组装环,给定一组满足以下条件的边(由两个顶点索引定义):


我的一般方法


我的实现

在 C++ 中,我实现了串行和并行版本。我使用一组 45,000 条边对其进行了测试,并获得了以下结果:

序列号:

#include <algorithm>
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
#include <fstream>
#include <iostream>
#include <chrono>

std::vector< std::vector<int> > rings_from_edges(std::vector<std::vector<int>> edges)
{
    int pStart, pEnd;

    std::vector<int> temp;
    std::vector<std::vector<int>> rings;

    temp = edges.back();
    edges.pop_back();

    pStart = temp[0];
    pEnd = temp[1];

    int p1, p2;

    while(not edges.empty())
        // Scan edges list until pEnd is found.
        for(auto const& pts: edges)
        {
            p1 = pts[0];
            p2 = pts[1];

            // Check if the start of the edge corresponds with the end of the ring.
            if(p1 == pEnd)
            {
                temp.push_back(p2);
                pEnd = p2;
                edges.erase(std::remove(edges.begin(), edges.end(), pts), edges.end());

                // Check if the beginning of the ring is the same as the end of the newly appended edge.
                if (p2 == pStart)
                {
                    // Add the newly created ring to the rings list.
                    rings.push_back(temp);
                    temp.clear();

                    // If the edges list contains more edges, reset the starting and end points to search for a new ring.
                    if(not edges.empty())
                    {
                        temp = edges.back();
                        edges.pop_back();

                        pStart = temp[0];
                        pEnd = temp[1];
                    }
                }
                break;
            }
                // Check if the end of the edge corresponds with the end of the ring.
            else if(p2 == pEnd)
            {
                temp.push_back(p1);
                pEnd = p1;
                edges.erase(std::remove(edges.begin(), edges.end(), pts), edges.end());

                // Check if the beginning of the ring is the same as the end of the newly appended edge.
                if (p1 == pStart)
                {
                    // Add the newly created ring to the rings list.
                    rings.push_back(temp);
                    temp.clear();

                    // If the edges list contains more edges, reset the starting and end points to search for a new ring.
                    if(not edges.empty())
                    {
                        temp = edges.back();
                        edges.pop_back();

                        pStart = temp[0];
                        pEnd = temp[1];
                    }
                }
                break;
            }
        }
    return rings;
}

int main() {

    std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();

    std::vector< std::vector<int> > vectIN, vectOUT;


    std::string fileName = "PATH TO CSV FILE";

    std::string delimeter = ",";

    std::ifstream file(fileName);

    std::string line = "";

    while (getline(file, line))
    {
        std::vector<std::string> vec;
        boost::algorithm::split(vec, line, boost::is_any_of(delimeter));
        std::vector<int> vec2;
        vec2.emplace_back(std::stoi(vec.data()[0]));
        vec2.emplace_back(std::stoi(vec.data()[1]));

        vectIN.push_back(vec2);
    }

    file.close();

    std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();

    vectOUT = rings_from_edges(vectIN);

    std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();

    for (auto const& ring:vectOUT)
    {
        for(auto const& pt:ring)
        {
            if(pt>=0)
                std::cout << pt << " ";
        }
        std::cout << std::endl;
    }

    std::chrono::steady_clock::time_point t4 = std::chrono::steady_clock::now();

    long t1_t2 = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
    long t2_t3 = std::chrono::duration_cast<std::chrono::milliseconds>(t3 - t2).count();
    long t3_t4 = std::chrono::duration_cast<std::chrono::milliseconds>(t4 - t3).count();

    std::cout << "Load csv:      " << t1_t2 << std::endl;
    std::cout << "Ring assembly: " << t2_t3 << std::endl;
    std::cout << "Output:        " << t3_t4 << std::endl;

    std::cout << "----------------- THAT'S ALL FOLKS!!! -----------------" << std::endl;

    return 0;
}

以上的CUDA推力版本:

#include <thrust/remove.h>
#include <thrust/device_vector.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/tuple.h>
#include <thrust/copy.h>
#include <thrust/sort.h>
#include <thrust/find.h>
#include <thrust/functional.h>

#include <iostream>
#include <string>
#include <vector>
#include <fstream>

#include <iterator>
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <chrono>
#include <thrust/binary_search.h>

#include <thrust/uninitialized_copy.h>
#include <thrust/device_malloc.h>
#include <thrust/device_vector.h>


int main(){
    std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();

    std::string fileName = "PATH TO CSV HERE";

    std::string delimeter = ",";

    std::ifstream file(fileName);

    std::vector<std::vector<int>> vectIN;

    std::string line = "";

    while (getline(file, line))
    {
        std::vector<std::string> vec;
        boost::algorithm::split(vec, line, boost::is_any_of(delimeter));
        std::vector<int> vec2;
        vec2.emplace_back(std::stoi(vec.data()[0]));
        vec2.emplace_back(std::stoi(vec.data()[1]));

        vectIN.push_back(vec2);
    }

    file.close();
    std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();


    std::vector<int> h_edge1, h_edge2;

    h_edge1.reserve(vectIN.size());
    h_edge2.reserve(vectIN.size());

    for(auto const& pts: vectIN)
    {
        h_edge1.emplace_back(pts[0]);
        h_edge2.emplace_back(pts[1]);
    }
    std::chrono::steady_clock::time_point t3 = std::chrono::steady_clock::now();

    thrust::device_vector<int> d_pStart(1);
    thrust::device_vector<int> d_pEnd(1);

    thrust::host_vector<int> h_rings;
    thrust::device_vector<int> d_rings;

    // Initialize edge vectors / pStart / pEnd /  while minimizing copying with CudaMalloc

    thrust::device_vector<int> d_edge1(vectIN.size());
    thrust::device_vector<int> d_edge2(vectIN.size());

    thrust::copy(thrust::make_zip_iterator(thrust::make_tuple(h_edge1.begin(), h_edge2.begin())),
                 thrust::make_zip_iterator(thrust::make_tuple(h_edge1.end(),   h_edge2.end())),
                 thrust::make_zip_iterator(thrust::make_tuple(d_edge1.begin(), d_edge2.begin())));

    // Arrange edges with edge1 as key and edge2 as value
    thrust::sort_by_key(d_edge1.begin(), d_edge1.end(), d_edge2.begin());

    d_rings.push_back(d_edge1.back());
    d_rings.push_back(d_edge2.back());

    d_edge1.pop_back();
    d_edge2.pop_back();

    d_pStart[0] = d_rings[0];
    d_pEnd[0] = d_rings[1];

    thrust::device_vector<int> element(1), p1(1), p2(1);

    while(not d_edge1.empty())
    {
        element.clear();

        int temp = d_pEnd[0];

        auto iter1 = thrust::equal_range(thrust::device, d_edge1.begin(), d_edge1.end(), temp);


        if(iter1.first != iter1.second)
        {
            element[0] = thrust::distance(d_edge1.begin(), iter1.first);
        }
        else
        {
            auto iter2 = thrust::find(thrust::device, d_edge2.begin(), d_edge2.end(), d_pEnd[0]);
            element[0] = thrust::distance(d_edge2.begin(), iter2);
        }

        // EDGE START INDEX (P1) AND END INDEX (P2)
        p1[0] = d_edge1[element[0]];
        p2[0] = d_edge2[element[0]];

        // ERASE THE EDGE FROM DEVICE LIST
        d_edge1.erase(d_edge1.begin()+element[0]);
        d_edge2.erase(d_edge2.begin()+element[0]);

        if(p1[0] == d_pEnd[0])
        {
            d_pEnd[0] = p2[0];

            if( d_pStart[0] == d_pEnd[0])
            {
                d_rings.push_back(-p2[0]);

                if(not d_edge1.empty())
                {
                    d_pStart[0] = d_edge1.back();
                    d_pEnd[0]   = d_edge2.back();

                    d_rings.push_back(d_pStart[0]);
                    d_rings.push_back(d_pEnd[0]);

                    d_edge1.pop_back();
                    d_edge2.pop_back();

                }
            }
            else
            {
                d_rings.push_back(p2[0]);
            }
        }
        else if(p2[0] == d_pEnd[0])
        {
            d_pEnd[0] = p1[0];

            if(d_pStart[0] == d_pEnd[0])
            {
                d_rings.push_back(-p1[0]);

                if(not d_edge1.empty())
                {
                    d_pStart[0] = d_edge1.back();
                    d_pEnd[0]   = d_edge2.back();

                    d_rings.push_back(d_pStart[0]);
                    d_rings.push_back(d_pEnd[0]);

                    d_edge1.pop_back();
                    d_edge2.pop_back();
                }
            }
            else
            {
                d_rings.push_back(p1[0]);
            }
        }
    }
    std::chrono::steady_clock::time_point t4 = std::chrono::steady_clock::now();

    // Copy rings to host and print them.
    h_rings = d_rings;

    for(auto const& pt:h_rings)
    {
        if(pt>=0)
            std::cout << pt << " ";
        else
            std::cout << -pt << std::endl;
    }
    std::cout << std::endl;

    long t1_t2 = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
    long t2_t3 = std::chrono::duration_cast<std::chrono::milliseconds>(t3 - t2).count();
    long t3_t4 = std::chrono::duration_cast<std::chrono::milliseconds>(t4 - t3).count();

    std::cout << "Load csv:      " << t1_t2 << std::endl;
    std::cout << "Create vector: " << t2_t3 << std::endl;
    std::cout << "Ring assembly: " << t3_t4 << std::endl;

    std::cout << "----------------- THAT'S ALL FOLKS!!! -----------------" << std::endl;

    return 0;
}

其他

我已经实现了与上面的 CUDA 代码类似的东西,但将数据组织到了存储桶中,因此只需对有限数量的数据进行搜索。不幸的是,我还没有让它完全工作。

最近我一直在研究图形库,看看我是否可以这样做,但我也没有成功地让这种方式工作。我知道 CUDA 工具包有一个和提升。


最后的评论

我想在至少不到 10 秒的时间内运行它,但理想情况下,我希望它在一秒钟内运行多达一百万个边缘。我不知道这是否现实,但我希望用 Cuda 加速它可以实现这一点,或者一起找到不同的算法。我正在伸出手来看看是否有人可以帮助我实现这一目标。

标签: c++algorithmcudageometrygraph-algorithm

解决方案


我敢打赌Hierholzer 算法的串行 C++ 实现可以在 10^6 条边上运行不到一秒,因为渐近运行时间是 O(|E|)。鉴于巡演,我们仍然需要将其分解为简单的循环,我们可以使用这样的 Python 代码来完成(警告:未经测试)。

def simple_cycles(tour_vertices):
    stack = []
    index = {}
    for v in tour_vertices:
        stack.append(v)
        i = index.get(v)
        if i is None:
            index[v] = len(stack) - 1
            continue
        yield stack[i:]
        for w in stack[i+1:]:
            del index[w]
        del stack[i+1:]

这是我想到的完整的 C++ 代码。编译但在其他方面完全未经测试。绝对没有任何形式的保证。

#include <list>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

struct HalfEdge {
  int head;
  std::list<HalfEdge>::iterator opposite;
};

std::vector<std::vector<int>> SimpleCyclesFromEdges(
    const std::vector<std::pair<int, int>>& edges) {
  std::unordered_map<int, std::list<HalfEdge>> graph;
  for (const auto& edge : edges) {
    auto& first_neighbors = graph[edge.first];
    auto& second_neighbors = graph[edge.second];
    auto forward = first_neighbors.emplace(first_neighbors.begin());
    auto backward = second_neighbors.emplace(second_neighbors.begin());
    *forward = {edge.second, backward};
    *backward = {edge.first, forward};
  }
  std::vector<std::vector<int>> simple_cycles;
  for (auto& item : graph) {
    int v = item.first;
    std::unordered_set<int> on_stack;
    std::stack<int> stack;
    while (true) {
      if (on_stack.insert(v).second) {
        stack.push(v);
      } else {
        std::vector<int> cycle = {v};
        while (stack.top() != v) {
          cycle.push_back(stack.top());
          on_stack.erase(stack.top());
          stack.pop();
        }
        simple_cycles.push_back(std::move(cycle));
      }
      auto& neighbors = graph[v];
      if (neighbors.empty()) {
        break;
      }
      auto forward = neighbors.begin();
      v = forward->head;
      graph[v].erase(forward->opposite);
      neighbors.erase(forward);
    }
  }
  return simple_cycles;
}

推荐阅读