C++ Coding Test Course, Minimum Spanning Tree

Hello! In this blog post, we will discuss one of the frequently asked questions in coding tests using C++, Minimum Spanning Tree. We will use Prim’s algorithm and Kruskal’s algorithm, and we will organize the theory by solving a concrete problem.

Problem Description

Given the following graph, find the minimum spanning tree. Finally, output the sum of the weights of the tree.

Input:
5 7
1 2 3
1 3 4
2 3 2
2 4 6
3 4 1
3 5 5
4 5 2

Output:
6

The structure of the input is as follows:

  • First line: number of vertices V and number of edges E
  • The next E lines: information about each edge (Vertex1, Vertex2, Weight)

Problem Solving Method

To solve the above problem, we can use Prim’s algorithm and Kruskal’s algorithm. Here, we will solve the problem using Prim’s algorithm. Prim’s algorithm starts from an arbitrary vertex and gradually expands the graph by selecting the edge with the smallest weight.

1. Overview of Prim’s Algorithm

Prim’s algorithm consists of the following steps:

  1. Select the starting vertex and choose the edge with the minimum weight among the edges connected to it.
  2. Include the vertex connected by the selected edge.
  3. Repeat the above steps until all vertices are included.

2. Implementing Prim’s Algorithm in C++

Now, let’s implement Prim’s algorithm in C++. Through this implementation, we will look at the process of solving a real problem.


#include 
#include 
#include 
#include 
#include 
using namespace std;

const int MAX_V = 100; // Maximum number of vertices
const int INF = 1e9; // Representation of infinity

vector> graph[MAX_V]; // Adjacency list
int key[MAX_V]; // Minimum weight array
bool inMST[MAX_V]; // Inclusion in MST

int prim(int start, int V) {
    priority_queue, vector>, greater>> pq;
    fill(key, key + V, INF);
    fill(inMST, inMST + V, false);

    key[start] = 0; // The key value of the starting vertex is 0
    pq.push({0, start}); // (Weight, Vertex)

    int totalWeight = 0; // Total weight of the minimum spanning tree

    while (!pq.empty()) {
        int u = pq.top().second;
        pq.pop();

        if (inMST[u]) continue; // If already included in MST

        totalWeight += key[u]; // Add the key value of the current vertex to the total weight
        inMST[u] = true; // Add to MST

        for (auto& edge : graph[u]) {
            int v = edge.first;
            int weight = edge.second;

            // If the current edge is not included in MST and weight is smaller
            if (!inMST[v] && weight < key[v]) {
                key[v] = weight; // Update minimum weight
                pq.push({key[v], v}); // Add to queue
            }
        }
    }

    return totalWeight;
}

int main() {
    int V, E;
    cin >> V >> E;

    for (int i = 0; i < E; i++) {
        int u, v, weight;
        cin >> u >> v >> weight;
        // Stored in 0-indexed
        graph[u - 1].push_back({v - 1, weight});
        graph[v - 1].push_back({u - 1, weight});
    }

    cout << prim(0, V) << endl; // Starting vertex 0
    return 0;
}

3. Code Explanation

The explanation of the above code is as follows:

  • The graph is represented using an adjacency list. Each vertex has a list of connected vertices and their weights.
  • A priority queue is used to efficiently select the edge with the minimum weight among the edges connected from the current vertex.
  • The vertices included in MST are managed by the inMST array.
  • Finally, it returns the total weight of the MST including all vertices.

4. Execution Example

When executing the input data, the following result is obtained:


5 7
1 2 3
1 3 4
2 3 2
2 4 6
3 4 1
3 5 5
4 5 2

Output result:


6

Finding Minimum Spanning Tree Using Kruskal's Algorithm

Following Prim's algorithm, we will look at how to solve the same problem using Kruskal's algorithm. Kruskal's algorithm operates based on edges, sorting all edges by weight and adding them from the smallest.

1. Overview of Kruskal's Algorithm

