Reject hydrology! Eight Sorting (3) [Suitable for Beginners] Quick Sorting


Hello everyone, my name is Ji Ning. This article will introduce to you a very famous sort: quick sort. I
recall when we first started learning C language. I often use a library function: qsort函数 , the time complexity of O(N*logN) is many times better than bubble sorting. At that time, I often think, holy shit, this efficiency is amazing. So this article will give you an in-depth understanding of the principles of quick sort and its various implementation methods.

Time complexity: O(N*logN)
Space complexity: O(1)
Stability:不稳定

Quick sort recursive implementation

Hall method

If there is an array now, we need to performAscending orderArrange, then after sorting, the position of each number should be fixed. The idea of ​​​​quick sort is this: first use a number as the key (generally choose the leftmost of the array), and then define two pointers to traverse from the left and right respectively. Array (starting from the right), place all numbers larger than key on the right side of the array, place all numbers smaller than key on the left side of the array, and then where the two pointers meet, compare the value at this position with the leftmost The key value is exchanged, then the key is placed in the correct position, that is, all the data on the left side of the key are smaller than the key, and all the data on the right side are larger than the key.
Insert image description here
After the end, use the current key as the dividing line, the left side of the key is a group, and the right side is a group, and then recursively repeat the above steps.

int QuickSortPart(int*a, int left, int right)
{
    
    
	int keyi = left;
	while (left < right)
	{
    
    
		while (right > left)
		{
    
    
			if (a[right] < a[keyi])
				break;
			right--;
		}
		while (left < right)
		{
    
    
			if (a[left] > a[keyi])
				break;
			left++;
		}
		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[keyi]);
	return left;
}
void QuickSort(int* arr, int begin, int end)
{
    
    
	if (begin >= end)//等于是只有一个数需要排,大于是没有数需要排
	{
    
    
		return;
	}
	int keyi = QuickSortPart(arr, begin, end);
	QuickSort(arr, begin, keyi - 1);
	QuickSort(arr, keyi + 1, end);
}

Code explanation: Use keyi to represent left because the value of left will change during the adjustment process; the function parameters passed into QuickSort are the subscript of the first element of the array and the subscript of the last data of the array; when the recursion enters again When using the QuickSort function, if begin>=end, it means that there are no elements to be sorted on a certain side or there is only one element that does not need to be sorted.

optimization

优化1:三数取中
existideal situationNext, divide the array into two parts each time. The time complexity of traversing the array each time is O(N), and the time complexity of bisection is O(logN), so after this process, the time complexity is O(logN)

But what if things aren’t ideal? If the array is already close to an ordered state, and the first number is the minimum value, then there is no smaller value on the right, and it keeps moving to the left. Finally, the first number can only be positioned, and so on. Then when dividing, it needs to be divided N times, and the time complexity instantly becomes (N^2).
Improvement method: Add a three-number middle function to the QuickSortPart function implementation to implement a function that finds the maximum value among the first, middle, and last three numbers, and then exchanges this number with the value corresponding to left. In this way, the leftmost value becomes the middle value, and the array can be divided better.

优化2:小区间优化
When the interval is relatively small, it is obviously not appropriate to continue to use recursion. If the recursion is too deep, the pressure on the function stack frame will be very large. Therefore, when the interval range is relatively small, the sorting can be changed to insertion sort, which is more efficient. Some.

优化后的代码

int QuickSortPart(int*a, int left, int right)
{
    
    
	int* Maxi = (&a[left], &a[right], &(a[(left + right) / 2]));//三数取中
	Swap(&a[left], Maxi);//换到最左边
	int key = a[left];
	int keyi = left;
	while (left < right)
	{
    
    
		while (right > left)
		{
    
    
			if (a[right] < a[keyi])
				break;
			right--;
		}
		while (left < right)
		{
    
    
			if (a[left] > a[keyi])
				break;
			left++;
		}
		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[keyi]);
	return left;
}
void QuickSort(int* arr, int begin, int end)
{
    
    
	if (begin >= end)//等于是只有一个数需要排,大于是没有数需要排
	{
    
    
		return;
	}
	int keyi = QuickSortPart(arr, begin, end);
	if (end - begin >= 5)
	{
    
    
		QuickSort(arr, begin, keyi - 1);
		QuickSort(arr, keyi + 1, end);
	}
	else
	{
    
    
		InsertSort(arr + begin, end - begin + 1);
		InsertSort(arr + keyi + 1, end - keyi);
	}
}

Digging method

Insert image description here

The idea of ​​the pit method is basically the same as that of the Hall method, but the idea is a little easier to understand.
Select a 'pit' (location). This pit is where the key value is to be placed. First save the value of the pit. The right pointer first moves to the left to find a value smaller than the key. After finding it, put this value. Put the value of the position into the pit and form a new pit by itself. Then move the left pointer to the right to find a value larger than the key. After finding it, put the value of this position into the newly generated pit, and then form a new pit by yourself. bit...and so on, until the final pit formed by the intersection of left and right, put the originally reserved key value into the pit, and it is completed in one trip.

