Skip to content

Commit ce0a4c7

Browse files
authored
Merge pull request networkit#1276 from Schwarf/feature/left_right_planarity_test
Add Left-Right Planarity Test Algorithm to NetworKit
2 parents 126d3c3 + 0696fc6 commit ce0a4c7

File tree

8 files changed

+1161
-0
lines changed

8 files changed

+1161
-0
lines changed

include/networkit/graph/Graph.hpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,17 @@ class Graph final {
993993
*/
994994
void TLX_DEPRECATED(compactEdges());
995995

996+
/**
997+
* Sorts the outgoing neighbors of a given node according to a user-defined comparison function.
998+
*
999+
* @param u The node whose outgoing neighbors will be sorted.
1000+
* @param lambda A binary predicate used to compare two neighbors. The predicate should
1001+
* take two nodes as arguments and return true if the first node should
1002+
* precede the second in the sorted order.
1003+
*/
1004+
template <typename Lambda>
1005+
void sortNeighbors(node u, Lambda lambda);
1006+
9961007
/**
9971008
* Sorts the adjacency arrays by node id. While the running time is linear
9981009
* this temporarily duplicates the memory.
@@ -2267,6 +2278,51 @@ std::pair<count, count> Graph::removeAdjacentEdges(node u, Condition condition,
22672278
return {removedEdges, removedSelfLoops};
22682279
}
22692280

2281+
template <typename Lambda>
2282+
void Graph::sortNeighbors(node u, Lambda lambda) {
2283+
if ((degreeIn(u) < 2) && (degree(u) < 2)) {
2284+
return;
2285+
}
2286+
// Sort the outEdge-Attributes
2287+
std::vector<index> outIndices(outEdges[u].size());
2288+
std::iota(outIndices.begin(), outIndices.end(), 0);
2289+
std::ranges::sort(outIndices,
2290+
[&](index a, index b) { return lambda(outEdges[u][a], outEdges[u][b]); });
2291+
2292+
Aux::ArrayTools::applyPermutation(outEdges[u].begin(), outEdges[u].end(), outIndices.begin());
2293+
2294+
if (weighted) {
2295+
Aux::ArrayTools::applyPermutation(outEdgeWeights[u].begin(), outEdgeWeights[u].end(),
2296+
outIndices.begin());
2297+
}
2298+
2299+
if (edgesIndexed) {
2300+
Aux::ArrayTools::applyPermutation(outEdgeIds[u].begin(), outEdgeIds[u].end(),
2301+
outIndices.begin());
2302+
}
2303+
2304+
// For directed graphs we need to sort the inEdge-Attributes separately
2305+
if (directed) {
2306+
std::vector<index> inIndices(inEdges[u].size());
2307+
std::iota(inIndices.begin(), inIndices.end(), 0);
2308+
2309+
std::ranges::sort(inIndices,
2310+
[&](index a, index b) { return lambda(inEdges[u][a], inEdges[u][b]); });
2311+
2312+
Aux::ArrayTools::applyPermutation(inEdges[u].begin(), inEdges[u].end(), inIndices.begin());
2313+
2314+
if (weighted) {
2315+
Aux::ArrayTools::applyPermutation(inEdgeWeights[u].begin(), inEdgeWeights[u].end(),
2316+
inIndices.begin());
2317+
}
2318+
2319+
if (edgesIndexed) {
2320+
Aux::ArrayTools::applyPermutation(inEdgeIds[u].begin(), inEdgeIds[u].end(),
2321+
inIndices.begin());
2322+
}
2323+
}
2324+
}
2325+
22702326
template <class Lambda>
22712327
void Graph::sortEdges(Lambda lambda) {
22722328

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/* LeftRightPlanarityCheck.hpp
2+
*
3+
* Created on: 03.01.2025
4+
* Authors: Andreas Scharf ([email protected])
5+
*
6+
*/
7+
8+
#ifndef NETWORKIT_PLANARITY_LEFT_RIGHT_PLANARITY_CHECK_HPP_
9+
#define NETWORKIT_PLANARITY_LEFT_RIGHT_PLANARITY_CHECK_HPP_
10+
#include <networkit/base/Algorithm.hpp>
11+
#include <networkit/graph/Graph.hpp>
12+
13+
namespace NetworKit {
14+
15+
class LeftRightPlanarityCheck final : public Algorithm {
16+
17+
public:
18+
/**
19+
* Implements the left-right planarity test as described in
20+
* [citation](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=7963e9feffe1c9362eb1a69010a5139d1da3661e).
21+
* This algorithm determines whether a graph is planar, i.e., whether it can be drawn on a plane
22+
* without any edges crossing. For an overview of planar graphs, refer to:
23+
* https://en.wikipedia.org/wiki/Planar_graph
24+
*
25+
* The algorithm achieves (almost) linear runtime complexity. The only non-linear component
26+
* arises from sorting the nodes of the depth-first search tree.
27+
*
28+
* @param G The input graph to test for planarity. The graph should be undirected.
29+
* @throws std::runtime_error if graph is not an undirected graph
30+
*/
31+
LeftRightPlanarityCheck(const Graph &G) : graph(&G) {
32+
if (G.isDirected()) {
33+
throw std::runtime_error("The graph is not an undirected graph.");
34+
}
35+
dfsGraph = Graph(graph->numberOfNodes(), false, true, false);
36+
}
37+
38+
/**
39+
* Executes the left-right planarity test on the input graph.
40+
* This method performs all necessary computations to determine
41+
* whether the graph is planar and prepares the result for retrieval
42+
* via the `isPlanar()` method.
43+
*/
44+
void run() override;
45+
46+
/**
47+
* Returns whether the input graph is planar.
48+
* The result is only valid after the `run()` method has been called.
49+
*
50+
* @return True if the graph is planar, false otherwise.
51+
* @throws std::runtime_error if called before `run()` has been executed.
52+
*/
53+
bool isPlanar() const {
54+
assureFinished();
55+
return isGraphPlanar;
56+
}
57+
58+
private:
59+
static const Edge noneEdge;
60+
static constexpr count noneHeight{std::numeric_limits<count>::max()};
61+
62+
struct Interval {
63+
Edge low{noneEdge};
64+
Edge high{noneEdge};
65+
66+
Interval() : low{noneEdge}, high{noneEdge} {};
67+
Interval(const Edge &low, const Edge &high) : low(low), high(high) {}
68+
bool isEmpty() const { return low == noneEdge && high == noneEdge; }
69+
70+
friend bool operator==(const Interval &lhs, const Interval &rhs) {
71+
return lhs.low == rhs.low && lhs.high == rhs.high;
72+
}
73+
};
74+
75+
struct ConflictPair {
76+
Interval left{};
77+
Interval right{};
78+
79+
ConflictPair() = default;
80+
ConflictPair(const Interval &left, const Interval &right) : left(left), right(right) {}
81+
82+
void swap() { std::swap(left, right); }
83+
84+
friend bool operator==(const ConflictPair &lhs, const ConflictPair &rhs) {
85+
return lhs.left == rhs.left && lhs.right == rhs.right;
86+
}
87+
};
88+
const ConflictPair NoneConflictPair{Interval(), Interval()};
89+
90+
const Graph *graph;
91+
bool isGraphPlanar{};
92+
void dfsOrientation(node startNode);
93+
bool dfsTesting(node startNode);
94+
bool applyConstraints(const Edge &edge, const Edge &parentEdge);
95+
void removeBackEdges(const Edge &edge);
96+
void sortAdjacencyListByNestingDepth();
97+
bool conflicting(const Interval &interval, const Edge &edge);
98+
count getLowestLowPoint(const ConflictPair &conflictPair);
99+
std::vector<count> heights;
100+
std::unordered_map<Edge, count> lowestPoint;
101+
std::unordered_map<Edge, count> secondLowestPoint;
102+
std::unordered_map<Edge, Edge> ref;
103+
std::vector<node> roots;
104+
std::unordered_map<Edge, Edge> lowestPointEdge;
105+
std::unordered_map<Edge, count> nestingDepth;
106+
std::unordered_map<index, Edge> parentEdges;
107+
std::stack<ConflictPair> stack;
108+
std::unordered_map<Edge, ConflictPair> stackBottom;
109+
Graph dfsGraph;
110+
};
111+
} // namespace NetworKit
112+
#endif // NETWORKIT_PLANARITY_LEFT_RIGHT_PLANARITY_CHECK_HPP_

networkit/cpp/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ add_subdirectory("linkprediction")
2828
add_subdirectory("matching")
2929
add_subdirectory("numerics")
3030
add_subdirectory("overlap")
31+
add_subdirectory("planarity")
3132
add_subdirectory("randomization")
3233
add_subdirectory("reachability")
3334
add_subdirectory("scd")

0 commit comments

Comments
 (0)