Kruskal's algorithm consists of the following steps:

  1. Sort the edges by weight.
  2. Select the smallest edge and add it to the MST if it does not create a cycle.
  3. Repeat until all edges are processed.

2. Implementing Kruskal's Algorithm in C++

Now, let's implement Kruskal's algorithm in C++.


#include 
#include 
#include 
using namespace std;

struct Edge {
    int u, v, weight;
};

bool compare(Edge a, Edge b) {
    return a.weight < b.weight;
}

int find(int parent[], int u) {
    if (parent[u] != u) {
        parent[u] = find(parent, parent[u]); // Path compression
    }
    return parent[u];
}

void unionSet(int parent[], int rank[], int u, int v) {
    int root_u = find(parent, u);
    int root_v = find(parent, v);
    
    if (root_u != root_v) {
        if (rank[root_u] > rank[root_v]) {
            parent[root_v] = root_u;
        } else if (rank[root_u] < rank[root_v]) {
            parent[root_u] = root_v;
        } else {
            parent[root_v] = root_u;
            rank[root_u]++;
        }
    }
}

int kruskal(int V, vector& edges) {
    sort(edges.begin(), edges.end(), compare);
    int parent[V], rank[V];
    for (int i = 0; i < V; i++) {
        parent[i] = i;
        rank[i] = 0;
    }

    int totalWeight = 0; // Total weight of the MST
    for (Edge edge : edges) {
        int u = edge.u, v = edge.v;
        
        // Add to MST if no cycle is formed
        if (find(parent, u) != find(parent, v)) {
            unionSet(parent, rank, u, v);
            totalWeight += edge.weight; // Add weight
        }
    }
    
    return totalWeight;
}

int main() {
    int V, E;
    cin >> V >> E;
    vector edges(E);

    for (int i = 0; i < E; i++) {
        cin >> edges[i].u >> edges[i].v >> edges[i].weight;
        edges[i].u--; // 0-indexed
        edges[i].v--; // 0-indexed
    }

    cout << kruskal(V, edges) << endl; // Output the total weight of the MST
    return 0;
}

3. Code Explanation

The explanation of the code above is as follows:

  • Define a structure Edge to store edges.
  • Sort the edges by weight.
  • Utilize the Union-Find data structure to check for cycles and add the edge to the MST if no cycles occur.

4. Execution Example

When executing the input data, the following result is obtained:


5 7
1 2 3
1 3 4
2 3 2
2 4 6
3 4 1
3 5 5
4 5 2

Output result:


6

Conclusion

In this session, we learned how to solve the Minimum Spanning Tree problem using Prim's algorithm and Kruskal's algorithm. Understanding the characteristics of each algorithm and their actual implementation methods is important, and one should familiarize themselves with these algorithms to solve various problems.

The Minimum Spanning Tree problem is one of the basic concepts in graph theory and frequently appears in algorithm problems. Utilize the implementations above to practice solving various graph problems efficiently.

I hope that viewers found this tutorial helpful, and if you have any further questions, please leave a comment. In the next post, we will cover another algorithm problem. Thank you!

C++ Coding Test Course, Minimum Cost Calculation

Hello! In this post, we will discuss a C++ algorithm problem titled Finding Minimum Cost. This problem presents a good opportunity to explore algorithmic concepts that are useful for finding optimal solutions in various situations. Through this article, we will delve into problem analysis, algorithm selection, code implementation, testing, and optimization methods in detail.

Problem Description

We have several cities and roads between them. Each road has a specific cost. Our goal when traveling between the given cities is to find the minimum travel cost from the starting city to the target city. This can be solved using Dijkstra’s algorithm.

Problem Definition

Here is the mathematical definition of the problem:

  • The number of given cities n (between 1 and 10,000)
  • The number of paths m (between 1 and 100,000)
  • Each path is given in the form of u, v, cost. This means that the cost to travel from city u to city v is cost.
  • The starting city start and the target city end are provided.

