DSA with Python
DSA with Python
Training report on
“DSA with Python”
submitted
in partial fulfilment
for the award of the Degree of
Bachelor of Technology
In Department of Computer Science & Engineering
(With specialisation in Computer Science & Engineering)
ii
CERTIFICATE
ACKNOWLEDGEMENT
iv
ABSTRACT
We have delved into the mastery of various algorithms, from sorting techniques like bubble
sort, merge sort, and quick sort, to search algorithms such as linear and binary search. We
explored the realms of graph traversal with depth-first and breadth-first searches, and
embraced the power of optimization with Dijkstra's and Prim's algorithms. Through the lens
of divide and conquer, greedy strategies, and backtracking, we learned to tackle complex
problems with clarity and precision.
v
TABLE OF CONTENTS
1.4 OOP 5
2.3 Arrays 16
3 DATA STRUCTURE 19
3.1 Introduction 19
3.3 Stack 21
3.4 Queue 23
3.5 Hash 27
3.6 Tree 28
3.7 Graph 30
3.8 Heap 31
4 ALGORITHMS 35
4.1 Introduction 35
4.7 Backtracking 45
CONCLUSION 49
REFERENCE 50
vii
Chapter – 1
INTRODUCTION TO PYTHON
Python is a high-level, interpreted programming language known for its readability and
simplicity. Its syntax allows developers to write code that is easy to understand and
maintain. Python's versatility and wide range of libraries make it a popular choice for
various applications, including web development, data analysis, artificial intelligence,
scientific computing, and more.
1
Multiplication (*): result = x * y
Division (/): result = x / y
Modulus (%): remainder = x % y
Comparison Operations:
Equal to (==): is_equal = (x == y)
Not equal to (!=): is_not_equal = (x != y)
Greater than (>): is_greater = (x > y)
Less than (<): is_lesser = (x < y)
Logical Operations:
And (and): result = (x > 0) and (y < 10)
Or (or): result = (x > 0) or (y < 0)
Not (not): result = not (x > y)
Syntax:
if condition:
# Code to execute if the condition is true
elif another_condition:
2
# Code to execute if another_condition is true
else:
# Code to execute if none of the conditions are true
Example:
x = 10
if x > 0:
print("x is positive")
elif x == 0:
print("x is zero")
else:
print("x is negative")
1.2.2 Loops
Loops are used to repeatedly execute a block of code as long as a condition is true.
For Loop: A for loop is used for iterating over a sequence (such as a list,
tuple, dictionary, set, or string).
Syntax:
for item in iterable:
# Code to execute for each item
Example:
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
While Loop: A while loop will repeatedly execute a block of code as long as
the condition is true.
Syntax:
while condition:
# Code to execute as long as the condition is
true
Example:
i = 1
while i < 6:
print(i)
i += 1
3
Try-Except Block: The try block lets you test a block of code for errors, the
except block lets you handle the error.
Syntax:
try:
# Code that might raise an exception
except SomeException as e:
# Code to handle the exception
else:
# Code to execute if no exception is raised
finally:
# Code that will always execute, regardless of
an exception
Example:
try:
x = 10 / 0
except ZeroDivisionError as e:
print("Cannot divide by zero:", e)
else:
print("No error occurred")
finally:
print("Execution completed")
Syntax:
def function_name(parameters):
# code block
return result
Example:
def greet(name):
return f"Hello, {name}!"
print(greet("Alice"))
4
1.3.2 Importing Modules
Modules are files containing Python code (variables, functions, classes) that you can
import and use in your programs. Python has a rich standard library, and you can
also create your own modules.
Syntax:
import module_name
Example:
import math
print(math.sqrt(16)) # Outputs: 4.0
1.4 OOP
Object-Oriented Programming (OOP) is a programming paradigm that uses objects and
classes to create models based on the real world. It’s useful for organizing complex
programs, improving reusability, and enhancing code readability. By encapsulating data
and functions into objects, OOP promotes modularity and helps in managing the
complexity of software systems by allowing for code to be more easily maintained and
extended.
5
1.4.1 Classes and Objects
Classes are blueprints for creating objects. A class defines a set of attributes and
methods that the created objects will have. Objects are instances of a class.
Syntax:
class ClassName:
# Attributes and methods
def __init__(self, attribute1, attribute2):
self.attribute1 = attribute1
self.attribute2 = attribute2
def method(self):
# Method definition
pass
Example:
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def bark(self):
return f"{self.name} says woof!"
1.4.2 Inheritance
Inheritance allows one class (child class) to inherit attributes and methods from
another class (parent class). This promotes code reusability and hierarchical
classification, making it easier to manage and understand the code structure. It also
supports polymorphism, enabling objects to be treated as instances of their parent
class, which enhances flexibility. Moreover, inheritance helps in reducing
redundancy by allowing common functionality to be defined in a base class.
6
Syntax:
class ParentClass:
# Parent class definition
class ChildClass(ParentClass):
# Child class definition
Example:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal):
def speak(self):
return f"{self.name} says woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Outputs: Buddy says woof!
print(cat.speak()) # Outputs: Whiskers says meow!
1.4.3 Encapsulation
Encapsulation is the process of wrapping data (attributes) and methods into a single
unit, a class. It also involves restricting access to certain details of an object.
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private attribute
def deposit(self, amount):
7
self.__balance += amount
def withdraw(self, amount):
if amount <= self.__balance:
self.__balance -= amount
else:
print("Insufficient funds")
def get_balance(self):
return self.__balance
account = BankAccount(1000)
account.deposit(500)
account.withdraw(200)
print(account.get_balance()) # Outputs: 1300
1.4.4 Polymorphism
Polymorphism allows methods to do different things based on the object it is acting
upon, even though they share the same name.
class Bird:
def fly(self):
return "Birds can fly"
class Penguin(Bird):
def fly(self):
return "Penguins cannot fly"
bird = Bird()
penguin = Penguin()
print(bird.fly()) # Outputs: Birds can fly
print(penguin.fly()) # Outputs: Penguins cannot fly
8
1.5 File Handling
File handling is an essential part of programming that allows you to read from, write to, and
manipulate files on your system. It’s crucial for various applications, such as data analysis,
logging, and more.
Syntax:
file = open('filename', 'mode')
Example:
file = open('example.txt', 'r')
content = file.read()
print(content)
file.close()
Using the with statement: The with statement is often used for file
operations because it ensures that the file is properly closed after its suite
finishes.
with open('example.txt', 'r') as file:
content = file.read()
print(content)
9
Appending to a File: Appending adds new content to the end of the file
without overwriting existing content.
with open('example.txt', 'a') as file:
file.write("\nThis is an appended line.")
10
for row in reader:
print(dict(row))
Writing to CSV Files from a Dictionary:
import csv
data = [
{'Name': 'Alice', 'Age': 25, 'City': 'New
York'},
{'Name': 'Bob', 'Age': 30, 'City': 'San
Francisco'}
]
writer.writeheader()
for row in data:
writer.writerow(row)
11
Chapter – 2
On the other hand, tuples are enclosed in parentheses and are immutable, meaning once they
are created, their contents cannot be altered. This immutability provides a form of integrity
and security, ensuring that the data cannot be changed accidentally or intentionally, which
can be crucial for maintaining consistent data states. Tuples are often used to represent fixed
collections of items, such as coordinates, RGB colour values, or any data set that should
remain constant throughout the program. The immutability of tuples can lead to more
predictable and bug-free code, as developers can be certain that the data will not be altered.
Additionally, because they are immutable, tuples can be used as keys in dictionaries, which
require immutable types. This makes tuples suitable for scenarios where constant and
unchanging data is necessary, enhancing both the readability and reliability of the code.
12
2.1.2 Basic Operations
Both lists and tuples support common operations such as indexing, slicing, and
iteration.
Indexing: You can access individual elements using an index. Indexing
starts from 0.
print(my_list[0]) # Outputs: 1
print(my_tuple[1]) # Outputs: 2
Slicing: You can access a range of elements using slicing.
print(my_list[1:3]) # Outputs: [2, 3]
print(my_tuple[:4]) # Outputs: (1, 2, 3, 4)
Iteration: You can loop through the elements using a for loop.
for item in my_list:
print(item)
for item in my_tuple:
print(item)
13
pop(): Removes and returns the item at a given position. If no index is
specified, removes and returns the last item.
my_list.pop()
print(my_list) # Outputs: [1, 2, 'a', 4, 5]
Tuple Methods:
count(): Returns the number of times a specified value occurs in a tuple.
print(my_tuple.count(2)) # Outputs: 1
index(): Searches the tuple for a specified value and returns the position
of where it was found.
print(my_tuple.index(3)) # Outputs: 2
Sets, on the other hand, are collections of unique elements, which makes them ideal for
operations involving membership tests, duplicates removal, and mathematical operations
like unions, intersections, and differences. In the context of machine learning, sets are often
used to handle unique elements such as in feature selection processes, where ensuring that
each feature is considered only once is crucial. Additionally, sets are employed to efficiently
manage and analyze datasets by quickly identifying and eliminating duplicates, thus
maintaining data integrity. Both dictionaries and sets contribute significantly to the
robustness and efficiency of programming solutions, providing the tools needed to handle
various data-centric tasks with precision and ease. Their distinct properties—dictionaries
with their key-value mappings and sets with their uniqueness constraints—complement
each other and enhance the flexibility and performance of Python applications in both
everyday programming and specialized fields like machine learning.
14
2.2.1 Creating Dictionaries and Sets
Dictionaries: Dictionaries are collections of key-value pairs. Each key maps
to a specific value, and keys must be unique. Dictionaries are created using
curly braces {} with a colon separating keys and values.
my_dict = {'name': 'Alice', 'age': 25, 'city': 'New
York'}
Sets: Sets are unordered collections of unique elements. Sets are created
using curly braces {} or the set() function.
my_set = {1, 2, 3, 4, 5}
Removing Elements: You can remove elements using the del statement
or pop() method.
del my_dict['city'] # Removes the key 'city'
Sets:
Adding Elements: Use the add() method to add elements to a set.
my_set.add(6) # Adds 6 to the set
15
2.2.3 Dictionary and Set Methods
Dictionary Methods:
keys(): Returns a view object that displays a list of all the keys in the
dictionary.
print(my_dict.keys()) # Outputs:
dict_keys(['name', 'age'])
values(): Returns a view object that displays a list of all the values in the
dictionary.
print(my_dict.values()) # Outputs:
dict_values(['Alice', 26])
Set Methods:
union(): Returns a new set containing all elements from both sets.
union_set = my_set.union(another_set) # {1, 2,
4, 5, 6, 7}
difference(): Returns a new set containing elements that are in the first
set but not in the second.
difference_set = my_set.difference(another_set)
# {1, 2}
2.3 Arrays
Arrays are one of the most fundamental data structures in computer science. They provide a
way to store a fixed-size sequential collection of elements of the same type. Arrays are
widely used because they offer efficient access to elements by their index and support
various operations that are foundational for many algorithms.
16
2.3.1 Creating Arrays in Python
The closest native data structure to arrays is the list, but for more efficiency with
numerical operations, the array module or NumPy arrays are often used.
Using the array Module:
import array
# Modifying elements
arr[1] = 10
print(arr) # Outputs: array('i', [1, 10, 3, 4, 5])
# Modifying elements
arr[1] = 10
print(arr) # Outputs: [ 1 10 3 4 5]
17
# Performing operations on arrays
print(arr + 2) # Outputs: [ 3 12 5 6 7]
print(arr * 2) # Outputs: [ 2 20 6 8 10]
18
Chapter – 3
DATA STRUCTURE
3.1 Introduction
Data structures are specialized formats for organizing, processing, and storing data.
19
3.2 Linked List
A linked list is a linear data structure where elements are stored in nodes. Each node
contains two parts: data and a reference (or link) to the next node in the sequence.
Singly Linked List
Doubly Linked List
Circular Linked List
20
3.2.3 Circular Linked List
In a circular linked list, the last node points back to the first node, forming a circle. It
can be singly or doubly linked.
(Using Singly Circular)
class Node:
def __init__(self, data):
self.data = data
self.next = None
class CircularLinkedList:
def __init__(self):
self.head = None
3.3 Stack
A stack is a linear data structure that follows a particular order for operations. The order
may be LIFO (Last In First Out) or FILO (First In Last Out).
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
if not self.is_empty():
21
return self.items.pop()
def peek(self):
if not self.is_empty():
return self.items[-1]
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
# Example usage
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.peek()) # Output: 3
print(stack.pop()) # Output: 3
print(stack.pop()) # Output: 2
print(stack.size()) # Output: 1
22
Pop:
Removes and returns the top element of the stack.
Time Complexity: O(1) (since removing an element from the end of the
list is a constant-time operation).
Peek:
Returns the top element of the stack without removing it.
Time Complexity: O(1) (since accessing the last element of the list is a
constant-time operation).
3.4 Queue
A queue is a linear data structure that follows the First In, First Out (FIFO) principle. This
means that the first element added to the queue will be the first one to be removed.
Simple Queue
Circular Queue
Priority Queue
23
def enqueue(self, item):
self.queue.append(item)
def dequeue(self):
if not self.is_empty():
return self.queue.pop(0)
def is_empty(self):
return len(self.queue) == 0
def front(self):
if not self.is_empty():
return self.queue[0]
# Example usage
queue = SimpleQueue()
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
print(queue.front()) # Output: 1
print(queue.dequeue()) # Output: 1
print(queue.dequeue()) # Output: 2
print(queue.is_empty()) # Output: False
24
self.rear = -1
def enqueue(self, item):
if ((self.rear + 1) % self.max_size ==
self.front):
print("Queue is full")
elif self.front == -1:
self.front = 0
self.rear = 0
self.queue[self.rear] = item
else:
self.rear = (self.rear + 1) % self.max_size
self.queue[self.rear] = item
def dequeue(self):
if self.front == -1:
print("Queue is empty")
elif self.front == self.rear:
temp = self.queue[self.front]
self.front = -1
self.rear = -1
return temp
else:
temp = self.queue[self.front]
self.front = (self.front + 1) % self.max_size
return temp
def is_empty(self):
return self.front == -1
def front(self):
if self.is_empty():
return None
25
return self.queue[self.front]
# Example usage
circular_queue = CircularQueue(5)
circular_queue.enqueue(1)
circular_queue.enqueue(2)
circular_queue.enqueue(3)
print(circular_queue.front()) # Output: 1
print(circular_queue.dequeue()) # Output: 1
print(circular_queue.dequeue()) # Output: 2
print(circular_queue.is_empty()) # Output: False
class PriorityQueue:
def __init__(self):
self.queue = []
def enqueue(self, item, priority):
heapq.heappush(self.queue, (priority, item))
def dequeue(self):
if not self.is_empty():
return heapq.heappop(self.queue)[1]
def is_empty(self):
return len(self.queue) == 0
def front(self):
26
if not self.is_empty():
return self.queue[0][1]
# Example usage
priority_queue = PriorityQueue()
priority_queue.enqueue('A', 2)
priority_queue.enqueue('B', 1)
priority_queue.enqueue('C', 3)
print(priority_queue.front()) # Output: B
print(priority_queue.dequeue()) # Output: B
print(priority_queue.dequeue()) # Output: A
print(priority_queue.is_empty()) # Output: False
3.5 Hash
A hash table is a data structure that maps keys to values. It uses a hash function to compute
an index into an array of buckets or slots, from which the desired value can be found. Hash
tables are highly efficient for lookups, insertions, and deletions.
def simple_hash(key, size):
return hash(key) % size
size = 10
index = simple_hash("example", size)
print(index) # This will print the index for the key
"example"
27
3.5.2 Collision Resolution Techniques
Since multiple keys can hash to the same index (a situation known as a collision), we
need ways to handle these collisions. The two primary techniques are chaining and
open addressing.
Chaining: In chaining, each bucket contains a linked list (or another
secondary data structure) of all elements that hash to the same index. This
way, multiple values can be stored at each index.
Open Addressing: In open addressing, when a collision occurs, the
algorithm searches for the next available slot within the table itself. There
are several strategies for open addressing, including linear probing,
quadratic probing, and double hashing.
3.6 Tree
A tree is a non-linear hierarchical data structure that consists of nodes connected by edges.
Each node contains a value, and nodes are organized in a hierarchical manner. The topmost
node is called the root, and each node has zero or more child nodes.
Binary Tree
Binary Search Tree (BST)
28
3.6.2 Binary Search Tree (BST)
A binary search tree (BST) is a binary tree with the additional property that for any
given node, the value of the left child is less than the value of the node, and the value
of the right child is greater than the value of the node. This property makes searching
operations efficient.
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
class BinarySearchTree:
def __init__(self):
self.root = None
def insert(self, data):
if self.root is None:
self.root = Node(data)
else:
self._insert(self.root, data)
def _insert(self, current_node, data):
if data < current_node.data:
if current_node.left is None:
current_node.left = Node(data)
else:
self._insert(current_node.left, data)
elif data > current_node.data:
if current_node.right is None:
current_node.right = Node(data)
else:
self._insert(current_node.right, data)
29
3.6.3 Traversal Method
Tree traversal methods are used to visit all the nodes in a tree and perform an
operation (e.g., printing the node's value) on each node. The three common traversal
methods for binary trees are in-order, pre-order, and post-order.
In-order Traversal:
Traverse the left subtree
Visit the root node
Traverse the right subtree
Pre-order Traversal:
Visit the root node
Traverse the left subtree
Traverse the right subtree
Post-order Traversal:
Traverse the left subtree
Traverse the right subtree
Visit the root node
3.7 Graph
A graph is a data structure that consists of a finite set of vertices (or nodes) and a set of
edges connecting them. Graphs are used to model pairwise relations between objects.
Directed Graph
Undirected Graph
30
3.7.2 Undirected Graph
In an undirected graph, edges do not have a direction. Each edge is an unordered
pair of vertices, representing a two-way relationship.
A - B – C
In this example, you can travel between A and B or B and C in either direction.
3.8.3 Representation
Graphs can be represented in several ways, with the most common being the
adjacency matrix and adjacency list.
Adjancency Matrix: An adjacency matrix is a 2D array of size V x V
where V is the number of vertices. If there is an edge from vertex i to vertex
j, then the matrix at position (i, j) will be 1 (or the weight of the edge if it's
weighted). Otherwise, it will be 0.
Adjacency List: An adjacency list is an array of lists. The array size is equal
to the number of vertices. Each entry i in the array contains a list of vertices
to which vertex i is connected.
3.8 Heap
A heap is a specialized tree-based data structure that satisfies the heap property. In a heap,
for every node i, the value of i is either greater than or equal to (in a max-heap) or less than
or equal to (in a min-heap) the value of its children, if they exist.
31
Max-Heap: In a max-heap, the value of the parent node is always greater
than or equal to the values of its children. The root node has the largest value
5
/ \
3 4
/ \
1 2
# Example usage
min_heap = MinHeap()
min_heap.insert(3)
32
min_heap.insert(1)
min_heap.insert(6)
min_heap.insert(5)
print(min_heap.heap) # Output: [1, 3, 6, 5]
Delete (Extract Min/Max): Deleting the root element from a heap involves
removing the root and replacing it with the last element in the heap. The heap
property is then restored by comparing the new root with its children and
swapping if necessary. This process is called "heapifying down".
(Max-Heap)
def delete_min(self):
if len(self.heap) == 0:
return None
if len(self.heap) == 1:
return self.heap.pop()
root = self.heap[0]
self.heap[0] = self.heap.pop()
self._heapify_down(0)
return root
def _heapify_down(self, index):
smallest = index
left_child = 2 * index + 1
right_child = 2 * index + 2
if left_child < len(self.heap) and
self.heap[left_child] < self.heap[smallest]:
smallest = left_child
if right_child < len(self.heap) and
self.heap[right_child] < self.heap[smallest]:
smallest = right_child
if smallest != index:
33
self.heap[index], self.heap[smallest] =
self.heap[smallest], self.heap[index]
self._heapify_down(smallest)
Peek: Peeking at the heap returns the root element without removing it. In a
min-heap, this is the smallest element; in a max-heap, this is the largest
element.
def peek(self):
if len(self.heap) > 0:
return self.heap[0]
return None
34
Chapter – 4
ALGORITHMS
4.1 Introduction
An algorithm is a step-by-step procedure or formula for solving a problem. They are
essential building blocks in computer science and are used to automate tasks and solve
complex problems efficiently.
Search Algorithms
Divide and Conquer
Sorting Algorithms
Greedy Algorithms
Backtracking
Graph Algorithms
Bit Manipulation
35
Optimization: Helps in identifying potential bottlenecks and optimizing the
code.
Resource Management: Ensures efficient use of computational resources.
36
4.3 Search Algorithms
Used to search for an element within a data structure.
Linear Search
Binary Search
Depth-First Search (DFS)
Breadth-First Search (BFS)
37
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1 # Return -1 if the target is not found
# Example usage
arr = [1, 2, 3, 4, 5]
target = 3
print(binary_search(arr, target)) # Output: 2
# Example usage
graph = {
'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F'],
38
'D': [],
'E': ['F'],
'F': []
}
dfs(graph, 'A') # Output: A B D E F C
# Example usage
graph = {
'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F'],
'D': [],
39
'E': ['F'],
'F': []
}
bfs(graph, 'A') # Output: A B C D E F
40
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
# Example usage
arr = [64, 34, 25, 12, 22, 11, 90]
print(bubble_sort(arr)) # Output: [11, 12, 22, 25, 34,
64, 90]
Time Complexity:
Worst and Average Case: O(n2)
Best Case: O(n) (when the array is already sorted)
41
arr[k] = left_half[i]
i += 1
else:
arr[k] = right_half[j]
j += 1
k += 1
while i < len(left_half):
arr[k] = left_half[i]
i += 1
k += 1
while j < len(right_half):
arr[k] = right_half[j]
j += 1
k += 1
return arr
# Example usage
arr = [38, 27, 43, 3, 9, 82, 10]
print(merge_sort(arr)) # Output: [3, 9, 10, 27, 38, 43,
82]
Time Complexity:
Worst, Average, and Best Case: O(n log n)
42
def partition(arr, low, high):
pivot = arr[high]
i = low - 1
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
# Example usage
arr = [10, 7, 8, 9, 1, 5]
print(quick_sort(arr, 0, len(arr) - 1)) # Output: [1,
5, 7, 8, 9, 10]
Time Complexity:
Worst Case: O(n2) (when the pivot is the smallest or largest element)
Average and Best Case: O(n log n)
43
4.6.1 Prim’s Algorithm
Prim's algorithm is a greedy algorithm that finds the Minimum Spanning Tree
(MST) for a weighted undirected graph. The MST is a subset of the edges that
connects all vertices in the graph with the minimum total edge weight and without
any cycles.
import heapq
# Example usage
graph = {
'A': [('B', 1), ('C', 3)],
'B': [('A', 1), ('C', 7), ('D', 5)],
'C': [('A', 3), ('B', 7), ('D', 12)],
'D': [('B', 5), ('C', 12)]
44
}
mst = prims_algorithm(graph, 'A')
print(mst) # Output: [(0, 'A'), (1, 'B'), (5, 'D'), (3,
'C')]
4.7 Backtracking
Backtracking is an algorithmic paradigm that tries to build a solution incrementally, one
piece at a time. It removes solutions that fail to meet the conditions of the problem at any
point in time (called constraints) as soon as it finds them. Backtracking is useful for solving
constraint satisfaction problems, where you need to find an arrangement or combination
that meets specific criteria.
N-Queen’s Algorithm
45
return False
for i, j in zip(range(row, N, 1), range(col, -1,
-1)):
if board[i][j] == 1:
return False
return True
def solve_n_queens_util(board, col, N):
if col >= N:
return True
for i in range(N):
if is_safe(board, i, col, N):
board[i][col] = 1
if solve_n_queens_util(board, col + 1, N):
return True
board[i][col] = 0 # Backtrack
return False
def solve_n_queens(N):
board = [[0] * N for _ in range(N)]
if not solve_n_queens_util(board, 0, N):
return "Solution does not exist"
return board
# Example usage
N = 4
solution = solve_n_queens(N)
for row in solution:
print(row)
46
Output:
[0, 0, 1, 0]
[1, 0, 0, 0]
[0, 0, 0, 1]
[0, 1, 0, 0]
47
distance = current_distance + weight
if distance < distances[neighbor]:
distances[neighbor] = distance
heapq.heappush(priority_queue, (distance,
neighbor))
return distances
# Example usage
graph = {
'A': [('B', 1), ('C', 4)],
'B': [('A', 1), ('C', 2), ('D', 5)],
'C': [('A', 4), ('B', 2), ('D', 1)],
'D': [('B', 5), ('C', 1)]
}
start_vertex = 'A'
print(dijkstra(graph, start_vertex)) # Output: {'A': 0,
'B': 1, 'C': 3, 'D': 4}
48
CONCLUSION
As the narrative unfolded, the power of algorithms took center stage. Sorting algorithms
like bubble, merge, and quick sort choreographed the transformation of unordered data into
organized sequences, each with its distinct rhythm and efficiency. Greedy algorithms and
backtracking illuminated the path to optimization and constraint satisfaction, with Prim's
algorithm and the N-Queens problem exemplifying their prowess. The divide and conquer
approach brought forth the beauty of breaking problems into manageable fragments,
elegantly demonstrated by merge sort and binary search. The climax was marked by the
elegance of Dijkstra's algorithm, a masterful technique that navigates the vertices of a graph
to unveil the shortest paths with precision and clarity. This journey, rich with insights and
computational artistry, stands as a testament to the profound depth and beauty of data
structures and algorithms in the realm of computer science.
49
REFRENCES
50