Connect the Dots

  • Given a set of points on a plane, determine the minimum cost to connect all these points
  • The cost of connecting two points is equal to the Manhattan distance between them, which is calculated as x1-x2 + y1-y2
  • At least 2 points on the plane

The point:

  • minimum spanning tree (MST) problem
  • Kruskal’s or Prim’s algo
  • Here, Kruskal’s algo
    • builds the MST, connecting nodes with the lowest weighted edges first and skipping edges that could cause a cycle
  • Use the Union-Find data structure to detect cycle, ie checking if 2 points we want to connect both belong to the same “community” Complexity :
Time Space
O(n² * log(n)) O(n²)
  • O(n² * log(n)) in time because
    • n² the number of edges
    • Sorting n² edges takes O(n² * log(n²)). Can be simplified to O(n² * log(n))
    • Up to one union operation for each edge. Each union taking amortized 0(1) time => O(n²)
    • O(n² + n² x log(n)) = O(n² * log(n))
  • O(n²) in space because of the edges array. UnionFind data structur take up to O(n)

First implementation

About Rust :

  • Checkout 280_merging_communities.ipynb for UnionFind data structure
  • I don’t like the way total_cost is returned twice
  • YES : tested on the Rust Playground
struct UnionFind{
    size : Vec<usize> ,
    parent : Vec<usize> 
}

impl UnionFind{
    fn new(size: usize) -> Self {
        Self {
            parent: (0..size).collect(),
            size: vec![1; size],
        }
    }

    fn union(&mut self, x: usize, y: usize) -> bool{       // ! Now return a bool
        let (rep_x, rep_y) = (self.find(x), self.find(y));
        if rep_x != rep_y{
            if self.size[rep_x] > self.size[rep_y]{
                // If rep_x represent a larger community, connect rep_y community to it
                self.parent[rep_y] = rep_x;
                self.size[rep_x] += self.size[rep_y] 
            }else{
                // connect rep_x to rep_y otherwise
                self.parent[rep_x] = rep_y;
                self.size[rep_y] += self.size[rep_x] 
            }
            // true if both groups were merged
            return true;
        }
        // false if the points belong to the same group
        false
    }

    fn find(&mut self, x: usize)->usize{ // returns the representative of x
        if x != self.parent[x] {
            self.parent[x] = self.find(self.parent[x]); // path compression
        }
        self.parent[x]
    }
}


fn connect_the_dots(points: &[(i32, i32)]) -> usize {
    let n = points.len();

    // Create and populate an array containing all possible edges
    let mut edges = Vec::new();
    for i in 0..n{
        for j in i+1..n{
            // Manhattan
            let cost = (points[i].0 - points[j].0).abs() + (points[i].1 - points[j].1).abs();
            edges.push((cost as usize, i, j))
        }
    } 

    // Sort the edges by their cost in ascending order
    edges.sort();
    let mut uf = UnionFind::new(n);

    let (mut total_cost, mut edges_added) = (0, 0);

    // Use Kruskal's algorithm to create the MST and identify its minimum cost
    for (cost, p1, p2) in edges{
        // If the points are not already connected (i.e., their representatives are not the same), connect them, and add the cost to the total cost
        if uf.union(p1, p2){
            total_cost += cost;
            edges_added +=1;
            // If n - 1 edges have been added to the MST, the MST is complete
            if edges_added == n - 1{
               return total_cost; 
            }
        }
    }
    total_cost
}

fn main() {
    let points = vec![
        (1, 1),
        (2, 6),
        (3, 2),
        (4, 3),
        (7, 1),
    ];
    println!("{:?}", connect_the_dots(&points)); // 15
}

V2

About Rust :

  • Use loop expression to evaluate the return value of connect_the_dots()
  • Use .abs_diff() for Manhattan
  • Set edges to the right capacity up front
  • Preferred solution?
  • YES : tested on the Rust Playground
struct UnionFind {
    size: Vec<usize>,
    parent: Vec<usize>,
}

impl UnionFind {
    fn new(size: usize) -> Self {
        Self {
            parent: (0..size).collect(),
            size: vec![1; size],
        }
    }

    fn union(&mut self, x: usize, y: usize) -> bool { // ! Now return a bool
        let (rep_x, rep_y) = (self.find(x), self.find(y));
        if rep_x != rep_y {
            if self.size[rep_x] > self.size[rep_y] {
                // If rep_x represent a larger community, connect rep_y community to it
                self.parent[rep_y] = rep_x;
                self.size[rep_x] += self.size[rep_y];
            } else {
                // connect rep_x to rep_y otherwise
                self.parent[rep_x] = rep_y;
                self.size[rep_y] += self.size[rep_x];
            }
            // true if both groups were merged
            return true;
        }
        // false if the points belong to the same group
        false
    }

    fn find(&mut self, x: usize) -> usize { // returns the representative of x
        if x != self.parent[x] {
            self.parent[x] = self.find(self.parent[x]); // path compression
        }
        self.parent[x]
    }
}

fn connect_the_dots(points: &[(i32, i32)]) -> usize {
    let n = points.len();

    // Create and populate an array containing all possible edges
    let mut edges = Vec::with_capacity(n * (n - 1) / 2);
    for i in 0..n {
        for j in i + 1..n {
            // Manhattan
            let cost = (points[i].0.abs_diff(points[j].0) + points[i].1.abs_diff(points[j].1)) as usize;
            edges.push((cost, i, j));
        }
    }

    // Sort the edges by their cost in ascending order
    edges.sort();

    let mut uf = UnionFind::new(n);
    let mut total_cost = 0;
    let mut edges_added = 0;

    // Use Kruskal's algorithm to create the MST and identify its minimum cost 
    // The returned value is the result of the loop expression (total_cost)
    loop {
        for (cost, p1, p2) in edges.iter().copied() {
            if uf.union(p1, p2) {
                total_cost += cost;
                edges_added += 1;
                if edges_added == n - 1 {
                    break; // <- Breaks the `for`, not the `loop`
                }
            }
        }
        break total_cost; // <- Breaks the `loop` 
    } // ! watchout no ; 
}

fn main() {
    let points = vec![
        (1, 1),
        (2, 6),
        (3, 2),
        (4, 3),
        (7, 1),
    ];
    println!("{:?}", connect_the_dots(&points)); // 15
}

Back to top

Published on: Jun 22 2025 at 09:00 AM | Last updated: Jun 22 2025 at 09:00 AM

Copyright © 1964-2025 - 40tude