Input


    n m
    u1 v1 cost1
    u2 v2 cost2
    ...
    um vm costm
    start end
    

Output

Output the minimum cost to reach the target city. If it is not possible to reach, output -1.

Algorithm Selection

This problem involves finding the shortest path in a weighted graph, which can be effectively solved using the representative algorithm Dijkstra’s algorithm. Dijkstra’s algorithm has a time complexity of O((n + m) log n), making it efficient even under maximum input conditions.

Dijkstra’s Algorithm Overview

The basic idea of Dijkstra’s algorithm is as follows:

  1. Select the starting node and initialize the distance to this node as 0.
  2. Initialize the distance to all other nodes as infinity.
  3. Calculate the distance to the nodes adjacent to the current node, updating the distance if a shorter distance is found.
  4. Repeat this process until all nodes have been visited.

Problem Solving Process

Step 1: Problem Analysis

We need to construct a graph from the given input and a data structure to store the cost information for each city. We will use an adjacency list.

Step 2: Code Implementation


#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <limits>

using namespace std;

const int INF = numeric_limits<int>::max();

vector<pair<int, int>> graph[10001];

void dijkstra(int start, int n) {
    vector<int> cost(n + 1, INF);
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;

    cost[start] = 0;
    pq.push(make_pair(0, start));

    while (!pq.empty()) {
        int currentCost = pq.top().first;
        int currentNode = pq.top().second;
        pq.pop();

        if (currentCost > cost[currentNode]) continue;

        for (auto &edge : graph[currentNode]) {
            int nextNode = edge.first;
            int nextCost = edge.second;

            if (cost[currentNode] + nextCost < cost[nextNode]) {
                cost[nextNode] = cost[currentNode] + nextCost;
                pq.push(make_pair(cost[nextNode], nextNode));
            }
        }
    }

    for (int i = 1; i <= n; ++i) {
        if (cost[i] == INF) {
            cout << -1 << endl;  // unreachable case
        } else {
            cout << cost[i] << endl;
        }
    }
}

int main() {
    int n, m;
    cin >> n >> m;

    for (int i = 0; i < m; ++i) {
        int u, v, cost;
        cin >> u >> v >> cost;
        graph[u].push_back(make_pair(v, cost));
        graph[v].push_back(make_pair(u, cost));  // assuming bidirectional roads
    }

    int start, end;
    cin >> start >> end;

    dijkstra(start, n);
    
    return 0;
}
    

Step 3: Code Testing

Test the input through the code above. For example, you can test using the following input:


5 6
1 2 1
1 3 4
2 3 2
2 4 5
3 4 1
4 5 3
1 5
    

The above input represents a case with 5 cities and 6 roads, where the starting city is 1 and the target city is 5. In this case, the algorithm should output the method to reach from city 1 to city 5 with the minimum cost.

Step 4: Optimization

Dijkstra’s algorithm is typically implemented using a Priority Queue; however, in limited cases, a Fibonacci Heap might be used. In this article, we demonstrated a simple and efficient implementation using a standard Priority Queue.

Conclusion

In this session, we examined how to solve the minimum cost problem using C++. We understood the principles of Dijkstra’s algorithm and how to apply it to real problems. This will be helpful in preparing for coding tests.

Continue learning and practicing how to solve more algorithm problems. Thank you!

C++ Coding Test Course, Finding Lowest Common Ancestor 1

This course will cover the problem of finding the Lowest Common Ancestor (LCA) using C++ programming.
The lowest common ancestor refers to the deepest node that is an ancestor of both nodes. This problem is useful for understanding tree data structures and depth-first search (DFS).

Problem Description

Given a tree, you are to solve the problem of finding the lowest common ancestor of two nodes A and B.
It is assumed that the tree is not empty and that there are no duplicate nodes. The nodes are numbered starting from 1.

Input Format

