Summary of ten classic sorting algorithms
Algorithm to compare
Each algorithm has its own time complexity and stability characteristics. The following is a comparison chart of common indicators of the ten classic algorithms
Algorithm indicators
Time complexity
When we compare the advantages and disadvantages of the algorithm, the limited consideration is to compare the time complexity, commonly used O notation. I’m not going to overstate it here.
Please note that when our O indexes are the same, that is to say, the grade is the same, we cannot compare them by constant terms. There is no proportional relationship between them, which can only be determined by actual calculation.
Such two expressions for example, a = > O (N ^ 2 + 200 9999 N) and b = > O (N ^ 2), you can’t say b is better, this was associated with the function of specific operations, for example, if the b is constant + – * / operations, such as constant but a running ^ | & arithmetic operations, such as Obviously bits are better than constants. This is where the constant index 9999 becomes a bit of a chicken.
Algorithm content
Bubble sort
Algorithm steps
-
Compare adjacent elements. If the first one is bigger than the second, swap them both.
-
Do the same for each pair of adjacent elements, starting from the first pair to the last pair at the end. When we do that, the last element is going to be the largest number.
-
Repeat this step for all elements except the last one.
-
Keep repeating the above steps for fewer and fewer elements at a time until there are no more pairs to compare
Images demonstrate
Code implementation
function swap(arr, i, j) {
var temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
function bubbleSort(arr) {
var len = arr.length;
// Total number of rounds to execute
for (var i = 0; i < len - 1; i++) {
// The number of comparisons per round
for (var j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// If the previous number is greater than the previous number, switch places
swap(arr, j, j + 1); }}}return arr;
}
console.log(
bubbleSort([3.5.38.15.36.26.27.2.44.46.4.19.47.48.50])
/ / [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
);
Copy the code
Selection sort
Selection sort is a simple and intuitive sorting algorithm, whatever data goes in is O(n²) time complexity. So when you use it, the smaller the data, the better. The only benefit might be that it doesn’t take up any extra memory.
Algorithm steps
-
First find the smallest (large) element in the unsorted sequence and store it at the beginning of the sorted sequence.
-
Find the smallest (largest) element from the remaining unsorted elements and place it at the end of the sorted sequence.
-
Repeat the second step until all elements are sorted.
Images demonstrate
Code implementation
function swap(arr, i, j) {
var temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
function selectionSort(arr) {
var len = arr.length;
// Record the current minimum index
var minIndex;
// Total number of rounds to execute
for (var i = 0; i < len - 1; i++) {
minIndex = i;
// Find the index with the smallest value for each round
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
// If the previous number is greater than the previous number, switch places
minIndex = j;
}
}
swap(arr, i, minIndex);
}
return arr;
}
console.log(
selectionSort([3.5.38.15.36.26.27.2.44.46.4.19.47.48.50]) / / [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
);
Copy the code
Insertion sort
Insertion sort is one of the most simple and intuitive sorting algorithms. Its working principle is to build an ordered sequence and scan the unsorted data from back to front in the sorted sequence to find the corresponding position and insert it. Like bubble sort, insert sort also has an optimization algorithm called split insert.
Algorithm steps
-
Treat the first element of the first to be sorted sequence as an ordered sequence and the second element to the last element as an unsorted sequence.
-
Scan the unordered sequence from beginning to end, inserting each scanned element into the appropriate place in the ordered sequence. (If the element to be inserted is equal to an element in the ordered sequence, the element to be inserted is inserted after the equal element.)
The demo
Code demo
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
// The current number to compare
const current = arr[i];
// Record the index of the number currently being compared
let preIndex = i - 1;
while (preIndex >= 0 && arr[preIndex] > current) {
// When the number being compared is larger than the current number, move it back one bit
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
// The number found is no longer greater than the current number or has reached the end, insert the current number after the number being compared
arr[preIndex + 1] = current;
}
return arr;
}
console.log(
insertionSort([3.5.38.15.36.26.27.2.44.46.4.19.47.48.50])
/ / [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
);
Copy the code
Hill sort (understood)
Hill sort, also known as the descending incremental sort algorithm, is a more efficient and improved version of insertion sort. But Hill sort is an unstable sort algorithm.
Hill sort is an improved method based on the following two properties of insertion sort:
- Insertion sort has high efficiency when it operates on almost ordered data, that is, it can achieve the efficiency of linear sort.
- But insert sort is generally inefficient because it can only move data one bit at a time;
The basic idea of Hill sorting is that the whole sequence of records to be sorted is divided into several sub-sequences for direct insertion sorting respectively. When the records in the whole sequence are “basically ordered”, the whole records are directly inserted in sequence.
Algorithm steps
Select an incremental sequence T1, T2… , where Ti > tj, tk = 1;
Sort the sequence k times according to the number of incremental sequences k;
In each sort, the sequence to be sorted is divided into several sub-sequences of length M according to the corresponding increment TI, and each sub-table is sorted by direct insertion. Only when the increment factor is 1, the whole sequence is treated as a table, and the length of the table is the length of the whole sequence.
The demo
Code implementation
function shellSort(arr) {
var len = arr.length,
current,
subAreaLen = 1;
while (subAreaLen < len / 3) {
// Dynamically define interval sequences
subAreaLen = subAreaLen * 3 + 1;
}
for (var gap = subAreaLen; gap > 0; gap = Math.floor(gap / 3)) {
for (var i = gap; i < len; i++) {
current = arr[i];
var preIndex = i - gap;
while (preIndex >= 0&& arr[preIndex] > current) { arr[preIndex + gap] = arr[preIndex]; preIndex -= gap; } arr[preIndex + gap] = current; }}return arr;
}
console.log(
shellSort([3.5.38.15.36.26.27.2.44.46.4.19.47.48.50])
/ / [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
);
Copy the code
Merge sort
Merge sort is an efficient sorting algorithm based on Merge operations. This algorithm is a very typical application of Divide and Conquer.
Algorithm steps
- Apply for a space equal to the sum of two sorted sequences, which is used to store the combined sequence;
- Set two Pointers, starting at the start of two sorted sequences;
- Compare the elements pointed to by the two Pointers, select the smaller element into the merge space, and move the pointer to the next position;
- Repeat step 3 until a pointer reaches the end of the sequence;
- Copies all remaining elements of the other sequence directly to the end of the merged sequence.
The demo
Code implementation
function sort(arr) {
if (arr == null || arr.length < 2) {
return;
}
mergeSort(arr, 0, arr.length - 1);
return arr;
}
function mergeSort(arr, l, r) {
if (l === r) return;
var m = l + ((r - l) >> 1);
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
// The merge process takes place in layers
merge(arr, l, m, r);
}
function merge(arr, l, m, r) {
var help = [];
var i = 0;
var p1 = l;
var p2 = m + 1;
while (p1 <= m && p2 <= r) {
// Get some smaller value and assign it to help. Pointer back one bit
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
// Copy the remaining values into the help array
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (var i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
console.log("merge", arr);
}
console.log(
sort([3.5.38.15.36.26.27])
//[3, 5, 15, 26,27, 36, 38]
);
/** * 3, 5, 15, 26,27, 36, 38 * / \ * 3,5,15 26,27,36,38 = > merge * / \ \ * 3, 5, 15, 26, 27, 36, 38 = > merge * / \ \ / \ * 5 15 26 and 27 36 38 = > merge * /
Copy the code
Calculation of time complexity
Since recursion is a classic divide-and-conquer strategy, follow the master formula.
The master formula (also known as the master method) is a time-complexity analysis method that is often used to solve problems using divide-and-conquer strategies. There are also two commonly used recursive solutions of divide-and-conquer strategy called substitution method and recursive tree method. As we all know, solving a problem using recursion in divide-and-conquer strategy is divided into three steps, namely decomposition, solution and merger. Therefore, the main method is expressed as follows:
T [n] = aT n/b + f (n) (directly for T = [n] aT [n/b] + T (n ^ d))
Where a >= 1 and b > 1 is constant, it represents the meaning of
n
Represents the size of the problem,a
The number of recursions is the number of subproblems generated,b
It means that each recursion is the same1/b
One scale,F (n)
Represents the sum of the time taken to decompose and merge.d
Represents the time complexity of the subprocess
The final solution
- when
d<logb a
, the time complexity isO(n^(logb a))
- when
d=logb a
, the time complexity isO((n^d)*logn)
- when
d>logb a
, the time complexity isO(n^d)
The sample
Our merge sort
a
is2Because themergeSort
Called twice;b
is2Because we have the same number of intervals each time1/2
;d
is1Because themergeSort
All you’re doing inside the function is shrinking the interval. Time complexity isO(n)
So the end result, merge sort is order NlogN time, order N extra space.
Quick sort
Algorithm steps
-
First find the left end of the position (random number probability event), and the right end of the number exchange (function is to swap the current number as the base number)
-
Partitioning operations
-
First, establish the less and more areas, the current number pointer, the position of the number to be compared
-
Compares the size relationship between the current number and the number being compared
- Less than,
less
The area expands to the right, switching the positions of the two numbers, and moving the cur pointer to the right one bit - More than,
more
The field expands to the left, and the two numbers are swapped. The cur pointer does not move (because the swapped numbers continue to be compared) - It’s equal to cur and we move it one place to the right
- Less than,
-
The swap position between the larger range and the index being compared
-
Returns the current middle position
-
-
After getting the position of the middle area, recalculate the less than area and greater than area, continue to partition sort
The demo
Code implementation
function swap(arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
function sort(arr) {
if(! arr || arr.length <2) {
return;
}
quickSort(arr, 0, arr.length - 1);
return arr;
}
function quickSort(arr, l, r) {
if (l < r) {
// Use random numbers to reduce complexity by making worst-case scenarios probabilistic
var random = Math.floor(Math.random() * (r - l + 1));
swap(arr, l + random, r);
var p = partition(arr, l, r);
// Less than the extent continues to partition sort
quickSort(arr, l, p[0] - 1);
// If the value is larger than the value, the partition sort continues
quickSort(arr, p[1] + 1, r); }}function partition(arr, l, r) {
var less = l - 1; // The index at the beginning of the "less than" area
var more = r; // Index at the beginning of the greater than area
var cur = l; // The index of the current comparison
var compare = r; // The index being compared
while (cur < more) {
if (arr[cur] < arr[compare]) {
// The current number is small, the < area expands to the right, swap values, the current number pointer back
swap(arr, ++less, cur);
cur++;
} else if (arr[cur] > arr[compare]) {
// If the current number is large, the > area expands to the left, swap values, the current number pointer does not move,
swap(arr, --more, cur);
} else {
// If this position is equal, the pointer will continue to move furthercur++; }}// Swap positions between the greater than range and the index being compared
swap(arr, more, compare);
// Return to the middle area
return [less + 1, more];
}
console.log(
sort([3.5.38.15.36.26.27.2.44.46.4.19.47.48.50])
/ / [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
);
Copy the code
Heap sort
Algorithm steps
- Create a large top heap H[0… R]
- Swap the heap head (maximum) with the heap tail;
- Reduce the size of the heap by 1 to fit the top of the new array.
- Repeat step 2 until the heap size is 1.
The demo
Code implementation
function heapSort(arr) {
if(! arr || arr.length <2) return arr;
Create a big top heap
buildMaxHeap(arr);
var size = arr.length;
for (var i = arr.length - 1; i > 0; i--) {
// 2. Swap the heap head (maximum) with the heap tail;
swap(arr, 0, i);
// 3. Reduce the size of the heap by 1, in order to adjust the top of the new array to the corresponding position;
heapify(arr, 0, --size);
// Repeat step 2 until the heap size is 1.
}
return arr;
}
/** * create a big top heap *@param {*} arr* /
function buildMaxHeap(arr) {
for (var i = Math.floor(arr.length / 2); i >= 0; i--) {
// Since leaf nodes do not have children, you can reduce the number of builds by eliminating the need to build the heapheapify(arr, i); }}/** * Convert a normal heap to a large root heap *@param {*} arr
* @param {*} index
* @param {*} size* /
function heapify(arr, index, size = arr.length) {
var left = index * 2 + 1;
var right = index * 2 + 2;
var largest = index;
if (left < size && arr[left] > arr[largest]) {
// The left child is larger than the parent node
largest = left;
}
if (right < size && arr[right] > arr[largest]) {
// The right child is larger than the parent node
largest = right;
}
if (largest !== index) {
swap(arr, index, largest);
heapify(arr, largest, size);
}
}
function swap(arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
console.log(
heapSort([3.5.38.15.36.26.27.2.44.46.4.19.47.48.50])
/ / [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
);
Copy the code
Count sorting
Algorithm steps
- (1) Find the largest and smallest elements in the array to be sorted
- (2) Count the number of occurrences of each element with the value of I in the array and store it into the i-th item of array C
- (3) Add all the counts (starting with the first element in C, add each term to the previous one)
- (4) Reverse fill the target array: place each element I in the C(I) item of the new array, and subtract 1 from C(I) for each element
Conditions of use
- It can only be used in scenarios where the data range is small. If the data range K is much larger than the data to be sorted n, it is not suitable for counting sort.
- Counting sort can only sort non-negative integers. Other types need to be converted to non-negative integers without changing their relative sizes.
- For example, if the test score is accurate to the last decimal place, you need to multiply all the scores by 10 to round them up.
The demo
Code implementation
function countingSort(arr) {
if(! arr || arr.length <2) {
return arr;
}
var max = Number.MIN_VALUE;
var min = 0; // min => Can be compatible with negative numbers to achieve the purpose of capacity expansion
for (var i = 0; i < arr.length; i++) {
// Calculate the maximum and minimum value of the array
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
var bucket = new Array(max - min + 1).fill(0);
for (var i = 0; i < arr.length; i++) {
// The index of each bucket represents the value that the bucket points to in the original array. The value stored is the number of occurrences of this element
bucket[arr[i] - min]++;
}
var i = 0;
for (var j = 0; j < bucket.length; j++) {
while (bucket[j]--) {
Bucket [50] = 3 => newArr:[50,50,50]arr[i++] = j + min; }}return arr;
}
console.log(
countingSort([3.5.38.15, -6.0.36.26, -27.2.44.46.4.19.47.48.50])
/ / [- 27-6, 0, 2, 3, 4, 5, 15, 19, 26, 36, 38, 44 46-48, 47, 48, 50]
);
Copy the code
Bucket sort
Algorithm steps
- Determine the number of buckets and the data range of each bucket;
- Put the values of the array into the appropriate bucket according to the data range;
- Sort the data for each bucket;
- Add the data for each bucket in turn to the array
The demo
Code implementation
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
// The current number to compare
const current = arr[i];
// Record the index of the number currently being compared
let preIndex = i - 1;
while (preIndex >= 0 && arr[preIndex] > current) {
// When the number being compared is larger than the current number, move it back one bit
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
// The number found is no longer greater than the current number or has reached the end, insert the current number after the number being compared
arr[preIndex + 1] = current;
}
return arr;
}
function bucketSort(arr, bucketSize) {
if(! arr || arr.length <2) return arr;
var minValue = arr[0];
var maxValue = arr[0];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < minValue) minValue = arr[i]; // Record the minimum value of the array
if (arr[i] > maxValue) maxValue = arr[i]; // Record the maximum value of the array
}
// Initialize buckets. By default, the length of each bucket is 5
const DEFAULT_BUCKET_SIZE = 5;
bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
// The number of buckets currently required
const bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
let buckets = new Array(bucketCount);
for (var i = 0; i < buckets.length; i++) {
buckets[i] = [];
}
// Allocate data to buckets using mapping functions
for (i = 0; i < arr.length; i++) {
const bucketIndex = Math.floor((arr[i] - minValue) / bucketSize);
buckets[bucketIndex].push(arr[i]);
}
arr.length = 0;
for (i = 0; i < buckets.length; i++) {
insertionSort(buckets[i]); // Sort each bucket, using insert sort
for (var j = 0; j < buckets[i].length; j++) {
// Add data for each bucket in turnarr.push(buckets[i][j]); }}return arr;
}
console.log(
bucketSort([3.5.38.15.0.36.26.2.44.46.4.19.47.48.50])
// [0, 2, 3, 4, 5, 15, 19, 26, 36, 38, 44, 46, 47, 48, 50]
);
Copy the code
Radix sort
Radix sort is a kind of non-comparative integer sort algorithm. Its principle is to cut the integers into different numbers according to the number of digits, and then compare them according to each number. Since integers can also represent strings (such as names or dates) and floating-point numbers in a particular format, radix sort is not restricted to integers.
Algorithm steps
Simple version of the idea:
- Prepare an extra space, S, and find the highest digit
arr
Starting with the units digit, stores the units digit and spaceS
Index the same number to the corresponding position- Use a space
S
The rearrangementarr
, so it can be in ascending order of one digit; - Repeat steps 2 and 3 until you reach the highest digit
The demo
Code implementation
-
Simple version of the
It’s order N^2.
function radixSort(arr, maxDigit) { var mod = 10; var dev = 1; var counter = new Array(10); for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) { for (var j = 0; j < arr.length; j++) { var bucket = parseInt((arr[j] % mod) / dev); if (counter[bucket] == null) { counter[bucket] = []; } // Store the current number of digits in the same space index to the corresponding location such as 13 => bucket[3] = 13 counter[bucket].push(arr[j]); } var pos = 0; for (var j = 0; j < counter.length; j++) { var value = null; if(counter[j] ! =null) { while((value = counter[j].shift()) ! =null) { // Follow the first-in, first-out principle // Reorganize the arR to achieve the ascending order of the current calculated bitsarr[pos++] = value; }}}}return arr; } /** * gets the maximum number of digits */ function getMaxBit(arr) { var max = Number.MIN_SAFE_INTEGER; for (var i = 0; i < arr.length; i++) { max = Math.max(max, arr[i]); } var res = 0; while(max ! =0) { res++; max = Math.floor(max / 10); } return res; } const arr = [3.5.38.15.0.36.26.2.44.46.4.19.47.48.50]; console.log( radixSort(arr, getMaxBit(arr)) // [0, 2, 3, 4, 5, 15, 19, 26, 36, 38, 44, 46, 47, 48, 50] ); Copy the code
-
Complex version
It’s order k times N.
/** * gets the maximum number of digits */ function getMaxBit(arr) { var max = Number.MIN_SAFE_INTEGER; for (var i = 0; i < arr.length; i++) { max = Math.max(max, arr[i]); } var res = 0; while(max ! =0) { res++; max = Math.floor(max / 10); } return res; } /** * gets the digit * of a digit in a digit@param {*} x * @param {*} d* / function getDigit(x, d) { return (x / Math.pow(10, d - 1)) % 10; } function radixSort(arr) { if(! arr || arr.length <2) return arr; partition(arr, 0, arr.length - 1, getMaxBit(arr)); return arr; } function partition(arr, begin, end, digit) { const radix = 10; var i = 0, j = 0; var bucket = new Array(end - begin + 1).fill(0); for (var d = 1; d <= digit; d++) { var count = new Array(radix).fill(0); for (i = begin; i <= end; i++) { j = getDigit(arr[i], d); count[j]++; } for (i = 1; i < radix; i++) { count[i] = count[i] + count[i - 1]; } for (i = end; i >= begin; i--) { j = getDigit(arr[i], d); bucket[count[j] - 1] = arr[i]; count[j]--; } for (i = begin, j = 0; i <= end; i++, j++) { arr[i] = bucket[j]; }}}const arr = [3.5.38.15.0.36.26.2.44.46.4.19.47.48.50]; console.log( radixSort(arr) // [0, 2, 3, 4, 5, 15, 19, 26, 36, 38, 44, 46, 47, 48, 50] ); Copy the code
Complexity contrast
Radix sort vs counting sort vs bucket sort
There are two ways to sort radix:
- MSD sorts from the top
- LSD sorts from the low end
These three sorting algorithms all use the concept of buckets, but there are obvious differences in the way buckets are used:
- Radix sort: buckets are allocated according to each digit of the key value;
- Counting sort: each bucket stores a single key;
- Bucket sorting: Each bucket stores a range of values;