Data Structure---Quick Sort


Data structure-other sorting methods (sort 1)
link: link .

1. Quick Sort

Quick sort is a binary tree structure exchange sorting method proposed by Hoare in 1962. Its basic idea is: take any element in the sequence of elements to be sorted as a reference value, and divide the set to be sorted into two sub-sequences according to the sort code. , All elements in the left subsequence are less than the reference value, all elements in the right subsequence are greater than the reference value, and then the leftmost subsequence repeats the process until all the elements are arranged in the corresponding positions. (To put it simply: In a given array, select the leftmost or rightmost number as the K value, and then put the K value in the appropriate position. In the ascending order, ensure that the numbers on the left are less than K, and the right The number of is greater than K, and then a similar process is performed on the left half of K, similar to a recursive form, until the left and right sides are ordered, then the whole is ordered )

1.1 hoare method (left and right pointer method)

Difficulties :

1. In order to find the K value conveniently, generally select the leftmost number or the rightmost number of the array (it can also be optimized)

2. Let begin find a value larger than K, and end find a value smaller than K

3. Make sure that the first move in the opposite direction of K is to find the corresponding number (if K is selected on the right, let begin go first, then the point where begin and end meet must be greater than K, so as to ensure The K value is exchanged with the value of this position to achieve the desired effect: the left side is smaller than the K value, and the right side is larger than the K value)

Insert picture description here
So why do I choose the K value on the right, and I want begin to find a value larger than K? Can this order be changed? , I let the end on the right move first, and look for a number smaller than K. You
Insert picture description here
will find that the value of the position where begin and end meet at this time is exchanged with the value of K. The value of K is not in the correct place (because your right Not all values ​​are greater than K)

Then your left side is smaller than the K value, and the right side is larger than the K value, but it is not in order, so solving the problem at this time is equivalent to a recursive form, and the left half of your K value can be selected again A K value, the same idea, when you have order on the left half and order on the right half, the whole is in order, much like the idea of ​​divide and conquer.

1.1.1 Analysis of time complexity

When the K value you choose is the median every time (the best case), then your traversal is equivalent to the preorder traversal of the binary tree, and the height of the tree is logN (base 2), then You have N layers, and your time complexity is O(N*logN) . When the number you choose each time is the largest or smallest (worst case), each layer is close to N, then the time The complexity is O(N*N) , so the time complexity is between O(N * logN) ~O(N*N)

1.1.2 Take the middle of the third number (optimize fast sorting)

Here it leads to an optimized when your period is nearing an ordered or ordered, you will either choose K value is the minimum or maximum value, the time complexity is O (N * N), I I hope that the selected K value is close to the middle value. In the process of selecting the K value, you may not be lucky enough to choose a K value close to the middle, but at least I want to ensure that the value he chooses must not be the smallest or the largest of. In this way, the worst situation is avoided to a certain extent, and it can be guaranteed that in an orderly situation, it will become the best situation

int GetMidIndex(int* a, int begin, int end)
{
    
    
	int mid = (begin + end) / 2;
	if (a[begin] < a[mid])
	{
    
    
		if (a[mid] < a[end])
			return mid;
		else if (a[begin] > a[end])
			return begin;
		else
			return end;
	}
	else //a[begin] >a[mid]
	{
    
    
		if (a[mid] > a[end])
			return mid;
		else if (a[begin] < a[end])
			return begin;
		else
			return end;
	}
}

* With the optimization of the third number, the time complexity of fast sorting no longer considers the worst case, but thinks that the time complexity symbol is O(N * logN)

1.1.3 Complete code

//这里写的只是你选择一次K遍历整个数组出来的结果
int PartSort1(int* a, int begin, int end)
{
    
    
	//选出合适的中位数
	int midIndex = GetMidIndex(a, begin, end);
	Swap(&a[midIndex], &a[end]);

	int keyIndex = end;
	while (begin < end)
	{
    
    
		//先让begin去找大于K的值
		while (begin<end && a[begin] <= a[keyIndex])
		{
    
    
			++begin;
		}

		//再让end去找比K小的值,此时在这里还要对end有限制条件,不然begin和end在内部循环的时候就错过了,而不是相遇停止
		while (begin<end && a[end] >= a[keyIndex])
		{
    
    
			--end;
		}
		Swap(&a[begin], &a[end]);
	}
	//此时你的begin和end在同一个位置了(选begin还是和end都是一样的),在交换
	Swap(&a[begin], &a[keyIndex]);

	//此时你的begin和end所停留的位置就是K的正确位置
	return begin;
}

