Summary of sorting algorithms (insert, Hill, selection, heap, bubble, fast, merge, count)

insert image description here

1. Sorting summary

Sort by:It is to rearrange a string of random data from small to large, or from large to small, so that it becomes orderly data, which is convenient for people to observe and extract data.

Common sorting algorithms are: insertion sort, selection sort, exchange sort, merge sort.

2. Insertion sort

Insert the records to be sorted into a sorted sequence one by one according to the size of their key values, until all the records are inserted, and a new sequence is obtained.

direct insertion sort

When inserting the i-th (i>=1) element, the previous arr[0], arr[1]...arr[i-1] have been sorted, and at this time use the sort code of arr[i] and the previous arr is compared, and arr[i] is inserted when the insertion position is found, and the elements at the original position are moved backwards in order.

//插入排序
void InsertSort(int* a, int n)
{
    
    
	//从头往后开始遍历
	for (int i = 0; i < n - 1; i++)
	{
    
    
		int end = i;
		int key = a[end + 1];  //保存最后的值
		while(end>=0)
		{
    
    

			//比前边小就交换
			if (key < a[end])
			{
    
    
				//前面的给后面
				a[end + 1] = a[end];
			}
			else
			{
    
    
				break;
			}
			end--;
		}
		a[end + 1] = key;
	}

}

Summary of features:
1. The element set is close to order, and the time efficiency of direct insertion sorting algorithm is higher.
2. Time complexity: O(N^2)
3. Space complexity: O(1)
4. Stability: stable

Hill sort (shrink incremental sort)

First select a step size gap, divide the data at every gap length into groups, all the data with gaps are in the same group, and insert and sort the data of each group, then reduce the gap, repeat the above process until the gap is 1, when the gap is 1, all records are sorted in the same group.

//希尔排序
void ShellSort(int* a, int n)
{
    
    
	int gap = n;
	//gap>1,预排
	//gap ==1 ,插入排序
	while (gap > 1)
	{
    
    
		gap = (gap / 3) + 1;
		for (int i = 0; i < n - gap; i++)
		{
    
    
			//单次排序调整
			int end = i;
			int key = a[end + gap];
			while (end >= 0)
			{
    
    
				if (key < a[end])
				{
    
    
					a[end + gap] = a[end];
				}
				else
				{
    
    
					break;
				}
				end -= gap;
			}
			a[end + gap] = key;
		}
	}
}

Feature summary:
1. Hill is an optimization for direct insertion sorting.
2. When gap>1 is pre-sorting, the purpose is to make the array closer to order. When gap==1, the array is already close to order, so it will be very fast.Pre-sorting can make large numbers go to the end faster, and small numbers go to the beginning faster.
3. Time complexity: close to O(N^1.3)
4. Space complexity: O(1)
5. Stability: unstable

3. Selection sort

Each time from the elements to be sorted, find the smallest (largest) element and store it at the beginning of the sequence until all the elements to be sorted are sorted.

direct selection sort

Find the smallest element in the element set 0~i-1, remember the subscript of the array element, if after traversing, the position of the subscript of the array is not the starting position of the current array, then exchange the element with the starting position, repeat Repeat the above steps until there is one element left in the set.

//选择排序
void SelectSort(int* a, int n)
{
    
    
	//每次选择最大和最小的
	int begin = 0;
	int end = n - 1;

	while (begin < end)
	{
    
    
		int min_index = begin;
		int max_index = begin;
		for (int i = begin; i <= end; i++)
		{
    
    
			//发现更小的
			if (a[min_index] > a[i])
			{
    
    
				min_index = i;
			}
			//发现更大的
			else if (a[max_index] < a[i])
			{
    
    
				max_index = i;
			}
		}
		Swap(&a[begin], &a[min_index]);
		//防止最大的数字被换走
		if (max_index == begin)
		{
    
    
			max_index = min_index;//把下标再赋回来
		}
		Swap(&a[end], &a[max_index]);
		end--;
		begin++;
	}

}


