Quick sort (non-recursive) and counting sort algorithms

After learning the recursive version of quick sort, why do we need to learn the non-recursive implementation? Because in the recursive process, if the amount of data is too large, it is easy to cause stack overflow during implementation. Although there is no problem with the code, it will crash, so it is necessary to change it to non-recursive implementation


1. Quick sort (non-recursive)

How to change recursive algorithm to non-recursive algorithm?

Simple recursion can directly change it to a loop (such as Fibonacci), but if it is a complex recursive algorithm, it is very complicated to directly change it to a loop, and generally use stack assistance to change it to non-recursive

Let's review the idea of ​​​​quick sorting again.

Idea: Select a keyword within the range, after a single sorting, the data on the left of the keyword position are all less than or equal to it, and the data on the right of the keyword are all greater than or equal to it. The keyword is sorted in one pass, and its left interval and right interval are also separated, and then the left interval and right interval are excluded in the same way until the interval range is 1 or does not exist.

The recursive implementation of quick sort is to control the interval each time, and each time the recursive call stack pushes the interval to the stack, and the interval changes every time the recursion is performed. Therefore, when using the stack to implement quick sorting, the stack is the interval, and since each recursion is the first left interval, and the stack is a last-in-first-out data structure, so when the interval is pushed into the stack, the right interval must first be sorted. The section is pushed onto the stack, and then the left section is pushed onto the stack to ensure that the left section is first arranged and then the right section is arranged in a single trip.
insert image description here

Single row. To discharge keyi, the left and right intervals of keyi are separated, and then the left and right intervals are pushed onto the stack. At this time, the left and right intervals are arranged in a single trip, and the keyi is discharged to separate the left and right intervals. After each single trip, it will be Separate the left and right intervals, and then push the left and right intervals into the stack. When the last interval is 1 or does not exist, there is no need to push it into the stack again. Overall is:

  1. 从栈中取一段区间,进行单趟排。
  2. 单趟排序后分隔子区间(左、右区间)入栈。
  3. 子区间只有一个值或者不存在就不入栈了
    insert image description here

How to arrange each single trip is realized here by using the forward and backward pointer method. After each single trip is arranged, the subscript where the keyword is located is returned

Then use the stack to assist to change the recursion. This stack comes from and. I wrote it before and saved it. In vs, you only need to select the source file and click the right mouse button to find the file that saved the stack implementation before, and copy
insert image description here
it
insert image description here
.
Then click on the source file
insert image description here
and then click oninsert image description here

insert image description here
It used to be a new one, but now it is clicked on the existing one, and then find the directory where the current program is located, and copy the file of the stack implementation just copied to the current program.

前后指针法排单趟

//前后指针法
int QuickSort3(int* a, int left, int right)
{
    
    
	
	int begin = left;
	int end = right;
	

	//随机选数,主要针对已经有序的情况,提高性能
	int randi = left + (rand() % (right - left));
	if (randi != left)
	{
    
    
		Swap(&a[randi], &a[left]);
	}

	int key = left;
	int cur = left + 1;
	int prev = left;

	while (cur <= right)
	{
    
    
		if (a[cur] < a[key] && ++prev != cur)
		{
    
    
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}

	Swap(&a[key], &a[prev]);
	key = prev;
	return key;
	
}

//取区间入栈然后对其单趟排,单趟排好keyi之后分割出它的子区间,将分割出的子区间入栈,再进行单趟排,再分割子区间入栈,每次单趟排排好一个数,直至区间范围为一或者不存在
//
void QuicksortNone(int* a, int begin, int end)
{
    
    
	St st;
	STInit(&st);
	STPush(&st, end);
	STPush(&st, begin);

	while (!STEmpty(&st))
	{
    
    
		int begin1 = STTop(&st);
		STPop(&st);
		int end1 = STTop(&st);
		STPop(&st);
		int keyi = QuickSort3(a, begin1, end1);

		if (keyi + 1 < end1)
		{
    
    
			STPush(&st, end1);
			STPush(&st, keyi+1);
		}

		if (keyi > begin1)
		{
    
    
			STPush(&st, keyi - 1);
			STPush(&st, begin1);
		}
	}
	STDestroy(&st);
}

insert image description here
Non-recursive quick sort time complexity O(nlog n ), space complexity o(1)

2. Counting sort

It is suitable for situations where there is a large amount of repeated data in the array and the difference between the maximum value and the minimum value is not particularly large.
Thoughts: 统计数组中每个数据出现的次数, how to count? Use a new array to store the number of occurrences of the original array data. How does this array come from? Of course, it is to dynamically apply to the memory to open up
a new array. How much space is appropriate to open up?
Since the number of occurrences of the original array data is stored, only one occurrence of the array data value is required, and its corresponding value as the subscript value is incremented by one (all the contents of the opened array are initialized to 0). As long as the value in the array appears once, then
it The corresponding value is added as the value corresponding to the subscript of the new array, so how to determine the range of the new array?
In fact, it is the difference between the maximum value and the minimum value of the original array. Since the original array value is used as the subscript, it is traversed to find the maximum value and minimum value in the array. Then determine the size of the new array.
insert image description here

Since the storage starts from 0, it is necessary to subtract the minimum value from the original array value, and finally add the subscript of the newly opened array to the minimum value of the original array to directly overwrite the original array. How to overwrite it?
insert image description here

void CountSort(int* a, int n)
{
    
    
	int max = a[0], min = a[0];
	for (int i = 1; i < n; i++)
	{
    
    
		if (a[i] > max)
		{
    
    
			max = a[i];
		}

		if (a[i] < min)
		{
    
    
			min = a[i];
		}
	}

	int range = max - min + 1;//要包含0,所以这个存储数组大小要开最大值和最小值之差再加一

	//开一个数组用来统计数组数据出现的次数
	int* countA = (int*)malloc(sizeof(int) * range);
	if (countA == NULL)
	{
    
    
		perror("ocuntA fail\n");
		return;
	}
	//将新开的这个数组内容全部都初始化为0
	//由于它存储的内容是原数组数据出现的次数
	memset(countA, 0, sizeof(int) * range);//c语言内置库函数将每个字节都初始化为0

	//以原数组中的数据作为新开数组的下标,然后统计它出现的次数
	//由于原数组最小值很可能不是从0开始
	//而新数组要从0开始存储
	//所以新数组下标是a[i] - min这个表达式
	for (int i = 0; i < n; i++)
	{
    
    
		//将原数组内容它对应的数字为countA下标加加,即数组对应数据在数组中出现的次数
		countA[a[i]-min]++;
	}

	int j = 0;
	for (int i = 0; i < n; )//i<n后面的调整不可再加,原因是下面加了,如果再加会造成越界
	{
    
    
		//可以在这里做出调整,将其i--,但是这里多此一举
		while (countA[j]--)
		{
    
    
			a[i++] = j + min;
		}
		j++;
	}

	free(countA);
}

insert image description here


The time complexity of counting and sorting is O(n+range). When range<n, the time complexity is O(n), and the space complexity is O(range), but its conditions are very harsh, requiring a lot of repeated data and these data The difference between the maximum value and the minimum value should not be too large, so it is rarely used in real life, but it will be used in some scenarios, so it is worth learning, after all, the time complexity is linear.

Guess you like

Origin blog.csdn.net/m0_67768006/article/details/130099754