Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ print(c) # array('i', [1, 2, 3, 4, 5])

3. **Array Reversal**

**Q3:**** Write a Python program to reverse an array without using slicing or the reverse() method.
**Q3:** Write a Python program to reverse an array without using slicing or the reverse() method.


4. **Insertion Operation**
Expand Down
174 changes: 174 additions & 0 deletions docs/python/Data_Structures/python-linked-list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Linked List in Python

A **Linked List** is a fundamental **linear data structure** where elements are **not** stored at contiguous memory locations (unlike arrays or Python lists). Instead, the elements, called **Nodes**, are linked together using **pointers** or **references**.

This structure allows for highly efficient **insertions** and **deletions** compared to arrays, where these operations can be slow.

---

## Structure of a Linked List

The linked list is built upon two core concepts:

1. **Node:** The basic building block, which contains:
* **Data:** The value stored.
* **Next Pointer (`next`):** The reference to the next node in the sequence.
2. **Head:** A pointer to the very first node in the list. It is the entry point for all operations. The last node's `next` pointer always points to **`None`**.

### Real-Life Analogy

Think of a **treasure hunt or a chain of clues**. Each clue (**Node**) holds the information (**Data**) and a direction to the next clue (**Next Pointer**). You must follow the chain from the beginning (**Head**) to find the end.

---

## Python Implementation: The Node Class

Since Python doesn't have a built-in linked list type, we define its structure using classes.

The `Node` class defines the element structure.

```python
class Node:
"""Represents a single element in the linked list."""
def __init__(self, data):
# Store the data
self.data = data
# Initialize the pointer to the next node
self.next = None
```

## Python Implementation: The LinkedList Class

The `LinkedList` class manages the list, primarily by keeping track of the `head`.

```python
class LinkedList:
"""Manages the linked list and its head pointer."""
def __init__(self):
# Initialize the list's entry point
self.head = None
```

### Traversal: Printing the List

To traverse, we start at the head and loop until the current node becomes None, updating the current node with its next pointer in each iteration.

```python
class LinkedList:
# __init__ and Node class definition here

def print_list(self):
current_node = self.head
print("List:", end=" ")
while current_node is not None:
print(current_node.data, end=" -> ")
current_node = current_node.next
print("None")
```
***Example***

```python
my_list = LinkedList()
my_list.head = Node(10)
second = Node(20)
third = Node(30)

# Linking the nodes: 10 -> 20 -> 30 -> None
my_list.head.next = second
second.next = third
my_list.print_list()
```

**Output:**

```
List: 10 -> 20 -> 30 -> None
```

### Insertion Operations

#### 1. Insertion at the Beginning (Prepend)

It only requires updating the head pointer.

```python
def insert_at_beginning(self, new_data):
# Create a new node
new_node = Node(new_data)

# Make the new node's next pointer point to the current head
new_node.next = self.head

# Update the list's head to point to the new node
self.head = new_node
```

***Example***

```python
my_list.insert_at_beginning(5)
my_list.print_list()
```

**Output:**

```
List: 5-> 10 -> 20 -> 30 -> None
```

#### 2. Insertion After a Node

```python
def insert_after(self, prev_node, new_data):
if prev_node is None:
print("Previous node cannot be None.")
return

# Create the new node
new_node = Node(new_data)

# Set new node's next to the previous node's next
new_node.next = prev_node.next

# Set the previous node's next to the new node
prev_node.next = new_node
```

***Example***

```python
my_list.insert_after(my_list.head, 15)
my_list.print_list()
```

**Output:**

```
List: 5-> 15-> 10 -> 20 -> 30 -> None
```

### Deletion Operation

Deleting the Head

```python
def delete_head(self):
if self.head is None:
return

# Move the head to the next node, which effectively "deletes" the old head
self.head = self.head.next
```

***Example***

```python
my_list.delete_head() # Deletes head (5)
my_list.print_list()
```

**Output:**

```
List: 15-> 10 -> 20 -> 30 -> None
```
95 changes: 95 additions & 0 deletions docs/python/Data_Structures/python-queue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Queue in Python

A **Queue** is a linear data structure that follows a specific order for all operations. This order is based on the **First-In, First-Out (FIFO)** principle.

Imagine a ticket line: the first person to join the line is the first person to be served and leave.

---

## Queue Operations

Queues have two distinct ends where operations occur: the **Rear** (for insertion) and the **Front** (for removal).

| Operation | Description | Analogy |
| :--- | :--- | :--- |
| **Enqueue** | Adds an element to the **Rear**. | Joining the back of the line. |
| **Dequeue** | Removes an element from the **Front**. | Leaving the front of the line. |
| **Peek** | Returns the front element without removing it. | Looking at the person next in line. |

