Graph – Adjacency List solved as Linked List

Question:

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 
Asked By: Heba Allah. Hashim

||

Answers:

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.

Answered By: trincot