Summary of features:
1. Although the idea is easy to understand, it is inefficient and rarely used.
3. Time complexity: O(N^2)
4. Space complexity: O(1)
5. Stability: unstable

heap sort

Heap sorting is an algorithm that uses a heap data structure to sort. Large heaps are sorted in ascending order, and small heaps are sorted in descending order. Only the top element of the heap is selected each time as the maximum or minimum value until the number of sets in the heap is 1. .

//堆排序(大堆)
//先建堆,从后往前建堆效率更高.
//向下建堆效率高,从后往前,每个结点的左右子树都建成堆
void AdjustDown(int* a, int n, int parent)
{
    
    
	//比较左右孩子较小的一个
	//如果条件满足再和父亲交换
	//再看调整的孩子需不需要
	int child = 2 * parent + 1;
	//有没有叶子结点
	while (child<n)
	{
    
    

        //右孩子存在,右孩子大于左孩子
        if (child + 1 < n && a[child] < a[child + 1])
        {
    
    
            child++;
        }
        //左孩子大于父亲
		if (a[child] > a[parent])
		{
    
    
			//孩子父亲交换,并且父亲变为孩子
			Swap(&a[child], &a[parent]);
			parent = child;
            child = 2*parent+1;
		}
		else
		{
    
    
			break;
		}
	}

}



//堆排序
void HeapSort(int* a, int n)
{
    
    
	//1.先建堆
	//2.然后交换首尾元素
	//再向下调整
	int parent = (n-1-1) / 2;
	while (parent>=0)
	{
    
    
		AdjustDown(a, n, parent);
		--parent;
	}
	int end = n-1;
	while (end > 0)
	{
    
    
		Swap(&a[0], &a[end]);//end是下标
		AdjustDown(a, end, 0); //end 代表个数
		--end;
	}
	
}

Summary of features:
1. Heap sorting uses heaps to select numbers, which greatly improves efficiency
3. Time complexity: O(NlogN)
4. Space complexity: O(1) loop construction
5. Stability: unstable

4. Swap sort

Swap, swaps the position of two records in the sequence based on the comparison of the key values ​​of the two records in the sequence. The characteristic of exchange sorting is: move the number with larger key value to the end, and move the number with smaller key value to the front of the sequence.

Bubble Sort


//冒泡排序
void BubbleSort(int* a, int n)
{
    
    
	//从小冒到大
	//重复
	int flag = 0;
	for (int j = 0; j < n - 1; j++)
	{
    
    
		//每次排序完一个,将flag置0
		flag = 0;
		for (int i = 0; i < n - 1 - j; i++)
		{
    
    
			if (a[i] > a[i + 1])
			{
    
    
				flag = 1;
				Swap(&a[i], &a[i + 1]);
			}
		}
		if (flag == 0)
		{
    
    
			break;
		}
	}

}

Feature summary:
1. Time complexity: O(N^2)
2. Space complexity: O(1)
3. Stability: stable

quick sort

Recursive thinking: According to the recursive method of preorder, first find the reference value, and then ensure that all the left subsequences are less than the reference value, and the values ​​of the right subsequence are all greater than the reference value. Then divide again until the subsequence is 1.

Quick sort framework:

// 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int array[], int left, int right)
{
    
    
 if(right - left <= 1)
 return;
 
 // 按照基准值对array数组的 [left, right)区间中的元素进行划分
 int div = partion(array, left, right);
 
 // 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right)
 // 递归排[left, div)
 QuickSort(array, left, div);
 
 // 递归排[div+1, right)
 QuickSort(array, div+1, right);
}

Hall version (hoare)

Make the key on the left, go to the right first, find the small, stop when encountering a number smaller than the key, then go to the left to find a large, stop when encountering a number greater than the key, exchange the numbers in the left and right positions, left==right.


