Linear Data Structures
Linear Data Structures
Linear data structures are types of data structures where data elements are arranged
sequentially, one after the other. Each element is connected to its previous and next
element in a single level, forming a linear sequence. Here are some common linear data
structures:
1. Array
2. Linked List
3. Stack
Description: A collection of elements that follows the Last In, First Out (LIFO)
principle.
Operations:
o Push: Add an element to the top of the stack.
o Pop: Remove the top element from the stack.
o Peek: Retrieve the top element without removing it.
Advantages: Easy to implement, useful for recursive algorithms.
Disadvantages: Limited access to elements (only the top element can be
accessed).
4. Queue
Description: A collection of elements that follows the First In, First Out (FIFO)
principle.
Types:
o Simple Queue: Basic FIFO structure.
o Circular Queue: The last position is connected back to the first position to
make a circle.
o Priority Queue: Elements are processed based on priority.
o Deque (Double-Ended Queue): Elements can be added or removed from
both ends.
Operations:
o Enqueue: Add an element to the end of the queue.
o Dequeue: Remove an element from the front of the queue.
o Front: Retrieve the front element without removing it.
o Rear: Retrieve the last element without removing it.
Advantages: Suitable for scheduling and managing tasks in order.
Disadvantages: Limited access to elements (only front and rear).
Arrays: Used in implementing other data structures like heaps, hash tables, etc.
Linked Lists: Used in dynamic memory allocation, implementing other data
structures like stacks, queues, etc.
Stacks: Used in expression evaluation, syntax parsing, backtracking algorithms,
etc.
Queues: Used in scheduling algorithms, managing resources in computing
environments, breadth-first search in graphs, etc.
These linear data structures form the foundation of more complex data structures and
algorithms, playing a crucial role in computer science and software engineering.
Characteristics:
Fixed Size: The size of an array is defined when it is created and cannot be
changed.
Random Access: Elements can be accessed directly using their index.
Homogeneous Elements: All elements in
Characteristics:
Fixed Size: The size of an array is defined when it is created and cannot be
changed.
Random Access: Elements can be accessed directly using their index.
Homogeneous Elements: All elements in the array are of the same data type.
Contiguous Memory Allocation: Elements are stored in contiguous memory
locations.
Example of an Array
plaintext
Copy code
Index: 0 1 2 3 4
Value: 10 20 30 40 50
1. Accessing Elements:
o Algorithm:
1. Read the index of the element to be accessed.
2. Return the element at the specified index.
o Example:
python
Copy code
arr = [10, 20, 30, 40, 50]
index = 2
element = arr[index] # element = 30
2. Updating Elements:
o Algorithm:
1. Read the index of the element to be updated.
2. Read the new value.
3. Update the element at the specified index with the new value.
o Example:
python
Copy code
arr = [10, 20, 30, 40, 50]
index = 2
new_value = 35
arr[index] = new_value # arr = [10, 20, 35, 40, 50]
3. Inserting Elements:
o Algorithm:
1. Check if there is space in the array.
2. Read the index where the new element is to be inserted.
3. Read the new value.
4. Shift elements from the specified index to the right.
5. Insert the new element at the specified index.
o Example:
python
Copy code
arr = [10, 20, 30, 40, 50]
index = 2
new_value = 25
arr.insert(index, new_value) # arr = [10, 20, 25, 30, 40, 50]
4. Deleting Elements:
o Algorithm:
1. Read the index of the element to be deleted.
2. Shift elements from the specified index to the left.
3. Reduce the size of the array by 1.
o Example:
python
Copy code
arr = [10, 20, 30, 40, 50]
index = 2
arr.pop(index) # arr = [10, 20, 40, 50]
python
Copy code
def print_array(arr):
print("Array:", arr)
# Initialize an array
arr = [10, 20, 30, 40, 50]
print("Initial Array")
print_array(arr)
# Access an element
index = 2
element = arr[index]
print(f"Element at index {index}: {element}")
# Update an element
new_value = 35
arr[index] = new_value
print(f"Array after updating element at index {index} to {new_value}")
print_array(arr)
# Insert an element
insert_index = 2
new_value = 25
arr.insert(insert_index, new_value)
print(f"Array after inserting {new_value} at index {insert_index}")
print_array(arr)
# Delete an element
delete_index = 2
arr.pop(delete_index)
print(f"Array after deleting element at index {delete_index}")
print_array(arr)
Output:
plaintext
Copy code
Initial Array
Array: [10, 20, 30, 40, 50]
Element at index 2: 30
Array after updating element at index 2 to 35
Array: [10, 20, 35, 40, 50]
Array after inserting 25 at index 2
Array: [10, 20, 25, 35, 40, 50]
Array after deleting element at index 2
Array: [10, 20, 35, 40, 50]
Definition: A linked list is a linear data structure in which elements are not stored in
contiguous memory locations. Instead, each element (called a node) contains a data
part and a reference (or link) to the next node in the sequence.
1. Singly Linked List: Each node points to the next node in the sequence.
2. Doubly Linked List: Each node points to both the next and the previous node.
3. Circular Linked List: The last node points back to the first node, forming a circle.
Characteristics
Dynamic Size: The size of a linked list can grow or shrink as needed.
Ease of Insertion/Deletion: Insertions and deletions can be done easily without
shifting elements, especially at the beginning of the list.
No Random Access: Elements must be accessed sequentially from the first node.
plaintext
Copy code
Head -> [10 | Next] -> [20 | Next] -> [30 | None]
1. Traversal:
o Algorithm:
1. Start from the head node.
2. While the current node is not None:
Process the data of the current node.
Move to the next node.
o Example:
python
Copy code
def traverse(head):
current = head
while current:
print(current.data)
current = current.next
2. Insertion:
o Algorithm:
1. Create a new node with the given data.
2. If inserting at the beginning:
Set the new node's next to the current head.
Update the head to the new node.
3. If inserting at a given position:
Traverse to the node after which the new node is to be
inserted.
Set the new node's next to the next of the current node.
Update the current node's next to the new node.
o Example:
python
Copy code
def insert(head, data, position):
new_node = Node(data)
if position == 0:
new_node.next = head
head = new_node
return head
current = head
for _ in range(position - 1):
current = current.next
new_node.next = current.next
current.next = new_node
return head
3. Deletion:
o Algorithm:
1. If deleting the head node:
Update the head to the next node.
2. If deleting a node at a given position:
Traverse to the node before the one to be deleted.
Update its next to the next of the node to be deleted.
o Example:
python
Copy code
def delete(head, position):
if position == 0:
head = head.next
return head
current = head
for _ in range(position - 1):
current = current.next
current.next = current.next.next
return head
python
Copy code
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def print_list(self):
current = self.head
while current:
print(current.data, end=" -> ")
current = current.next
print("None")
# Insert elements
ll.insert_at_beginning(30)
ll.insert_at_beginning(20)
ll.insert_at_beginning(10)
ll.insert_at_position(25, 2)
print("List after insertions:")
ll.print_list()
# Delete an element
ll.delete_at_position(2)
print("List after deletion:")
ll.print_list()
Output:
plaintext
Copy code
List after insertions:
10 -> 20 -> 25 -> 30 -> None
List after deletion:
10 -> 20 -> 30 -> None
Definition: A stack is a linear data structure that follows the Last In, First Out (LIFO)
principle. In a stack, the most recently added element is the first one to be removed.
Characteristics
Applications of Stacks
1. Push Operation:
o Algorithm:
1. Check if the stack is full (in case of a bounded stack).
2. If not, increment the top index.
3. Add the element at the top position.
o Example:
python
Copy code
stack = []
stack.append(element) # Push element onto stack
2. Pop Operation:
o Algorithm:
1. Check if the stack is empty.
2. If not, access the element at the top position.
3. Decrement the top index.
4. Return the accessed element.
o Example:
python
Copy code
if stack:
element = stack.pop() # Pop element from stack
3. Peek Operation:
o Algorithm:
1. Check if the stack is empty.
2. If not, access the element at the top position without removing it.
o Example:
python
Copy code
if stack:
top_element = stack[-1] # Peek at the top element
4. IsEmpty Operation:
o Algorithm:
1. Check if the stack size is zero.
o Example:
python
Copy code
is_empty = len(stack) == 0 # Check if stack is empty
5. Size Operation:
o Algorithm:
1. Return the size of the stack.
o Example:
python
Copy code
size = len(stack) # Get the size of the stack
python
Copy code
class Stack:
def __init__(self):
self.stack = []
def is_empty(self):
return len(self.stack) == 0
def pop(self):
if self.is_empty():
print("Stack is empty, cannot pop")
return None
return self.stack.pop()
def peek(self):
if self.is_empty():
print("Stack is empty, cannot peek")
return None
return self.stack[-1]
def size(self):
return len(self.stack)
def print_stack(self):
print("Stack:", self.stack)
Output:
plaintext
Copy code
Pushed 10 onto stack
Pushed 20 onto stack
Pushed 30 onto stack
Stack after pushing elements:
Stack: [10, 20, 30]
Top element: 30
Popped element: 30
Stack after popping an element:
Stack: [10, 20]
Is stack empty? False
Size of the stack: 2
This program demonstrates the basic operations on a stack: pushing, popping, peeking,
checking if the stack is empty, and determining the size of the stack.
Characteristics
Types of Queues
Applications of Queues
1. Enqueue Operation:
o Algorithm:
1. Check if the queue is full (in case of a bounded queue).
2. If not, add the element to the rear of the queue.
3. Update the rear index.
o Example:
python
Copy code
queue = []
queue.append(element) # Enqueue element to the rear
2. Dequeue Operation:
o Algorithm:
1. Check if the queue is empty.
2. If not, access the element at the front.
3. Remove the element from the front.
4. Update the front index.
o Example:
python
Copy code
if queue:
element = queue.pop(0) # Dequeue element from the front
3. Front Operation:
o Algorithm:
1. Check if the queue is empty.
2. If not, access the element at the front without removing it.
o Example:
python
Copy code
if queue:
front_element = queue[0] # Access the front element
4. IsEmpty Operation:
o Algorithm:
1. Check if the queue size is zero.
o Example:
python
Copy code
is_empty = len(queue) == 0 # Check if queue is empty
5. Size Operation:
o Algorithm:
1. Return the size of the queue.
o Example:
python
Copy code
size = len(queue) # Get the size of the queue
python
Copy code
class Queue:
def __init__(self):
self.queue = []
def is_empty(self):
return len(self.queue) == 0
def enqueue(self, item):
self.queue.append(item)
print(f"Enqueued {item} to the queue")
def dequeue(self):
if self.is_empty():
print("Queue is empty, cannot dequeue")
return None
return self.queue.pop(0)
def front(self):
if self.is_empty():
print("Queue is empty, cannot access front")
return None
return self.queue[0]
def size(self):
return len(self.queue)
def print_queue(self):
print("Queue:", self.queue)
# Enqueue elements
queue.enqueue(10)
queue.enqueue(20)
queue.enqueue(30)
print("Queue after enqueuing elements:")
queue.print_queue()
# Dequeue an element
dequeued_element = queue.dequeue()
print(f"Dequeued element: {dequeued_element}")
print("Queue after dequeuing an element:")
queue.print_queue()
Output:
plaintext
Copy code
Enqueued 10 to the queue
Enqueued 20 to the queue
Enqueued 30 to the queue
Queue after enqueuing elements:
Queue: [10, 20, 30]
Front element: 10
Dequeued element: 10
Queue after dequeuing an element:
Queue: [20, 30]
Is queue empty? False
Size of the queue: 2