N
A B
where N is the number of nodes, and A and B are the two nodes for which to find the lowest common ancestor.

The tree is given as follows.
M lines are given, consisting of a parent node and a child node, 
where each line represents parent P and child C in the format 'P C'.

Output Format

Print the node number of the lowest common ancestor of nodes A and B.

Example

Input

7
4 5
4 6
5 3
6 2
1 4
1 7

Output

4

Problem Analysis

In the given example, the tree can be represented as follows:

        1
       / \
      4   7
     / \
    5   6
   /     \
  3       2

The topmost parent of nodes A (5) and B (3) is 4, which is the lowest common ancestor.
If nodes A and B are the same, that node itself will be the lowest common ancestor.

Algorithm Approach

Various algorithms exist for finding the lowest common ancestor, but here we will explain a method using depth-first search (DFS) and parent tracking.

1. Storing the Tree Structure

First, we will store the tree using an adjacency list. If we know the parent node, we construct the tree by adding child nodes to the list.

2. Storing Depth via DFS

This method records the depth of each node and traces the parent nodes to find the LCA.
While exploring each node, we keep track of the depth and also store the parent node.

3. Finding the LCA

By comparing the depths of nodes A and B, we lift the shallower node while tracking the parent nodes.
When the two nodes become equal, that node is the LCA.

C++ Code Implementation

Below is the algorithm code written in C++:


#include 
#include 
#include 

using namespace std;

vector tree[100001];
int parent[100001];
int depth[100001];

// Store depth and parent through DFS
void dfs(int node, int par, int dep) {
    parent[node] = par;
    depth[node] = dep;
    for (int child : tree[node]) {
        if (child != par) {
            dfs(child, node, dep + 1);
        }
    }
}

// Finding LCA of two nodes
int findLCA(int a, int b) {
    // Align depths
    while (depth[a] > depth[b]) {
        a = parent[a];
    }
    while (depth[b] > depth[a]) {
        b = parent[b];
    }

    // Finding LCA
    while (a != b) {
        a = parent[a];
        b = parent[b];
    }
    return a;
}

int main() {
    int N;
    cin >> N;

    for (int i = 0; i < N - 1; i++) {
        int u, v;
        cin >> u >> v;
        tree[u].push_back(v);
        tree[v].push_back(u);
    }

    // Store depth and parent information via DFS
    dfs(1, 0, 0);

    int A, B;
    cin >> A >> B;

    cout << findLCA(A, B) << endl;
    return 0;
}

Code Explanation

1. Tree Representation

According to the given input, the tree is represented in the form of an adjacency list.
Each parent-child relationship is stored for later reference during the DFS.

2. DFS Function

The dfs function receives the current node, parent node, and depth, storing the parent and depth of that node while traversing.
It only explores child nodes when they are not the same as the parent.

3. LCA Function

After adjusting the depths of the two nodes, the function climbs up through the parents to find the point where the two nodes become equal.
This point is the lowest common ancestor.

Conclusion

In this course, we explored the concept, approach, and C++ implementation of the lowest common ancestor problem.
This problem is very useful for understanding the basic concepts of tree data structures and frequently appears in various application problems.
To enhance your algorithm problem-solving skills, I encourage you to tackle this problem from various angles.

C++ Coding Test Course, Finding the Lowest Common Ancestor 2

Problem Description

There is a tree consisting of N nodes and N-1 edges. Each node is numbered from 1 to N. Given two nodes A and B, the task is to find the Lowest Common Ancestor (LCA) of these two nodes. However, since the tree can be very large, an efficient algorithm needs to be used.

Input

  • The first line contains the number of nodes N (1 ≤ N ≤ 100,000).
  • The second line provides the N-1 edge information. Each edge is given in the form of “parent child.”
  • The third line consists of the number of queries Q (1 ≤ Q ≤ 100,000) for which A and B are given.

Example Input

7
1 2
1 3
2 4
2 5
3 6
3 7
4 5
3
4 5
4 6
5 6