Ask why the left side is the key, and the right side goes first?
Make sure that the position smaller than the key is smaller than the key or the position of the key: 1. R goes first, R stops, and L meets R (because R is looking for small, the position where R stops must be smaller than the key); 2. R Go first, R did not find a value smaller than the key, R went to meet L (the position where the encounter was stopped in the previous round, either the position of the key, or smaller than the key)

//快速排序(霍尔法)
void QuickSort_old_hoare(int* a, int begin, int end)
{
    
    
	//左边作key,右边先走找小,找到了停下
	//左边再走找大,找到停下,交换
	//重复

	if (begin >= end)
	{
    
    
		return;
	}

	//一次排序
	int left = begin;
	int right = end;
	int keyi = begin;
	while (left < right)
	{
    
    
		//找小
		while (left < right && a[keyi] <= a[right])
		{
    
    
			--right;
		}
		//找大
		while (left < right && a[left] <= a[keyi])
		{
    
    
			++left;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);
	keyi = left;

	//递归
	QuickSort_old_hoare(a, begin, keyi - 1);
	QuickSort_old_hoare(a, keyi+1, end);


}

Function split version:

void QuickSort(int* a, int begin,int end)
{
    
    
	if (begin >= end)
	{
    
    
		return;
	}

	if (end-begin>10)
	{
    
    
		int keyi = Partition2(a, begin, end);
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi+1, end);
	}
	else
	{
    
    
		InsertSort(a + begin, end - begin + 1);
	}
}
//快排的霍尔版本
int Partition1(int* a, int begin, int end)
{
    
    
	int left = begin;
	int right = end;
	int keyi = begin;
	
	while (left < right)
	{
    
    
		//找小
		while (left < right && a[right] >= a[keyi])
		{
    
    
			--right;
		}
		//找大
		while (left < right && a[left] <= a[keyi])
		{
    
    
			++left;
		}
		Swap(&a[right], &a[left]);
	}
	Swap(&a[keyi], &a[left]);
	keyi = left;
	return keyi;

}

pit digging

The well-known fast sorting is this idea, which is easier to understand clearly than Hall's method. First put the first number in the temporary variable key, and form a pit at this time, then walk to the right to find the small one, stop when encountering the small one, assign the value to the pit, and form a new pit. And find the big one on the left side, assign a value, and form a new pit until the two sides meet, and assign the key value to the left side.


//快速排序(挖坑法)
void QuickSort_dig(int* a, int begin, int end)
{
    
    
	//左边作key,右边先走找小,找到了停下
	//左边再走找大,找到停下,交换
	//重复

	if (begin >= end)
	{
    
    
		return;
	}

	//一次排序
	int left = begin;
	int right = end;
	int keyi = GetMidIndex(a,left,right);
	int key = a[keyi];
	while (left < right)
	{
    
    
		//找小
		while (left < right && a[keyi] <= a[right])
		{
    
    
			--right;
		}
		a[keyi] = a[right];
		keyi = right;  //right变为坑
		//找大
		while (left < right && a[left] <= a[keyi])
		{
    
    
			++left;
		}
		a[keyi] = a[left];
		keyi = left;
	}
	a[keyi] = key;

	//递归
	QuickSort_old_hoare(a, begin, keyi - 1);
	QuickSort_old_hoare(a, keyi + 1, end);


}

Function split version:


void QuickSort(int* a, int begin,int end)
{
    
    
	if (begin >= end)
	{
    
    
		return;
	}

	if (end-begin>10)
	{
    
    
		int keyi = Partition2(a, begin, end);
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi+1, end);
	}
	else
	{
    
    
		InsertSort(a + begin, end - begin + 1);
	}
}


