Quick sort, Hill sort, merge sort, heap sort, insertion sort, bubble sort, selection sort (recursive, non-recursive) C language detailed

1. The concept of sorting and its application

1.1 The concept of sorting

Sorting : The so-called sorting is the operation of arranging a string of records in increasing or decreasing order according to the size of one or some of the keywords.

Stability : Assume that there are multiple records with the same keyword in the sequence of records to be sorted. If sorted, the relative order of these records remains unchanged, that is, in the original sequence, r[i]=r[j] , and r[i] is before r[j], and in the sorted sequence, r[i] is still before r[j], the sorting algorithm is said to be stable; otherwise it is called unstable.

Internal sorting : A sorting in which all data elements are placed in memory.

External sorting : Too many data elements cannot be placed in memory at the same time, and the sorting of data cannot be moved between internal and external memory according to the requirements of the sorting process.

1.2 Sorting application
1.3 Common sorting algorithms
// 排序实现的接口

// 插入排序
void InsertSort(int* a, int n);

// 希尔排序
void ShellSort(int* a, int n);

// 选择排序
void SelectSort(int* a, int n);

// 堆排序
void AdjustDwon(int* a, int n, int root);
void HeapSort(int* a, int n);

// 冒泡排序
void BubbleSort(int* a, int n)

// 快速排序递归实现
// 快速排序hoare版本
int PartSort1(int* a, int left, int right);
// 快速排序挖坑法
int PartSort2(int* a, int left, int right);
// 快速排序前后指针法
int PartSort3(int* a, int left, int right);
void QuickSort(int* a, int left, int right);

// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)

// 归并排序递归实现
void MergeSort(int* a, int n)
// 归并排序非递归实现
void MergeSortNonR(int* a, int n)

// 计数排序
void CountSort(int* a, int n)

// 测试排序的性能对比
void TestOP()
{
     
     
	srand(time(0));
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int)*N);
	int* a2 = (int*)malloc(sizeof(int)*N);
	int* a3 = (int*)malloc(sizeof(int)*N);
	int* a4 = (int*)malloc(sizeof(int)*N);
	int* a5 = (int*)malloc(sizeof(int)*N);
	int* a6 = (int*)malloc(sizeof(int)*N);

	for (int i = 0; i < N; ++i)
	{
     
     
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];

	}

	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();

	int begin3 = clock();
	SelectSort(a3, N);
	int end3 = clock();

	int begin4 = clock();
	HeapSort(a4, N);
	int end4 = clock();

	int begin5 = clock();
	QuickSort(a5, 0, N-1);
	int end5 = clock();

	int begin6 = clock();
	MergeSort(a6, N);
	int end6 = clock();

	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
}
Sort OJ (you can use various sorts to run this OJ) OJ link

2. Implementation of common sorting algorithms

2.1 Insertion sort
2.1.1 Basic idea:

Direct insertion sorting is a simple insertion sorting method, the basic idea of ​​which is:

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 .

In practice, when we play poker, we use the idea of ​​insertion sort

2.1.2 Direct insertion sort:

When inserting the i-th (i>=1) element, the previous array[0], array[1],...,array[i-1] have been sorted, and at this time use the sort code of array[i] and Array[i-1], array[i-2],... compare the order of the sorting codes, find the insertion position and insert array[i], and the order of the elements at the original position is moved backward

Summary of the characteristics of direct insertion sort:

  1. The closer the element set is to order, the higher the time efficiency of the direct insertion sort algorithm
  2. Time complexity: O(N^2)
  3. Space complexity: O(1), it is a stable sorting algorithm
  4. Stability: Stable
//插入排序
void InsertSort(int* a, int length)
{
     
     
	for (int i = 1; i < length; i++)
	{
     
     
		int end = i - 1;
		int num = a[i];
		while (end >= 0)
		{
     
     
			if (num < a[end])
			{
     
     
				a[end + 1] = a[end];//挪动数组
				end--;
			}
			else
			{
     
     
				break;//找到了要插入的点
			}
		}
		a[end + 1] = num;
	}
}
2.1.3 Hill sorting (reducing incremental sorting)

The Hill sorting method is also known as the shrinking increment method. The basic idea of ​​the Hill sorting method is: first select an integer, divide all records in the file to be sorted into groups, and group all records with a distance of 0 into the same group, and sort the records in each group. Then, take and repeat the above grouping and sorting work. When reach = 1, all records are sorted in the same group .

