Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Essential Algorithms and Their Python Implementations

Tech May 5 13
  1. Bubble Sort


def organize_with_bubble(arr):
    length = len(arr)
    for i in range(length):
        for j in range(0, length - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr


data = [64, 34, 25, 12, 22, 11, 90]
print(organize_with_bubble(data))

How it works: Bubble sort operates by repeatedly stepping through the list, comparing adjacent elements, and swapping them if they are in the wrong order. Each pass through the list places the largest unsorted element in its correct position at the end of the unsorted portion. This process continues until the entire list is sorted.

  1. Selection Sort


def arrange_with_selection(arr):
    for i in range(len(arr)):
        minimum_index = i
        for j in range(i + 1, len(arr)):
            if arr[j] < arr[minimum_index]:
                minimum_index = j
        arr[i], arr[minimum_index] = arr[minimum_index], arr[i]
    return arr


data = [64, 25, 12, 22, 11]
print(arrange_with_selection(data))

How it works: Selection sort works by dividing the list into a sorted and an unsorted region. It repeatedly selects the smallest element from the unsorted region and swaps it with the first element of that region. The sorted region grows from left to right with each iteration until the entire array is sorted.

  1. Insertion Sort


def sort_with_insertion(arr):
    for i in range(1, len(arr)):
        current_value = arr[i]
        position = i - 1
        while position >= 0 and current_value < arr[position]:
            arr[position + 1] = arr[position]
            position -= 1
        arr[position + 1] = current_value
    return arr


data = [12, 11, 13, 5, 6]
print(sort_with_insertion(data))

How it works: Insertion sort builds the final sorted array one item at a time. It takes each element and inserts it into its correct position within the already-sorted portion of the array. This is similar to how people naturally sort playing cards in their hands.

  1. Merge Sort


def arrange_with_merge(items):
    if len(items) <= 1:
        return items
    
    midpoint = len(items) // 2
    left_segment = items[:midpoint]
    right_segment = items[midpoint:]
    
    arrange_with_merge(left_segment)
    arrange_with_merge(right_segment)
    
    left_ptr = right_ptr = output_ptr = 0
    
    while left_ptr < len(left_segment) and right_ptr < len(right_segment):
        if left_segment[left_ptr] < right_segment[right_ptr]:
            items[output_ptr] = left_segment[left_ptr]
            left_ptr += 1
        else:
            items[output_ptr] = right_segment[right_ptr]
            right_ptr += 1
        output_ptr += 1
    
    while left_ptr < len(left_segment):
        items[output_ptr] = left_segment[left_ptr]
        left_ptr += 1
        output_ptr += 1
    
    while right_ptr < len(right_segment):
        items[output_ptr] = right_segment[right_ptr]
        right_ptr += 1
        output_ptr += 1
    
    return items


data = [12, 11, 13, 5, 6, 7]
print(arrange_with_merge(data))

How it works: Merge sort employs the divide-and-conquer strategy. It recursively splits the array into smaller subarrays until each subarray contains a single element. Then, it merges these subarrays back together in sorted order, repeatedly combining smaller sorted lists into larger ones until the complete sorted array is reconstructed.

  1. Quick Sort


def organize_with_quick(arr):
    if len(arr) <= 1:
        return arr
    
    pivot_element = arr[len(arr) // 2]
    smaller = [x for x in arr if x < pivot_element]
    equal = [x for x in arr if x == pivot_element]
    larger = [x for x in arr if x > pivot_element]
    
    return organize_with_quick(smaller) + equal + organize_with_quick(larger)


data = [3, 6, 8, 10, 1, 2, 1]
print(organize_with_quick(data))

How it works: Quick sort selects a pivot element and partitions the array into three groups: elements less than the pivot, elements equal to the pivot, and elements greater than the pivot. The algorithm recursively applies this process to the smaller and larger partitions. The pivot is correctly positioned in the final array, and this positioning is used to efficiently sort the remaining elements.

  1. Binary Search


def locate_with_binary(sorted_data, target):
    left_index = 0
    right_index = len(sorted_data) - 1
    
    while left_index <= right_index:
        middle = left_index + (right_index - left_index) // 2
        
        if sorted_data[middle] == target:
            return middle
        elif sorted_data[middle] < target:
            left_index = middle + 1
        else:
            right_index = middle - 1
    
    return -1


data = [2, 3, 4, 10, 40]
target = 10
print(locate_with_binary(data, target))

How it works: Binary search efficiently locates a target value within a sorted array by repeatedly dividing the search interval in half. At each step, the algorithm compares the target value with the middle element of the interval. If they match, the search succeeds. If the target is less than the middle element, the search continues in the left half; otherwise, it continues in the right half. This approach eliminates half of the remaining elements with each comparison.

  1. Fibonacci Sequence


def generate_fibonacci(count):
    if count <= 0:
        return []
    if count == 1:
        return [0]
    if count == 2:
        return [0, 1]
    
    fib_series = [0, 1]
    for index in range(2, count):
        fib_series.append(fib_series[index - 1] + fib_series[index - 2])
    
    return fib_series


length = 10
print(generate_fibonacci(length))

How it works: The Fibonacci sequence is a series where each number equals the sum of the two preceding numbers, beginning with 0 and 1. This implementation builds the sequence iteratively, starting with the base cases and generating subsequent values by summing the two most recent elements in the sequence.

  1. Euclidean Algorithm for GCD


def compute_gcd(first, second):
    while second != 0:
        first, second = second, first % second
    return first


value_a = 60
value_b = 48
print(compute_gcd(value_a, value_b))

How it works: The Euclidean algorithm efficiently computes the greatest common divisor of two integers by leveraging the mathematical principle that the GCD of two numbers also divides their difference. The algorithm repeatedly replaces the larger number with the remainder of dividing the larger by the smaller, continuing until one number becomes zero. The remaining non-zero number is the GCD.

  1. Depth-First Search


def traverse_dfs(graph_structure, starting_node, visited_nodes=None):
    if visited_nodes is None:
        visited_nodes = set()
    
    visited_nodes.add(starting_node)
    print(starting_node)
    
    for adjacent in graph_structure[starting_node] - visited_nodes:
        traverse_dfs(graph_structure, adjacent, visited_nodes)
    
    return visited_nodes


network = {
    'A': {'B', 'C'},
    'B': {'A', 'D', 'E'},
    'C': {'A', 'F'},
    'D': {'B'},
    'E': {'B', 'F'},
    'F': {'C', 'E'}
}
traverse_dfs(network, 'A')

How it works: Depth-first search explores graph structures by going as deep as possible along each branch before backtracking. Starting from the initial node, the algorithm visits a node and then recursively explores each unvisited neighbor. When no unvisited neighbors exist, it backtracks to the previous node and continues exploring alternative paths. This approach systematically visits every reachable node in the graph.

  1. Breadth-First Search


from collections import deque

def explore_bfs(graph_structure, starting_node):
    visited_nodes = set()
    node_queue = deque([starting_node])
    visited_nodes.add(starting_node)
    
    while node_queue:
        current = node_queue.popleft()
        print(current)
        
        for neighbor in graph_structure[current]:
            if neighbor not in visited_nodes:
                visited_nodes.add(neighbor)
                node_queue.append(neighbor)
    
    return visited_nodes


network = {
    'A': {'B', 'C'},
    'B': {'A', 'D', 'E'},
    'C': {'A', 'F'},
    'D': {'B'},
    'E': {'B', 'F'},
    'F': {'C', 'E'}
}
explore_bfs(network, 'A')

How it works: Breadth-first search examines graph structures level by level, starting from the initial node. The algorithm maintains a queue of nodes to visit and processes each node by first marking it as visited and then adding all its unvisited neighbors to the queue. This systematic approach ensures that nodes are visited in order of their distance from the starting node, making BFS particularly useful for finding the shortest path in unweighted graphs.

Tags: algorithms

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.