[Eight classic sorting algorithms] Quick sort


Insert image description here


I. Overview

Speaking of quick sort, we have to mention its founder Hoare. In the 1950s, computer scientists began studying how to sort data to make computer programs more efficient. At that time, commonly used sorting algorithms included bubble sort, insertion sort, and selection sort.

However, these algorithms are relatively inefficient, especially when processing large amounts of data. As a result, people began to look for faster sorting algorithms. In his research, Tony Hoare discovered a sorting method based on the divide-and-conquer idea, namely quick sort.

2. Implementation of ideas

The idea of ​​quick sort is to take any element in the sequence of elements to be sorted as the benchmark value, and divide the set to be sorted into two subsequences according to the sorting code. All elements in the left subsequence are smaller than the benchmark value, and all elements in the right subsequence are smaller than the benchmark value. are greater than the baseline value, and then the process is repeated for the left and right subsequences until all elements are arranged at the corresponding positions.

code show as below:

// 假设按照升序对array数组中[left, right]区间中的元素进行排序
void QuickSort(int* a, int begin, int end)
{
    
    
	if (begin >= end)
		return;
	// 按照基准值对数组的 [left, right)区间中的元素进行划分
	int keyi = PartSort(a, begin, end);
	//分成[begin,keyi-1] keyi [keyi+1,end]
	
	 // 递归排[left, div)
	QuickSort(a, begin, keyi - 1);
	 // 递归排[div+1, right)
	QuickSort(a, keyi + 1, end);
}

The above is the main framework of the recursive implementation of quick sort. It is found that it is very similar to the pre-order traversal rule of the binary tree. Next, we only need to analyze how to divide the data in the interval according to the benchmark value.
When the sorted set is divided, there are three common ways to divide the interval into left and right halves according to the reference value.

2.1 hoare version

Idea :

  1. Select a base element (key), which can be the leftmost or rightmost element.
  2. Define two pointers, one pointing to the first element of the array (left pointer) and one pointing to the last element of the array (right pointer). ( It should be noted that if you select the leftmost data as the key, you need to go right first; if you select the rightmost data as the key, you need to go left first )
  3. Move the left pointer until you find an element greater than or equal to the base element (key); move the right pointer until you find an element less than or equal to the base element (key). Then swap the elements pointed to by the left and right pointers. Then keep repeating the above steps until the left pointer is greater than the right pointer
  4. Finally, the base element is exchanged with the element pointed to by the right pointer. At this time, the base element is in the correct position. At this time, the left element >= key and the right element <= key.

Tips: The blogger explains here why " if you select the leftmost data as the key, you need to go right first; if you select the rightmost data as the key, you need to go left first. " The same applies to other subsequent methods.
①: Make the key on the left, and go first on the right, ensuring that the value of the encounter position is less than the key or the position of the key.
②: Make the key on the right, and move on the left first, ensuring that the value of the encounter position is greater than the key or the position of the key.

Taking ① as an example, there are only two situations when L and R meet: L meets R, and R meets L.
Situation 1: L meets R. After R stopped, L was still walking. Since R goes first, the position where R stops must be smaller than Key. The encounter position is the position where R stops, which must be smaller than the key.
Situation 2: R meets L. In the next round of encounter, L is stationary and R is moving. The meeting position is the position of L. The position of L is the position of the key or has been exchanged for some rounds. At this time, the meeting position must be smaller than the key.

[Animation Demonstration]:
Insert image description here
The code is as follows:

 //[left, right]--采用左闭右闭