4

//希尔排序
void ShellSort(int* a, int length)
{
     
     
	//接近有序
	int gap = length;
	while (gap > 1)
	{
     
     
		gap /= 2;
		for (int i = 0; i < length - gap; i++)
		{
     
     
			int end = i;
			int num = a[i + gap];
			while (end >= 0)
			{
     
     
				if (a[end] > num)
				{
     
     
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
     
     
					break;
				}
			}
			a[end + gap] = num;
		}
	}
}

Summary of the characteristics of Hill sorting:

  1. Hill sort is an optimization of direct insertion sort.

  2. When gap > 1, it is pre-sorted, 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. In this way, the overall optimization effect can be achieved. After we implement it, we can compare performance tests.

  3. The time complexity of Hill sorting is not easy to calculate, because there are many ways to value the gap, which makes it difficult to calculate. Therefore, the time complexity of Hill sorting given in many trees is not fixed:

"Data Structure (C Language Edition)" - Yan Weimin

"Data Structure - Using Object-Oriented Method and C++ Description" - Yin Renkun

Because our gap is valued according to the method proposed by Knuth, and Knuth has conducted a large number of experimental statistics, we temporarily follow: O ( n 1.25 ) O(n^{1.25})O ( n1.25 )toO ( 1.6 ∗ n 1.25 ) O(1.6*n^{1.25})O(1.6n1.25 )Future calculation.

  1. Stability: Unstable
2.2 Selection sort

2.2.1 Basic idea:

Select the smallest (or largest) element from the data elements to be sorted each time, and store it at the beginning of the sequence until all the data elements to be sorted are exhausted.

2.2.2 Direct selection sort:
  • Select the data element with the largest (smallest) key in the element set array[i]–array[n-1]

  • If it is not the last (first) element in the set, swap it with the last (first) element in the set

  • In the remaining array[i]–array[n-2] (array[i+1]–array[n-1]) collection, repeat the above steps until there is 1 element left in the collection

//选择排序
void SelectSort(int* a, int length)
{
     
     
	int left = 0, right = length - 1;
	while (left < right)
	{
     
     
		int maxi = left, mini = left;
		for (int i = left + 1; i <= right; i++)
		{
     
     
			if (a[i] < a[mini])
			{
     
     
				mini = i;
			}
			if (a[i] > a[maxi])
			{
     
     
				maxi = i;
			}
		}
		Swap(&a[left], &a[mini]);
		if (left == maxi) maxi = mini;
		Swap(&a[right], &a[maxi]);
		left++;
		right--;
	}
}
Summary of features of direct selection sort:
  1. Direct selection sort thinking is very easy to understand, but the efficiency is not very good. rarely used in practice
  2. Time complexity: O(N^2)
  3. Space complexity: O(1)
  4. Stability: Unstable
2.2.3 Heap sort

Heapsort (Heapsort) refers to a sorting algorithm designed using a stacked tree (heap) data structure, which is a type of selection sort. It selects data through the heap. It should be noted that you need to build a large heap for ascending order, and a small heap for descending order.

6

//堆排序
void AdjustDown(int* a, int sz, int parent)
{
     
     
	//调大堆
	assert(a);
	int child = parent * 2 + 1;
	while (child < sz)//儿子节点要存在
	{
     
     
		//找左右儿子中最大的那个
		if (child + 1 < sz && a[child] < a[child + 1])
		{
     
     
			child++;//找到了最大的那个
		}

		if (a[child] > a[parent])
		{
     
     
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
     
     
			break;
		}
	}
}

void SetHeap(int* a, int sz)
{
     
     
	assert(a);
	for (int i = (sz - 1 - 1) / 2; i >= 0; i--)
	{
     
     
		AdjustDown(a, sz, i);
	}
}

void heap_sort(int* a, int sz)
{
     
     
	int num = sz;
	SetHeap(a, sz);
	while (num)
	{
     
     
		Swap(&a[0], &a[num-1]);
		num--;
		AdjustDown(a, num, 0);
	}
}
Summary of features of direct selection sort:
  1. Heap sort uses the heap to select numbers, which is much more efficient.
  2. Time complexity: O(N*logN)
  3. Space complexity: O(1)
  4. Stability: Unstable
2.3 Swap sort

Basic idea: The so-called exchange is to exchange the positions of the two records in the sequence according to the comparison result of the key values ​​of the two records in the sequence. Records with smaller key values ​​are moved to the front of the sequence.