//快排
void QuickSort(int* a, int left, int right)
{
    
    
	assert(a);
	//if (left < right) //连等于的时候都可以不用排了,因为此时就剩下一个值了
	//{
    
    
	//	int div = PartSort1(a, left, right);
	//	//递归下去
	//	//[left, div - 1] div [div+1,right]
	//	QuickSort(a, left, div - 1);
	//	QuickSort(a, div + 1, right);
	//}

	if (left >= right)
		return;
	int div = PartSort1(a, left, right);
	//递归下去
	//[left, div - 1] div [div+1,right]
	QuickSort(a, left, div - 1);
	QuickSort(a, div + 1, right);
}

1.2 Digging method (key solution method is easy to understand)

This is ideologically basically consistent with the left and right pointer method, which is to avoid the thought process of letting the one who chooses the opposite direction of the K value to go first when understanding.
Digging method: first select the leftmost or rightmost as K, here it is assumed that the rightmost is selected as K. At this time, after the K value is taken out, it is equivalent to this position as a pit, and then a begin is defined on the leftmost side Find a value greater than K, and then take it to fill the hole. At this time, this position is a hole. Define an end on the far right to let it find a value smaller than K. Then fill in the previous hole and let begin go. Until begin and end meet, select this pit and fill in the K value
Insert picture description here

//挖坑法
int PartSort2(int* a, int begin, int end)
{
    
    
	//选出合适的中位数
	int midIndex = GetMidIndex(a, begin, end);
	Swap(&a[midIndex], &a[end]);

	//最开始的坑
	int key = a[end];
	while (begin < end)
	{
    
    
		//让begin去找大于K的值 ,  
		//这里值得注意的就是一定要加=,因为当begin所走到的那个数如果此时和你的key值相等,要么留在左边,要么留在右边都可以
		//但是你不加,就会造成死循环
		while(begin < end && a[begin] <= key)
		{
    
    
			++begin;
		}
		a[end] = a[begin];

		//让end去找比K小的值
		while (begin < end && a[end] >= key)
		{
    
    
			--end;
		}
		a[begin] = a[end];
	}
	//走到这里说明begin和end 相遇了,这里放上K的值
	a[begin] = key;
	return begin;
}

1.3 Forward and backward pointer method

Define a prev and cur, let cur find a value smaller than the key, stop when it finds it, then ++prev, exchange the two values, until cur finishes the array, in ++prev, let prev The value and key exchange. (At this time, the left side is less than the value of the key, and the right side is greater than the value of the key)
Insert picture description here
If it is written well, the code will be very redundant.

//前后指针法
int PartSort3(int* a, int begin, int end)
{
    
    
	//选出合适的中位数
	int midIndex = GetMidIndex(a, begin, end);
	Swap(&a[midIndex], &a[end]);
	
	int prev = begin - 1;
	int cur = begin;
	int keyIndex = end;
	while (cur <= end) //
	{
    
    
		//让cur去找小于K的值 ,但是你会发现有的时候你的cur和prev在同一个位置,所以可以不考虑交换
		if (a[cur] < a[keyIndex] && ++prev != cur)
			Swap(&a[prev], &a[cur]);

		//当a[cur]比a[keyIndex]小的时候,停下++完prev之后,cur要在++
		//当a[cur]比a[keyIndex]大的时候,直接++cur,所以宗的来说,都是要进行++cur的操作的,所以可以直接合并
		++cur;	
	}
	Swap(&a[++prev], &a[keyIndex]);
	return prev;
}

1.4 Interval optimization

Even if there are only 10 numbers left here that are not in order, we still have to recurse, first select a K, then the left row is ordered, the right row is ordered, and finally the 10 numbers are ordered. In fact, this is redundant for recursion. It may not only not help you optimize, but it is more complicated, because in the process of recursion, you need to constantly borrow stack pins. Maybe when the array you need to arrange is a When it is very large, the stack space will overflow, so optimization is introduced here: when a certain number range is reached, the recursive method is no longer used, but the direct insertion method is used to achieve the remaining number sorting, in time That said, there is basically no difference.
Insert picture description here

//插入排序
void InsertSort(int* a, int n)
{
    
    
	assert(a);
	//把第一个数当成有序的,然后拿后面的数据和第一个数据比较插入
	for (int i = 0; i < n - 1; ++i)
	{
    
    
		//可以把任何一种排序都分解开来为单步分析在整合整体的过程来思考问题
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0) //当这个tmp和第一个值相比较还是小的话,再次移动end就会发现他已经到-1的位置了,说明他依旧和所有的元素都比较过了
		{
    
    
			if (tmp < a[end])
			{
    
    
				a[end + 1] = a[end];
				--end;
			}
			else
			{
    
    
				break;
			}
		}
		//这里跳出来会有两种可能,第一个就是tmp此时大于end下角标所在的元素,break出来
		//第二种就是while循环结束end已经到了-1的位置,(也就是比第一个值还要小)
		a[end + 1] = tmp;
	}
}

