Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Core Graph Algorithms and Data Structures Reference

Tech May 19 1

Adjacency List Implementation (Static Forward Star)

Graphs can be efficiently represented using a static array-based linked structure. This approach avoids dynamic memory allocation overhead and provides fast edge traversal.

struct GraphEdge {
    int weight;
    int target;
    int nextEdge;
} edges[MAX_E];
int head[MAX_V];
int edgeCount = 0;

inline void addEdge(int u, int v, int w) {
    edges[++edgeCount] = {w, v, head[u]};
    head[u] = edgeCount;
}

// Traversal example
void processOutEdges(int u) {
    for (int e = head[u]; e != 0; e = edges[e].nextEdge) {
        int v = edges[e].target;
        int w = edges[e].weight;
        // Process edge (u, v) with weight w
    }
}

Tree Centroid Detection

A tree centroid is a node that minimizes the maximum size of the resulting subtrees when removed. The algorithm computes subtree sizes via DFS and tracks the optimal balance.

int subtreeSize[MAX_V], centroidBalance = INT_MAX;

void computeCentroid(int u, int parentNode) {
    subtreeSize[u] = 1;
    int maxBranch = 0;
    for (int e = head[u]; e; e = edges[e].nextEdge) {
        int v = edges[e].target;
        if (v == parentNode) continue;
        computeCentroid(v, u);
        subtreeSize[u] += subtreeSize[v];
        maxBranch = max(maxBranch, subtreeSize[v]);
    }
    maxBranch = max(maxBranch, totalVertices - subtreeSize[u]);
    centroidBalance = min(centroidBalance, maxBranch);
}

Tree Diameter Calculation

The diameter is found by performing two depth-first searches. The first DFS locates the farthest node from an arbitrary starting point, and the second DFS finds the maximum distance from that node.

bool visitedD[MAX_V];

void dfsDistance(int u, int currentNode, int distArray[]) {
    visitedD[u] = true;
    for (int e = head[u]; e; e = edges[e].nextEdge) {
        int v = edges[e].target;
        int w = edges[e].weight;
        if (!visitedD[v]) {
            distArray[v] = distArray[u] + w;
            dfsDistance(v, currentNode, distArray);
        }
    }
}

// Main execution flow
memset(visitedD, 0, sizeof(visitedD));
dfsDistance(startNode, 1, distFirst);
int endpointA = 1;
for (int i = 2; i <= n; ++i) 
    if (distFirst[i] > distFirst[endpointA]) endpointA = i;

memset(visitedD, 0, sizeof(visitedD));
dfsDistance(endpointA, 1, distSecond);
int diameter = 0;
for (int i = 1; i <= n; ++i) 
    diameter = max(diameter, distSecond[i]);
printf("%d\n", diameter);

Topological Sorting

Kahn's algorithm processes nodes with zero in-degree using a queue, decrementing neighbor counts as each node is extracted. It produces a valid linear ordering for DAGs.

int inDegree[MAX_V], topoResult[MAX_V], topoPtr = 0;
queue<int> processQueue;

void executeTopoSort() {
    for (int i = 1; i <= n; ++i)
        if (inDegree[i] == 0) processQueue.push(i);
    
    while (!processQueue.empty()) {
        int u = processQueue.front();
        processQueue.pop();
        topoResult[++topoPtr] = u;
        
        for (int e = head[u]; e; e = edges[e].nextEdge) {
            int v = edges[e].target;
            inDegree[v]--;
            if (inDegree[v] == 0) processQueue.push(v);
        }
    }
}</int>

Lowest Common Ancestor (Binary Lifting)

Preprocessing ancestors in powers of two allows logarithmic-time queries. Depth normalization is required before lifting nodes to the same level.

int depth[MAX_V], ancestor[MAX_V][20];

void dfsLCASetup(int u, int p) {
    for (int e = head[u]; e; e = edges[e].nextEdge) {
        int v = edges[e].target;
        if (v == p) continue;
        depth[v] = depth[u] + 1;
        ancestor[v][0] = u;
        dfsLCASetup(v, u);
    }
}

void buildLCA() {
    for (int j = 1; j < 20; ++j)
        for (int i = 1; i <= n; ++i)
            ancestor[i][j] = ancestor[ancestor[i][j-1]][j-1];
}

int queryLCA(int u, int v) {
    if (depth[u] < depth[v]) swap(u, v);
    for (int j = 19; j >= 0; --j)
        if (depth[ancestor[u][j]] >= depth[v]) 
            u = ancestor[u][j];
    if (u == v) return u;
    for (int j = 19; j >= 0; --j)
        if (ancestor[u][j] != ancestor[v][j]) {
            u = ancestor[u][j];
            v = ancestor[v][j];
        }
    return ancestor[u][0];
}

