Some sorting algorithm
Sorting Algorithm
Motivation
why we need sorting?
- Problems become easy once items become sorted, e.g. finding a medium
- Data compression
- Computer Graphic
- ….
Bubble sort
Idea : floating the “bubble” to top via comparision&swaping in each round
it is only for educational purpose, don’t use it in real world.
time complexity is $O(n^2)$
Implementation
1
2
3
4
5
6
7
8void bubblesort(int * array , size_t size){
for (int i = 0 ; i < size ; i++){ //O(n)
for (int j = 0 ; j + 1 < size ; j++){ //O(n)
if(array[j] < array[j+1])
swap(&array[j] , &array[j+1]);
}
}
}- The bubble sort algorithm can be optimized by observing that the n-th round finds the n-th largest element and puts it into its final place. So, the inner loop can avoid looking at the last n − 1 items when running for the n-th time:
- Also, if n-1 elements are already in the right position, the left 1 must be in the right position, too.
1
2
3
4
5
6
7
8void bubblesort(int * array , size_t size){
for (int i = 0 ; i < size - 1 ; i++){
for (int j = 0 ; j + 1 < size - i ; j++){
if(array[j] < array[j+1])
swap(&array[j] , &array[j+1]);
}
}
}Insertion sort
Idea : assume we have an empty sorted array, then float elements into right position in the array via swaping&comparision
time complexity(worst case) : $T = O(1+2+3+..+n) = O(n^2)$
we might obtain $O(n\log n)$ complexity by finding the right place via binary search first, then insert it directly. However, shifting an array need time, linked list do not support binary search, the idea is not practical.
Implementation
1
2
3
4
5
6
7
8void insertion_sort(int * array , size_t size){
for (int i = 0 ; i < size ; i ++ ){
for (int j = i ; j - 1 >= 0 ; j--){
if (array[j-1] < array[j])
swap(&array[j] , &array[j-1]);
}
}
}- we can also start from
i=1
(start with a sorted one element array instead of an empty one.) and do the shifting.
1
2
3
4
5
6
7
8
9
10void insertion_sort(int * array , size_t size){
for (int i = 1 ; i < size ; i ++ ){
int j = i;
int key = array[i];
for ( ; j - 1 >= 0 && array[j-1] < key; j--){
array[j] = array[j-1]; //shift elements
}
array[j] = key; //insert to the right position
}
}Merge sort
- we can also start from
*Idea : divide and conquer, recurisively merging**
Merge sort is not an in-place sort, it needs $O(n)$ auxiliary space
There exists an in-place merge sort, but the performence is not quite well in practice
Time complexity : $T (n) = 2T(\frac{n}{2}) + \underbrace{O(1)}_\text{split time} + c\cdot\underbrace{O(n)}_\text{merge time}$
$$
T(n) = n\log n
$$We can use a recursion tree to solve recursion like this.
the total cost time is sum of sum of each level, or in other words, the sum of all the nodes.
$$
\sum c\cdot n + (c\cdot\frac{n}{2} + c\cdot\frac{n}{2}) + …+n\cdot c = [(\log_2n)-1]c\cdot n = O(n \log n)
$$Implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47void mymerge(int * array , int start , int mid , int end ){
int head1 = start ;
int head2 = mid+1 ;
int size = end - start + 1;
int auxiliary [size]; //this can be replaced by malloc
int ptr = 0;
while(head1 <= mid && head2 <= end){
if(array[head1] < array[head2]){
auxiliary[ptr++] = array[head1++];
}
else{
auxiliary[ptr++] = array[head2++];
}
}
//concatenate the rest
int head = 0;
int final = 0;
if (head1 <= mid){
head = head1;
final = mid;
}
else{
head = head2;
final = end;
}
for (int i = head ; i <= final ; i++){
auxiliary[ptr++] = array[i];
}
//override the original array
for(int j = 0; j < size ; j++){
array[start++] = auxiliary[j];
}
}
void mymergesort(int * array , int start , int end ){
if (end - start == 0) return ; //recursion base, there is only one element in array.
int mid = (start + end) / 2;
mymergesort(array , start , mid);
mymergesort(array , mid + 1 , end);
mymerge(array , start , mid , end);
}
void Mergesort(int * array , size_t size ){
mymergesort(array , 0 , size-1);
}Note that the auxiliary array is used for merging.
Quick sort
Idea : divide and conquer , recursively partition
This is an in-place sort
very practical
How:
Divide:
Partition the array into 2 subarray around a pivot such that
Conquer:
recursively sort the left of pivot and the right of the pivot
The key part of the algorithm is the partitioning
- we arbitrarily pick up an element and make it pivot, for example the most left one.
1 | //we can start with |
the standard version
When we run out of the unknow part, we swap the pivot to the middle
1
swap(&a[0] , &a[i]);
Then recursively sort the left and right, don’t forget to
return i
, we need to pass the position to recursion.In the worst case(the input array is already sorted), the complexity will be $O(n^2)$
In the best case(we partition at the middle every time), the complexity will be $O(n \log n)$
If we partition at $\frac{1}{10},\frac{9}{10}$ at each time, we still get $O(n \log n )$(verify this by using a recursion tree)
In order to be “lucky”$O(n \log n)$ most of the time, we can choose the pivot randomly to avoid sorting a sorted array.
Randomized Quicksort
- the complexity is independent of the input, it is always $O(n \log n)$
- the worst case will only appear if our RNG gives a “bad” random number.
Implementation
1 | int partition(int * array , int start , int end){ |
Selection sort
Idea : The algorithm proceeds by finding the smallest/largest element in the unsorted sublist, swapping it into the right place and moving the sublist boundaries one element to the right.
time complexity : $O(n^2)$
in-place sort
generally performs worse than the similar insertion sort
Implementation
1
2
3
4
5
6
7
8
9
10
11
12
13void selectionsort(int * array , size_t size){
for (int i = 0 ; i < size ; i++){
int smallest = i;
for ( int j = i ; j < size ; j++){
if (array[smallest] > array[j]){
smallest = j;
}
}
if(i!=smallest){
swap(&array[smallest] , &array[i]);
}
}
}Shell sort
Idea : take elements at certain interval as a subarray, use input sensitive sorting alogrithm(for example, insertion sort) to sort it, and then repeat the operation with a smaller interval. Finally reduce the interval to 1.
Repeatedly do insertion sort on all elements at fixed, decreasing distances apart: hk, hk-1, ..., h1= 1
. The fact that the last increment used should be 1 means that regular insertion sort is done at the last step, guaranteeing that the array will be sorted.
- in-place sort
- time complexity is influenced by the “gap/interval sequence”,
1 | find k0 so that 2k0 - 1 < size |
- Implementation
1 | void shellsort(int * array , size_t size){ |