Sorting algorithm - quick sort (implemented in 4 ways)

The source code of this article is here, you need to get it yourself: Gitee

What is quick sort?

Quick sort is a common sorting algorithm whose basic principles are divide and conquer and recursion. Its basic idea is to select an element in the array as the base value, and then move the elements in the array that are smaller than the base value to its left, and the elements that are greater than the base value to its right. This process is then repeated recursively for the left and right subarrays until the size of the subarrays is 1 or 0.

When implementing quick sort, you can use the three-number method to select the base value and partition, which can effectively avoid the worst case scenario.

Three-number middle method: Select an intermediate value from the numbers at the first, middle and last positions of the interval to be sorted as the base value.

Hit the right number among three numbers:


//三数取中
int GetMidIndex(int* a, int left, int right)
{
    
    
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
    
    
		if (a[mid] < a[right])
		{
    
    
			return mid;
		}
		else if (a[left] < a[right])
		{
    
    
			return right;
		}
		else
			return left;
	}
	else//a[left] > a[mid]
	{
    
    
		if (a[mid] > a[right])
		{
    
    
			return mid;
		}
		else if(a[left] > a[right])
		{
    
    
			return right;
		}
		else
		{
    
    
			return left;
		}
	}
}

  1. The GetMidIndex function accepts an integer array a, and the left and right boundary indices left and right of the base element to be selected. The function first calculates the middle index mid, which is obtained by (left + right) / 2.

  2. The function then compares the values ​​of three elements in the array, a[left], a[mid], and a[right], to determine the index of the base element.

  3. If a[left] is less than a[mid], continue comparing a[mid] and a[right]. If a[mid] is less than a[right], it means that a[mid] is the middle element and its value is between a[left] and a[right], so mid is returned as the index of the base element.

  4. If a[mid] is not smaller than a[right], the index of the base element is selected based on the size relationship between a[left] and a[right]. If a[left] is less than a[right], it means that a[left] is the middle element and its value is between a[mid] and a[right], so right is returned as the index of the base element. Otherwise, if a[left] is greater than or equal to a[right], it means that a[right] is the middle element and its value is between a[left] and a[mid], so left is returned as the index of the base element.

  5. If a[left] is greater than a[mid], continue comparing a[mid] and a[right]. If a[mid] is greater than a[right], it means that a[mid] is the middle element and its value is between a[left] and a[right], so mid is returned as the index of the base element.

  6. If a[mid] is not larger than a[right], the index of the base element is selected based on the size relationship between a[left] and a[right]. If a[left] is greater than a[right], it means that a[left] is the middle element and its value is between a[right] and a[mid], so right is returned as the index of the base element. Otherwise, if a[left] is less than or equal to a[right], it means that a[left] is the middle element and its value is between a[mid] and a[right], so left is returned as the index of the base element.

By using the three-number middle method to select the benchmark element, elements close to the middle value can be selected in most cases, improving the efficiency and performance of quick sort, and reducing the occurrence of the worst case scenario.

1. Digging method (recommended to master)

The following is the detailed process of the pit digging method:

  1. Choose a value base value (here we use the middle of three numbers). Normally, the first element in the array is selected as the base value.
  2. Move the elements in the array that are smaller than the pivot value to its left, and move the elements that are larger than the pivot value to its right. (Look for the big one on the left, the small one on the right).
  3. Repeat the above process recursively for the left and right subarrays until the size of the subarray is 1 or 0.
  4. Merge subarrays to get the sorted array.

Insert image description here

