Great notes on data structure, Lecture notes of Data Structures and Algorithms

This is shot not I prepared on data structure and algorithm

Typology: Lecture notes

2023/2024

Uploaded on 07/06/2024

abaska-elon
abaska-elon 🇰🇪

1 / 9

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Data Structures
Stacks, Lists, Queues, and Pointers
1. Stacks:
oDefinition: A linear data structure following the Last In, First Out (LIFO) principle.
oOperations:
Push: Add an element to the top.
Pop: Remove the top element.
Peek/Top: View the top element without removing it.
oApplications:
Function call management in recursion.
Expression evaluation and syntax parsing.
oExample:
stack = []
stack.append(1) # push
stack.append(2)
stack.pop() # pop, returns 2
top_element = stack[-1] # peek, returns 1
2. Lists:
oArray Lists:
Fixed size, direct access via index.
Efficient in terms of space.
oLinked Lists:
Dynamic size, elements linked using pointers.
Operations:
Insert: Add an element.
Delete: Remove an element.
Traverse: Access elements sequentially.
Example:
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def insert(self, data):
new_node = Node(data)
new_node.next = self.head
self.head = new_node
def delete(self, key):
temp = self.head
if temp is not None:
if temp.data == key:
self.head = temp.next
temp = None
return
while temp is not None:
if temp.data == key:
break
prev = temp
temp = temp.next
if temp == None:
return
prev.next = temp.next
temp = None
pf3
pf4
pf5
pf8
pf9

Partial preview of the text

Download Great notes on data structure and more Lecture notes Data Structures and Algorithms in PDF only on Docsity!

Data Structures

Stacks, Lists, Queues, and Pointers

1. Stacks:

o Definition: A linear data structure following the Last In, First Out (LIFO) principle.

o Operations:

 Push: Add an element to the top.

 Pop: Remove the top element.

 Peek/Top: View the top element without removing it.

o Applications:

 Function call management in recursion.

 Expression evaluation and syntax parsing.

o Example:

stack = [] stack.append(1) # push stack.append(2) stack.pop() # pop, returns 2 top_element = stack[-1] # peek, returns 1

2. Lists:

o Array Lists:

 Fixed size, direct access via index.

 Efficient in terms of space.

o Linked Lists:

 Dynamic size, elements linked using pointers.

 Operations:

 Insert: Add an element.

 Delete: Remove an element.

 Traverse: Access elements sequentially.

 Example:

class Node: def init(self, data): self.data = data self.next = None class LinkedList: def init(self): self.head = None def insert(self, data): new_node = Node(data) new_node.next = self.head self.head = new_node def delete(self, key): temp = self.head if temp is not None: if temp.data == key: self.head = temp.next temp = None return while temp is not None: if temp.data == key: break prev = temp temp = temp.next if temp == None: return prev.next = temp.next temp = None

3. Queues:

o Definition: A linear data structure following the First In, First Out (FIFO) principle.

o Operations:

 Enqueue: Add an element to the rear.

 Dequeue: Remove an element from the front.

o Applications:

 Printer queue management.

 Breadth-First Search (BFS) in graphs.

o Example:

from collections import deque queue = deque() queue.append(1) # enqueue queue.append(2) queue.popleft() # dequeue, returns 1

4. Pointers:

o Definition: Variables storing the memory address of another variable.

o Applications: Crucial in implementing dynamic data structures like linked lists, trees, etc.

Hashing

 Definition: A technique to convert a range of key values into a range of indexes of an array.

 Hash Function: Maps keys to array indices.

 Collision Resolution Techniques:

o Separate Chaining: Each array element points to a linked list of entries.

o Open Addressing: All elements are stored in the array itself (e.g., linear probing, quadratic

probing, double hashing).

 Example:

def hash_function(key, size): return key % size class HashTable: def init(self, size): self.size = size self.table = [[] for _ in range(size)] def insert(self, key, value): index = hash_function(key, self.size) self.table[index].append((key, value)) def search(self, key): index = hash_function(key, self.size) for k, v in self.table[index]: if k == key: return v return None

Binary Search Trees (BST)

 Definition: A tree structure where each node has at most two children. The left child is less than the

parent, and the right child is greater.

 Operations:

o Search, Insert, Delete.

 Example:

class BSTNode: def init(self, key): self.left = None self.right = None self.value = key def insert(root, key): if root is None: return BSTNode(key) if key < root.value:

o Linear Search:

 Worst Case: O(n)O(n)O(n)

 Best Case: O(1)O(1)O(1)

 Average Case: O(n)O(n)O(n)

Dynamic Programming

 Definition: A method for solving complex problems by breaking them down into simpler

subproblems. It stores the results of subproblems to avoid redundant computations.

 Example:

o Fibonacci Sequence:

def fibonacci(n): dp = [0, 1] for i in range(2, n+1): dp.append(dp[i-1] + dp[i-2]) return dp[n]

Greedy Algorithms

 Definition: Algorithms that make the locally optimal choice at each stage with the hope of finding a

global optimum.

 Example:

o Fractional Knapsack Problem:

def fractional_knapsack(items, capacity): items.sort(key=lambda x: x.value/x.weight, reverse=True) total_value = 0 for item in items: if capacity >= item.weight: capacity -= item.weight total_value += item.value else: total_value += item.value * (capacity / item.weight) break return total_value