//挖坑版本
int Partition2(int* a, int begin, int end)
{
    
    
	int left = begin;
	int right = end;
	//int keyi = begin;//坑在起始位置
	int keyi = GetMidIndex(a,begin,end);//坑在起始位置
	int key = a[keyi];
	while (left < right)
	{
    
    
		//找小
		while (left < right &&a[right] >= key)
		{
    
    
			--right;
		}
		a[keyi] = a[right];//填坑
		keyi = right;//换坑
		//找大
		while (left < right && a[left] <= key)
		{
    
    
			++left;
		}
		a[keyi] = a[left];//填坑
		keyi = left;//换坑
	}
	a[keyi] = key;
	return keyi;
}

double pointer version

This version uses double 'pointers' to optimize the code structure and make the code more concise, but the readability of the code becomes worse. The function of the fast pointer is to find the small number, and the function of the slow pointer is to keep the boundary. The middle of the two pointers is a large number, and it moves forward in the form of somersaults.

//双指针版本
int Partition3(int* a, int begin, int end)
{
    
    
	//prev在起始位置,cur在下一个位置
	//判断cur,若cur小于prev,则prev+1交换
	//大于prev,cur++
	int prev = begin;
	int cur = prev + 1;
	int key = a[begin];

	while (cur <= end)
	{
    
    
		当prev+1 == cur时,如果再交换的话就浪费了
		//if (a[cur] < key )
		//{
    
    
		//	++prev;
		//	Swap(&a[cur], &a[prev]);
		//}
		//++cur;

		//当prev+1 == cur时,如果再交换的话就浪费了
		if (a[cur] < key&&++prev!=cur)
		{
    
    
			Swap(&a[cur], &a[prev]);
		}
		++cur;
	}
	Swap(&a[begin], &a[prev]);
	return prev;
}


void QuickSort(int* a, int begin,int end)
{
    
    
	if (begin >= end)
	{
    
    
		return;
	}

	if (end-begin>10)
	{
    
    
		int keyi = Partition2(a, begin, end);
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi+1, end);
	}
	else
	{
    
    
		InsertSort(a + begin, end - begin + 1);
	}
}

Quick sort optimization

In order to prevent the stack from bursting in the worst case, we need to use the three-number method to optimize the location of keyi. The depth of such recursion is close to logN.

//为了防止最坏情况爆栈,我们可以用三数取中法来优化,keyi
// 三数取中法
int GetMidIndex(int* a, int begin, int end)
{
    
    
	int mid = (begin + end) / 2;
	if (a[begin] > a[mid])
	{
    
    
		if (a[mid] > a[end])
		{
    
    
			return mid;
		}
		else
		{
    
    
			if (a[end] > a[begin])
			{
    
    
				return begin;
			}
			else
			{
    
    
				return end;
			}
		}
	}
	else
	{
    
    
		if (a[end] < a[begin])
		{
    
    
			return begin;
		}
		else
		{
    
    
			if (a[end] > a[mid])
			{
    
    
				return mid;
			}
			else
			{
    
    
				return end;
			}
		}
	}
}

quick sort non recursive

Because the idea of ​​quick sort is close to the pre-order traversal of the binary tree, we can consider using the stack to simulate the recursive process. The core idea is: use the stack to save the interval of each quick sort. It is somewhat similar to the method of layer order traversal.


//快排非递归(需要使用栈)
// 要求掌握,递归改非递归
// 递归大问题,极端场景下面,如果深度太深,会出现栈溢出
// 1、直接改循环 -- 比如斐波那契数列、归并排序
// 2、用数据结构栈模拟递归过程
void QuickSort_nonrecursive(int* a, int begin, int end)
{
    
    
	//先将左右入栈
	//出栈,调用快排函数
	//调用完了再入栈
	Stack q;
	StackInit(&q);
	StackPush(&q, end);
	StackPush(&q, begin);

	//栈不空
	while (!StackEmpty(&q))
	{
    
    
		int left = StackTop(&q);
		StackPop(&q);
		int right = StackTop(&q);
		StackPop(&q);
		如果左小于右说明可以划分,就继续入栈
		//if (left < right)
		//{
    
    
		//	int keyi = Partition2(a, left, right);
		//	StackPush(&q, keyi - 1);
		//	StackPush(&q, left);
		//	StackPush(&q, right);
		//	StackPush(&q, keyi + 1);
		//}
		

		int keyi = Partition2(a, left, right);
		//小区间能继续划分,就入栈
		if (left < keyi - 1)
		{
    
    
			StackPush(&q, keyi - 1);
			StackPush(&q, left);
		}
		if (keyi + 1 < right)
		{
    
    
			StackPush(&q, right);
			StackPush(&q, keyi + 1);
		}


	}
	StackDestroy(&q);
}

