[Data Structure] Eight Sorting Algorithms

Table of contents

1. Direct insertion sort

2. Hill sort

3. Selection sort

4. Heap sort

Five, bubble sort

Six, quick sort

  1. Recursive version

     1.1 Hoare method

     1.2 Digging method

     1.3 Back and forth pointer method

  2. Non-recursive version

  3. Optimization of quick sort

     3.1 Take the middle of three numbers

     3.2 Inter-cell optimization

Seven, merge sort

  1. Recursive version

  2. Non-recursive version

8. Counting and sorting

 


1. Direct insertion sort

Animation demo:

Ideas: (1) First write according to one insertion sort, the last element of the array is end, the data to be inserted is the temporary data tmp = a[end+1], if tmp is less than the data at the end position, the end position will be replaced The data is moved back to the position of end+1 until it is inserted into it; (2) and then inserted according to the overall insertion sort, assuming that there is only one data in the array for the first time, so the end position is 0, and the end condition is less than n -1, otherwise out of bounds.

Note: save the data at end+1 first, otherwise it will be overwritten when moving.

 

Two animation demonstrations are given above for better understanding.

//插入排序    时间复杂度:O(N^2)
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

2. Hill sort

Hill sorting can reduce incremental sorting, also known as shrinking incremental method. The basic idea of ​​the Hill sorting method is: first select an integer, divide all the records in the file to be sorted into several groups, and group all the 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 gap=1 is reached, all records are sorted in the same group. 

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.
//希尔排序      时间复杂度:O(N^1.3)
void ShellSort(int* a, int n)
{
	//gap > 1 预排序
	//gap越大,大的数可以更快的到后面,小的数可以更快的到前面。越不接近有序。
	//gap越小,数据跳动越慢,越接近有序。
	//gap == 1 直接插入排序
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 2;
		//gap = gap / 3 + 1;
		for (int j = 0; j < gap; j++)//分gap组排序
		{
			for (int i = j; i < n - gap; i += gap)//每一个组进行插入排序
			{
				int end = i;
				int tmp = a[end + gap];
				while (end >= 0)
				{
					if (tmp < a[end])
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}
				a[end + gap] = tmp;
			}
		}
	}
}

3. Selection sort

Animation demo:

Idea: Set two variables mini and maxi to store the subscript of the minimum data position and the subscript of the maximum position data respectively, mark the subscript of the maximum data and the subscript of the minimum position data each time the array is traversed, and compare them with the last Data and header data are exchanged.

//选择排序     时间复杂度:O(N^2)
//和直接插入排序相比,插入排序更好
//插入适应性很强,对于有序,局部有序,都能效率提升
void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int mini = begin, maxi = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
		}
        //此时,已经选出了最大的,和最小的
		//位置交换
		Swap(&a[begin], &a[mini]);
		//begin跟maxi重叠了,第一步交换之后maxi位置变了
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&a[end], &a[maxi]);
		begin++;
		end--;
	}

Note: This situation is a special case. The position of the maximum value (maxi) is at begin. After exchanging a[begin] and a[mini], at this time, the position of maxi is exchanged to the position of mini, so the position of maxi Something has changed and we need to deal with the position of maxi.

4. Heap sort

Heap sort refers to a sorting algorithm designed using the data structure of a stacked tree (heap), 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 . This article builds a large pile, sorted in ascending order.

//堆排序     时间复杂度:O(N*logN)
//交换
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//向下调整
void AdjustDown(int* a, int n, int parent)//n为数组的大小
{
	int child = parent * 2 + 1;//左孩子
	while (child < n)
	{
		//确认child指向大的那个孩子
		if (child + 1 < n && a[child + 1] > a[child])//child+1 < n 右孩子存在的情况
		{
			++child;//默认指向左孩子,++child就指向右孩子
		}
		//孩子大于父亲,交换,继续向下调整
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;//孩子小于父亲,跳出循环
		}
	}
}
void HeapSort(int* a, int n)
{
	//向下调整建堆--- O(N) --- 好一点
	//升序:建大堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}
	//O(N*logN)
	int end = n - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

Five, bubble sort

Animation demo:

Idea: compare the data before and after, and exchange if the data in the front is greater than the data in the back, so that after the first round of sorting, the maximum value is changed to the last position, and then the second round of sorting is performed, and so on. Large data is placed in the penultimate position until all data is sorted.

//冒泡排序    时间复杂度:O(N^2)
void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; j++)
	{
		int exchange = 0;
		for (int i = 1; i < n - j; i++)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = 1;
			}
		}
		//一趟冒泡过程中,没有发生交换,说明已经有序了,不需要进行处理
		if (exchange == 0)
		{
			break;
		}
	}
}

Six, quick sort

The basic idea is: any element in the element sequence to be sorted is taken as the 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, and all elements in the right subsequence are is greater than the reference value, and then the leftmost subsequence repeats the process until all elements are arranged in the corresponding positions.