---

## Queue Operations Using Python List

In the simplest Python implementation, we use a built-in **`list`** as the underlying storage. For a true FIFO queue:

* **Enqueue** is performed using `list.append()` (at the rear/end).
* **Dequeue** is performed using `list.pop(0)` (from the front/start).
* **Peek** is performed using list indexing `list[0]` (at the fromt/start).

### The Queue Class

```python
class Queue:
"""Implements a Queue using the FIFO principle with a Python list."""
def __init__(self):
# The storage container for the queue elements
self._items = []

def is_empty(self):
"""Check if the queue is empty."""
return not self._items

def enqueue(self, data):
"""Adds an element to the rear of the queue (list.append()). (O(1))"""
self._items.append(data)
print(f"Enqueued: {data}")

def dequeue(self):
"""Removes and returns the front element (list.pop(0)). (O(N))"""
if self.is_empty():
raise IndexError("Error: Cannot dequeue from an empty queue.")

# pop(0) removes the first item (the front of the queue)
return self._items.pop(0)

def peek(self):
"""Returns the front element without removing it."""
if self.is_empty():
raise IndexError("Error: Cannot peek at an empty queue.")

# Access the first item (0 index)
return self._items[0]
```

***Example***

```python
my_queue = Queue()
my_queue.enqueue("Task 1")
my_queue.enqueue("Task 2")
my_queue.enqueue("Task 3")

print("\nFront element (Peek):", my_queue.peek())

served_item = my_queue.dequeue()
print("Dequeued element:", served_item)

print("Queue Status:", my_queue._items)

my_queue.dequeue()
my_queue.dequeue()

# my_queue.dequeue() # Uncommenting this line will raise an IndexError
```

**Output**:

```
Enqueued: Task 1
Enqueued: Task 2
Enqueued: Task 3

Front element (Peek): Task 1
Dequeued element: Task 1
Queue Status: ['Task 2', 'Task 3']
```
99 changes: 99 additions & 0 deletions docs/python/Data_Structures/python-stack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Stack in Python

A **Stack** is a linear data structure that follows a specific order for all operations. This order is based on the **Last-In, First-Out (LIFO)** principle.

Imagine a stack of plates: you can only add a new plate to the top, and you can only remove the plate that is currently on the top.

---

## Stack Operations

A stack has three primary operations, all of which occur at the **Top** of the stack:

1. `Push`: **Adds** an element to the top of the stack.
2. `Pop`: **Removes** and returns the element from the top of the stack.
3. `Peek`: **Returns** the element at the top.

| Operation | Description | Analogy |
| :--- | :--- | :--- |
| **Push** | Add element to the **Top** | Placing a plate on top of the stack |
| **Pop** | Remove element from the **Top** | Taking the top plate off the stack |
| **Peek** | View the element at the **Top** | Looking at the top plate |

---

## Stack Operations Using Python
ListIn the simplest Python implementation, we use a built-in list as the underlying storage. For a true LIFO stack, all primary operations are performed at the end of the list:

* **Push** is performed using `list.append()`.
* **Pop** is performed using `list.pop()`.
* **Peek** is performed using list indexing `list[-1]`.

---
## The Stack Class

```python
class Stack:
"""Implements a Stack using the LIFO principle with a Python list."""
def __init__(self):
# The storage container for the stack elements
self._items = []

def is_empty(self):
"""Check if the stack is empty."""
return not self._items

def push(self, data):
"""Adds an element to the top of the stack (list.append())."""
self._items.append(data)
print(f"Pushed: {data}")

def pop(self):
"""Removes and returns the top element (list.pop())."""
if self.is_empty():
raise IndexError("Error: Cannot pop from an empty stack.")

# Pop removes the last item (the top of the stack)
return self._items.pop()

def peek(self):
"""Returns the top element without removing it."""
if self.is_empty():
raise IndexError("Error: Cannot peek at an empty stack.")

# Access the last item (-1 index)
return self._items[-1]
```

***Example***

```python
my_stack = Stack()
my_stack.push("A")
my_stack.push("B")
my_stack.push("C")

print("\nTop element (Peek):", my_stack.peek())

removed_item = my_stack.pop()
print("Popped element:", removed_item)

print("Current size:", len(my_stack._items))

my_stack.pop()
my_stack.pop()

# my_stack.pop() # Uncommenting this line will raise an IndexError
```

**Output**:

```
Pushed: A
Pushed: B
Pushed: C

Top element (Peek): C
Popped element: C
Current size: 2
```
Loading
Loading