In this section, we will explore some advanced data structures that are essential for efficient programming and problem-solving. These data structures go beyond the basic lists, tuples, dictionaries, and sets, and include more complex structures such as linked lists, stacks, queues, trees, and graphs.

Key Concepts

  1. Linked Lists
  2. Stacks
  3. Queues
  4. Trees
  5. Graphs

  1. Linked Lists

A linked list is a linear data structure where each element is a separate object, known as a node. Each node contains two items: the data and a reference (or link) to the next node in the sequence.

Example: Singly Linked List

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

class LinkedList:
    def __init__(self):
        self.head = None

    def append(self, data):
        new_node = Node(data)
        if not self.head:
            self.head = new_node
            return
        last_node = self.head
        while last_node.next:
            last_node = last_node.next
        last_node.next = new_node

    def print_list(self):
        current_node = self.head
        while current_node:
            print(current_node.data, end=" -> ")
            current_node = current_node.next
        print("None")

# Usage
ll = LinkedList()
ll.append(1)
ll.append(2)
ll.append(3)
ll.print_list()

Explanation

  • Node Class: Represents a single node in the linked list.
  • LinkedList Class: Manages the linked list, including methods to append data and print the list.

Exercise

Create a method prepend in the LinkedList class that adds a node at the beginning of the list.

  1. Stacks

A stack is a linear data structure that follows the Last In First Out (LIFO) principle. Elements are added and removed from the same end, known as the top of the stack.

Example: Stack Implementation Using List

class Stack:
    def __init__(self):
        self.stack = []

    def push(self, data):
        self.stack.append(data)

    def pop(self):
        if not self.is_empty():
            return self.stack.pop()
        return "Stack is empty"

    def peek(self):
        if not self.is_empty():
            return self.stack[-1]
        return "Stack is empty"

    def is_empty(self):
        return len(self.stack) == 0

# Usage
s = Stack()
s.push(1)
s.push(2)
s.push(3)
print(s.pop())  # Output: 3
print(s.peek())  # Output: 2

Explanation

  • push: Adds an element to the top of the stack.
  • pop: Removes and returns the top element of the stack.
  • peek: Returns the top element without removing it.
  • is_empty: Checks if the stack is empty.

Exercise

Implement a method size in the Stack class that returns the number of elements in the stack.

  1. Queues

A queue is a linear data structure that follows the First In First Out (FIFO) principle. Elements are added at the rear and removed from the front.

Example: Queue Implementation Using List

class Queue:
    def __init__(self):
        self.queue = []

    def enqueue(self, data):
        self.queue.append(data)

    def dequeue(self):
        if not self.is_empty():
            return self.queue.pop(0)
        return "Queue is empty"

    def is_empty(self):
        return len(self.queue) == 0

# Usage
q = Queue()
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
print(q.dequeue())  # Output: 1

Explanation

  • enqueue: Adds an element to the rear of the queue.
  • dequeue: Removes and returns the front element of the queue.
  • is_empty: Checks if the queue is empty.

Exercise

Implement a method size in the Queue class that returns the number of elements in the queue.

  1. Trees

A tree is a hierarchical data structure consisting of nodes, with a single node as the root and zero or more child nodes. Each child node can have zero or more child nodes, and so on.

Example: Binary Tree

class TreeNode:
    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

class BinaryTree:
    def __init__(self):
        self.root = None

    def insert(self, data):
        if self.root is None:
            self.root = TreeNode(data)
        else:
            self._insert(data, self.root)

    def _insert(self, data, node):
        if data < node.data:
            if node.left is None:
                node.left = TreeNode(data)
            else:
                self._insert(data, node.left)
        else:
            if node.right is None:
                node.right = TreeNode(data)
            else:
                self._insert(data, node.right)

    def inorder_traversal(self, node):
        if node:
            self.inorder_traversal(node.left)
            print(node.data, end=" ")
            self.inorder_traversal(node.right)

# Usage
bt = BinaryTree()
bt.insert(10)
bt.insert(5)
bt.insert(15)
bt.inorder_traversal(bt.root)  # Output: 5 10 15

Explanation

  • TreeNode Class: Represents a single node in the binary tree.
  • BinaryTree Class: Manages the binary tree, including methods to insert data and perform inorder traversal.

Exercise

Implement methods for preorder and postorder traversal in the BinaryTree class.

  1. Graphs

A graph is a collection of nodes (vertices) and edges connecting them. Graphs can be directed or undirected, and weighted or unweighted.

Example: Graph Representation Using Adjacency List

class Graph:
    def __init__(self):
        self.graph = {}

    def add_vertex(self, vertex):
        if vertex not in self.graph:
            self.graph[vertex] = []

    def add_edge(self, vertex1, vertex2):
        if vertex1 in self.graph and vertex2 in self.graph:
            self.graph[vertex1].append(vertex2)
            self.graph[vertex2].append(vertex1)  # For undirected graph

    def print_graph(self):
        for vertex in self.graph:
            print(f"{vertex}: {self.graph[vertex]}")

# Usage
g = Graph()
g.add_vertex(1)
g.add_vertex(2)
g.add_vertex(3)
g.add_edge(1, 2)
g.add_edge(1, 3)
g.print_graph()

Explanation

  • Graph Class: Manages the graph, including methods to add vertices and edges, and print the graph.

Exercise

Implement a method remove_edge in the Graph class that removes an edge between two vertices.

Conclusion

In this section, we covered advanced data structures including linked lists, stacks, queues, trees, and graphs. Understanding these data structures is crucial for efficient problem-solving and algorithm design. Practice implementing these structures and their associated methods to gain a deeper understanding and improve your programming skills.

Python Programming Course

Module 1: Introduction to Python

Module 2: Control Structures

Module 3: Functions and Modules

Module 4: Data Structures

Module 5: Object-Oriented Programming

Module 6: File Handling

Module 7: Error Handling and Exceptions

Module 8: Advanced Topics

Module 9: Testing and Debugging

Module 10: Web Development with Python

Module 11: Data Science with Python

Module 12: Final Project

© Copyright 2024. All rights reserved