挖坑法代码

int QuickSortPart(int*a, int left, int right)
{
    
    
	int* Maxi = (&a[left], &a[right], &(a[(left + right) / 2]));//三数取中
	Swap(&a[left], Maxi);//换到最左边
	int holei = left;
	int hole = a[left];
	while (left < right)
	{
    
    
		while (left < right && a[right] >= hole)
		{
    
    
			right--;
		}
		a[holei] = a[right];
		holei = right;
		while (left < right && a[left] <= hole)
		{
    
    
			left++;
		}
		a[holei] = a[left];
		holei = left;
	}
	return left;
}
void QuickSort(int* arr, int begin, int end)
{
    
    
	if (begin >= end)//等于是只有一个数需要排,大于是没有数需要排
	{
    
    
		return;
	}
	int keyi = QuickSortPart(arr, begin, end);
	if (end - begin >= 5)
	{
    
    
		QuickSort(arr, begin, keyi - 1);
		QuickSort(arr, keyi + 1, end);
	}
	else
	{
    
    
		InsertSort(arr + begin, end - begin + 1);
		InsertSort(arr + keyi + 1, end - keyi);
	}
}

back and forth pointer method

It is still the case that after taking the middle of the three numbers, the subscript of the leftmost number is keyi. Define a pointer prev to point to left, and define a pointer cur to point to left+1. Cur traverses backward first. If a number smaller than key is found, it is added to prev first, and then the sum of the prev position and the cur position are exchanged. , if a number greater than or equal to key is found, cur will continue to traverse backward, while prev will not move.
Finally, exchange the value of the prev position with the value of the keyi position, and then return to prev, which is equivalent to finding the key: after adjustment, the left side of prev is smaller than the key, and the right side is larger than the key.
Insert image description here

int QuickSortPart4(int* a, int left, int right)
{
    
    
	int* Maxi = (&a[left], &a[right], &(a[(left + right) / 2]));
	Swap(&a[left], Maxi);
	int keyi =left;
	int cur = left+1;
	int prev = left;
	while (cur <= right)
	{
    
    
		if(a[cur] >= a[keyi])
		{
    
    
			cur++;
		}
		else
		{
    
    
			prev++;
			if(cur!=prev)//防止无用交换
			{
    
    
				Swap(&a[cur], &a[prev]);
			}
			cur++;
		}
	}
	Swap(&a[prev], &a[keyi]);
	return prev;
}
void QuickSort(int* arr, int begin, int end)
{
    
    
	if (begin >= end)//等于是只有一个数需要排,大于是没有数需要排
	{
    
    
		return;
	}
	int keyi = QuickSortPart4(arr, begin, end);
	if (end - begin >= 5)//小区间优化
	{
    
    
		QuickSort(arr, begin, keyi - 1);
		QuickSort(arr, keyi + 1, end);
	}
	else
	{
    
    
		InsertSort(arr + begin, end - begin + 1);
		InsertSort(arr + keyi + 1, end - keyi);
	}
}

精简版
It is recommended that you read this after learning the above.

int QuickSortPart3(int* a, int left, int right)//快排快慢指针
{
    
    
	int* Maxi = (&a[left], &a[right], &(a[(left + right) / 2]));
	Swap(&a[left], Maxi);
	int keyi = left;
	int prev = left;
	int cur = left+1;
	while (cur <= right)
	{
    
    
		if (a[cur] < a[keyi]&& ++prev!= cur)
		{
    
    
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[prev],&a[keyi]);
	return prev;
}

quicksort non-recursive

The non-recursive version of quick sort is implemented with the help of the stack, but it also requires the use of a function to find keyi. First push the last element and the first element of the group of data that need to be sorted into the stack in sequence, save them and pop them out once, and then split them (the positions have been adjusted during splitting). After splitting, push the tail and head of the previous sequence and the following sequence onto the stack in sequence, then save the first and last elements of the previous sequence, and pop them off the stack... until the stack is empty. When the stack is empty, it means that all the data in the array has been adjusted.

void QuickSortNorn(int* a, int begin, int end)
{
    
    
	ST st;
	STInit(&st);
	STPush(&st,end);
	STPush(&st,begin);
	while (!STEmpty(&st))
	{
    
    
		int left =STTop(&st);
		STPop(&st);
		int right =STTop(&st);
		STPop(&st);
		int keyi = QuickSortPart1(a, left, right);
		if (keyi + 1 < right)
		{
    
    
			STPush(&st, right);
			STPush(&st, keyi + 1);
		}
		if (keyi - 1 > left)
		{
    
    
			STPush(&st, keyi - 1);
			STPush(&st, left);
		}
	}
	STDestroy(&st);
}

Guess you like

Origin blog.csdn.net/zyb___/article/details/133497759