Classic Sorting Algorithms
Bubble sorting
The whole process look like bubbles going up to surface. While sorting (target to acending order), the most right element becomes the largest one.
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
if (nums.size() == 0) {
return nums;
}
// bubble sort
for (int i = 0; i < nums.size(); i++) {
// -1 because compares to next element
// -i because the right most element is the least int after every loop
for (int j = 0; j < nums.size() - 1 - i; j++) {
if (nums[j] > nums[j+1]) {
int tmp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = tmp;
}
}
}
return nums;
}
};
Select Sort
Keep finding the most less element and swap with the most left one in sub array (ascending order).
/*select sort*/
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
if (nums.size() == 0) { // no element for sorting
return nums;
}
for (int i = 0; i < nums.size(); i++) {
int min = i; // assume the left is the least
for (int j = i; j < nums.size(); j++) { // start from i not 0
if (nums[j] < nums[min]) { // keep finding the least one
min = j;
}
}
// swap the left most and the min one in the sub array
int tmp = nums[i];
nums[i] = nums[min];
nums[min] = tmp;
}
return nums;
}
};
Insertion Sort
Use double pointer, one points to last element of sorted sub array, one for the comparing element [i+1].
If the element is larger, move backward. If not, insert the current value into the position.
// insertion sort
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
if (nums.size() == 0) {
return nums;
}
int cur; // record current value
for (int i = 0; i < nums.size() - 1; i++) { // no need to compare last element
// compare start from nums[i + 1]
// i stand for the compared sub array
int cur = nums[i + 1];
int tmpIndex = i; // the right index for current element in sub array
// move elements backward
while (tmpIndex >= 0 && cur < nums[tmpIndex]) {
nums[tmpIndex+1] = nums[tmpIndex];
tmpIndex--;
}
// right position (tmpIndex + 1) in sub array
nums[tmpIndex + 1] = cur;
}
return nums;
}
};
Quick Sort
classic (one pivot)
- select a pivot (randomly or just pick as you like).
- Sort the array, put the elemtnts that less than pivot to left and larger to right, then we have two sub array divided by pivot
- sort recursively sort the sub array until the array size go to zero
- do all above in the original array to save space:
- move pivot to
right
first - sort element in
left
toright - 1
- swap middle first element of larger array (
i+1
) with pivot (right
) - then,
i+1
is the middle point the 'true'pivot
, return it as the divide point for next recursion
- move pivot to
// quick sort
class Solution {
int partition(vector<int>& nums, int left, int right) {
int index = (right - left) / 2 + left; // middle point as pivot
swap(nums[right], nums[index]); // move pivot to last position
int pivot = nums[right];
int i = left - 1; // i for last element in left sub array (less than pivot)
// j for index for comparing
// right - 1 that left most right element (pivot) for swaping at last step
for (int j = left; j <= right - 1; ++j) {
// swap all elements that smaller than pivot to front sub array
if (nums[j] <= pivot) {
swap(nums[++i], nums[j]);
}
}
// for first elements that lager than pivot in right sub array,
// which should be the position of pivot
swap(nums[i+1], nums[right]); // move pivot to middle point
return i+1;
}
void quick_sort(vector<int>& nums, int left, int right) {
if (left < right) {
int pos = partition(nums, left, right);
quick_sort(nums, left, pos - 1);
quick_sort(nums, pos + 1, right);
}
}
public:
vector<int> sortArray(vector<int>& nums) {
quick_sort(nums, 0, (int)nums.size() - 1);
return nums;
}
};
Shell Sort
Best time complexity: \( O(nlogn) \)
Add a gap compare to insertion sort:
- every time compare the element that has distance of gap
- reduce the gap (by dividing 2 or using other method) until the gap equal to 1 (when gap euqal to 1, it becomes the classic insertion sort).
- the introduce of gap make the insertion "faster".
// shell sort
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int cur;
int gap = nums.size();
while (gap > 0) { // reduce gap until gap equal to 1
// perform insertion sort within the gap
for (int i = gap; i < nums.size(); i++) {
cur = nums[i];
int pre = i - gap;
while (pre >= 0 && cur < nums[pre]) {
nums[pre+gap] = nums[pre];
pre -= gap;
}
nums[pre+gap] = cur;
}
gap /= 2; // reduce the gap
}
return nums;
}
};
Merge Sort
Divide and Conquer:
- recursively divide the original array into two parts
- sort the sub array and merge the result into one
// merge sort
class Solution {
vector<int> merge(vector<int> left, vector<int> right) {
vector<int> result;
for (int index=0, i=0, j=0; index < left.size()+right.size(); index++) {
if(i >= left.size()) { // left merge finished
result.push_back(right[j++]);
} else if(j >= right.size()) { // right merge finished
result.push_back(left[i++]);
} else if (left[i] < right[j]) { // put the smaller into the result
result.push_back(left[i++]);
} else {
result.push_back(right[j++]);
}
}
return result;
}
public:
vector<int> sortArray(vector<int>& nums) {
if (nums.size() < 2) {
return nums;
}
// keep devide the array into two sub array
int mid = nums.size() / 2;
vector<int> left (nums.begin(), nums.begin()+mid);
vector<int> right (nums.begin()+mid, nums.end());
return merge(sortArray(left), sortArray(right)); // merge the result
}
};
Heap Sort
Use heap to maintain the array.
Heap: complete binary tree, besides, each root node of sub tree larger or less than its child nodes.
- make the array to a heap (if ascending, make a big top one)
- swap the root in the heap with the last element in the array (so that the last element is at the target position)
- adjust the array again to a heap (so that the root is always the largest or the least value)
- keep doing step 2 and 3
The root node always record the largest or least value, so it can be swap to the end of the array to make the array ordered.
// heap sort
/* for element at k:
left node index is 2*k+1
right node index is 2*(k+1), which is 2*k+2
last none leaf node index is N/2-1 (N is the length of array)
*/
class Solution {
// adjust sub trees to make the binary tree to a heap (again)
// i for the root node of sub tree (last non leaf node)
void maxHeapify(vector<int>& nums, int i, int len) {
while ((i<<1)+1 <= len){ // i<<1 is i*2, which is faster
int lson = (i<<1) +1;
int rson = (i<<1) +2;
int large;
if (lson <= len && nums[lson] > nums[i]) {
large = lson;
} else {
large = i;
}
if (rson<= len && nums[rson] > nums[large]) {
large = rson;
}
// the root node of sub tree is not larger than child node, swap,
// so that it becomes a heap
if (large != i) {
swap(nums[i], nums[large]);
i = large; // update index in the array
} else {
break;
}
}
}
// initialize the heap
void buildMaxHeap(vector<int>& nums, int len) {
for (int i = len / 2; i >= 0; --i) {
maxHeapify(nums, i, len); // i is the last non leaf node
}
}
void heapSort(vector<int>& nums) {
int len = nums.size() - 1;
buildMaxHeap(nums, len); // initialize the heap first
for (int i = len; i>= 1; --i) {
// swap the largest nums[0] with the last element (becomes ascending order)
swap(nums[i], nums[0]);
len--; // reduce range
maxHeapify(nums, 0, len); // keep adjust the heap
}
}
public:
vector<int> sortArray(vector<int>& nums) {
heapSort(nums);
return nums;
}
};
Counting Sort (Bucket Sort)
- use an additional array to record the quantity of elements that shows up
- use the counting array to put the elements in order
Limites:
- only suite for the data set that in a tiny range with repeated value (if large range, space complexity could be bad)
- only suite for counting integer (sorting integer)
Bucket Sort
- assume the data are evenly separate.
- divide the range into multipule bucket (for range of 1 to 100, have buckets 1~10, 11~20...)
- sort each buckets
Limites:
- need to know the data range so can design the suitable size of bucket, otherwise waste the space and make bucket meaningless.
- better combine with other sorting algorithm to divide large task to small one
Radix Sort
- find largest element and calculate the highest place
- put ones digit into bucket and sort each bucket, then put back
- put tenes place into bucket and sort each bucket, then put back...
Integer, radix for 10; binary radix for 2; 8 bits ASCII char radix for 256.
Best practice in most situation
Use quick sort first and when data size becomes small, use non-recursion algorithm like insertion sort, etc.
Heap vs quick: refactor the heap will consume more resource in the real situation.