Summary of features:
1. Quick sort, as the name suggests, has the best comprehensive performance and adapts to the most scenarios
2. Time complexity: O(NlogN)
3. Space complexity: O(logN)
4. Stability: unstable

5. Merge sort

Merge sort is an effective sorting algorithm based on the merge operation, which uses divide and conquer (Divide and Conquer). Combine the ordered subsequences to obtain a completely ordered sequence; that is, first ensure that the subsequences are in order, and then merge the two subsequences into an ordered sequence, which is called a two-way merge. Core steps: first decompose and then merge.

merge recursive version

// 归并排序递归

void MergeSort(int* a, int n)
{
    
    
	int* tem = (int*)malloc(sizeof(int) * n);
	if (tem == NULL)
	{
    
    
		printf("malloc failed");
		exit(0);
	}

	_MergeSort(a, 0, n - 1, tem);

	free(tem);
}
void _MergeSort(int* a, int begin, int end, int* tem)
{
    
    
	//begin和end左闭右闭区间
	if (begin >= end) //只有一个元素就不需要归并了
	{
    
    
		return;
	}

	//1.分解
	int mid = (begin + end) / 2;
	_MergeSort(a, begin, mid, tem);
	_MergeSort(a, mid+1, end, tem);

	//2.合并(都是闭区间)
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int j = begin;
	//开始合并
	while (begin1 <= end1 && begin2 <= end2)
	{
    
    
		if (a[begin1] < a[begin2])
		{
    
    
			tem[j++] = a[begin1++];
		}
		else
		{
    
    
			tem[j++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
    
    
		tem[j++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
    
    
		tem[j++] = a[begin2++];
	}

	//合并好一部分就拷贝回去,左闭右闭加1
	memcpy(a + begin, tem + begin, sizeof(int) * (end - begin + 1));

}

recursive non-recursive version

Because the recursion of merging is similar to subsequent traversal, it is not suitable to use the stack to simulate the implementation, which will lead to the problem of missing intervals. So we consider using a loop to modify. Use gap to traverse the array and simulate the process of two-way merging,Pay attention to the control problem of the interval (very tricky)

Agree to merge after merging once:

// 归并排序非递归
//后序遍历用栈不好改非递归
//考虑用循环
//第一轮,一个一个排,第二轮两个两个排,
void MergeSortNonR(int* a, int n)
{
    
    
	int begin = 0;
	int end = n - 1;
	int* tem = (int*)malloc(sizeof(int) * n);
	assert(tem);
	memset(tem, 0, sizeof(int) * n);
	//排序
	int gap = 1;
	while (gap < n)
	{
    
    
		//printf("gap = %d: ", gap);
		for (int i = 0; i < n; i = i + 2 * gap)
		{
    
    
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = begin2 + gap - 1;

			//防止越界,修正边界
			//end1越界
			if (end1 >= n)
			{
    
    
				//把end1修成最后一个数
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if(begin2 >= n)// begin2越界
			{
    
    
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n) // end2越界
			{
    
    
				end2 = n - 1;
			}



			//printf("[%d,%d] [%d,%d]->", begin1, end1, begin2, end2);
			//往tem,排序
			int j = i;//比较位置开始,即从begin1
			while (begin1 <= end1 && begin2 <= end2)
			{
    
    
				if (a[begin1] <= a[begin2])
				{
    
    
					tem[j++] = a[begin1++];
				}
				else
				{
    
    
					tem[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
    
    
				tem[j++] = a[begin1++];

			}
			while (begin2 <= end2)
			{
    
    
				tem[j++] = a[begin2++];
			}
	
		}
		//排完全部的再拷贝
		//合并
		//printf("\n");
		memcpy(a, tem, sizeof(int) * (end - begin + 1));
		gap = gap * 2;
	}

}

Merging a range just merges:

// 归并排序非递归
//后序遍历用栈不好改非递归
//考虑用循环
//第一轮,一个一个排,第二轮两个两个排,
void MergeSortNonR(int* a, int n)
{
    
    
	int begin = 0;
	int end = n - 1;
	int* tem = (int*)malloc(sizeof(int) * n);
	assert(tem);
	memset(tem, 0, sizeof(int) * n);
	//排序
	int gap = 1;
	while (gap < n)
	{
    
    
		//printf("gap = %d: ", gap);
		for (int i = 0; i < n; i = i + 2 * gap)
		{
    
    
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = begin2 + gap - 1;

			//防止越界,修正边界
			//end1越界
			if (end1 >= n)
			{
    
    
				//把end1修成最后一个数
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)// begin2越界
			{
    
    
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n) // end2越界
			{
    
    
				end2 = n - 1;
			}
			//记录每次归并几个,就拷贝几个
			int count = end2 - begin1 + 1;

			//printf("[%d,%d] [%d,%d]->", begin1, end1, begin2, end2);
			//往tem,排序
			int j = i;//比较位置开始,即从begin1
			while (begin1 <= end1 && begin2 <= end2)
			{
    
    
				if (a[begin1] <= a[begin2])
				{
    
    
					tem[j++] = a[begin1++];
				}
				else
				{
    
    
					tem[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
    
    
				tem[j++] = a[begin1++];

			}
			while (begin2 <= end2)
			{
    
    
				tem[j++] = a[begin2++];
			}
			//排完一部分就拷贝
			memcpy(a + i, tem + i, sizeof(int) * count);

		}

		gap = gap * 2;
	}

}

Summary of features
1. Disadvantages The space complexity is O(N), and this algorithm is mainly used for the idea of ​​external sorting.
2. Time complexity: O(NlogN)
3. Space complexity: O(N)
4. Stability: stable

6. Counting sort

Counting sorting, also known as the pigeonhole principle, is similar to the hash addressing method: the number of occurrences of the value is counted for the first time, and the sequence is recycled to the original sequence according to the statistical result.

//计数排序
void CountSort(int* a, int n)
{
    
    
	//找出数据的范围,找出最大值与最小值然后做差,就是数组的范围
	int max = a[0], min = a[0];
	for (int i = 0; i < n; i++)
	{
    
    
		if (a[i] > max)
		{
    
    
			max = a[i];
		}
		if (a[i] < min)
		{
    
    
			min = a[i];
		}
	}
	//创建计数数组
	int count = max - min + 1;
	int* tem = (int*)malloc(sizeof(int) * count);
	assert(tem);
	memset(tem, 0, sizeof(int) * count);

	//遍历数组记录数据出现次数
	for (int i = 0; i < n; i++)
	{
    
    
		tem[a[i] - min]++;
	}

	int j = 0;
	//排序,tem中出现几次就往a写几个
	for (int i = 0; i < count; i++)
	{
    
    
		//只要tem[i]不为0,就会进入循环
		while (tem[i]--)
		{
    
    
			a[j++] = i + min;
		}
	}
}

Feature summary
1. Counting sort is very efficient when the data range is concentrated, but it is not suitable for floating-point numbers and data whose data ranges are too different.
2. Time complexity: O(max(N, range))
3. Space complexity: O(range)
4. Stability: stable

The above is the summary of sorting knowledge in this article. If you have any questions, please discuss them in the comment area.

Guess you like

Origin blog.csdn.net/weixin_45153969/article/details/131047869