Amortized Analysis

 Definition: Analyzes the average time per operation over a sequence of operations, ensuring that the

worst-case time is spread out over multiple operations.

 Example:

o Dynamic Array Resizing:

class DynamicArray: def init(self): self.array = [] self.capacity = 1 self.size = 0 def append(self, item): if self.size == self.capacity: self.capacity *= 2 new_array = [None] * self.capacity for i in range(self.size): new_array[i] = self.array[i] self.array = new_array self.array[self.size] = item self.size += 1

Graph Algorithms

Basic Graph Algorithms

1. Breadth-First Search (BFS):

o Definition: Explores neighbors level by level.

o Example:

from collections import deque def bfs(graph, start): visited = set() queue = deque([start]) while queue: vertex = queue.popleft() if vertex not in visited: visited.add(vertex) queue.extend(graph[vertex] - visited) return visited

2. Depth-First Search (DFS):

o Definition: Explores as far as possible along each branch.

o Example:

def dfs(graph, start, visited=None): if visited is None: visited = set() visited.add(start) for next in graph[start] - visited: dfs(graph, next, visited) return visited

Graph Analysis

 Definition: Analyzing properties such as connectivity, cycles, and components within graphs.

Spanning Trees

1. Minimum Spanning Tree (MST):

o Definition: A subset of the edges which connects all the vertices together, without any cycles

and with the minimum possible total edge weight.

o Algorithms:

 Kruskal's Algorithm: Uses union-find to avoid cycles.

 Prim's Algorithm: Uses a priority queue to select the minimum edge.

o Example Diagram:

Shortest Path Algorithms

1. Dijkstra's Algorithm:

o Definition: Finds the shortest paths from a single source to all other vertices in a graph with

non-negative edge weights.

o Example:

import heapq def dijkstra(graph, start): queue = [(0, start)] distances = {vertex: float('infinity') for vertex in graph} distances[start] = 0 while queue: current_distance, current_vertex = heapq.heappop(queue) if current_distance > distances[current_vertex]: continue for neighbor, weight in graph[current_vertex]: distance = current_distance + weight if distance < distances[neighbor]: distances[neighbor] = distance heapq.heappush(queue, (distance, neighbor)) return distances

length = lps[length-1] else: lps[i] = 0 i += 1 return lps lps = compute_lps_array(pattern) i = j = 0 while i < len(text): if pattern[j] == text[i]: i += 1 j += 1 if j == len(pattern): print(f"Found pattern at index {i-j}") j = lps[j-1] elif i < len(text) and pattern[j] != text[i]: if j != 0: j = lps[j-1] else: i += 1

2. Rabin-Karp Algorithm:

o Definition: Uses hashing to find any one of a set of pattern strings in a text.

o Example:

def rabin_karp(pattern, text, q): d = 256 M = len(pattern) N = len(text) p = 0 t = 0 h = 1 for i in range(M-1): h = (h * d) % q for i in range(M): p = (d * p + ord(pattern[i])) % q t = (d * t + ord(text[i])) % q for i in range(N - M + 1): if p == t: match = True for j in range(M): if text[i + j] != pattern[j]: match = False break if match: print(f"Pattern found at index {i}") if i < N - M: t = (d * (t - ord(text[i]) * h) + ord(text[i + M])) % q if t < 0: t += q

Algebraic Algorithms

 Fast Fourier Transform (FFT):

o Definition: A fast algorithm to compute the Discrete Fourier Transform (DFT) and its

inverse.

o Example Diagram:

NP-Completeness

 Definition: Class of problems for which no polynomial-time solutions are known, but a solution can

be verified in polynomial time.

 Example:

o Traveling Salesman Problem (TSP):

 Definition: Given a list of cities and the distances between each pair, what is the

shortest possible route that visits each city once and returns to the origin city?

 Example Diagram:

Approximation Algorithms

 Definition: Algorithms that find near-optimal solutions to NP-hard problems within a guaranteed

bound.

 Example:

o Approximation for TSP:

 Definition: An algorithm that provides a route not much longer than the optimal

route.

 Example Diagram:

Probabilistic and Parallel Algorithms

1. Probabilistic Algorithms:

o Definition: Algorithms that use randomness to achieve good expected performance.

o Example:

 Randomized Quick Sort:

import random def randomized_partition(arr, low, high): pivot_idx = random.randint(low, high) arr[pivot_idx], arr[high] = arr[high], arr[pivot_idx] return partition(arr, low, high) 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 def randomized_quicksort(arr, low, high): if low < high: pi = randomized_partition(arr, low, high) randomized_quicksort(arr, low, pi-1) randomized_quicksort(arr, pi+1, high)

2. Parallel Algorithms:

o Definition: Algorithms that execute multiple operations simultaneously.

o Example:

 Parallel Merge Sort:

from multiprocessing import Pool def merge(left, right): if not left: return right if not right: return left if left[0] < right[0]: return [left[0]] + merge(left[1:], right) return [right[0]] + merge(left, right[1:]) def parallel_merge_sort(arr): if len(arr) <= 1: return arr mid = len(arr) // 2 with Pool(2) as pool: left, right = pool.map(parallel_merge_sort, [arr[:mid], arr[mid:]]) return merge(left, right)

Intractable Problems

 Definition: Problems that are computationally infeasible to solve exactly.

 Example:

o Boolean Satisfiability Problem (SAT):