Shortest Path Algorithms

SPFA with Negative Cycle Detection

Single-source shortest paths can be found using a queue-based relaxation. Tracking path length helps identify negative cycles when it exceeds the vertex count.

int distance[MAX_V], pathSteps[MAX_V];
bool inQueue[MAX_V];
queue<int> q;

bool detectNegativeCycle(int src) {
    memset(distance, 0x3f, sizeof(distance));
    memset(pathSteps, 0, sizeof(pathSteps));
    distance[src] = 0;
    q.push(src); inQueue[src] = true;
    
    while (!q.empty()) {
        int u = q.front(); q.pop();
        inQueue[u] = false;
        for (int e = head[u]; e; e = edges[e].nextEdge) {
            int v = edges[e].target;
            int w = edges[e].weight;
            if (distance[u] + w < distance[v]) {
                distance[v] = distance[u] + w;
                pathSteps[v] = pathSteps[u] + 1;
                if (pathSteps[v] >= n) return true;
                if (!inQueue[v]) {
                    q.push(v);
                    inQueue[v] = true;
                }
            }
        }
    }
    return false;
}</int>

Heap-Optimized Dijkstra

Non-negative edge weights are handled efficiently by extracting the minimum tentative distance node from a priority queue.

typedef pair<long int="" long=""> NodeDist;
priority_queue<nodedist vector="">, greater<nodedist>> minHeap;
long long distHeap[MAX_V];
bool finalized[MAX_V];

void runDijkstra(int start) {
    fill(distHeap, distHeap + MAX_V, LLONG_MAX);
    distHeap[start] = 0;
    minHeap.push({0, start});
    
    while (!minHeap.empty()) {
        int u = minHeap.top().second;
        minHeap.pop();
        if (finalized[u]) continue;
        finalized[u] = true;
        
        for (int e = head[u]; e; e = edges[e].nextEdge) {
            int v = edges[e].target;
            int w = edges[e].weight;
            if (distHeap[u] + w < distHeap[v]) {
                distHeap[v] = distHeap[u] + w;
                if (!finalized[v]) minHeap.push({distHeap[v], v});
            }
        }
    }
}</nodedist></nodedist></long>

Floyd-Warshall and Transitive Closure

All-pairs shortest paths and reachability matrices are computed using dynamic programming over intermediate vertices.

int adjMatrix[MAX_V][MAX_V];

void floydWarshall() {
    for (int k = 1; k <= n; ++k)
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j)
                adjMatrix[i][j] = min(adjMatrix[i][j], adjMatrix[i][k] + adjMatrix[k][j]);
}

void transitiveClosure() {
    for (int k = 1; k <= n; ++k)
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= n; ++j)
                adjMatrix[i][j] |= adjMatrix[i][k] & adjMatrix[k][j];
}

Minimum Spanning Tree: Kruskal's Algorithm

Edges are sorted by weight and processed sequentially. Disjoint Set Union (DSU) structures track connected components to prevent cycles.

struct RawEdge { int u, v, w; } rawEdges[MAX_E];
int parentDSU[MAX_V];

int findSet(int x) {
    return parentDSU[x] == x ? x : parentDSU[x] = findSet(parentDSU[x]);
}

void uniteSets(int x, int y) {
    x = findSet(x), y = findSet(y);
    if (x != y) parentDSU[x] = y;
}

void buildMST() {
    for (int i = 1; i <= n; ++i) parentDSU[i] = i;
    sort(rawEdges + 1, rawEdges + m + 1, [](const RawEdge& a, const RawEdge& b) {
        return a.w < b.w;
    });
    
    long long mstCost = 0;
    for (int i = 1; i <= m; ++i) {
        if (findSet(rawEdges[i].u) == findSet(rawEdges[i].v)) continue;
        uniteSets(rawEdges[i].u, rawEdges[i].v);
        mstCost += rawEdges[i].w;
    }
}

Tarjan's Algorithm Variants

Strongly Connected Components (Directed Graph)

int discTime[MAX_V], lowLink[MAX_V], timerVal = 0;
int stackArr[MAX_V], stackTop = 0;
bool onStack[MAX_V];
int sccId[MAX_V], sccCount = 0;

