Betweenness Centrality

The betweenness centrality for each vertex is the number of these shortest paths that pass through the vertex. perform BFS (or SSSP if weighted graphs) for each vertex keep a stack of path for backtracking, i.e., traversing the graph in reverse BFS order #include <iostream> #include <queue> #include <stack> #include <vector> auto brandes(const std::vector<int>& rowPtr, const std::vector<int>& colIdx) { const auto numVertices = rowPtr.size() - 1; std::vector<float> betweenness(numVertices, 0.0f); //For each vertex s, perform a BFS to establish levels and predecessors //!...

2023.09.18 · 392 字 · YuAng Chen

Connected Components

Description Three different variants of Connected Component (CC) algorithms are implemented, and the comparisons are provided as follows: Algorithm Time Complexity Parallelism Techniques DFS $(O(V + E))$ Poor Recursive Traversal Union-Find $(O(V + E \alpha(V)))$ Poor Path Compression, Union by Rank Shiloach-Vishkin $(O(\log^* V))$ Highly Parallel Pointer Jumping Here, $( \log^* )$ is the iterated logarithm, which is extremely slow-growing, making the algorithm very fast. $( \alpha(V) )$ is the inverse Ackermann function, practically a constant for all feasible input sizes....

2023.09.12 · 604 字 · YuAng Chen

List

Description STL indeed offers std::list and std::forward_list, which are essentially double-linked list and single-linked list, respectively. std::list provides operations like push_back/front, pop_back/front with a time complexity of O(1), and supports bidirectional iterators. On the other hand, std::forward_list only allows fronting operations with O(1) and insert/erase_after for backing operations, which have a time complexity of O(n); also, it only supports forward iterators. A valuable feature of lists is that they prohibit iterator invalidation compared to some other data structures....

2023.09.11 · 707 字 · YuAng Chen

SSSP

Two variants of Single-Source Shortest Path (SSSP) have been implemented as follows. Bellman-Ford is the one that is widely implemented in parallel graph frameworks. This is because the use of a heap in Dijkstra’s algorithm can limit the parallelism of the code. Criteria Dijkstra’s Algorithm Bellman-Ford Algorithm Type Greedy Dynamic Programming Usage Positive weights Negative weights OK Time Complexity O((V + E) * log(V)) O(V * E) Negative Cycles No Yes (Detectable) Data Structures Priority Queue None (Arrays) Initialization Start node: 0, rest ∞ Start node: 0, rest ∞ Relaxation Decrease Key Relaxation BellmanFord BellmanFord: Perform numVertices - 1 iterations of graph traversal to find the shortest path an additional iteration checks if negative cycles exist $O(|V| * |E|)$ time complexity Code #include <iostream> #include <queue> #include <vector> std::vector<int> bellmanFord(const int root, const std::vector<int>& rowPtr, const std::vector<int>& colIdx, const std::vector<float>& weight) { const auto numVertices = rowPtr....

2023.09.09 · 845 字 · YuAng Chen

Deque

Description std::deque extends the interfaces of std::vector with push_front, pop_front, etc., such that elements can be inserted or removed at the end or beginning at constant time. I’ve hardly ever incorporated std::deque in my own coding projects, and it’s a rarity in other people’s work as well. Code std::deque is essentially a sequence of individually allocated fixed-size arrays. The real challenge lies in the bookkeeping. Four variables are relied on to keep track of data:...

2023.09.04 · 1309 字 · YuAng Chen

Vector & Array

Array is allocated in stack memory Vector is allocated in heap memory. Its capacity is “pre-allocated”. #include <iostream> template<typename T> class Vector { private: T* data_; size_t size_; size_t capacity_; public: Vector(): data_(nullptr), size_(0), capacity_(0) {} Vector(size_t n_): size_(n_), capacity_(n_) { data_ = new T[n_]; } ~Vector() { delete [] data_; }; T& operator[] (size_t index) { return data_[index]; } const T& operator[] (size_t index) const { return data_[index]; } size_t size() const { return size_; } void push_back(const T& value) { if(size_ == capacity_) { capacity_ = size_ == 0?...

2023.09.02 · 202 字 · YuAng Chen

BFS & DFS

Iterative BFS Despite its apparent simplicity, this approach relies heavily on the utilization of various STL containers. std::unordered_map records the parent of each node std::unordered_set checks if a node has been visited std::queue allows the nodes be accessed in the width-first flow; using std::stack for depth-first flow std::stack reverses the parents, so the path can be printed in root-to-target order. #include <iostream> #include <vector> #include <unordered_map> #include <unordered_set> #include <queue> #include <stack> std::stack<int> BFS(const int root, const int target, const std::vector<int>& rowPtr, const std::vector<int>& colIdx) { std::unordered_map<int, int> parent; std::unordered_set<int> visited; std::queue<int> nodeQue; // std::stack<int> nodeStk for DFS std::stack<int> path; bool hasFound = false; nodeQue....

2023.09.01 · 434 字 · YuAng Chen

Graph Algorithms

Considering myself a researcher in graph algorithms, I’ve come to the surprising realization that my grasp of these algorithms is not as solid as I thought. Hence, this blog series aims to document my exploration of various graph algorithms I’ve encountered thus far, regardless of their complexity. The algorithms are selected from the parallel graph frameworks GAP and GBBS, focusing on their single-threaded versions to assess their complexity. Breadth-First Search (BFS) Single-Source Shortest Paths (SSSP) Connected Components (CC) Betweenness Centrality (BC) Triangle Counting (TC) Minimum Spanning Tree (MST) Strongly Connected Components (SCC) SCAN Clustering (SCAN) Low Diameter Decomposition (LDD) Biconnected-Components (BC) Graph Coloring (COLOR) Maximal Matching (MM) Maximal Independent Set (MIS)

2023.08.31 · 111 字 · YuAng Chen

STL Containers

In my HPC-oriented programming, my go-to choices are typically limited to arrays and vectors because of their memory efficiency. Linked lists and hash maps, being non-contiguous in memory space, rarely find their way into my toolkit. These containers draw upon many classic algorithmic designs. Lately, as I’ve been revisiting fundamental graph algorithms, I’ve also decided to take on the tasks of re-implementing these containers in a simplified illustration. They are:...

2023.08.30 · 310 字 · YuAng Chen

Scope Guard

Background Scope Guard is a concept reminiscent of the RAII (Resource Acquisition Is Initialization) principle in C++. The idea is to manage resources (like memory, files, network sockets, etc.) using object lifetime. When the object goes out of scope, its destructor ensures that the resource is cleaned up properly. The scope guard is intended to run a given callable (like a function or lambda) when it is destroyed. RAII (Resource Acquisition Is Initialization) is a programming idiom used in C++ where the lifetime of an object is bound to the lifetime of its scope (typically represented by a block of code wrapped in curly braces {})....

2023.08.29 · 629 字 · YuAng Chen