int PartSort(int* a, int left, int right)
{
    
    
	int keyi = left;
	while (left < right)
	{
    
    
		//找到右边比key小的数
		while (left < right && a[right] <= a[keyi])
		{
    
    
			right--;
		}

		//找到左边比key大的数
		while (left < right && a[left] >= a[keyi])
		{
    
    
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[keyi], &a[left]);
	return left;
}

void Swap(int* p1, int* p2)
{
    
    
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

2.2 Digging method

Idea :

  1. .Select a piece of data (usually the leftmost or rightmost one) and store it in the key variable, and at the same time, the data position forms a pit.
  2. Or the left and right pointers left and right, left goes from left to right, and right goes from right to left. (If you dig a hole on the far left, you need R to go first; if you dig a hole on the far right, you need L to go first)
  3. Move the right pointer to find the first number smaller than the key and fill it into the hole. At this time, the position of the right pointer becomes a new hole. Then move the left pointer to find the first number larger than the key and fill it into the pit. At this time, the position of the left pointer becomes a new pit. Then just keep repeating the above steps just like the hoare version.

[Animation Demonstration]:
Insert image description here
The code is as follows:

//挖坑法
int PartSort(int* a, int left, int right)
{
    
    
	int key = a[left];
	int hole = left;
	while (left < right)
	{
    
    
		//找到右边比key小的值
		while (left < right && a[right] >= key)
		{
    
    
			right--;
		}
		a[hole] = a[right];
		hole = right;

		//左边比key大的值
		while (left < right && a[left] <= key)
		{
    
    
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	return hole;
}

void Swap(int* p1, int* p2)
{
    
    
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

2.3 Before and after pointer version

Idea :

  1. Select a key, usually the leftmost or rightmost one.
  2. At the beginning, the prev pointer points to the beginning of the sequence, and the cur pointer points to prev+1.
  3. If the content pointed by cur is less than key, then prev first moves backward one bit, then exchanges the content pointed by prev and cur pointers, and then the cur pointer ++; if the content pointed by cur is greater than key, then the cur pointer directly ++. Continue in this way until cur reaches the end position, at which time the key and the content pointed to by the ++prev pointer can be exchanged.

[Animation Demonstration]:
Insert image description here
The code is as follows:

//前后指针法
int PartSort(int* a, int left, int right)
{
    
    
	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]);
	keyi = prev;
	return keyi;
}

void Swap(int* p1, int* p2)
{
    
    
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

3. Optimization

Although the problem can be solved, there is still a problem:
when the selected key is the median every time, the efficiency is the best, and the time complexity is O(N*logN); but when the array is ordered, it becomes the most efficient Bad, the time complexity becomes O(N^2)!
For the above situation, there are two optimization methods:

  1. Three-number method to select key
  2. When recursing to a small subrange, you can consider using insertion sort

3.1 Find the right number among three numbers

The blogger here gives the easiest method:

int GetMidIndix(int* a, int left, int right)
{
    
    
	int mid = left + (right - left) / 2;
	if (a[left] < a[mid])
	{
    
    
		if (a[mid] < a[right])
			return mid;
		else if (a[mid] > a[right])
			return right;
		else
			return left;
	}
	else//a[left]>=a[mid]
	{
    
    
		if (a[mid] > a[right])
			return mid;
		else if (a[mid] < a[right])
			return right;
		else
			return left;
	}
}

3.1.1 Final code

void Swap(int* p1, int* p2)
{
    
    
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

int GetMidIndix(int* a, int left, int right)
{
    
    
	int mid = left + (right - left) / 2;
	if (a[left] < a[mid])
	{
    
    
		if (a[mid] < a[right])
			return mid;
		else if (a[mid] > a[right])
			return right;
		else
			return left;
	}
	else//a[left]>=a[mid]
	{
    
    
		if (a[mid] > a[right])
			return mid;
		else if (a[mid] < a[right])
			return right;
		else
			return left;
	}
}

// hoare
// [left, right]
//int PartSort(int* a, int left, int right)
//{
    
    
//	int midi = GetMidIndix(a, left, right);
//	Swap(&a[left], &a[midi]);
//
//	int keyi = left;
//	while (left < right)
//	{
    
    
//		//找到右边比key小的数
//		while (left < right && a[right] <= a[keyi])
//		{
    
    
//			right--;
//		}
//
//		//找到左边比key大的数
//		while (left < right && a[left] >= a[keyi])
//		{
    
    
//			left++;
//		}
//		Swap(&a[left], &a[right]);
//	}
//	Swap(&a[keyi], &a[left]);
//	return left;
//}


//挖坑法
//int PartSort(int* a, int left, int right)
//{
    
    
//	int midi = GetMidIndix(a, left, right);
//	Swap(&a[left], &a[midi]);
//
//	int key = a[left];
//	int hole = left;
//	while (left < right)
//	{
    
    
//		//找到右边比key小的值
//		while (left < right && a[right] >= key)
//		{
    
    
//			right--;
//		}
//		a[hole] = a[right];
//		hole = right;
//
//		//左边比key大的值
//		while (left < right && a[left] <= key)
//		{
    
    
//			left++;
//		}
//		a[hole] = a[left];
//		hole = left;
//	}
//	a[hole] = key;
//	return hole;
//}


//前后指针法
int PartSort(int* a, int left, int right)
{
    
    
	int midi = GetMidIndix(a, left, right);
	Swap(&a[left], &a[midi]);

	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]);
	keyi = prev;
	return keyi;
}

void QuickSort(int* a, int begin, int end)
{
    
    
	if (begin >= end)
		return;
	int keyi = PartSort(a, begin, end);
	//分成[begin,keyi-1] keyi [keyi+1,end]
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

3.1.2 Summary of features of quick sort

  1. Time complexity: O(N*logN)

Insert image description here

  1. Space complexity: O(logN)
  2. Stability: Unstable

4. Non-recursive implementation of quick sorting

Idea:

  1. Define a stack, and then add the starting index and ending index of the array to be sorted onto the stack.
  2. The elements between the start index and the end index of the array are divided into two parts through the three methods of dividing the interval. The left part is less than or equal to the reference element, and the right part is greater than or equal to the reference element.
  3. Since in non-recursive implementation, we maintain the subscripts of the array to be sorted by taking them out of the stack in pairs, so the next step is to put the starting index and ending index of the left part into the stack if the length of the left part is greater than 1; if If the length of the right part is greater than 1, the start index and end index of the right part are pushed onto the stack. Finally, this operation is looped until the stack is empty.

code show as below:

//前后指针法
int PartSort(int* a, int left, int right)
{
    
    
	int midi = GetMidIndix(a, left, right);
	Swap(&a[left], &a[midi]);

	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]);
	keyi = prev;
	return keyi;
}

//快排非递归
void QuickSortNonR(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 = PartSort(a, left, right);
		//[left,keyi-1] keyi [keyi+1,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);
}

Insert image description here
Insert image description here

Guess you like

Origin blog.csdn.net/Zhenyu_Coder/article/details/132920906