"Sort Algorithms" Recursion and Non-Recursion of Quick Sort

1. The focus of this chapter

  1. quicksort thinking

  2. Three ways to achieve a single quick row (hoare, digging, front and rear pointers)

  3. Recursive implementation of quicksort

  4. Quicksort recursive algorithm time complexity calculation

  5. Optimize the quicksort (middle of three numbers, optimization between cells)

  6. Non-recursive implementation of quick queue (stack or queue implementation)

2. Quick Queue

2.1 The idea of ​​quick row

Quicksort is essentially an exchange sort. Let's start with a single-pass perspective: the single-pass sorting of quicksort will allow you to choose the key to be placed in the correct position of the array. What is the correct position? That is, after you sort in a single pass, the number (Key) has already been arranged, and there is no need to change it later. How to ensure that it is in the correct position? As long as all numbers to its left are less than or equal to it, and all numbers to its right are greater than or equal to it, then it is in the correct position. (ascending order).

Quick-sort single-pass steps: select a key number from the array, generally choose the leftmost or rightmost number, here I choose the leftmost number in the array, for example: 5 3 2 8 6 1 10 9 3 4 7, where Keyi is 0 , a[keyi] is 5

How do I swap the array elements so that the 5 is in the correct position?

2.2 Three kinds of single-pass sorting

There are three single-pass sorting algorithms for quicksort:

The first one: hoare , written by the first inventor of the quicksort algorithm-----Tony Hoare (Tony  Hoare )

This method is: first select a Keyi, take two integer variables left and right, these two integer variables represent the subscripts of the array, and initially they point to 0 and n-1 respectively. Then let right move first, find a number greater than a[keyi], then right stop, let left move, find a number less than a[keyi], stop, and then swap a[left] and a[right], if In the way of right or left moving, right==left, that is, when right and left meet, right and left must point to a number smaller than a[keyi]. Then a[keyi] and this smaller number are swapped (meeting points), and they end up with a[keyi] in the correct position, that is, all numbers to the left of it are less than or equal to it, and all numbers to the right are greater than or equal to it.

Icon:

 It should be noted that: you need to let right go first, and then left go. Otherwise the point where they meet may not be a number smaller than a[keyi].

Reference Code:

int Q_PartSort1(int* a, int begin, int end)//hoare
{
	int keyi = begin;
	int left = begin;
	int right = end;
	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])//右边找小于a[keyi]的数
		{
			right--;
		}
		while (left < right && a[left] <= a[keyi])//左边找大于a[keyi]的数
		{
			left++;
		}
		Swap(&a[left], &a[right]);
	}
	//将a[keyi]与相遇点交换(要保证相遇点比a[keyi]小,需要让right先走)
	Swap(&a[keyi], &a[right]);
	return right;
}

The second: digging method

Steps: First save the value of a[keyi] to int temp, then make keyi first, int left=0, int right=n-1

Let right go first, find a number less than a[keyi], put it at a[hole], and update hole=right. Then left goes again, finds a number greater than a[keyi], then puts it at a[hole], updates hole=left again, then moves right, and left moves until left is equal to right. At this point, the meeting point must be a pit, and finally temp is placed at a[hole].

Icon:

 Unlike hoare, there is no need to guarantee that the value of the encounter point is smaller than temp.

Reference Code:

int Q_PartSort2(int* a, int begin, int end)//挖坑法
{
	int key = a[begin];
	int hole = begin;
	int left = begin;
	int right = end;
	while (left < right)
	{
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[hole] = a[right];
		hole = right;
		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[hole] = a[left];
		hole = left;
	}
	a[hole] = key;
	return hole;
}

The third type: front and rear pointer method

Take the leftmost subscript left keyi, prev=begin, next=begin+1

Next, find the small number. If you find a number less than a[keyi], let prev++, and then swap a[prev] and a[next].

End until next is greater than n.

Finally let a[keyi] and a[prev] swap.

Icon:

 Reference Code:

int Q_PartSort3(int* a, int begin, int end)//前后指针法
{
	int keyi = begin;
	int prev = begin;
	int next = begin + 1;
	while (next <= end)
	{
		if (a[next] < a[keyi] && ++prev != next)
		{
			Swap(&a[prev], &a[next]);
		}
		next++;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}

We all need to master the three single-pass sorting, and sometimes we will examine the following questions

It should be noted that although the results of the above set of data are the same after the three single-pass sorting, this is a coincidence. If more data is added, the results may be different after a single-pass.

Suppose a set of initial record keyword sequences are (65, 56, 72, 99, 86, 25, 34, 66), then the result of a quick sort based on the first keyword 65 is ()
A 34,56,25,65,86,99,72,66
B 25,34,56,65,99,86,72,66
C 34,56,25,65,66,99,86,72
D 34,56,25,65,99,86,72,66
The question does not specify which single-pass quicksort to use. For such a question, you need to try all three single-passes.

2.3 Recursive implementation of quicksort

Put the reference code first, and then we will draw a picture of the recursive process.

 Reference Code:

void _QuickSort1(int* a,int begin,int end)//递归
{
	if (begin >= end)
	{
		return;
	}
	int keyi = Q_PartSort2(a, begin, end);

	_QuickSort1(a, begin, keyi - 1);
	_QuickSort1(a, keyi + 1, end);
}

 Due to space reasons, the right half is not drawn.

2.4 Time Complexity Calculation of Quick Sort Recursive Algorithm

Worst Case: Ordered

 The approximate number of executions is T(N) = N+N-1+N-2+.....+3+2+1

The time complexity is O(N*N)

Best case: The key taken each time is the median

Equivalent to a full binary tree with a height of logN

The time complexity is N*logN

2.5 Optimize the quicksort

2.5.1 Optimization 1: Take the middle of three numbers

We know that ordered arrays are not good for quick sorting. From this perspective, we have the optimization method of taking the middle of the three numbers:

That is, select mid=(left+right)/2

Among the three numbers a[left], a[right], and a[mid], the value is the number of the median, and then it is exchanged with a[keyi].

 Reference Code:

int GetMidIndex(int* a, int begin, int end)
{
	int mid = begin + ((end - begin)>>1);
	if (
		(a[mid] >= a[begin] && a[mid] <= a[end])
		|| 
		(a[mid]>=a[end] && a[mid] <= a[begin])
		)
	{
		return mid;
	}
	if (
		(a[begin]<=a[mid] && a[begin]>=a[end])
		||
		(a[begin] >= a[mid] && a[begin] <= a[end])
		)
	{
		return begin;
	}
	return end;
}

int Q_PartSort3(int* a, int begin, int end)//前后指针法
{
	//三数取中优化
	int ki = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[ki]);
	int keyi = begin;
	int prev = begin;
	int next = begin + 1;
	while (next <= end)
	{
		if (a[next] < a[keyi] && ++prev != next)
		{
			Swap(&a[prev], &a[next]);
		}
		next++;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}

2.5.2 Inter-cell optimization

When the interval is very small, the insertion sort is used directly, and there is no need to continue recursion.

 Reference Code:

void _QuickSort1(int* a,int begin,int end)//递归
{
	//小区间优化
	if (end - begin + 1 <= 12)
	{
		InsertSort(a, end - begin + 1);
	}
	if (begin >= end)
	{
		return;
	}
	int keyi = Q_PartSort2(a, begin, end);

	_QuickSort1(a, begin, keyi - 1);
	_QuickSort1(a, keyi + 1, end);
}

Finally, the general sorting is to pass a and n. In order not to pass the interval, a layer of encapsulation is added here.

void QuickSort(int* a, int n)
{
	_QuickSort1(a, 0, n - 1);//递归
}

2.6 Non-recursive quicksort

When the number to be sorted is large, it may cause stack overflow, so a non-recursive quicksort algorithm is required.

Here, stack + loop is used to simulate the recursive calling process, and the time efficiency is not much different from calling recursion.

Essentially the same procedure as calling recursion

 Reference Code:

void _QuickSort2(int* a, int begin, int end)//非递归
{
	ST st;
	STInit(&st);
    //检查传递的end和begin
	if (end > begin)
	{
		STPush(&st, begin);
		STPush(&st, end);
	}
	while (!STEmpty(&st))
	{
		int right = STRear(&st);
		STPop(&st);
		int left = STRear(&st);
		STPop(&st);
		int mid = Q_PartSort2(a, left, right);
        if(left<mid-1)
        {
            STPush(&st, left);
	    	STPush(&st, mid - 1);
        }    

		if(mid+1<right)
        {
            STPush(&st, mid + 1);
			STPush(&st, right);
        }
	}
	STDestroy(&st);
}

The queue implements a quick-sort non-recursive algorithm:

 Reference Code:

void _QuickSort3(int* a, int begin, int end)//非递归
{
	Queue q;
	QueueInit(&q);
	if (end > begin)
	{
		QueuePush(&q, begin);
		QueuePush(&q, end);
	}
	while (!QueueEmpty(&q))
	{
		int left = QueueFront(&q);
		QueuePop(&q);
		int right = QueueFront(&q);
		QueuePop(&q);
		int keyi = Q_PartSort1(a, left, right);
		if (left < keyi-1)
		{
			QueuePush(&q, left);
			QueuePush(&q, keyi-1);
		}
		if (keyi + 1 < right)
		{
			QueuePush(&q, keyi+1);
			QueuePush(&q, right);
		}
	}
	QueueDestroy(&q);
}

Guess you like

Origin blog.csdn.net/m0_62171658/article/details/124313424