The phased result characteristics of quick sorting: when the i-th pass is completed, more than i data will appear in its final position.

  1. Recursive version

     1.1 Hoare method

 Animation demo:

Ideas: (1) Select the key position, usually the first left position (or the last right position).

            (2) If the key is selected at the left position, the right will go first, and stop when the data smaller than the key position is found.

            (3) Go left and go, find the data larger than the key position and stop.

            (4) Exchange the data of the left and right positions, and repeat the above operations until the left and right meet.

            (5) After the meeting is over, exchange the data of the key position and the left position (that is, the meeting position). At this time, the data on the left of the meeting position is less than or equal to the data of the meeting position, and the data on the right is greater than or equal to the data of the meeting position. So, this data is adjusted to its correct position.

//Hoare
int PartSort1(int* a, int begin, int end)
{
	int left = begin, right = end;
	int key = left;
	while (left < right)
	{
		//右边先走,找小
		while (left < right && a[right] >= a[key])//left < right条件是防止越界
		{
			right--;
		}
		//左边再走,找大
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[key]);
	key = left;
	return key;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if ((end - begin + 1) < 15)
	{
		//优化方法:小区间用直接插入替代,减少递归调用次数
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int key = PartSort1(a, begin, end);

		//[begin,key-1] key [key+1 , end] 三段位置
		QuickSort(a, begin, key - 1);
		QuickSort(a, key + 1, end);
	}
}

     1.2 Digging method

 Animation demo:

Ideas: (1) Assign the data at the left position of the array to the key, forming the first hole, which is the left position, and save the value at the key position.

           (2) Right goes first, finds a value smaller than the key position data, stops, and puts the data here into the hole, and the right position forms a new hole.

           (3) Then go left again, find a value larger than the key position data, stop, put the data here into the hole, and at this time, the left position forms a new hole.

           (4) When left and right meet, put the data at the key position into the pit, and at this time, the key data is placed at the correct position.

//挖坑法
int PartSort2(int* a, int begin, int end)
{
	int left = begin, right = end;
	int key = left;
	int hole = left;
	while (left < right)
	{
		//右边找小,填到左边的坑里面
		if (left < right && a[right] >= a[key])
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;
		//左边找大,填到右边的坑里面
		if (left < right && a[left] <= a[key])
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = a[key];
	return hole;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}

	if ((end - begin + 1) < 15)
	{
		//优化方法:小区间用直接插入替代,减少递归调用次数
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int key = PartSort2(a, begin, end);

		//[begin,key-1] key [key+1 , end] 三段位置
		QuickSort(a, begin, key - 1);
		QuickSort(a, key + 1, end);
	}
}

     1.3 Back and forth pointer method

 Animation demo:

Ideas: (1) First assume that the key is the initial position of the array, and then use the forward and backward pointer method, prev points to the first element, and cur points to the second element.

           (2) cur moves first, and stops when it finds the position data smaller than the key.

           (3)++prev, exchange the data of prev and cur position.

           (4) When cur points to the position next to the last position of the array, the loop stops.

           (5) Exchange the data of key subscript and prev subscript.

//前后指针法
int PartSort3(int* a, int begin, int end)
{
	int prev = begin;
	int cur = begin + 1;
	int key = begin;
	while (cur <= end)
	{
		//找到比key小的值时,跟++prev位置交换,小的往前翻,大的往后翻
		while (a[cur] < a[key] && ++prev != cur)
        {
            //满足条件,进行交换
            Swap(&a[prev], &a[cur]);
        }
		cur++;
	}
	Swap(&a[key], &a[prev]);
	key = prev;
	return key;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if ((end - begin + 1) < 15)
	{
		//优化方法:小区间用直接插入替代,减少递归调用次数
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int key = PartSort3(a, begin, end);

		//[begin,key-1] key [key+1 , end] 三段位置
		QuickSort(a, begin, key - 1);
		QuickSort(a, key + 1, end);
	}
}

  2. Non-recursive version

Idea: If it is implemented in a non-recursive way, we need to use the memory structure of the stack to let the first come in and then go out, so we need to begin first and then enter end.

//非递归版本
void QuickSortNonR(int* a, int begin, int end)
{
	//借助栈实现非递归
	ST st;
	StackInit(&st);
	StackPush(&st, begin);
	StackPush(&st, end);
	while (!StackEmpty(&st))
	{
		int right = StackTop(&st);
		StackPop(&st);
		int left = StackTop(&st);
		StackPop(&st);

		int key = PartSort1(a, left, right);
		//[left,key-1] key [key+1,right]
		if (key + 1 < right)
		{
			StackPush(&st, key + 1);
			StackPush(&st, right);
		}
		if (left < key - 1)
		{
			StackPush(&st, left);
			StackPush(&st, key - 1);
		}
	}
	StackDestory(&st);
}

  3. Optimization of quick sort

     3.1 Take the middle of three numbers