2.3.1 Bubble sort
//冒泡排序
void BubbleSort(int* a, int n)
{
     
     
	for (int i = 0; i < n - 1; i++)
	{
     
     
		bool exchange = false;
		for (int j = 0; j < n - i - 1; j++)
		{
     
     
			if (a[j] > a[j + 1])
			{
     
     
				Swap(&a[j], &a[j + 1]);
				exchange = true;
			}
		}
		if (exchange == false)
		{
     
     
			break;
		}
	}
}

Summary of the characteristics of bubble sort:

  1. Bubble sort is a very easy to understand sort
  2. Time complexity: O(N^2)
  3. Space complexity: O(1)
  4. Stability: Stable
2.3.2 Quick Sort

Quick sorting is a binary tree structure exchange sorting method proposed by Hoare in 1962. Its basic idea is: any element in the sequence of elements to be sorted is taken as a reference value, and the set to be sorted is divided into two subsequences according to the sorting code , all elements in the left subsequence are less than the reference value, all elements in the right subsequence are greater than the reference value, and then the leftmost subsequence repeats the process until all elements are arranged in the corresponding position .

// 假设按照升序对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);
}

The above is the main framework for the recursive realization of quicksort. It is found that it is very similar to the preorder traversal rules of the binary tree. When writing the recursive framework, students can think about the preorder traversal rules of the binary tree and write it out quickly. The postorder only needs to analyze how to follow the reference value The way to divide the data in the interval is enough.

Common ways to divide the interval into left and right halves according to the base value are:

  1. hoare version
//hoare版本
void QuickSort(int* a, int left,int right)
{
     
     
	if (left >= right) return;
	
	int begin = left, end = right;
	int keyi = left;
	int mid = GetMid(a, left, right);
	Swap(&a[left], &a[mid]);
	while (left < right)
	{
     
     
		while (left < right && a[right] >= a[keyi])
		{
     
     
			right--;
		}

		while (left < right && a[left] <= a[keyi])
		{
     
     
			left++;
		}

		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);
	keyi = right;
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}
  1. pit digging
//挖坑法
void QuickSort(int* a, int left, int right)
{
     
     
	if (left >= right) return;
	
	int key = a[left];
	int begin = left, end=right;
	while (left < right)
	{
     
     
		while (left < right && a[right] >= key)
		{
     
     
			right--;
		}
		a[left] = a[right];
		
		while (left < right && a[left] <= key)
		{
     
     
			left++;
		}
		a[right] = a[left];
	}
	a[left] = key;
	int keyi = left;
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}
  1. Front and back pointer version
