❤ Detailed explanation of top ten sorting algorithms ❤ This is probably the most complete, complete version of code you've ever seen

preface

Brothers, in response to the requirements of the data structure in the previous article, I started working today and started the liver algorithm. The sword finger offer is still on the road. I really want to drive to pick it up. However, the code God drives without a driver's license. Forget it, get the sorting algorithm. It's a little long. Be patient. It's not easy to be original. You know, first get a picture
It can be seen that there are still many sorting algorithms. Forget it, I won't say much. You and I just go out with 4 years of internship experience!

Intersection sorting

Bubbling

Bubble, I generally call it enumeration, which is to go through everything. The efficiency is relatively low. Generally, in the algorithm competition, if there is no better one to use, let's talk about a simple enumeration first!

Enumeration dictionary order
First of all, some students may not know what is the dictionary order. Please see:
(1, 2, 3), (1, 3, 2)... This is the embodiment of dictionary order. The official interpretation is as follows:

Lexicographic order is a sort method to form a sequence of random variables. The method is to form a sequence from small to large in alphabetical order or in numerical order.
For example, a random variable X contains {1 2 3} three values.
Its dictionary order is {1 2 3} {1 3 2} {2 1 3} {2 3 1} {3 1 2} {3 2 1}
If it is a letter: first compare the first character i and b, b < i, b is the second, i is the ninth, 2 < 9, so if the first digit is the same, compare the second digit,
For example: abcdd < ABCDE aaaay < aaaaz if one of them is the prefix of the other, the shorter row is in front of: aaa
The following code is used to realize the arrangement of 1-n:

//Bubble sort, which I also call enumeration
#include<iostream>
#include<cstdio>
using namespace std;
void print(int n, int *a, int cur)
{
	if (cur == n)//Recursive boundary
	{
		for (int i = 0; i < n; i++)
		{
			printf("%d", a[i]);
		}
		printf("\n");
	}
	else for (int i = 1; i <= n; i++)
	{
		int OK = 1;
		for (int j = 0; j < cur; j++)
		{
			if (a[j] == i)//Determine if i has occurred
				OK = 0;
			if (OK)//i there is no next one
			{
				a[cur] = i;
				print(n, a, cur + 1);//recursion
			}
		}
	}
}
int main()
{

}

Let's take a look at the authentic bubble sorting. The general idea is: if you exchange in reverse order until there is no reverse order record, the code implementation is relatively simple, which is the nesting of two for loops

