Introduction to Algorithms - Lecture 3: Sorting
This post is a derivative from Introduction to Algorithms of MIT OpenCourseWare, used under CC BY-NC-SA 4.0.
Review
-
Interface
Collection of operations (e.g., sequence & set)
-
Data structure Way to store data that supports a set of operations
Set Interface
A set interface is an object that you can keep adding things to it.
Sorting Vocabulary
- Input: Array of n numbers(keys)
- Output: Sorted array
- Destructive: Overwrite the input array. Rather than reserving some new memory for sorted array B and then putting a sorted version of A into B, a destructive algorithm is one that just overwrites A with a sorted version of A.
- In place: Uses O(1) extra space. It doesn’t use extra memory in the process of sorting. in place ⊆ destructive.
Permutaion Sort
If I have an input that’s a list of numbers, there exists a permutation of that list of numbers that is sorted because a sort is a permutation of your original list. List every possible permutation and then just double check which one is the right order.
- Example: [2, 3, 1] -> {[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], … }
def permitation_sort (A):
'''Sort A'''
for B in permutations (A): # O(n!)
if is_sorted (B): # O(n)
return B # O(1)
- Correct by case analysis: try all possibilities (Brute Force)
- Running time: Ω(n! · n) which is exponential
Selection Sort
- Find the biggest with index <= i
- Swap
- Sort 1, … , i - 1
def selection_sort (A, i = None): # T(i)
'''Sort A[:i + 1]'''
if i is None: i = len (A) - 1 # O(1)
if i > 0: # O(1)
j = prefix_max (A, i) # S(i)
A[i], A[j] = A[j], A[i] # O(1)
selection_sort (A, i - 1) # T(i-1)
def prefix_max (A, i): # S(i)
'''Return index of maximum in A[:i+ 1 ]'''
if i > 0: # O(1)
j = prefix_max (A, i - 1) # S(i - 1)
if A[i] < A[j]: # O(1)
return j # O(1)
return i # O(1)
Biggest element in 0, … , i
- It is at index i
- It has index < i
prefix_max analysis
S(1) = Θ(1), S(n)= S(n − 1) + Θ(1)
S(n) = Θ(n)
selection_sort analysis
T(1) = Θ(1), T(n)= T(n − 1) + Θ(n)
S(n) = Θ(n^2)
Insertion Sort
Merge Sort
- Recursively sort first half and second half (may assume power of two)
- Merge sorted halves into one sorted list (two finger algorithm)
def merge_sort (A, a = 0, b = None): # T(b - a = n)
'''Sort A[a:b]'''
if b is None: b = len (A) # O(1)
if 1 < b - a: # O(1)
c = (a + b + a) // 2 # O(1)
merge_sort(A, a, c) # T(n / 2)
merge_sort(A, c, b) # T(n / 2)
L, R = A[a:c], A[c:b] # O(n)
merge(L, R, A, len (L), len (R), a, b) # S(n)
def merge(L, R, A, i, j, a, b): # S(b - a = n)
'''Merge sorted L[:i] and R[:j] into A[a:b]'''
if a < b: # O(1)
if (j <= 0) or (i > 0 and L[i - 1] > R[j - 1]): # O(1)
A[b - 1] = L[i - 1] # O(1)
i = i - 1 # O(1)
else:
A[b - 1] = R[j - 1] # O(1)
j = j - 1 # O(1)
merge(L, R, A, i, j, a, b - 1) # S(n-1)
merge analysis
S(0) = Θ(1), S(n)= S(n − 1) + Θ(1) -> S(n) = Θ(n)
merge_sort analysis
T(1) = Θ(1), T(n) = 2T(n/2) + Θ(n)
T(n) = Θ(n log n)
Leave a comment