Taking the middle of three numbers is an optimization algorithm. In order to prevent the data at the key position from being the minimum value in the array, after a quick sort, there is no change. We use the middle of three numbers method to select in an array An intermediate value is used as a key to perform quick sorting, which will greatly improve efficiency.

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[begin] > a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
	else  //a[begin] > a[mid]
	{
		if (a[mid] > a[end])
		{
			return mid;
		}
		else if (a[begin] > a[end])
		{
			return end;
		}
		else
		{
			return begin;
		}
	}
}

     3.2 Inter-cell optimization

The idea of ​​quick sorting is to continuously divide small sequences, and then realize it recursively. The number of recursions in each layer increases by 2 times. It is good to implement recursively when there are many elements, but when there are few sequence elements, it is unnecessary to use recursion. We can choose to use other sorting methods to realize the sorting of small sequences.

void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if ((end - begin + 1) < 10)
	{
		InsertSort(a, (end - begin + 1));
	}
	int mid = GetMidIndex(a, begin, end);
	swep(&a[begin], &a[mid]);
	int keyi = PartSort3(a, begin, end);
	QuickSort1(a, begin, keyi - 1);
	QuickSort1(a, keyi+1, end);
}

Seven, merge sort

  1. Recursive version

 Animation demo:

Combined thoughts:

        Merge sort is  an effective sorting algorithm based on the merge operation , which is a very typical application of the divide and conquer method.
        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.

//归并排序    时间复杂度:O(N*logN)   空间复杂度:O(N)
void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
	{
		return;
	}
	int mid = (begin + end) / 2;
	//[begin,mid]  [mid+1,end]  递归让子区间有序
	_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 n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
	tmp = NULL;
}

  2. Non-recursive version

 We can merge by controlling each interval, because the merge is dichotomous.

rangeN means: the number of merged data in each group.

//非递归归并排序
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	//归并每组数据个数,从1开始,因为1个认为是有序的,可以直接归并
	int rangeN = 1;
	while (rangeN < n)
	{
		for (int i = 0; i < n; i += rangeN * 2)
		{
			//[begin1,end1] [begin2,end2]  归并
			int begin1 = i, end1 = i + rangeN - 1;
			int begin2 = i + rangeN, end2 = i + 2 * rangeN - 1;
			int j = i;
			//end1  begin2 end2越界
			if (end1 >= n)
			{
				break;
			}
			else if (begin2 >= n)//begin2 end2 越界
			{
				break;
			}
			else if (end2 >= n)//end2 越界
			{
				//修正区间
				end2 = n - 1;
			}
			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));
		}
		rangeN *= 2;
	}
	free(tmp);
	tmp = NULL;
}

Note: When writing code, you need to strictly control the interval, otherwise overflow will occur and the program will crash.

There are the following three situations: (1) end1 out of bounds, begin2 out of bounds, end2 out of bounds; (2) begin2 out of bounds, end2 out of bounds; (3) end2 out of bounds

8. Counting and sorting

The core idea of ​​counting and sorting: in fact, it is mapping. The data to be sorted is mapped to the corresponding position in the auxiliary space, and then the number of occurrences is counted, and finally put back into the original array.

//计数排序
void CountSort(int* a, int n)
{
	assert(a);
	int min = a[0];
	int max = a[0];
	for (int i = 1; i < n; ++i)
	{
		//求最大值和最小值
		if (a[i] < min)
			min = a[i];
		if (a[i] > max)
			max = a[i];
	}
	int range = max - min + 1;
	int* countArr = (int*)malloc(sizeof(int) * range);
	memset(countArr, 0, sizeof(int) * range);
	//统计次数
	for (int i = 0; i < n; ++i)
	{
		countArr[a[i] - min]++;
	}
	//排序
	int index = 0;
	for (int j = 0; j < range; ++j)
	{
		while (countArr[j]--)
		{
			//相对位置
			a[index++] = j + min;
		}
	}
	free(countArr);
}

Nine, sorting comparison:

sorting method time complexity space complexity stability
Bubble Sort O(n^{2}) O(1) Stablize
selection sort O(n^{2}) O(1) unstable
insertion sort O(n^{2}) O(1) Stablize
Hill sort O(n^{1.3}) O(1) unstable
heap sort O(N*logN) O(1) unstable
merge sort O(N*logN) O(N) Stablize
quick sort O(N*logN) O(logN) unstable
counting sort O(N) O(Max-Min+1) unstable

 


 

If there are deficiencies in this article, you are welcome to comment below, and I will correct it as soon as possible.

 

 Old irons, remember to like and pay attention!!!  

Guess you like

Origin blog.csdn.net/m0_63198468/article/details/131147889