void tarjanSCC(int u) {
    discTime[u] = lowLink[u] = ++timerVal;
    stackArr[++stackTop] = u;
    onStack[u] = true;
    
    for (int e = head[u]; e; e = edges[e].nextEdge) {
        int v = edges[e].target;
        if (!discTime[v]) {
            tarjanSCC(v);
            lowLink[u] = min(lowLink[u], lowLink[v]);
        } else if (onStack[v]) {
            lowLink[u] = min(lowLink[u], discTime[v]);
        }
    }
    
    if (lowLink[u] == discTime[u]) {
        sccCount++;
        int popped;
        do {
            popped = stackArr[stackTop--];
            onStack[popped] = false;
            sccId[popped] = sccCount;
        } while (popped != u);
    }
}

Articulation Points and Vertex Biconnected Components

int dfsNum[MAX_V], lowVal[MAX_V], visitTime = 0;
int stk[MAX_V], stkPtr = 0;
bool isArticulation[MAX_V];
vector<int> vertexBCC[MAX_V];
int bccCount = 0;

void tarjanArticulation(int u, int root) {
    dfsNum[u] = lowVal[u] = ++visitTime;
    stk[++stkPtr] = u;
    bool hasChild = false;
    
    for (int e = head[u]; e; e = edges[e].nextEdge) {
        int v = edges[e].target;
        if (!dfsNum[v]) {
            hasChild = true;
            tarjanArticulation(v, root);
            lowVal[u] = min(lowVal[u], lowVal[v]);
            if (lowVal[v] >= dfsNum[u]) {
                if (u != root || hasChild) isArticulation[u] = true;
                bccCount++;
                int popped;
                do {
                    popped = stk[stkPtr--];
                    vertexBCC[bccCount].push_back(popped);
                } while (popped != v);
                vertexBCC[bccCount].push_back(u);
            }
        } else {
            lowVal[u] = min(lowVal[u], dfsNum[v]);
        }
    }
}</int>

Bridges and Edge Biconnected Components

int disc[MAX_V], low[MAX_V], timeCounter = 0;
bool isBridge[MAX_E];

inline int getReverseEdge(int id) { return (id & 1) ? id + 1 : id - 1; }

void findBridges(int u, int parentEdge) {
    disc[u] = low[u] = ++timeCounter;
    for (int e = head[u]; e; e = edges[e].nextEdge) {
        int v = edges[e].target;
        if (!disc[v]) {
            findBridges(v, e);
            low[u] = min(low[u], low[v]);
            if (low[v] > disc[u]) {
                isBridge[e] = isBridge[getReverseEdge(e)] = true;
            }
        } else if (e != getReverseEdge(parentEdge)) {
            low[u] = min(low[u], disc[v]);
        }
    }
}

int compId[MAX_V], compCount = 0;
vector<int> edgeBCC[MAX_V];

void colorComponent(int u) {
    edgeBCC[compId[u]].push_back(u);
    for (int e = head[u]; e; e = edges[e].nextEdge) {
        if (isBridge[e]) continue;
        int v = edges[e].target;
        if (!compId[v]) {
            compId[v] = compId[u];
            colorComponent(v);
        }
    }
}</int>

Block-Cut Tree Construction

int discovery[MAX_V], minLow[MAX_V], clockTick = 0;
int treeStack[MAX_V], treeTop = 0;
int virtualNodeIdx = 0;
Graph tree; // Adjacency list for the new tree

void buildBlockCutTree(int u) {
    discovery[u] = minLow[u] = ++clockTick;
    treeStack[++treeTop] = u;
    
    for (int e = head[u]; e; e = edges[e].nextEdge) {
        int v = edges[e].target;
        if (discovery[v]) {
            minLow[u] = min(minLow[u], discovery[v]);
        } else {
            buildBlockCutTree(v);
            minLow[u] = min(minLow[u], minLow[v]);
            if (minLow[v] == discovery[u]) {
                virtualNodeIdx++;
                int curr = 0;
                while (curr != v) {
                    curr = treeStack[treeTop--];
                    tree.addEdge(virtualNodeIdx, curr);
                    tree.addEdge(curr, virtualNodeIdx);
                }
                tree.addEdge(u, virtualNodeIdx);
                tree.addEdge(virtualNodeIdx, u);
            }
        }
    }
}

Bipartite Matching: Hungarian Algorithm

Maximum matching in bipartite graphs is found via augmenting path searches using DFS and a visitation array to prevent cycles during a single search phase.

int matchRight[MAX_V];
bool visitedPhase[MAX_V];

bool findAugmentingPath(int u) {
    for (int e = head[u]; e; e = edges[e].nextEdge) {
        int v = edges[e].target;
        if (visitedPhase[v]) continue;
        visitedPhase[v] = true;
        if (matchRight[v] == 0 || findAugmentingPath(matchRight[v])) {
            matchRight[v] = u;
            return true;
        }
    }
    return false;
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.