//前后指针快速排序
void QuickSort(int* a, int left, int right)
{
     
     
	if (left >= right)
	{
     
     
		return;
	}
	
	int cur = left + 1, prev = left;
	int keyi = left;
	while (cur <= right)
	{
     
     
		if (a[cur] < a[keyi] && cur > prev)
		{
     
     
			prev++;
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	int mid = prev;
	QuickSort(a, left, mid - 1);
	QuickSort(a, mid+1, right);
}

2.3.2 Quick Sort Optimization

  1. Three numbers take the middle method to choose the key
int GetMid(int* a,int left,int right)
{
     
     
	int mid = left + right >> 1;
	if (a[left] < a[mid])
	{
     
     
		if (a[mid] < a[right])
		{
     
     
			return mid;
		}
		else if (a[left] > a[right])
		{
     
     
			return left;
		}
		else
		{
     
     
			return right;
		}
	}
	else//a[left]>a[mid]
	{
     
     
		if (a[mid] > a[right])
		{
     
     
			return mid;
		}
		else if (a[left] < a[mid])
		{
     
     
			return left;
		}
		else
		{
     
     
			return right;
		}
	}
}
  1. When recursing into small subranges, consider using insertion sort
//小区间优化
void QuickSort(int* a, int left, int right)
{
     
     
	if (left >= right)
	{
     
     
		return;
	}
	if ((right - left + 1) > 10)
	{
     
     
		int cur = left + 1, prev = left;
		int keyi = left;
		while (cur <= right)
		{
     
     
			if (a[cur] < a[keyi] && cur > prev)
			{
     
     
				prev++;
				Swap(&a[cur], &a[prev]);
			}
			cur++;
		}
		Swap(&a[prev], &a[keyi]);
		int mid = prev;
		QuickSort(a, left, mid - 1);
		QuickSort(a, mid + 1, right);
	}
	else
	{
     
     
		InsertSort(a + left, right - left + 1);
	}
}
2.3.2 Quick sort non-recursive
void QuickSortNonR(int* a, int left, int right)
{
	Stack st;
	StackInit(&st);
	StackPush(&st, left);
	StackPush(&st, right);

	while (StackEmpty(&st) != 0)
	{
		right = StackTop(&st);
		StackPop(&st);
		left = StackTop(&st);
		StackPop(&st);

      if(right - left <= 1)
          continue;

		int div = PartSort1(a, left, right);
       // 以基准值为分割点,形成左右两部分:[left, div) 和 [div+1, right)
      StackPush(&st, div+1);
	    StackPush(&st, right);

		StackPush(&st, left);
		StackPush(&st, div);
	}

  StackDestroy(&s);
}
int OnceSort(int* a, int left, int right)
{
     
     
	if (left > right)
	{
     
     
		return;
	}
	int key = a[left];
	while (left < right)
	{
     
     
		//先算右边,右边找大
		while (left<right&&a[right] >= key)
		{
     
     
			right--;
		}
		//找到了就交换
		a[left] = a[right];
		while (left<right&&a[left] <= key)
		{
     
     
			left++;
		}
		a[right] = a[left];
	}
	a[left] = key;//将key放在正确的位置上
	int meeti = left;//相遇的点
	return meeti;
}

void QuickSort(int* a, int left, int right)
{
     
     
	ST st;//创建一个栈来模拟递归的过程
	STInit(&st);
	STPush(&st,right);
	STPush(&st,left);
	while (!STEmpty(&st))
	{
     
     
		//左区间
		int begin = STTop(&st);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);
		int mid = OnceSort(a, begin, end);
       
		if(end > mid + 1)
		{
     
     
			STPush(&st, end);
			STPush(&st, mid + 1);
		}
       //如果left>=mid-1说明左边已经排完序了
       if(begin < mid - 1)
		{
     
     
			STPush(&st,mid - 1);
			STPush(&st, begin);
		}
	}
	STDestroy(&st);
}

ifWhy is the condition of the judgment statement not taken here =?

If we take the equal sign:

There will be many unnecessary judgments. When begin and end are equal, there is only one element. An element does not need to be sorted, so there is no need to take a =number

If no =number is taken:

Summary of quick sort features:
  1. The overall comprehensive performance and usage scenarios of quick sort are relatively good, so I dare to call it quick sort

  2. Time complexity: O(N*logN)

15

  1. Space complexity: O(logN)

  2. Stability: Unstable

2.4 Merge sort

Basic idea:

Merge sort (MERGE-SORT) is an effective sorting algorithm based on the merge operation, which is a very typical application of divide and conquer (Divide and Conquer). Combine the ordered subsequences to obtain a completely ordered sequence; that is, first make each subsequence in order, and then make the subsequence segments in order. Merging two sorted lists into one sorted list is called a two-way merge.
The core steps of merging and sorting: [External link image transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the image and upload it directly (img-svQBGJGJ-1683775858015) (C:/Users/19735/Desktop/%E6%95% B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%88%9D%E9%98%B6V5-2021%E4%BF%AE%E8%AE%A2/Lesson6–%E6 %8E%92%E5%BA%8F/12.jpg)]

//归并排序递归
void _MergeSort(int* a, int begin, int end, int* tmp)
{
     
     
	if (begin >= end)
	{
     
     
		return;
	}
	
	int mid = begin + end >> 1;
	_MergeSort(a, begin, mid,tmp);
	_MergeSort(a, mid+1, end,tmp);
	
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
     
     
		if (a[begin1] < a[begin2])
		{
     
     
			tmp[i++] = a[begin1++];
		}
		else
		{
     
     
			tmp[i++] = a[begin2++];
		}
	}
	
	//处理剩余的
	while (begin1 <= end1)
	{
     
     
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
     
     
		tmp[i++] = a[begin2++];
	}

	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

void MergeSort(int* a, int sz)
{
     
     
	int* tmp = (int*)malloc(sizeof(int) * sz);
	if (tmp == NULL)
	{
     
     
		perror("malloc fail");
		exit(-1);
	}
	_MergeSort(a, 0, sz - 1, tmp);
	
	free(tmp);
}
//归并排序非递归
void MergeSortNonR(int* a, int n)
{
     
     
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
     
     
		perror("malloc fail");
		exit(-1);
	}
	int gap = 1;//分组,组间距
	while (gap < n)
	{
     
     
		for (int i = 0; i < n; i += gap * 2)
		{
     
     
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			int j = i;
			if (begin1>=n||end1 >= n || begin2 >= n)
			{
     
     
				break;
			}

			if (end2 >= n)
			{
     
     
				end2 = n - 1;
			}
			printf("[%d %d] [%d %d]\n", begin1, end1, begin2, end2);
			while (begin1 <= end1 && begin2 <= end2)
			{
     
     
				if (a[begin1] < a[begin2])
				{
     
     
					tmp[j++] = a[begin1++];
				}
				else
				{
     
     
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
     
     
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
     
     
				tmp[j++] = a[begin2++];
			}
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		gap *= 2;
	}
	free(tmp);
}

If the interval is not corrected:

It can be seen that the original array originally only has 9 elements, and the effective range is [0,8]but in the above operation, there is obviously a range that exceeds this range, so what is the reason for this?

The code in the box will cross the boundary when the gap is large, but the program will continue because begin2it is still less thanend2

Code running process:

Summary of the characteristics of merge sort:
  1. The disadvantage of merging is that it requires O(N) space complexity, and the thinking of merging and sorting is more to solve the problem of external sorting in the disk.
  2. Time complexity: O(N*logN)
  3. Space complexity: O(N)
  4. Stability: Stable
2.5 Non-comparison sorting

Idea: Counting sorting, also known as the pigeonhole principle, is a modified application of the hash direct addressing method.
Steps:

  1. Count the number of occurrences of the same element
  2. Recycle the sequence to the original sequence according to the statistical results13
//计数排序
void CountSort(int* a, int n)
{
     
     
	int max = a[0], min = a[0];
	for (int i = 1; i < n; ++i)
	{
     
     
		if (a[i] > max)
		{
     
     
			max = a[i];
		}

		if (a[i] < min)
		{
     
     
			min = a[i];
		}
	}

	int range = max - min + 1;
	int* countA = (int*)malloc(sizeof(int) * range);
	if (countA == NULL)
	{
     
     
		perror("malloc fail\n");
		return;
	}
	memset(countA, 0, sizeof(int) * range);

	// 计数
	for (int i = 0; i < n; i++)
	{
     
     
		countA[a[i] - min]++;
	}

	// 排序
	int j = 0;
	for (int i = 0; i < range; i++)
	{
     
     
		while (countA[i]--)
		{
     
     
			a[j++] = i + min;
		}
	}

	free(countA);
}
Summary of features of counting sort:
  1. Counting sort is highly efficient when the data range is concentrated, but its scope of application and scenarios are limited.
  2. Time complexity: O(MAX(N,range))
  3. Space complexity: O(range)
  4. Stability: Stable

3. Sorting algorithm complexity and stability analysis

16

17

4. Multiple choice practice

1. 快速排序算法是基于(    )的一个排序算法。
A 分治法
B 贪心法
C 递归法
D 动态规划法

2.对记录(54,38,96,23,15,72,60,45,83)进行从小到大的直接插入排序时,当把第8个记录45插入到有序表时,为找到插入位置需比较( )次?(采用从后往前比较)
A 3
B 4
C 5
D 6

3.以下排序方式中占用O(n)辅助存储空间的是
A 选择排序
B 快速排序
C 堆排序
D 归并排序

4.下列排序算法中稳定且时间复杂度为O(n2)的是( )
A 快速排序
B 冒泡排序
C 直接选择排序
D 归并排序

5.关于排序,下面说法不正确的是
A 快排时间复杂度为O(N*logN),空间复杂度为O(logN)
B 归并排序是一种稳定的排序,堆排序和快排均不稳定
C 序列基本有序时,快排退化成冒泡排序,直接插入排序最快
D 归并排序空间复杂度为O(N), 堆排序空间复杂度的为O(logN)

6.下列排序法中,最坏情况下时间复杂度最小的是( )
A 堆排序
B 快速排序
C 希尔排序
D 冒泡排序

7.设一组初始记录关键字序列为(65,56,72,99,86,25,34,66),则以第一个关键字65为基准而得到的一趟快速排序结果是()
A 3456256586997266
B 2534566599867266
C 3456256566998672
D 3456256599867266
答案:
1.A
2.C
3.D
4.B
5.D
6.A
7.A

Guess you like

Origin blog.csdn.net/AkieMo/article/details/130591011