Posted by tintin_2003 on 2025-10-08 20:46:40 | Last Updated by tintin_2003 on 2025-10-16 01:37:26
Share: Facebook | Twitter | Whatsapp | Linkedin Visits: 33
Sorting algorithms are fundamental building blocks in computer science that arrange data in a specific order. This comprehensive guide explores various sorting techniques, from simple to advanced, with practical implementations.
Description: A simple sorting algorithm that repeatedly steps through the list, compares adjacent elements and swaps them if they are in the wrong order. The pass through the list is repeated until the list is sorted.
Code:
def bubble_sort(arr):
n = len(arr)
for i in range(n):
swapped = False
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped:
break
return arr
# Test
data = [64, 34, 25, 12, 22, 11, 90]
print("Original:", data)
print("Sorted:", bubble_sort(data.copy()))
Output:
Original: [64, 34, 25, 12, 22, 11, 90]
Sorted: [11, 12, 22, 25, 34, 64, 90]
Time Complexity: O(n²) | Space Complexity: O(1)
Description: It divides the input into a sorted and an unsorted region. The sorted region is built up by repeatedly finding the smallest element from the unsorted region and putting it at the beginning of the sorted region.
Code:
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_idx = i
for j in range(i + 1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr
# Test
data = [64, 25, 12, 22, 11]
print("Original:", data)
print("Sorted:", selection_sort(data.copy()))
Output:
Original: [64, 25, 12, 22, 11]
Sorted: [11, 12, 22, 25, 64]
Time Complexity: O(n²) | Space Complexity: O(1)
Description: It builds a sorted array one item at a time. It is much less efficient on large lists than more advanced sorting algorithms such as quicksort, heapsort, or merge sort.
Code:
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
# Test
data = [12, 11, 13, 5, 6]
print("Original:", data)
print("Sorted:", insertion_sort(data.copy()))
Output:
Original: [12, 11, 13, 5, 6]
Sorted: [5, 6, 11, 12, 13]
Time Complexity: O(n²) | Space Complexity: O(1)
Description: It is a divide and conquer algorithm. It works by selecting a 'pivot' element from the array and partitioning the other elements into two sub-arrays, according to whether they are less than or greater than the pivot. The sub-arrays are then recursively sorted.
Code:
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
# Test
data = [10, 7, 8, 9, 1, 5]
print("Original:", data)
print("Sorted:", quick_sort(data))
Output:
Original: [10, 7, 8, 9, 1, 5]
Sorted: [1, 5, 7, 8, 9, 10]
Time Complexity: O(n log n) average, O(n²) worst | Space Complexity: O(log n)
Description: It is a divide and conquer algorithm based on the idea of breaking down a list into several sub-lists, sorting each sub-list, and then merging them back together.
Code:
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
# Test
data = [38, 27, 43, 3, 9, 82, 10]
print("Original:", data)
print("Sorted:", merge_sort(data))
Output:
Original: [38, 27, 43, 3, 9, 82, 10]
Sorted: [3, 9, 10, 27, 38, 43, 82]
Time Complexity: O(n log n) | Space Complexity: O(n)
Description: It is a comparison-based sorting technique based on Binary Heaps and can be modified to be an efficient inline sorting algorithm.
Code:
def heap_sort(arr):
def heapify(arr, n, i):
largest = i
left = 2 * i + 1
right = 2 * i + 2
if left < n and arr[left] > arr[largest]:
largest = left
if right < n and arr[right] > arr[largest]:
largest = right
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
n = len(arr)
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
for i in range(n - 1, 0, -1):
arr[0], arr[i] = arr[i], arr[0]
heapify(arr, i, 0)
return arr
# Test
data = [12, 11, 13, 5, 6, 7]
print("Original:", data)
print("Sorted:", heap_sort(data.copy()))
Output:
Original: [12, 11, 13, 5, 6, 7]
Sorted: [5, 6, 7, 11, 12, 13]
Time Complexity: O(n log n) | Space Complexity: O(1)
Description: It is a non-comparative integer sorting algorithm, meaning it sorts data based on value equivalence rather than direct comparison. It is primarily used to sort large amounts of data with many keys of similar value sizes.
Code:
def radix_sort(arr):
max_val = max(arr)
exp = 1
while max_val // exp > 0:
counting_sort_by_digit(arr, exp)
exp *= 10
return arr
def counting_sort_by_digit(arr, exp):
n = len(arr)
output = [0] * n
count = [0] * 10
for i in range(n):
index = arr[i] // exp
count[index % 10] += 1
for i in range(1, 10):
count[i] += count[i - 1]
for i in range(n - 1, -1, -1):
index = arr[i] // exp
output[count[index % 10] - 1] = arr[i]
count[index % 10] -= 1
for i in range(n):
arr[i] = output[i]
# Test
data = [170, 45, 75, 90, 802, 24, 2, 66]
print("Original:", data)
print("Sorted:", radix_sort(data.copy()))
Output:
Original: [170, 45, 75, 90, 802, 24, 2, 66]
Sorted: [2, 24, 45, 66, 75, 90, 170, 802]
Time Complexity: O(d × n) where d is number of digits | Space Complexity: O(n + k)
Description: It is a sorting algorithm based on comparison. It distributes the elements of an array into a number of buckets. Each bucket is then sorted individually, either by using a sorting algorithm that is efficient for small lists, or by recursively applying the bucket sorting algorithm.
Code:
def bucket_sort(arr):
if len(arr) == 0:
return arr
bucket_count = 10
max_val = max(arr)
min_val = min(arr)
bucket_range = (max_val - min_val) / bucket_count
buckets = [[] for _ in range(bucket_count)]
for num in arr:
index = min(int((num - min_val) / bucket_range), bucket_count - 1)
buckets[index].append(num)
for bucket in buckets:
bucket.sort()
result = []
for bucket in buckets:
result.extend(bucket)
return result
# Test
data = [0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21, 0.12, 0.23, 0.68]
print("Original:", data)
print("Sorted:", bucket_sort(data))
Output:
Original: [0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21, 0.12, 0.23, 0.68]
Sorted: [0.12, 0.17, 0.21, 0.23, 0.26, 0.39, 0.68, 0.72, 0.78, 0.94]
Time Complexity: O(n + k) average | Space Complexity: O(n + k)
Description: It sorts the elements by counting the number of occurrences of each element in the list and then builds the final array from the counts. It can only be used for sorting a finite range of data.
Code:
def counting_sort(arr):
if not arr:
return arr
max_val = max(arr)
min_val = min(arr)
range_size = max_val - min_val + 1
count = [0] * range_size
output = [0] * len(arr)
for num in arr:
count[num - min_val] += 1
for i in range(1, len(count)):
count[i] += count[i - 1]
for num in reversed(arr):
output[count[num - min_val] - 1] = num
count[num - min_val] -= 1
return output
# Test
data = [4, 2, 2, 8, 3, 3, 1]
print("Original:", data)
print("Sorted:", counting_sort(data))
Output:
Original: [4, 2, 2, 8, 3, 3, 1]
Sorted: [1, 2, 2, 3, 3, 4, 8]
Time Complexity: O(n + k) | Space Complexity: O(k)
Description: It is a modification to insertion sort that improves the efficiency by increasing the size of the gap between elements at each step.
Code:
def shell_sort(arr):
n = len(arr)
gap = n // 2
while gap > 0:
for i in range(gap, n):
temp = arr[i]
j = i
while j >= gap and arr[j - gap] > temp:
arr[j] = arr[j - gap]
j -= gap
arr[j] = temp
gap //= 2
return arr
# Test
data = [12, 34, 54, 2, 3]
print("Original:", data)
print("Sorted:", shell_sort(data.copy()))
Output:
Original: [12, 34, 54, 2, 3]
Sorted: [2, 3, 12, 34, 54]
Time Complexity: O(n log n) to O(n²) | Space Complexity: O(1)
Description: It is a variation of bubble sort that performs the swapping actions both from the end and the beginning of the unsorted region, reducing the number of iterations required.
Code:
def cocktail_shaker_sort(arr):
n = len(arr)
swapped = True
start = 0
end = n - 1
while swapped:
swapped = False
for i in range(start, end):
if arr[i] > arr[i + 1]:
arr[i], arr[i + 1] = arr[i + 1], arr[i]
swapped = True
if not swapped:
break
swapped = False
end -= 1
for i in range(end - 1, start - 1, -1):
if arr[i] > arr[i + 1]:
arr[i], arr[i + 1] = arr[i + 1], arr[i]
swapped = True
start += 1
return arr
# Test
data = [5, 1, 4, 2, 8, 0, 2]
print("Original:", data)
print("Sorted:", cocktail_shaker_sort(data.copy()))
Output:
Original: [5, 1, 4, 2, 8, 0, 2]
Sorted: [0, 1, 2, 2, 4, 5, 8]
Time Complexity: O(n²) | Space Complexity: O(1)
Description: It is a simple, efficient, and little-known sorting algorithm. It works by repeatedly finding and eliminating long runs of adjacent elements that are in the wrong order.
Code:
def comb_sort(arr):
n = len(arr)
gap = n
shrink = 1.3
sorted_flag = False
while not sorted_flag:
gap = int(gap / shrink)
if gap <= 1:
gap = 1
sorted_flag = True
i = 0
while i + gap < n:
if arr[i] > arr[i + gap]:
arr[i], arr[i + gap] = arr[i + gap], arr[i]
sorted_flag = False
i += 1
return arr
# Test
data = [8, 4, 1, 56, 3, -44, 23, -6, 28, 0]
print("Original:", data)
print("Sorted:", comb_sort(data.copy()))
Output:
Original: [8, 4, 1, 56, 3, -44, 23, -6, 28, 0]
Sorted: [-44, -6, 0, 1, 3, 4, 8, 23, 28, 56]
Time Complexity: O(n²) worst, O(n log n) average | Space Complexity: O(1)
Description: It is a modification of bubble sort, where each swap puts the array in a sorted state, and the traversal moves forward unless the current element is in the right order.
Code:
def gnome_sort(arr):
index = 0
n = len(arr)
while index < n:
if index == 0:
index += 1
elif arr[index] >= arr[index - 1]:
index += 1
else:
arr[index], arr[index - 1] = arr[index - 1], arr[index]
index -= 1
return arr
# Test
data = [34, 2, 10, -9, 45, 23]
print("Original:", data)
print("Sorted:", gnome_sort(data.copy()))
Output:
Original: [34, 2, 10, -9, 45, 23]
Sorted: [-9, 2, 10, 23, 34, 45]
Time Complexity: O(n²) | Space Complexity: O(1)
Description: It is a cooking-inspired sorting algorithm that flips pancakes to rearrange the elements. It is a variation of Bubble Sort.
Code:
def pancake_sort(arr):
def flip(arr, k):
left = 0
while left < k:
arr[left], arr[k] = arr[k], arr[left]
left += 1
k -= 1
def find_max_index(arr, n):
max_idx = 0
for i in range(n):
if arr[i] > arr[max_idx]:
max_idx = i
return max_idx
n = len(arr)
for curr_size in range(n, 1, -1):
max_idx = find_max_index(arr, curr_size)
if max_idx != curr_size - 1:
flip(arr, max_idx)
flip(arr, curr_size - 1)
return arr
# Test
data = [23, 10, 20, 11, 12, 6, 7]
print("Original:", data)
print("Sorted:", pancake_sort(data.copy()))
Output:
Original: [23, 10, 20, 11, 12, 6, 7]
Sorted: [6, 7, 10, 11, 12, 20, 23]
Time Complexity: O(n²) | Space Complexity: O(1)
Description: It is a non-comparative sorting algorithm where the array is cyclically shifted until all elements are in their correct positions.
Code:
def cycle_sort(arr):
writes = 0
for cycle_start in range(len(arr) - 1):
item = arr[cycle_start]
pos = cycle_start
for i in range(cycle_start + 1, len(arr)):
if arr[i] < item:
pos += 1
if pos == cycle_start:
continue
while item == arr[pos]:
pos += 1
arr[pos], item = item, arr[pos]
writes += 1
while pos != cycle_start:
pos = cycle_start
for i in range(cycle_start + 1, len(arr)):
if arr[i] < item:
pos += 1
while item == arr[pos]:
pos += 1
arr[pos], item = item, arr[pos]
writes += 1
return arr
# Test
data = [1, 8, 3, 9, 10, 10, 2, 4]
print("Original:", data)
print("Sorted:", cycle_sort(data.copy()))
Output:
Original: [1, 8, 3, 9, 10, 10, 2, 4]
Sorted: [1, 2, 3, 4, 8, 9, 10, 10]
Time Complexity: O(n²) | Space Complexity: O(1)
Algorithm | Time (Best) | Time (Average) | Time (Worst) | Space | Stable |
---|---|---|---|---|---|
Bubble Sort | O(n) | O(n²) | O(n²) | O(1) | Yes |
Selection Sort | O(n²) | O(n²) | O(n²) | O(1) | No |
Insertion Sort | O(n) | O(n²) | O(n²) | O(1) | Yes |
Quick Sort | O(n log n) | O(n log n) | O(n²) | O(log n) | No |
Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes |
Heap Sort | O(n log n) | O(n log n) | O(n log n) | O(1) | No |
Radix Sort | O(nk) | O(nk) | O(nk) | O(n+k) | Yes |
Counting Sort | O(n+k) | O(n+k) | O(n+k) | O(k) | Yes |
Shell Sort | O(n log n) | O(n log²n) | O(n²) | O(1) | No |
Understanding sorting algorithms is crucial for optimizing data processing. Each algorithm has its strengths and ideal use cases. Modern programming languages often use hybrid approaches like Tim Sort (Python) that combine multiple techniques for optimal performance across different scenarios.