Modelling with Graphs

A set of algorithms on graphs

In this coursework I implemented a range of graph algorithms using Python and NetworkX.

Basic greedy colouring

This can be found in the file Part A/greedy_col_basic.py

This is designed to:

  • Visit the vertices of the graph sequentially
  • At every step assign in a greedy fashion the smallest possible colour
  • Output the constructed colouring and the number of different colours in the colouring

For this I implemented two functions

find_smallest_color - Given the graph and a given vertex, return the smallest colour that can be assigned

def find_smallest_color(G,i):
    n = len(G.nodes())
    keys = G.adj[i]
    list = []
    for key in keys:
        list.append(G.nodes[key]['color'])
    color=1
    while color in list:
        color += 1
    return color

greedy - Actually perform the greedy algorithm

def greedy(G):
    global kmax
    for i in G.nodes():
        G.nodes[i]['color'] = find_smallest_color(G,i)
    kmax = max(G.nodes[i]['color'] for i in G.nodes)

    for i in G.nodes():
        print('vertex', i, ': color', G.node[i]['color'])
    print()
    print('The number of colors that Greedy computed is:', kmax)

Variation on Greedy Colouring

This can be found in the file Part A/greedy_col_variation.py

Instead of visiting the nodes sequentially, this should visit the vertices such that the next visited node is adjacent to at least one visited node. Amongst the list of adjacent vertices, the smallest should be chosen. Then the rest of the algorithm will be carried out as before.

For this the function find_smallest_color doesn't require any editing, but the function greedy is changed to look like this

def greedy(G):
    n = len(G.nodes())
    global kmax
    global visited_counter
    G.nodes[1]['color']=1
    G.nodes[1]['visited']='yes'
    for i in range(1,len(G)):
        next_vertex=find_next_vertex(G)
        G.nodes[next_vertex]['color']=find_smallest_color(G,next_vertex)
        G.nodes[next_vertex]['visited'] = 'yes'
    visited = nx.get_node_attributes(G, 'color')
    kmax=(max([v for k, v in visited.items()]))
    print()
    for i in G.nodes():
        print('vertex', i, ': color', G.node[i]['color'])
    print()
    print('The number of colors that Greedy computed is:', kmax)
    print()

This also has the newly implemented function find_next_vertex which provides the index of the vertex to choose next

def find_next_vertex(G):
    visited = nx.get_node_attributes(G, 'visited')
    visited=[k for k, v in visited.items() if v=="yes"]
    adjacent=set()
    for item in visited:
        adjacent=adjacent|(set(G.neighbors(item)))
    visited=set()
    for item in adjacent:
        if G.nodes[item]['visited']=='yes':
            visited.add(item)
    adjacent-=visited
    return min(adjacent)

Breadth First Search

This can be found in the file Part B/breadth_first.py

This performs a breadth first search from a given node a to a given node b

This is implemented in just one function

def bfs(G,a,b):
    G.add_nodes_from(G.nodes(), label = -1) # initialization of all labels
    G.node[a]['label'] = 0
    i=0
    while G.node[b]['label']==-1:
        vertex_list=[node for node in G.nodes() if G.node[node]['label']==i]
        for u in vertex_list:
            adjacent=list(G.adj[u])
            for v in adjacent:
                G.node[v]['label']=i+1
        i+=1
    return G.node[b]['label']

Depth first search

This can be found in the file Part B/depth_first_pair_nodes.py

This performs a depth first search starting from a given node a to a given node b

At every step, among the neighbours of the currently visited vertex, the algorithm chooses the smallest one to continue the exploration from it.

In addition, it adds a label to each of the visited vertices with the path length

This is implemented with just one function

def dfs(G,a,b,u):
    if a==u:
        G.node[u]['label']=0
    else:
        G.node[u]['label']=G.node[a]['label']+1
    G.node[u]['visited'] = 'yes'
    print(u)
    if u==b:
        return
    sort_list = list(G.neighbors(u))
    sort_list.sort()
    for v in sort_list:
        if G.node[v]['visited'] == 'no':
            dfs(G,u,b, v)

Diameter

The code for this can be found in the file Part B/Diameter.py

The diameter is the greatest distance between any pair of vertices in the input graph. This is implemented with the breadth first algorithm seen before, along with an additional function

def max_distance(G):
    max=0
    nodes=list(G.nodes())
    pairs=[[i,j] for i in nodes for j in nodes]
    for i in pairs:
        distance=bfs(G,i[0],i[1])
        if distance>max:
            max=distance
    return max