Railroad Construction: Added initial boilerplate

This commit is contained in:
2025-10-19 20:26:07 -04:00
parent a730e01e9c
commit 97e92b8b14
5 changed files with 242 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
# Lab 12: Railroad Construction
## Specification
There are _n_ cities that want to create a shared train network so that each
pair of cities is connected by rail to each of the other cities. The cost of
laying track is directly proportional to the distance between two cities. The
cost to lay one mile of railroad track is $1M. Your task is to determine the
lowest cost possible for laying the track so that all the cities are connected.
## Input
A single line containing the number n of cities followed by n city coordinate
lines. Each of the coordinate lines is a pair of floating point numbers x y
giving the coordinates of the cities on a square grid (for this problem you may
assume the earth is flat and that the units of the grid structure are given in
miles).
## Output
The minimum amount of money (in millions) that must be spent to create the
railroad network. You can round this to a single decimal place.
<table>
<tr>
<td>Sample Input</td>
<td>Sample Output</td>
</tr>
<tr>
<td><pre>
3
0 1.5
1 0
0 0</pre></td>
<td><pre>$2.5M</pre></td>
</tr>
<tr>
<td><pre>
8
0 0
1 1
0 1
3 3
4 5
2 2
1 0
3 2</pre></td>
<td><pre>$8.7M</pre></td>
</tr>
</table>
## Hints
1. You may use our [connected_components.py](./connected_components.py) Download
connected_components.py in your code if you want., which will produce labels
for each of the vertices that correspond to their component. You will need to
use the adjacency list structure as suggested below if you wish to use this
code in its unmodified state.
2. You may have already envisioned a way to represent this problem as a graph.
Draw the graph in the small example above on paper. How many edges does it
have? If you are unsure of this, check with me before you start coding the
solution.
## Adjacency List
Augment your dictionary/set based adjacency structure to use a dictionary for
the edges (instead of a set as before). This will allow you to store the edge
weights as the values of this second dictionary.
## Method/Programming requirement
Your program must implement either Borůvka's algorithm (I suggest you use
Borůvka's) or Prims (not Kruskal). You must specify which algorithm you are
implementing in the comments. Any other method to compute the answer will result
in zero credit.
## Submission
Submit the code in a file named [cs412_railroad_a.py](./cs412_railroad_a.py) to
Gradescope. If you use the supplied copy of
[connected_components.py](./connected_components.py), you only need to import it
into your code (it will be available on Gradescope, so, no need to submit). If
you change connected_components.py, then please rename it and submit it as part
of your code (or you can copy/paste the code into your lab and just submit a
single file, it is up to you). You can import the code using the following
python:
```
from connected_components import count_and_label
```

View File

@@ -0,0 +1,71 @@
# Implementation of the count-and-label
# connected component counting algorithm.
#
# Author: John Bowers
# Version: Mar 17, 2021
# March 2022 -- molloykp
# Changed code to label components starting a 0 (instead of 1)
# October 2023 -- molloykp
# Change code to accept new adj list/hash map structure
# Version of the iterative dfs that takes as input
# a list of labels, one per vertex, and a starting
# vertex v and uses iterative depth-first-search
# to label each vertex with a given currentLabel
#
# Parameters:
# * graph is an adjaceny list where vertices name/keys are stored
# in a dictionary. Each vertex has its own dictionary
# where the key is the edge endpoint and the value is the
# weight of the edge.
# * v is the vertex to DFS from
# * labels is an array of length V which is -1 if the vertex is unvisited
# * currentLabel is the label to set on every visited vertex
#
# labels is an out-parameter and will be modified destructively during the
# run of this operation.
#
def dfs_label(graph, v, labels, currentLabel):
bag = [v]
while bag: # while bag is not empty
u = bag.pop()
if labels[u] == -1:
labels[u] = currentLabel
for w in graph[u]:
bag.append(w)
# Counts the number of connected components in the graph
# and labels each vertex with its connected component index
#
# Parameters:
# * graph given as an adjaceny list/set structure with vertices 0..(n-1)
#
# Returns:
# count, labels
# where count is the number of connected components in the graph
# and labels is the labeling of each vertex's connected component
# starting from index 0
def count_and_label(graph):
labels = [-1 for _ in range(len(graph))] # Initially all labels are -1
count = -1
for v in range(len(graph)): # for each vertex
if labels[v] == -1: # if v is not visited
count += 1
dfs_label(graph, v, labels, count)
return count+1, labels
if __name__ == "__main__":
graph = {
0: {1:2}, # 0's neighbors
1: {0:2, 2:4, 3:1}, # 1's neighbors
2: {1:4, 3:5}, # 2's neighbors
3: {1:1, 2:5}, # 3's neighbors
4: {5:8}, # 4's neighbors
5: {4:8} # 5's neighbors
}
count, labels = count_and_label(graph)
print(f"Number of connected components: {count}")
print(f"Vertex labels: {labels}")

View File

@@ -0,0 +1,68 @@
"""
name: Nicholas Tamassia
Honor Code and Acknowledgments:
This work complies with the JMU Honor Code.
Comments here on your code and submission.
"""
import pprint
from collections import defaultdict
from math import sqrt
type Coordinate = tuple[float, float]
type CoordMap = dict[int, Coordinate]
type WeightedGraph = dict[int, dict[int, float]]
def distance(a: Coordinate, b: Coordinate) -> float:
x1, y1 = a
x2, y2 = b
return sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
def create_graph(coords: CoordMap) -> WeightedGraph:
graph: WeightedGraph = defaultdict(dict)
coord_items = coords.items()
for label1, coord1 in coord_items:
for label2, coord2 in coord_items:
if label1 == label2:
continue
weight = distance(coord1, coord2)
graph[label1][label2] = weight
graph[label2][label1] = weight
return graph
def calculate_cost(graph: WeightedGraph) -> float:
cost: float = 0.0
return cost
# All modules for CS 412 must include a main method that allows it
# to imported and invoked from other python scripts
def main():
n: int = int(input())
coords: CoordMap = {}
for i in range(0, n):
tokens = input().split()
coords[i] = (float(tokens[0]), float(tokens[1]))
weighted_graph = create_graph(coords)
pprint.pprint(weighted_graph)
# print(f"${cost:.1f}M")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,4 @@
3
0 1.5
1 0
0 0

View File

@@ -0,0 +1,9 @@
8
0 0
1 1
0 1
3 3
4 5
2 2
1 0
3 2