pythonalgorithmgraphlinked-listadjacency-list

Graph - Adjacency List solved as Linked List


What does this line do?

node.next = self.graph[src]

EX: [1:2] here I know how to make 2 as a node, then make index 1 equal it, but what if i have [1:3] too, how to add 3 to 2?

Here's the full code of Adjacency List implementation

class AdjNode:
    def __init__(self, data):
        self.vertex = data
        self.next = None
        

class Graph:
    def __init__(self, vertices):
        self.vertices = vertices
        self.graph = [None] * self.vertices
        
    def add_edge(self, src, dest):
        node = AdjNode(dest)
#==================================================
        node.next = self.graph[src]
#==================================================
        self.graph[src] = node
        
        
    def print_graph(self):
        for i in range(self.vertices):
            print("Adjacency list of vertex {}\n {}".format(i,i), end="")
            temp = self.graph[i]
            while temp:
                print(" -> {}".format(temp.vertex), end="")
                temp = temp.next
            print(" \n")              
        

V = 5
graph = Graph(V)
graph.add_edge(0, 1)
graph.add_edge(0, 4)
graph.add_edge(1, 2)
graph.add_edge(1, 3)
graph.add_edge(1, 4)
graph.add_edge(2, 3)
graph.add_edge(3, 4)

graph.print_graph()

Output

Adjacency list of vertex 0
 0 -> 4 -> 1 

Adjacency list of vertex 1
 1 -> 4 -> 3 -> 2 

Adjacency list of vertex 2
 2 -> 3 

Adjacency list of vertex 3
 3 -> 4 

Adjacency list of vertex 4
 4 

Solution

  • EX: [1:2] here I know how to make 2 as a node, then make index 1 equal it, but what if i have [1:3] too, how to add 3 to 2?

    It may help to visualise the process. So let's say there are four nodes, numbered 0, 1, 2 and 3, then we have self.graph initialised as follows (boxes are accessed by index):

                   self.graph
                        ↓
    ┌─────────┬─────────┬─────────┬─────────┐
    │ 0: None │ 1: None │ 2: None │ 3: None │
    └─────────┴─────────┴─────────┴─────────┘
    

    Let's say graph.add_edge(1, 2) would be the first action on that graph, so then src is 1 and dst is 2. The first statement node = AdjNode(dest) will bring this state:

                   self.graph
                        ↓
    ┌─────────┬─────────┬─────────┬─────────┐
    │ 0: None │ 1: None │ 2: None │ 3: None │
    └─────────┴─────────┴─────────┴─────────┘
              
            ┌────────────┐
     node → │ vertex: 2  │
            │ next: None │ 
            └────────────┘
    

    The next statement node.next = self.graph[src] doesn't do much now, as it just assigns None to node.next, which is already there. Finally, graph[src] = node does make an important link:

                   self.graph
                        ↓
    ┌─────────┬─────────┬─────────┬─────────┐
    │ 0: None │ 1: ─┐   │ 2: None │ 3: None │
    └─────────┴─────│───┴─────────┴─────────┘
                    │
                    │
                    │
                    ▼
            ┌────────────┐
     node → │ vertex: 2  │
            │ next: None │ 
            └────────────┘
    

    Then the function returns, and node is no longer a name that is in scope.

    Now, let's look at what graph.add_edge(1, 3) does. So src is 1 again, but dst is 3. The usual node is created with node = AdjNode(dest) giving this state (with a new node name for the new node):

                   self.graph
                        ↓
    ┌─────────┬─────────┬─────────┬─────────┐
    │ 0: None │ 1: ─┐   │ 2: None │ 3: None │
    └─────────┴─────│───┴─────────┴─────────┘
      node          │
       ↓            │
     ┌────────────┐ │
     │ vertex: 3  │ │
     │ next: None │ │
     └────────────┘ │
                    ▼
            ┌────────────┐
            │ vertex: 2  │
            │ next: None │ 
            └────────────┘
    

    And now the "magic" assignment happens: node.next = self.graph[src]. This will copy the reference that is at self.graph[src] into node.next. In the image that means the arrow is copied so that they point to the same thing:

                   self.graph
                        ↓
    ┌─────────┬─────────┬─────────┬─────────┐
    │ 0: None │ 1: ─┐   │ 2: None │ 3: None │
    └─────────┴─────│───┴─────────┴─────────┘
      node          │
       ↓            │
     ┌────────────┐ │
     │ vertex: 3  │ │
     │ next: ─┐   │ │
     └────────│───┘ │
              ▼     ▼
            ┌────────────┐
            │ vertex: 2  │
            │ next: None │ 
            └────────────┘
    

    And finally, we execute graph[src] = node:

                   self.graph
                        ↓
    ┌─────────┬─────────┬─────────┬─────────┐
    │ 0: None │ 1: ─┐   │ 2: None │ 3: None │
    └─────────┴─────│───┴─────────┴─────────┘
            node    │
             ↓      ▼
          ┌────────────┐ 
          │ vertex: 3  │ 
          │ next: ─┐   │
          └────────│───┘
                   ▼
            ┌────────────┐
            │ vertex: 2  │
            │ next: None │ 
            └────────────┘
    

    So, after all this, the later destination node was prefixed to a linked list before the node(s) that were already in the list that started at self.graph[src].

    I hope this clarifies it.