//快排
void QuickSort(int* a, int left, int right)
{
    
    
	assert(a);
	if (left >= right)
		return;
 //小区间优化--不再使用递归的方式,而是改用直接插入排序
	if ((right - left + 1) > 10)
	{
    
    
		int div = PartSort3(a, left, right);
		//递归下去
		//[left, div - 1] div [div+1,right]
		QuickSort(a, left, div - 1);
		QuickSort(a, div + 1, right);
	}
	else
	{
    
    
		//区间小于10个数的时候,不再使用递归,而是改用直接插入的方法
		//然而你这里需要排序的数组范围,不再是最初的,可能是此时递归到某一层的左右子树
		InsertSort(a + left, right - left + 1);
	}
}

1.5 Non-recursive fast sorting

Since I haven't learned C++ yet, I still need to write a stack manually, and then we need to use the stack to achieve non-recursive fast sorting . The essence of recursion is to save the parameters with the help of the stack pin, and the main idea implemented here is: save the interval of the stack pin to the stack.

Meaning of non-recursion :

①Because it is still costly to recursively build stack pins, but for modern computers, this optimization is minimal and can be ignored.
②The biggest drawback of recursion is that if the depth of the stack needle is too deep, it may cause the stack to overflow. Because the system stack space is generally not large at the M level, but the data structure stack simulates non-recursion, the data is stored on the heap, and the heap is at the G level.

Insert picture description here

void StackInit(Stack* pst)
{
    
    
	assert(pst);
	//这种方式是有不好的地方的,因为但当你需要增容的时候,你就会发现,他的capacity初始化是0,那么你乘2依旧是0,所以建议一开始就给一个固定值
	//pst->_a = NULL;
	//pst->_top = 0;
	//pst->_capacity = 0;
	pst->_a = (STDateType*)malloc(sizeof(STDateType)*4);
	pst->_top = 0;
	pst->_capacity = 4;
}

//销毁
void StackDestory(Stack* pst)
{
    
    
	assert(pst);
	free(pst->_a);
	pst->_a = NULL;
	pst->_top = pst->_capacity = 0;
}

//入栈
void StackPush(Stack* pst, STDateType x)
{
    
    
	assert(pst);
	//空间不够则增容
	if (pst->_top == pst->_capacity)
	{
    
    
		pst->_capacity *= 2;
		STDateType* tmp = (STDateType*)realloc(pst->_a, sizeof(STDateType)*pst->_capacity);
		if (tmp == NULL)
		{
    
    
			printf("内存不足\n");
			exit(-1);
		}
		else
		{
    
    
			pst->_a = tmp;
		}
	}
	pst->_a[pst->_top] = x;//你所定义的栈顶总是在你放入数据的下一个位置
	pst->_top++;
}

//出栈
void StackPop(Stack* pst)
{
    
    
	assert(pst);
	assert(pst->_top > 0);
	--pst->_top;
}

//获取数据个数
int StackSize(Stack* pst)
{
    
    
	assert(pst);
	return pst->_top;
}


//返回1是空,返回0是非空
int StackEmpty(Stack* pst)
{
    
    
	assert(pst);
	return pst->_top == 0 ? 1 : 0;
}


//获取栈顶的数据
STDateType StackTop(Stack* pst)
{
    
    
	assert(pst);
	assert(pst->_top > 0);
	return pst->_a[pst->_top - 1];//你所定义的栈顶总是在你放入数据的下一个位置
}




//快排的非递归
void QuickSortNonR(int* a, int left, int right)
{
    
    
	Stack st;
	StackInit(&st);

	//先入右在入左
	//那么出的时候就会先出左在出右
	StackPush(&st, right);
	StackPush(&st, left);
	while (!StackEmpty(&st))
	{
    
    
		int begin = StackTop(&st);
		StackPop(&st);
		int end = StackTop(&st);
		StackPop(&st);
		//此时相当于我们拿到这这段区间[begin,end]
		//先进行大区间单趟排
		int div = PartSort3(a, begin, end);
		//[begin,div-1] div [div+1,end]
		//原来是对[begin,div-1] 和[div+1,end]进行递归的操作,但是现在思路不变但换成用栈来实现
		//这里为了好理解对照着二叉树的前序遍历的思想,选择了先入右在入左,这样出栈以后就可以先堆最左边区间进行操作
		//入右边
		if (div + 1 < end)//当你是一个数的时候就相当于有序了,不再入栈了
		{
    
    
			StackPush(&st, end);
			StackPush(&st, div+1);
		}
		//入左边
		if (begin < div - 1) 
		{
    
    
			StackPush(&st, div-1);
			StackPush(&st, begin);
		}
	}
	StackDestory(&st);
}

1.6 Summary of Features of Quick Sort

  1. The overall performance and usage scenarios of quick sort are relatively good, so I dare to call quick sort
  2. Time complexity: O(N*logN)
  3. Space complexity: O(logN)
  4. Stability: unstable
    Insert picture description here

Guess you like

Origin blog.csdn.net/MEANSWER/article/details/113104009