Key Takeaways: A Graph is a non-linear data structure consisting of Vertices (nodes) and Edges (lines that connect the nodes). Graphs are the ultimate tool for representing real-world networks.
So far, we have looked at Trees. A Tree is actually just a highly restricted, specialized type of Graph!
In a Tree, data flows strictly from top to bottom (Root to Leaves), and cycles (loops) are forbidden.
In a Graph, there are no rules. Any node can connect to any other node, in any direction, forming massive, complex webs of data.
Graphs are arguably the most practical data structure in all of Computer Science. They power the apps you use every single day:
A Graph: Nodes can connect to any other node, allowing for cycles and complex relationships.
To talk about Graphs, you need to know the specific vocabulary:
Graphs come in several different "flavors" depending on how their edges behave.
In code, we don't draw circles and lines. We have to represent these connections using math and standard data structures. There are two standard ways to do this:
An Adjacency Matrix is a 2D Array (a grid).
1 in the grid. If there is no Edge, you place a 0.O(1) fast.O(V²) space), even if there are barely any connections!An Adjacency List uses a Hash Map (Dictionary) or an Array of Arrays.
O(V + E) space). This is what you will use 99% of the time in interviews.Let's write a simple Python script to create an Undirected, Unweighted Graph using an Adjacency List (a Python Dictionary).
class Graph: def __init__(self): # We use a dictionary to map each Vertex to a List of connections self.adj_list = {}<span style="color: #569CD6;">def</span> add_vertex(self, vertex): <span style="color: #7c0075;"># If the vertex doesn't exist, add it with an empty array</span> <span style="color: #569CD6;">if</span> vertex <span style="color: #569CD6;">not</span> <span style="color: #569CD6;">in</span> self.adj_list: self.adj_list[vertex] = [] <span style="color: #569CD6;">def</span> add_edge(self, v1, v2): <span style="color: #7c0075;"># Make sure both vertices exist in the graph</span> <span style="color: #569CD6;">if</span> v1 <span style="color: #569CD6;">in</span> self.adj_list <span style="color: #569CD6;">and</span> v2 <span style="color: #569CD6;">in</span> self.adj_list: <span style="color: #7c0075;"># Since this is Undirected, we must add a connection both ways!</span> self.adj_list[v1].<span style="color: #8f0000;">append</span>(v2) self.adj_list[v2].<span style="color: #8f0000;">append</span>(v1) <span style="color: #569CD6;">def</span> display(self): <span style="color: #7c0075;"># Loop through the dictionary and print connections</span> <span style="color: #569CD6;">for</span> vertex <span style="color: #569CD6;">in</span> self.adj_list: <span style="color: #8f0000;">print</span>(vertex, <span style="color: #CE9178;">"-->"</span>, self.adj_list[vertex])# Let's test our Graph! g = Graph()
# Add Cities (Vertices) g.add_vertex("New York") g.add_vertex("London") g.add_vertex("Paris") g.add_vertex("Tokyo")
# Add Flights (Edges) g.add_edge("New York", "London") g.add_edge("London", "Paris") g.add_edge("Paris", "Tokyo") g.add_edge("London", "Tokyo") # London connects to two places!
print("Graph Adjacency List:") g.display()
(Note: V stands for Vertices, and E stands for Edges).
When using an Adjacency List:
O(1). It is an instant Dictionary insertion.O(1). It is an instant Array append.O(V + E). You only store the nodes that exist and the actual connections between them. Highly efficient.In a standard Undirected Graph, what happens when you add an Edge between Node A and Node B?
Which of the following is the most memory-efficient way to represent a Graph in code?