Professional Documents
Culture Documents
Phase2 Report1 Updated
Phase2 Report1 Updated
Phase2 Report1 Updated
Homework <No.>
Date: <Date>
Connected Graph: A graph is termed connected if there's a path between every pair of vertices,
ensuring no vertex remains isolated. In real-world scenarios, this represents interconnectivity, be
it in a network of computers, cities, or social relations. The primary importance of such graphs
lies in ensuring accessibility between nodes, making it a critical factor in numerous algorithms,
including our TSP variant.
Weighted Graph: Unlike simple graphs where edges only represent connections, in a weighted
graph, edges bear specific weights. These weights can be equated to distances, cost, time, or any
quantitative measure defining the relationship between two vertices. In the domain of problems
like routing, shortest path, and particularly TSP, weights signify the cost or distance between
nodes, making the graph a precise representation of real-world problems where decisions are
based on quantitative measures.
Traveling Salesman Problem (TSP): One of the most studied optimization problems in
computational theory, TSP presents a scenario where a salesman is tasked with finding the
shortest route that lets him visit each city precisely once and then return to his starting point. The
essence of this problem is to minimize the total distance (or cost) traveled. As simple as it
sounds, the TSP is notorious for its complexity, belonging to the class of NP-hard problems. This
means that as the number of cities increases, the problem's solution space grows exponentially,
making it computationally challenging to find the exact optimal solution within a reasonable
time frame.
In this report, we examine a modified version of the TSP. The crux of our modification lies in the
stipulation that each city must be visited at least once, but it's permissible to forgo some streets
or connections and finishing at the city he starts from. This nuance introduces an intriguing layer
of complexity, as the backward path may not be the same as the forward path.
Algorithm:
first_vertex:
1. Calls the first_vertex function to begin the traversal and keeps processing the remaining
cities until all are visited.
2. Print the order of visited cities and total cost of the traveled path.
3. Tracks and prints the algorithm execution time.
4. Calls plot_graph function to plot the Graph
In summary, this is a heuristic-based algorithm to tackle a variant of the Traveling Salesman
Problem (TSP) where every city needs to be visited at least once without necessarily traversing
first_vertex Function:
Main Function:
• The while loop in the main function will run until all cities are visited. In the worst case,
this can be O(n) iterations. In each iteration, it invokes the rest_of_vertices function,
which has O(n^2) complexity. Therefore, the loop in the main function has a worst-case
time complexity of O(n^3).
So, the overall worst-case time complexity of the algorithm is O(n^3), where n is the number of
vertices (cities) in the graph.
Data Structures:
1. adjacency_list: A dictionary where key is a vertex and value is a list of dictionaries with
'to_vertex' and 'weight' representing the connected vertices and their weights.
2. vertices: A list containing the keys of adjacency_list.
Source Code:
import networkx as nx
import matplotlib.pyplot as plt
import time
import random
class Vertex:
def __init__(self, key):
self.key = key
self.arc = None
class Arc:
def __init__(self, destination, weight):
self.destination = destination
self.weight = weight
self.nextArc = None
class Graph:
def __init__(self):
self.vertices = []
fromPtr = self.find_vertex(fromKey)
toPtr = self.find_vertex(toKey)
if not fromPtr or not toPtr:
print("Vertex not found!")
return
def print_graph(self):
adjancy_list = {}
for vertex in self.vertices:
adjancy_list[vertex.key]=[]
print(f"{vertex.key} ->", end=" ")
arc = vertex.arc
while arc:
adjancy_list[vertex.key].append({'to_vertex':
arc.destination.key, 'weight': arc.weight})
print(f"({arc.destination.key}, {arc.weight})", end=" -> ")
arc = arc.nextArc
print("None")
return adjancy_list
def phase1_main():
g = Graph()
end_time = time.time()
print(f"\nRunning time of phase_1 algorithm: {end_time - start_time:.6f}
seconds")
return x
adjacency_list = phase1_main()
vertices = list(adjacency_list.keys())
num_vertices = len(vertices)
new_vertices = vertices
visited_vertices = []
cost = []
process_indicator =0
start_vertex = vertices[0]
last_vertex = ''
backward = {}
visited_vertices.append(start_vertex)
num_vertices -=1
backward [start_vertex] = {'back_vertices':[], 'back_cost':[0],
'total_back_cost': 0}
new_vertices.remove(start_vertex)
weights = []
min_weight=0
for i in adjacency_list[start_vertex]:
weights.append(i['weight'])
if len(weights) == 1 :
min_weight = weights[0]
else:
min_weight = min(weights)
next_vertex = ''
for i in adjacency_list[start_vertex]:
if i['weight'] == min_weight:
next_vertex = i['to_vertex']
cost.append(min_weight)
visited_vertices.append(next_vertex)
num_vertices -=1
new_vertices.remove(next_vertex)
parent_vertex = start_vertex
parent_cost = min_weight
backward [next_vertex] = {'back_vertices':[parent_vertex],
'back_cost':[parent_cost], 'total_back_cost': parent_cost}
return next_vertex,parent_vertex,parent_cost
def rest_of_vertices(next_vertex,parent_vertex,parent_cost):
global num_vertices, new_vertices, visited_vertices, cost,
process_indicator, last_vertex, backward
connected_vertices = []
for i in adjacency_list[next_vertex]:
connected_vertices.append(i['to_vertex'])
new_connected_vertices = []
for i in connected_vertices:
if i not in visited_vertices:
new_connected_vertices.append(i)
if next_vertex == parent_vertex:
for i in range(0, len(visited_vertices)):
if visited_vertices[i] == next_vertex:
parent_vertex = visited_vertices[i-1]
parent_cost = cost[i-1]
break
weights = []
min_weight=0
for i in adjacency_list[next_vertex]:
if i['to_vertex'] in new_connected_vertices:
weights.append(i['weight'])
if len(weights) == 0:
...
elif len(weights) == 1:
min_weight = weights[0]
else:
min_weight = min(weights)
weights2=[]
min_weight2=0
cost_second = 0
if min_weight > parent_cost:
for i in adjacency_list[parent_vertex]:
if i['to_vertex'] not in visited_vertices:
weights2.append(i['weight'])
if len(weights2) == 0:
...
elif len(weights2) == 1:
min_weight2 = weights2[0]
cost_second = min_weight2 + parent_cost
else:
min_weight2 = min(weights2)
cost_second = min_weight2 + parent_cost
if temp_cost > 0:
backward[next_vertex] = {'back_vertices':[parent_vertex],
'back_cost':[parent_cost], 'total_back_cost': parent_cost}
for i in range(0,len(backward
[parent_vertex]['back_vertices'])):
backward[next_vertex]['back_vertices'].append(backward
[parent_vertex]['back_vertices'][i])
backward[next_vertex]['back_cost'].append(backward
[parent_vertex]['back_cost'][i])
backward [next_vertex]['total_back_cost'] = backward
[parent_vertex]['total_back_cost'] + parent_cost
if num_vertices == 0 :
last_vertex = next_vertex
for i in range(0,len(backward
[last_vertex]['back_vertices'])):
visited_vertices.append(backward
[last_vertex]['back_vertices'][i])
cost.append(backward [last_vertex]['back_cost'][i])
process_indicator = 1
return next_vertex,parent_vertex,parent_cost
return next_vertex,parent_vertex,parent_cost
def plot_graph():
G = nx.Graph()
for vertex in adjacency_list:
for i in range(0, len(adjacency_list[vertex])):
node = vertex
neighbor = adjacency_list[vertex][i]['to_vertex']
weight = adjacency_list[vertex][i]['weight']
G.add_edge(node, neighbor, weight=weight)
pos= nx.circular_layout(G)
nx.draw(G, pos, with_labels=True, node_color='lightblue', node_size=500,
font_size=12, edge_color='gray', width=1)
def main():
v = num_vertices
start_time = time.time()
next_vertex,parent_vertex,parent_cost= first_vertex()
while True:
next_vertex,parent_vertex,parent_cost =
rest_of_vertices(next_vertex,parent_vertex,parent_cost)
if process_indicator == 1:
cost_as_string = [str(num) for num in cost]
print(f"\nShortest path = {', '.join(visited_vertices)}")
print(f"Path Cost = {' + '.join(cost_as_string)} = {sum(cost)}")
break
end_time = time.time()
print(f"\nAlgorithm Running time for a Graph of Size {v} is: ({end_time -
start_time:.6f}) seconds")
plot_graph()
main()
Example1:
Example3:
Conclusion:
Results Explanation:
Upon analyzing the provided algorithm, it is evident that while it aims to find an approximate
solution to our modified TSP, its computational demands increase substantially with the size of
the graph. As demonstrated by the plotted chart, there's a noticeable upward trend in the
execution time as the graph size enlarges, hinting at the algorithm's non-linear time complexity.
This observation aligns well with our analytical conclusion, which places the algorithm in a high
polynomial time complexity bracket.
It's worth noting that the world of algorithmic challenges, particularly optimization problems like
TSP, often demands a trade-off between accuracy and computational efficiency. Exact
algorithms might offer optimal solutions but are computationally infeasible for larger datasets.
On the other hand, heuristic or approximation algorithms, like the one explored in this report,
provide near-optimal solutions in a reasonable time frame.
Difficulties Faced:
Identifying the right heuristic and ensuring that all cities are visited were some challenges.
Handling edge cases, especially during backtracking, added complexity to the algorithm design.