Intro to Sorting
Bubble Sort
Bubble Sort: comparing elements side by side and switching them if necessary.
While sorting, the biggest (or smallest) element will be BUBBLED UP to the top.
For example, we have an array [8, 3, 1, 7, 0]
and want to sort them from smallest to biggest. We need to iterate through the array (at least one time)
First iteration:
- We compare the first and second elements:
8
and3
. Because8 > 3
we need to switch them. The array becomes[3, 8, 1, 7, 0]
- Continuing to the second and third elements:
8
and1
,8 > 1
, switch them. The array now is[3, 1, 8, 7, 0]
- Next are the third and fourth elements:
8
and7
,8 > 7
, switch them. We have[3, 1, 7, 8, 0]
- Finally (actually not final yet), the fourth and last elements,
8
and0
, switch. Now the array is[3, 1, 7, 0, 8]
. Not really what we want.
Second iteration: we do the same thing as the first iteration with a sorted array [3, 1, 7, 0, 8]
:
- Compare first and second elements and switch, the array becomes
[1, 3, 7, 0, 8]
- Compare second and third elements. No need to switch
- Compare the third and fourth elements. Switch and the array become
[1, 3,0, 7, 8]
- Compare the fourth and last elements. No need to switch.
Third iteration: repeat the boring steps, we will have the array [1, 0, 3, 7, 8]
. Still not look good.
Fourth iteration: go again and finally; the array is sorted as we want [0, 1, 3, 7, 8]
The efficiency of Bubble Sort
In every iteration above, we had to do 4 steps for 5 elements, which is n - 1
. And we did 4 iterations in total.
Let's make a table and see more closely
Iteration # | 1 | 2 | 3 | 4 |
# of comparisons | n - 1 | n - 1 | n - 1 | n - 1 |
So we have (n - 1)
iterations and (n - 1)
comparisons. Which give us (n - 1)(n - 1) = n
2
- 2n + 1
With the approximative approach, we will have the efficiency of Bubble Sort:
O
(n
2
)
in time
O
(n)
in space
In fact, we could save some time of the algorithm by not checking the last element (and last element += 1) after every iteration. However, this does not help much.
Notes:
- Worst Case =
O
(n
2
)
- Average Case =
O
(n
2
)
- Best Case =
O
(n)
- where the array is already sorted and we only have to iterate through the array only once.
See more in Bubble Sort - Wikipedia
Merge Sort
Merge Sort: split a huge array down as much as possible and build it back up while comparing and sorting at each step along the way.
The idea of breaking for sorting and building again is called Divide and Conquer.
Suppose we have an array [8, 3, 1, 7, 0, 10, 2]
We firstly break it up into smaller arrays [8]
, [3]
, [1]
, [7]
, [0]
, [10]
, and [2]
.
We also need placeholders for the first merge [ ]
, [ , ]
, [ , ]
, and [ , ]
First iteration (3 comparison steps): we compare (switch if needed) two arrays and put them into the placeholders
[8]
- no need to compare[3]
,[1]
- switch to[1, 3]
- [
7]
,[0]
- switch to[0, 7]
[10]
,[2]
- switch to[2, 10]
Second iteration (5 comparison steps): we will do the same with larger size of place holder [ , , ]
and [ , , , ]
Compare and merge: [8]
and [1, 3]
- Compare
8
and1
,8 > 1
, put1
into the placeholder[1, , ]
- Compare
8
and3
,8 > 3
, put3
then8
to the placeholder[1, 3, 8]
Compare and merge: [0, 7]
and [2, 10]
- Compare
0
and2
,0 < 2
, put0
into placeholder[0, , , ]
- Compare
7
and2
,2 < 7
, put2
into placeholder[0, 2, , ]
- Compare
7
and10
,7 < 10
, put7
and then10
into placeholder[0, 2, 7, 10]
Third iteration (6 comparison steps): we now compare two array [1, 3, 8]
and [0, 2, 7, 10]
then put them into the placeholder with size 7 [ , , , , , , ]
- Compare
1
and0
,0 < 1
, put0
into the placeholder[ 0, , , , , , ]
- Compare
1
and2
,1 < 2
, put1
into the placeholder[ 0, 1, , , , , ]
- Compare
2
and3
,2 < 3
, put2
into the placeholder[ 0, 1, 2, , , , ]
- Compare
3
and7
,3 < 7
, put3
into the placeholder[ 0, 1, 2, 3, , , ]
- Compare
8
and7
,7 < 8
, put7
into the placeholder[ 0, 1, 2, 3, 7, , ]
- Compare
8
and10
,8 < 10
, put8
then10
into the placeholder[ 0, 1, 2, 3, 7, 8, 10]
The efficiency of Merge Sort
We have 3 iterations and 14 steps of comparison for a size 7 array from the above example.
With an array size of n, we need 2n steps of comparison, or approximately O
(n)
We can make a table as before:
Array size | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
# Iterations | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 4 |
With an array size of 1, we need 0 iteration
With an array size of 2, we need 1 iteration
Array size 3 or 4, we need 2 iterations
Array size 5, 6, 7, or 8, we need 3 iterations (as the example above)
Array size 9 to 16, we need 4 iterations, and so on.
We can see after every power of 2, we will need to increase the number of iteration by 1. Similar to the binary search, we have the time efficiency of log(n)
.
Combining the number of iterations and comparison steps, we have the time efficiency of Merge Sort:
O
(nlog(n))
Another example of Merge Sort:
[21, 4, 1, 3, 9, 20, 25] (Original Array)
[21, 1, 4, 3, 9, 20, 25] (1 - iteration)
[1, 4, 21, 3, 9, 20, 25] (2 - iterations)
[1, 3, 4, 9, 20, 21, 25] (3 - iterations)
Quick Sort
In many cases, Quick Sort is one of the most efficient sorting algorithms.
The Quick Sort needs an element as a pivot, divide and conquer, and then sort the array in-place but unstable.
For the pivot, we can pick the first element, last element, random element, or the median element.
In this example, we will use the last element as our pivot.
To start Quick Sort, we need to initially pick the last element as a pivot. Comparing the pivot with the first element, if the value of that element is larger than the value of the pivot, we swap their positions.
However, we are doing an in-place sort, we will need to move the element in front of the pivot to make room instead of using a placeholder.
Let's go with the example of the array: [ 8, 3, 1, 7, 0, 10, 2]
- First, we pick
2
as the pivot. Compare2
to8
- the first element,8 > 2
, we move8
to the position of2
, we also need to move2
to the position in front of it where10
is. And we move10
to the position of8
. The new array become[10, 3, 1, 7, 0, 2, 8]
- Continue with our pivot
2
, comparing2
to10
,10 > 2
, move10
to the position of2
, move2
to the position of0
- the front position of the pivot, and move0
to the position of10
. We have[ 0, 3, 1, 7, 2, 10, 8]
- Compare
2
to0
- the first element,2 > 0
, check the next element3
,3 > 2
, move3
to the position of2
, move2
to the front position7
, move 7 to the position of3
. The new array[ 0, 7, 1, 2, 3, 10, 8]
- Compare
2
to7
, move7
to the position of2
, move2
to the front element -1
, and move1
to the position of7
. We have[ 0, 1, 2, 7, 3, 10, 8]
2
is in the right position since it is bigger than0
and1
.- We now do the same process with the element smaller than the pivot and then bigger than the pivot. We start checking
0
and1
, they are in a good place. So we check the bigger elements than2
. - We need to pick a new pivot -
8
as a convention. Compare8
with0
,1
,2
,7
,3
,10
.10 > 8
, we swap the position of10
and8
. We now have[ 0, 1, 2, 7 , 3, 8, 10]
8
or10
are in a good position because it is bigger than any element in front of it. We continue checking the elements smaller than8
.- Now we only have
7
and3
. Pick3
as the new pivot, compare3
to7
,7 > 3
, swap them. Now we have the whole array sorted[ 0, 1, 2, 3, 7, 8 10]
The efficiency of Quick Sort
In the worst case, time efficiency is:
O
(n
2
)
Implementation
In the implementation, we will use the first element as our pivot.
def quick_sort(arr):
arr_len = len(arr)
if arr_len <= 1:
return arr
pivot = 0
for i in range(1, arr_len):
if arr[i] <= arr[0]:
pivot += 1
arr[i], arr[pivot] = arr[pivot], arr[i]
arr[0], arr[pivot] = arr[pivot], arr[0]
smaller = quick_sort(arr[0:pivot])
larger = quick_sort(arr[pivot + 1:arr_len])
arr = smaller + [arr[pivot]] + larger
return arr
array = [8, 3, 1, 7, 0, 10, 2]
print("Original Array: ",array)
print("Sorted Array: ",quick_sort(array))