#include<iostream>
#Include < algorithm > / / call the algorithm library and use the swap function swap
#include<cstdio>
using namespace std;
int main()
{
	int a[10];
	for (int i = 0; i < 10; i++)
	{
		cin >> a[i];

	}
	for (int i = 0; i < 10; i++)
	{
		for (int j = i + 1; j < 10; j++)
		{
			if (a[i] < a[j])
				swap(a[i], a[j]);
		}
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

Generally speaking, it's relatively simple, but it takes time and memory. Anyway, it's not good. Let's optimize it. Why not? In the final analysis, many repeated operations and a lot of comparisons have been made,
The general idea is as follows: after another comparison, it is found that all the data have become in order. Directly exit the cycle and no longer continue the cycle

//Change for to
	bool flag = true;
	for (int i = 0; i < 10 && flag; i++)
	{
		flag = false;
		for (int j = 10 - 1; j >= i; j--)
		{
			if (a[j] > a[i])
			{
				swap(a[j], a[j + 1]);
					flag = true;
			}
		}
	}

Algorithm complexity: two for, which is O (n^2), is a little large

simple

Select sort
First, compare it with bubble sort. The main difference between them is that the data of bubble sort is constantly exchanged, while quick sort first exchanges the alias of the data, and then exchanges itself. An analogy is that one is a short-term stock speculator who fantasizes about falling pie in the sky and fighting back, while the other is a stock speculator who sees the opportunity, commonly known as the stock god.
Well, the comparison is over. Let's look at the simple code implementation

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int main()
{
	int a[10];
	for (int i = 0; i < 10; i++)
	{
		cin >> a[i];
	}
	for (int i = 0; i < 10; i++)
	{
		int min = i;
		for (int j = i + 1; j < 10; j++)
		{
			if (a[min] > a[j])
				min = j;//Swap subscript position
		}
		if (i != min)
			swap(a[i], a[min]);
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

If you analyze the algorithm complexity, you will be surprised to find that the time complexity is still O (n^2), but what I want to say is that it is still better than bubble sorting, why?
Bubble sorting and selective sorting are relatively simple and easy to implement algorithms in sorting algorithms. The idea of bubble sorting is: in each sorting process, the maximum (small) in the currently unfilled order is moved to the rightmost (left) end of the array through the exchange of adjacent elements. The idea of selecting sorting is also very intuitive: in each sorting process, we obtain the exchange between the largest (small) element in the current non arranged order and the element at the rightmost (left) end of the array, and cycle this process to sort the whole array.
The average time complexity of selection sorting is slightly lower than that of bubble sorting:
In the case of the same data, the cycle times of the two algorithms are the same, but the selective sorting has only 0 to 1 exchange, while the bubble sorting has only 0 to n exchange

Quick sort

It is similar to bubble sort, but better than bubble. It is generally a divide and conquer idea, exchanging pivot elements

  1. Partition: rearrange the elements in the array into left and right parts, so that the left is less than or equal to any element on the right
  2. Recursive solution: sort the left and right respectively
  3. Merge: at this time, you will find that it has been arranged

Or arrange a string of numbers for code implementation:

#include<iostream>
using namespace std;

void quickSort(int *arr,int begin,int end)
{
//begin is left and end is right
	//If the interval is more than one number
	if(begin < end)
	{
		int temp = arr[begin]; //Take the first number of the interval as the reference number
		int i = begin; //The "pointer" when searching from left to right indicates the current left position
		int j = end; //The "pointer" when searching from right to left indicates the current right position
		//No repeat traversal
		while(i < j)
		{
			//When the number on the right is greater than the reference number, skip and continue to search to the left
			//If the conditions are not met, the loop will jump out. At this time, the element corresponding to j is smaller than the reference element
			while(i<j && arr[j] > temp)
				j--;
			//Fill the number of datum elements less than or equal to the right into the corresponding position on the right
			arr[i] = arr[j];
			//When the number on the left is less than or equal to the benchmark number, skip and continue to search to the right
			//(set the repeated datum elements to the left interval)
			//If the conditions are not met, the loop will jump out. At this time, the element corresponding to i is greater than or equal to the reference element
			while(i<j && arr[i] <= temp)
				i++;
			//Fill in the corresponding position on the left with the number greater than the reference element on the left
			arr[j] = arr[i];
		}
		//Fill the datum element in the corresponding position
		arr[i] = temp;
		//At this time, i is the position of the datum element
		//Perform a similar quick sort on the left sub interval of the base element
		quickSort(arr,begin,i-1);
		//Perform similar quick sorting on the right sub interval of the benchmark element
		quickSort(arr,i+1,end);
	}
	//If the interval has only one number, return
	else
		return;
}
int main()
{
	int num[12] = {23,45,17,11,13,89,72,26,3,17,11,13};
	int n = 12;
	quickSort(num,0,n-1);
	cout << "The sorted array is:" << endl;
	for(int i=0;i<n;i++)
		cout << num[i] << ' ';
	cout << endl;
	return 0;
}

Let's calculate the complexity. The worst case O (n^2) and the average O (nlogn) have almost no worst case, so the efficiency is still relatively high. Think about how to get the maximum value?

#include<iostream>
using namespace std;
int Partition(int *a, int i, int j)
{
	int tmp = a[j];
	int index = i;
	if (i < j)
	{
		for (int k = i; k < j; k++) {
			if (a[k] >= tmp) {
				swap(a[index++], a[k]);
			}
		}
		swap(a[index], a[j]);
		return index;
	}
}


int Search(int a[], int i, int j, int k)
{
	int m = Partition(a, i, j);
	if (k == m - i + 1) return a[m];
	else if (k < m - i + 1)
	{
		return Search(a, i, m - 1, k);
	}
	//Second half
	else
	{

		//The second half of the core: just find the largest number k-(m-i+1)
		return Search(a, m + 1, j, k - (m - i + 1));
	}
}
int main()
{
	int a[7] = { 8,7,6,1,2,3,4 };
	int k = 3;
	cout << Search(a, 2, 6, k);
}

Insert sort

Direct insert sort

In other words, codegod has been playing landlords recently. What do you think is the biggest difference between mobile landlords and real landlords, or what are the benefits? I feel that there is no need to insert cards on the mobile phone to save time. This uses the principle of insertion sorting, which can be said to be "landlords sorting"

Basic operation: insert a record into the arranged ordered table to get a new ordered table with record data + 1
Basic operation, see the code:

void insertionSort(int *arr, int len) {
    if (len<2) {
        return ;
    }
    
    for (int i=1; i<len; i++) {
        int insertValue = arr[i];//Temporary storage requires an element to be inserted
        int j = i-1;
 
        //Compare elements from right to left
        for (; j>=0 && insertValue<array[j]; j--) {
            arr[j+1] = arr[j];
        }
 
        arr[j+1] = insertValue;
    }
}

According to the old rule, when analyzing the time complexity, the best case is that the order is well arranged. At this time, only comparison is needed. The time complexity is O (n), and the worst case is O (n^2). On average, it is n ^ 2/4, so the average time complexity is also o (n^2)

Shell Sort

If who is the first to break through the complexity of sorting algorithm O (n^2), then I think hill is the first. It can be said that hill sorting is an improvement on insertion sorting. The difference is that hill sorting can be said to be a continuous grouping sorting

First, divide the whole record sequence to be sorted into several subsequences for direct insertion sorting. The specific algorithm description is as follows:

Select an incremental sequence t1, t2,..., tk, where ti > TJ, tk=1;
Sort the sequence k times according to the number of incremental sequences k;
For each sorting, the sequence to be sorted is divided into several subsequences with length m according to the corresponding increment ti, and each sub table is directly inserted and sorted. Only when the increment factor is 1, the whole sequence is treated as a table, and the table length is the length of the whole sequence.

The implementation is as follows:

 //Shell Sort 
void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap>1)
	{
		//Half gap every time
		gap = gap / 2;
		//Single pass sorting
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tem = arr[end + gap];
			while (end >= 0)
			{
				if (tem < arr[end])
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tem;
		}
	}
}
                                          

The time complexity, awesome people, through a lot of calculations, found that it is O (n^3/2), less than o
(n^2), because it is a jump sort, it is not a stable sort

Select sort

Simple selection sort

See above

Heap sort

What is a heap? If you have learned heap, it is the heap opposite to the stack,
Basic idea: construct the generation sequence into a pile. At this time, the maximum value of the whole sequence is the root node at the top of the heap. Remove it, that is, exchange it with the end element of the heap array. At this time, the end element is the maximum value, and then reconstruct the remaining n-1 sequences into a heap, so as to obtain the sub maximum value of n elements, In this way, recursion will get an ordered sequence

varlen;   // Because multiple functions declared need data length, set len as a global variable
 
function buildMaxHeap(arr) {  // Build large top reactor
    len = arr.length;
    for(vari = Math.floor(len/2); i >= 0; i--) {
        heapify(arr, i);
    }
}
 
function heapify(arr, i) {    // Heap adjustment
    varleft = 2 * i + 1,
        right = 2 * i + 2,
        largest = i;
 
    if(left < len && arr[left] > arr[largest]) {
        largest = left;
    }
 
    if(right < len && arr[right] > arr[largest]) {
        largest = right;
    }
 
    if(largest != i) {
        swap(arr, i, largest);
        heapify(arr, largest);
    }
}      
 
function swap(arr, i, j) {
    vartemp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
 
function heapSort(arr) {
    buildMaxHeap(arr);
 
    for(vari = arr.length - 1; i > 0; i--) {
        swap(arr, 0, i);
        len--;
        heapify(arr, 0);
    }
    return arr;
}

It can be said that the time is mainly spent on initial heap building and repeated screening in rebuilding heap
1. Construction heap: O (n)
2. Rebuild heap: complete binary tree: Data structure, detailed explanation The time complexity is O(nlogn)
Because heap sorting is not sensitive to the sorting state of the original records, the time complexity of heap sorting is O (nlogn) for good or bad, and Hill sorting is unstable

Merge sort

Complete binary tree is a magical tree. It can be said that merging and sorting fully reflects the nature of complete binary tree

Second way

If two ordered tables are merged into one ordered table, it is called 2-way merging.

  • The input sequence with length n is divided into two subsequences with length n/2;
  • The two subsequences are sorted by merging;
  • Merge two sorted subsequences into a final sorting sequence.
#include<iostream>
using namespace std;
void Merge(int[], int, int[], int, int, int)  
void MergeSort(int numbers[], int length, int temp[], int begin, int end)
{
	//1. Similarly, judge whether the passed parameters are valid
	if (numbers == nullptr || length <= 0 || begin < 0 || end >= length)
		throw new exception("Invalid input.");
	
	//2. As the end condition of recursion, when the start subscript and end subscript are equal, it indicates that there is only one element in the subsequence, which is regarded as ordered
	if (end - begin == 0)
		return;

	//3. Define intermediate variables and divide the array into half [if there are 7 elements and subscripts 0-6, then middle=3, and the array is divided into two segments with lengths of 4 and 3]
	int middle = ((end - begin) / 2 ) + begin;
	//4. Recursion: recurse the left half first, then the right half, and divide the left and right subsequences into subsequences of length 1 before stopping recursion
	MergeSort(numbers, length, temp, begin, middle);
	MergeSort(numbers, length, temp, middle + 1, end);
	//5. Merge slowly
	Merge(numbers, length, temp, begin, end, middle);
}

void Merge(int numbers[], int length, int temp[], int begin, int end, int middle)
{
	//1. Judge whether any parameters that do not meet the requirements are passed in, and throw an error if any
	if (numbers == nullptr || length <= 0 || begin < 0 || end >= length)
		throw new exception("Invalid input.");

	//2. Separate the original sequence
	int leftIndex = begin;		//The beginning of the left sequence (the end of the left sequence is middle)
	int rightIndex = middle + 1;//The beginning of the right sequence (the end of the right sequence is end)
	int tempIndex = begin;		//Subscript of auxiliary array
	//3. When the left and right subsequences have not reached the end, cycle
	while (leftIndex <= middle && rightIndex <= end)
	{
		//4. Compare and judge by pairs. Whoever is older will be put into the auxiliary array, and the pointer will move back at the same time
		if (numbers[leftIndex] < numbers[rightIndex])
			temp[tempIndex] = numbers[leftIndex++];
		else
			temp[tempIndex] = numbers[rightIndex++];
		//5. Auxiliary array subscript++
		++tempIndex;
	}

	//6. When the left or right subsequence has not reached the end, it is directly put into the auxiliary array
	while (leftIndex <= middle)
		temp[tempIndex++] = numbers[leftIndex++];

	while (rightIndex <= end)
		temp[tempIndex++] = numbers[rightIndex++];

	//7. Replace the ordered elements in the auxiliary array with the unordered elements in the original array to make the original array partially ordered
	for (int i = begin; i <= end; ++i)
		numbers[i] = temp[i];
}

int main(int arvc, char* argv[])
{
	const int length = 9;
	int nums[length] = { 18, 7, 23, 3, 9, 32, 10 , 99, 0};
	int *temp = new int[length];

	MergeSort(nums, length, temp, 0, 8);

	for (int i = 0; i < length; i++)
		cout << nums[i] << "  ";

	delete[] temp;
	temp = nullptr;
	cout << endl;
	return 0;
}

multiple

Similarly, merging multiple ordered tables is called multi-channel merging, which is almost the same as two-way merging, so I won't repeat it,

Non comparative class

Count sort

Counting sort is not a sort algorithm based on comparison. Its core is to convert the input data values into keys and store them in the additional array space. As a sort with linear time complexity, count sort requires that the input data must be integers with a certain range.

  • Find the largest and smallest elements in the array to be sorted;
  • Count the number of occurrences of each element with value i in the array and store it in item i of array C;
  • All counts are accumulated (starting from the first element in C, and each item is added to the previous item;
  • Reverse fill the target array: put each element i in item C(i) of the new array, and subtract 1 from C(i) for each element.
#include <iostream>
using namespace std;
const int MAXN = 1000;
int arr[MAXN];

void counting_sort(int n)
{
	int min_value = 0x3f3f3f3f, max_value = 0;
	for (int i = 0; i < n; i++)
	{
		if (arr[i] > max_value)
			max_value = arr[i];
		if (arr[i] < min_value)
			min_value = arr[i];
	}
	int len = max_value - min_value + 1;
	int* bucket = new int[len]();
	for (int i = 0; i < n; i++)
	{
		bucket[arr[i] - min_value]++;
	}
	for (int i = 0, j = 0; i < len; i++)
	{
		while (bucket[i] != 0)
		{
			arr[j++] = i + min_value;
			bucket[i]--;
		}
	}
	delete bucket;
}

int main()
{
	int n;
	cout << "Please enter the number of elements in the array:";
	cin >> n;
	cout << "Please enter an element: " << endl;
	for (int i = 0; i < n; i++)
	{
		cin >> arr[i];
	}
	cout << "Before sorting:" << endl;
	for (int i = 0; i < n; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	counting_sort(n);
	cout << "After sorting:" << endl;
	for (int i = 0; i < n; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

The time complexity is O(n+k), and the space complexity is O(n+k). Its sorting speed is faster than any comparison sorting algorithm. When k is not very large and the sequences are concentrated, counting sorting is a very effective sorting algorithm.

Bucket sorting

Bucket sorting is an upgraded version of counting sorting. It makes use of the mapping relationship of the function. The key to efficiency lies in the determination of the mapping function. Working principle of bucket sort: assuming that the input data is uniformly distributed, divide the data into a limited number of buckets, and sort each bucket separately (it is possible to use another sorting algorithm or continue to use bucket sorting recursively).

  • Set a quantitative array as an empty bucket;
  • Traverse the input data and put the data into the corresponding bucket one by one;
  • Sort each bucket that is not empty;
  • Splice the ordered data from a bucket that is not empty.
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 1000;
const int BUCKET_SIZE = 10;//Default range per bucket
int arr[MAXN];

void insert_sort(vector<int> &v){
	int len=v.size(),temp,i,j;
	for(i=1;i<len;i++){
		temp = v[i];
		for(j=i;j>0 && v[j-1]>temp;j--){
			v[j]=v[j-1];
		}
		v[j]=temp;
	}
}

void bucket_sort(int n){
	int min_value = 0x3f3f3f3f, max_value = 0;
	for (int i = 0; i < n; i++)
	{
		if (arr[i] > max_value)
			max_value = arr[i];//Gets the maximum value of the input data
		if (arr[i] < min_value)
			min_value = arr[i];//Gets the minimum value of the input data
	}
	//Initialization of bucket
	int len = (max_value-min_value)/BUCKET_SIZE+1;
	vector<int> bucket[len];
	//Assign data to bucket
	for(int i=0;i<n;i++){
		bucket[(arr[i]-min_value)/BUCKET_SIZE].push_back(arr[i]);
	}
	for(int i=0,j=0;i<len;i++){
		//It is recommended to use insert sort or count sort. Of course, you can also use heap sort, quick sort, and so on
		insert_sort(bucket[i]);
		for(auto x:bucket[i]){
			arr[j++]=x;
		}
	}
}

int main(){
	int n;
	cout << "Please enter the number of elements in the array:";
	cin >> n;
	cout << "Please enter an element: " << endl;
	for (int i = 0; i < n; i++)
	{
		cin >> arr[i];
	}
	cout << "Before sorting:" << endl;
	for (int i = 0; i < n; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	bucket_sort(n);
	cout << "After sorting:" << endl;
	for (int i = 0; i < n; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

In the best case, the linear time O(n) is used for bucket sorting. The time complexity of bucket sorting depends on the time complexity of sorting the data between buckets, because the time complexity of other parts is O(n). Obviously, the smaller the bucket division, the less data between buckets, and the less time it takes to sort. Is a space for time algorithm

Cardinality sort

Cardinality sorting is sorting according to the low order, and then collecting; Then sort according to the high order, and then collect; And so on until the highest order. Sometimes some attributes are prioritized. They are sorted first by low priority and then by high priority. The final order is the high priority, the high priority, the same high priority, and the low priority, the high priority.

  • Get the maximum number in the array and get the number of bits;
  • arr is the original array, and each bit is taken from the lowest bit to form an r array;
  • Count and sort r (using the characteristics that count sorting is suitable for a small range of numbers);
#include <iostream>
#include <queue>
using namespace std;
using namespace std;
const int MAXN = 1000;
int arr[MAXN];
queue<int> q[10];

void radix_sort(int n)
{
	//Gets the number of digits of the maximum value
	int max_value = 0;
	for (int i = 0; i < n; i++)
	{
		if (max_value < arr[i])
			max_value = arr[i];
	}
	int max_digit = 0;
	while (max_value)
	{
		max_digit++;
		max_value /= 10;
	}
	//Start sorting
	int mod = 10, dev = 1;
	for (int i = 0; i < max_digit; i++, mod *= 10, dev *= 10)
	{
		for (int j = 0; j < n; j++)
		{
			int ix = arr[j] % mod / dev;
			q[ix].push(arr[j]);
		}
		int pos = 0;
		for (int j = 0; j < 10; j++)
		{
			while (!q[j].empty())
			{
				arr[pos++] = q[j].front();
				q[j].pop();
			}
		}
	}
}

int main()
{
	int n;
	cout << "Please enter the number of elements in the array:";
	cin >> n;
	cout << "Please enter an element: " << endl;
	for (int i = 0; i < n; i++)
	{
		cin >> arr[i];
	}
	cout << "Before sorting:" << endl;
	for (int i = 0; i < n; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	radix_sort(n);
	cout << "After sorting:" << endl;
	for (int i = 0; i < n; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

Cardinality sorting is based on sorting separately and collecting separately, so it is stable. However, the performance of cardinality sorting is slightly worse than bucket sorting. Each bucket allocation of keywords needs O(n) time complexity, and it needs O(n) time complexity to get a new keyword sequence after allocation. If the data to be sorted can be divided into D keywords, the time complexity of cardinality sorting will be O(d*2n). Of course, D is much less than N, so it is basically linear. The spatial complexity of cardinality sorting is O(n+k), where k is the number of buckets. Generally speaking, n > > k, so about n additional spaces are required.

last

It's the lyric stage of the code God again. First of all, it's not easy to be original, three company support, and the sword finger offer. I spent a week reading it myself. If it's good, I'll write it to you. I hope you and I will get better and better in the future, and the process of becoming bald must be stronger ❤️

Tags: Algorithm

Posted on Sun, 05 Sep 2021 14:57:59 -0400 by jimmy patel