Example Output

2
1
3

Solution Strategy

To efficiently find the Lowest Common Ancestor, we will use the following approach.

  • Representation of Tree Structure: The tree is represented using an adjacency list. Each node is connected to its parent and children, thus forming the tree.
  • Creating Depth and Parent Arrays using DFS: We use the Depth First Search (DFS) algorithm to determine the depth of each node and to record the parent nodes. This information is essential for finding the LCA.
  • Binary Lifting: To efficiently find the LCA, we use the binary lifting method (Fast LCA). The process involves aligning the depths of the two nodes and finding their common ancestor.

Implementation Steps

1. Creating Tree Structure

First, we build the tree based on the edge information. In this process, we define the relationship between parents and children and represent it through an adjacency list.

2. Generating Depth and Parent Arrays using DFS

Now, we need to record the depth and parent of each node using DFS. The depth indicates how far each node is from the root, and the parent information is crucial for finding the LCA in the next step.

3. Implementing LCA Function

We implement a function to find the Lowest Common Ancestor of two nodes using binary lifting. This function compares the depths of the two nodes and adjusts the depths if needed, subsequently finding their ancestors.

4. Processing Queries

For each query, we find and output the LCA of the given two nodes A and B.

Code Implementation

#include 
#include 
#include 
#include 

using namespace std;

const int MAX_N = 100000;
const int LOG = 17; // log2(100000) ≈ 16.61

int parent[MAX_N + 1][LOG + 1]; // Parent array
int depth[MAX_N + 1];            // Depth array
vector tree[MAX_N + 1];     // Tree in adjacency list format
int N;

void dfs(int node, int par, int d) {
    depth[node] = d;
    parent[node][0] = par;
    for (int i = 1; i <= LOG; i++) {
        parent[node][i] = parent[parent[node][i - 1]][i - 1];
    }
    
    for (int child : tree[node]) {
        if (child != par) {
            dfs(child, node, d + 1);
        }
    }
}

int lca(int a, int b) {
    if (depth[a] < depth[b]) swap(a, b);

    for (int i = LOG; i >= 0; i--) {
        if (depth[parent[a][i]] >= depth[b]) {
            a = parent[a][i];
        }
    }

    if (a == b) return a;

    for (int i = LOG; i >= 0; i--) {
        if (parent[a][i] != parent[b][i]) {
            a = parent[a][i];
            b = parent[b][i];
        }
    }

    return parent[a][0];
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> N;
    for (int i = 0; i < N - 1; i++) {
        int u, v;
        cin >> u >> v;
        tree[u].push_back(v);
        tree[v].push_back(u);
    }

    dfs(1, 0, 0); // Root is 1
    int Q;
    cin >> Q;

    for (int i = 0; i < Q; i++) {
        int a, b;
        cin >> a >> b;
        cout << lca(a, b) << '\n';
    }

    return 0;
}

Conclusion

This problem helps understand the use of DFS and binary lifting methods to find the Lowest Common Ancestor. Learning various algorithms to process trees and enhancing coding skills will be greatly beneficial. Practicing different approaches to explore tree structures and find the LCA based on the given problem will be very useful for coding test preparation.

C++ Coding Test Course, Lowest Common Ancestor

Hello! In this lecture, we will discuss one of the concepts that often appears in coding tests, ‘Lowest Common Ancestor’. This lecture focuses on learning how to solve problems using C++. First, I will explain what the lowest common ancestor is and detail the algorithms to find it.

1. Problem Description

We will address the problem of finding the lowest common ancestor of two nodes when given a binary tree. The nodes in a binary tree have a parent-child relationship, and each node has a unique value.

Problem Definition

Given a binary tree and two nodes A and B, write a function that finds and returns the lowest common ancestor of A and B.

Input Format

  • Binary Tree: The root node of the tree is provided.
  • Two Nodes A and B: Both nodes are valid nodes within the tree.