//挖坑法
int PartSort2(int* a, int left, int right)
{
    
    
	//三数取中
	int midi = GetMidIndex(a, left, right);
	Swap(&a[midi], &a[left]);//把中间值放到left位置
	
	int keyi = left;
	while (left < right)
	{
    
    
		while (left < right && a[right] >= a[keyi])
		{
    
    
			right--;
		}
		Swap(&a[keyi], &a[right]);
		keyi = right;

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

//快排
void QuickSort(int* a, int left,int right)
{
    
    
	if (left >= right)
	{
    
    
		return ;
	}
	int keyi = PartSort2(a, left, right);
	//[left,keyi-1][keyi][keyi+1,right]
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

  1. The PartSort2 function is the core implementation of the pit digging method. It accepts an integer array a, and the left and right boundary indices left and right to be sorted. The function first selects a middle index midi and exchanges a[midi] with a[left], using a[left] as the base element.
  2. The function then scans through the array using two pointers left and right. Starting from the right, when a[right] is greater than or equal to the base element a[keyi], move the right pointer to the left until an element smaller than the base element is found.
  3. Then, exchange the element with a[keyi], updating keyi to right.
  4. Next, starting from the left, when a[left] is less than or equal to the base element a[keyi], move the left pointer right until an element larger than the base element is found.
  5. Then, exchange the element with a[keyi], updating keyi to left.
  6. Repeat this process until the left and right pointers meet, then return keyi, which divides the array into two parts: elements on the left that are less than or equal to the base element, and elements on the right that are greater than or equal to the base element.

The QuickSort function accepts an integer array a, and the left and right boundary indices left and right to be sorted. First, it checks whether the recursion termination condition is met, that is, left >= right. If the condition is met, it returns directly. Otherwise, it calls the PartSort2 function to get the index keyi of the base element, and then divides the array into three parts: [left, keyi-1], [keyi] and [keyi+1, right]. Next, it recursively calls the QuickSort function to sort the left and right subarrays.

2. Back and forth pointer method (recommended to master)

Insert image description here

//前后指针法
int PartSort3(int* a, int left, int right)
{
    
    
	int midi = GetMidIndex(a, left, right);
	Swap(&a[midi], &a[left]);
	//end找小,如果	a[end]<a[keyi],++begin(这时begin位置的值一定比keyi位置值大),再交换begin和end的位置	
	int keyi = left;
	int begin = left;
	int end = left+1;
	while (end <=right)
	{
    
    
		if (a[end] < a[keyi] )
		{
    
    
			++begin;
			Swap(&a[begin], &a[end]);
		}
		++end;
	}
	Swap(&a[begin], &a[keyi]);
	return begin;
}


//快排
void QuickSort(int* a, int left,int right)
{
    
    
	if (left >= right)
	{
    
    
		return ;
	}
	int keyi = PartSort3(a, left, right);
	//[left,keyi-1][keyi][keyi+1,right]
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}
	
  1. The PartSort3 function uses the front and rear pointer method (double pointer method) for array partitioning. The function accepts an integer array a, and the left and right boundary indices left and right to be partitioned.
  2. First, the function calls the GetMidIndex function to obtain the index midi of the base element, then exchanges a[midi] and a[left], and sets a[left] as the base element.
  3. Next, the function initializes two pointers, begin and end, and traverses the array starting from left and left + 1 respectively.
  4. During the traversal, the end pointer moves to the right, scanning the array elements. When a[end] is smaller than the reference element a[keyi], move the begin pointer one bit to the right and exchange the values ​​of a[begin] and a[end]. In this way, the smaller elements will be moved to the position of begin, and the elements before begin are smaller than the base element.
  5. Finally, move the base element a[keyi] to the appropriate position, that is, exchange it with a[begin]. At this time, the array is divided into two parts: the element on the left is smaller than the base element, and the element on the right is greater than or equal to the base element.
  6. Finally, the function returns the index begin of the base element.

The QuickSort function works the same as above

3. Left and right pointer method (Hall version) (error-prone)

Quick sorting's left and right pointer method (double pointer method) is a common implementation method. It uses two pointers starting from both ends of the array, gradually moving to the middle, and comparing and exchanging elements to achieve array partitioning and sorting. Sort.

The basic idea is as follows:

  • Select a base element (usually the first element of the array).

  • Use two pointers, one starting from the left (usually called the left pointer) and one starting from the right (usually called the right pointer).

  • The left pointer starts from the left and moves to the right until it finds an element that is larger than the base element.

  • The right pointer starts from the right and moves to the left until it finds an element smaller than the base element.

  • If the position of the left pointer is smaller than the position of the right pointer, the elements pointed to by the left and right pointers are swapped.

  • Repeat steps 3-5 until the left and right pointers meet.

  • Exchange the base element with the element pointed to by the left pointer. At this time, the position of the base element has been determined.

  • According to the position of the reference element, the array is divided into two parts. The elements on the left are smaller than the reference element, and the elements on the right are greater than the reference element.

  • Repeat the above steps for the sub-arrays on the left and right of the reference element until all sub-arrays are in order.

//左右指针(霍尔版本)(容易出错)
int PartSort1(int* a, int left,int right)
{
    
    
	int midi = GetMidIndex(a, left, right);
	Swap(&a[midi], &a[left]);

	int keyi = left;
	while (left < right)
	{
    
    
		while (left < right && a[keyi]<=a[right])
		{
    
    
			right--;
		}
		while (left < right && a[keyi]>=a[left])
		{
    
    
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);
	return left;
}
	//快排
	void QuickSort(int* a, int left,int right)
	{
    
    
		if (left >= right)
		{
    
    
			return ;
		}
		int keyi = PartSort1(a, left, right);
		//[left,keyi-1][keyi][keyi+1,right]
		QuickSort(a, left, keyi - 1);
		QuickSort(a, keyi + 1, right);
	}
  1. The PartSort1 function uses the left and right pointer method (Hall version) for array partitioning. The function accepts an integer array a, and the left and right boundary indices left and right to be partitioned.
  2. First, the function calls the GetMidIndex function to obtain the index midi of the base element, then exchanges a[midi] and a[left], and sets a[left] as the base element.
  3. Next, the function uses two pointers left and right to traverse from the left and right ends of the array respectively.
  4. During the traversal process, first start from the right, find the first element that is smaller than the base element, and move the right pointer one bit to the left until an element that is smaller than the base element is found or the left and right pointers meet.
  5. Then, starting from the left, find the first element that is larger than the base element, and move the left pointer one position to the right until an element that is larger than the base element is found or the left and right pointers meet.
  6. If left is smaller than right, swap a[left] and a[right], move elements smaller than the base element to the left, and move elements larger than the base element to the right.
  7. Repeat the above steps until the left and right pointers meet, at which point a partition is completed. Move the base element a[keyi] to the appropriate position, that is, exchange it with a[left].
  8. Finally, the function returns the index left of the base element.

QuickSort function same as above

4. Non-recursive implementation

  • Non-recursive quick sort uses a stack to store the start and end positions of the subarrays to be processed. Initially, the start and end positions of the entire array are pushed onto the stack.

  • Then, enter the loop, pop a subarray from the stack, perform a partition operation on it, and obtain the position of the reference element. According to the partition result, the subarray is divided into two parts: one part is the subarray to the left of the reference element, and the other part is the subarray to the right of the reference element.

  • Next, the start and end positions of the subarray that require further processing are pushed onto the stack. In this way, what is stored in the stack is the subarray to be processed.

  • Repeat the above steps until the stack is empty. This means that all subarrays have been processed and sorted.

  • By using the stack to simulate the recursive call process, non-recursive quick sort can effectively partition and sort the array while avoiding the function call overhead caused by recursion. This implementation usually has better performance and efficiency, and is especially suitable for processing large-scale data sets.

void QuickSortNonR(int* a, int begin, int end)
{
    
    
	ST st;
	StackInit(&st);
	StackPush(&st, end);
	StackPush(&st,begin);
	
	while (!StackEmpty(&st))
	{
    
    
		int left = StackTop(&st);
		StackPop(&st);
		int right = StackTop(&st);
		StackPop(&st);
		int keyi = PartSort2(a, left, right);
		//[left,keyi-1][keyi][keyi+1,right]
		if(keyi+1<right)
		{
    
    
			StackPush(&st, right);
			StackPush(&st, keyi + 1);
		}
		if (left < keyi-1)
		{
    
    
			StackPush(&st, keyi - 1);
			StackPush(&st, left);
		}
	}
	StackDestory(&st);
}

  1. The QuickSortNonR function implements a non-recursive version of quick sort. It accepts an integer array a, as well as the starting position begin and the ending position end to be sorted.
  2. First, the function creates a stack st to store the starting and ending positions of the subarray to be processed. Pushing end and begin onto the stack respectively means sorting the entire array.
  3. Entering the loop, as long as the stack is not empty, perform the following operations:
  4. Pop two elements from the stack and assign them to left and right respectively, indicating the starting and ending positions of the subarray currently to be processed.
  5. Call the PartSort2 function to partition the subarray and obtain the position keyi of the reference element.
  6. According to the partition result, the subarray is divided into three parts: [left, keyi-1], [keyi], [keyi+1, right].
  7. If keyi + 1 < right, it means that there are still elements in the right subarray that need to be sorted, and the starting position keyi + 1 and the ending position right of the right subarray are pushed onto the stack.
  8. If left < keyi - 1, it means that there are still elements in the left subarray that need to be sorted, and the starting position left and the ending position keyi - 1 of the left subarray are pushed onto the stack.
  9. The loop continues until the stack is empty, indicating that all subarrays have been processed.
  10. Finally, stack st is destroyed and the non-recursive version of quick sort is completed.

(End of chapter)

Guess you like

Origin blog.csdn.net/originalHSL/article/details/131078302
Recommended