Output Format

Returns the lowest common ancestor of nodes A and B. If there is no common ancestor, return nullptr.

Example Input

     3
    / \
   5   1
  / \ / \
 6  2 0  8
   / \
  7   4

Example Output

Lowest Common Ancestor: 5 (A: 5, B: 1)

2. Algorithm Approaches

There are two main approaches commonly used to find the lowest common ancestor:

  1. Recursive Method
  2. Storing Parent Nodes to Search Indirectly

2.1. Recursive Approach

The most common method is to start at the root of the tree and explore recursively. When both nodes are in the left or right subtree of the current node, continue exploring that subtree. If the current node finds A or B, return that node; if both nodes are found, return the current node.

2.2. Parent Storing Method

This method involves storing the parent information of each node. It finds the common ancestor indirectly by following the parent information of the two nodes. However, this method requires additional memory.

3. C++ Code Implementation

Below is the C++ code for finding the lowest common ancestor.

#include 
using namespace std;

// Definition of the binary tree node
struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

// Function to find the lowest common ancestor
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    // Base case: return root if it's nullptr or is p or q
    if (root == nullptr || root == p || root == q) {
        return root;
    }

    // Search for the lowest common ancestor in the left and right subtrees
    TreeNode* left = lowestCommonAncestor(root->left, p, q);
    TreeNode* right = lowestCommonAncestor(root->right, p, q);

    // If both left and right subtrees found a node, return the current node
    if (left != nullptr && right != nullptr) {
        return root; // Current node is the lowest common ancestor
    }

    // Return the ancestor from one of the subtrees if found
    return left != nullptr ? left : right;
}

// Main function
int main() {
    // Create a binary tree
    TreeNode *root = new TreeNode(3);
    TreeNode *n1 = new TreeNode(5);
    TreeNode *n2 = new TreeNode(1);
    TreeNode *n3 = new TreeNode(6);
    TreeNode *n4 = new TreeNode(2);
    TreeNode *n5 = new TreeNode(0);
    TreeNode *n6 = new TreeNode(8);
    TreeNode *n7 = new TreeNode(7);
    TreeNode *n8 = new TreeNode(4);

    root->left = n1;
    root->right = n2;
    n1->left = n3;
    n1->right = n4;
    n2->left = n5;
    n2->right = n6;
    n4->left = n7;
    n4->right = n8;

    // Call to find the lowest common ancestor
    TreeNode* ancestor = lowestCommonAncestor(root, n1, n2);
    cout << "Lowest Common Ancestor: " << ancestor->val << endl;

    return 0;
}

4. Code Explanation

The above code implements a method to find the lowest common ancestor of two nodes A and B in a binary tree. Now, let me explain each part.

4.1. TreeNode Structure

The TreeNode structure defines a node in a binary tree. Each node includes an integer value and pointers to the left and right child nodes.

4.2. lowestCommonAncestor Function

  • Handles the base case: returns the current node if it’s nullptr or if it’s A or B.
  • Searches for the lowest common ancestor in the left and right subtrees.
  • If a node is found in both left and right subtrees, it returns the current node.
  • If found in only one subtree, it returns the ancestor from that subtree.

4.3. Main Function

It creates a binary tree and includes a call to search for the lowest common ancestor of the specified nodes A and B. The result is printed to the console.

5. Complexity Analysis

The time complexity of this algorithm is O(N), where N is the number of nodes in the binary tree. In the worst case, all nodes may need to be checked.

The space complexity is O(H), where H is the height of the binary tree. In the worst case, the stack depth can be equal to the depth of the tree.

6. Conclusion

In this lecture, we covered the lowest common ancestor problem. This problem frequently appears in various algorithm questions related to binary trees, so it is important to understand and practice it thoroughly. Solving additional problems can be a good method to enhance your understanding.

Thank you! In the next lecture, we will cover another interesting algorithm topic. If you have any questions or feedback, please leave them in the comments.