[Basic Data Structure] 10. Quick sort (comparison sort) explanation and implementation (three recursive quick sort versions + non-recursive quick sort version - C language implementation)

=========================================================================

Aikanyo gitee own take

C language learning diary: keep working hard (gitee.com)

 =========================================================================

approach period:

[Elementary Data Structure] 9. Explanation and implementation of sorting (direct insertion \ Hill \ direct selection \ heap \ bubbling -- C language) - CSDN Blog

 =========================================================================

                     

Implementation of common sorting algorithms (continued from the previous issue)

(Details are explained in the comments of the picture, and the code is divided into files under the next title)

                 

3. Exchange sorting

Basic idea:

The so-calledexchange is based on the comparison result of the two record key values ​​in the sequenceExchange the positions of these two records in the sequenceCome

             

Characteristics of exchange sort

generalkeyword recorddirectionhierarchyTailMovement, keyboard recorddirectionMovementAnteriorOrdinal

                      

                      

Exchange sort -- quick sort
                     

The basic idea of ​​this algorithm is:
  • Quick SortisHoare(Hoare a>) proposed in 1962 a exchange sorting method for binary tree structure
                     
  • The basic idea为:
    A certain element in the hierarchy of elementsWork a>Key standard 值key 值)、
    按光该Key standard 值key 值Short order exclusion setDivided generation and child order(child section)、
    Zozi rank(Zozi section) middle a>Owned ElementUnique XiaoyuBasic 值( key 值)、
    Right child order(Right child ward rankDirectly reach the possessing element, eliminate the capital, and stop at the mutual position, repeat the above process)child section(douba left and right child order 下后), key值 (Basic standardUniformityOwned element)中


                       
  • The above is the main framework of quick sort recursive implementation, which can be foundThe process is very similar to binary tree preorder traversal rule, When writing a recursive framework, you can refer to the binary tree pre-order traversal rules , subsequent Justanalyze how to follow thebenchmark value(key value ) to divide the data in the interval


                             
  • Create the interval according to base value (key value) Common ways to divide it into left and right halves include:
    hoare version ( Original version) Quick sort , Pit digging method Quick sort , a>Quick sortBefore and after pointer version

                     
Summary of quick sort features:
  • Quick sortOverallComprehensive performanceandusage scenarios They are all relatively good, so I dare to call them quick sort
                    

  • Calculation method time rateO(N*logN)
                       

  • Calculation method space strengthO(logN)
                         

  • The stability of this algorithm:Unstable
                   

---------------------------------------------------------------------------------------------

                       

GetMidi internal function -- quick sorting "mid-three" optimization function

  • Usebase value(key value)Subscript the middle value of medium or small and return ittake out one, OperationGet the middle number among three numbers " should be carried out so , The efficiency of multiple quick sorting is not High , will only divide a right interval a>,if it is the minimum value againthe new key value in the right interval, at this time will actually divide only one right interval then , Ranked at the far left of the array, it will beminimum value iskey value is possible, is divided into two intervalsWhen the current arraykey value uses , When performing quick sort




                    

  • Withthe current rangestarting element subscript(left) and tail element subscript (right compare and determine the size of the corresponding values ​​​​of these three subscripts and returns the middle value subscript takes out , ), mid ( middle element subscript of the current interval obtain a
    can ),
Illustration:

                          

                          
---------------------------------------------------------------------------------------------

                       

PartSort1 internal function -- one-pass bubble sort implementation function (hoare version)

  • Usethe three-number middle function to obtain a relativelymiddle value index a>, will the middle value and left Scaling values ​​ transposed ,
    we default Key value subscript isleft subscript, that is,current interval< /span>,Position subscript) Leftmost( Start)Array(
                       
  • Usewhile loop,
    to make smaller value< /span> embed the first while loop find the subscripts of values ​​smaller than key and larger than key respectively , greater than the key value Element subscripts find from left to rightleftLet, embed the second while loop and then , The element index less than the key valueLook from right to leftrightLet, first , On the right side of the array) to The larger value of key (The larger value, array) to the left side of the is smaller than key (



                           
  • while circulation ends at time, left == right,
    At this timeleft(orright)Lower titleJoinThis iskey 值current number of members Authentic key值General
    RequiredThis timeleft值(right值)< /span>travel exchange
                            
  • lastreplyleft(orrightkey at this time bottom page, bottom page
Illustration:

This function executes the logic diagram:

                          

                          
---------------------------------------------------------------------------------------------

                       

PartSort2 internal function -- one-pass bubble sort implementation function (digging method version)

  • Similarly usethe three-number middle functionto obtain a relativelymiddle value subscript< /span> instead of its subscript keyi, directly obtain the key value This time we, Position subscript) Leftmost (Start)Array( the current interval , that is, left subscript is The key value subscript default we , transposed left Subscript values ​​ and the middle value will ,


                   
  • Savekey value later, at the beginning of the array (Leftmost) Position Form the first"pit"
    ( After obtaining or adjusting an element value, define the subscript of the element value as < /span>")pit"
                       
  • After that the general idea is similar to hoare version, < /span> forms a new one", also",< /span>"pit" forms a new The subscript "smaller value"at thenpit"Put it directly into the last definedlarger value"After finding the left in";pit"The subscript "The smaller value" in again", pit" directly put it into the last defined", just After the smaller value"right is only found on




                           
  • right Japanese left Yoritsugu completion"Middle "pit"releasekey 值Last commander Reason, Genuine bottom positionkey 值immediately"pit" ",pit"The end is empty,back"Punch filling pit


                            
  • FinallyReturnThe final "pit" subscript,< a i=4>That is, the subscript of the key value at this time
Illustration:

This function executes the logic diagram:

                          

                          
---------------------------------------------------------------------------------------------

                       

PartSort3 internal function -- one-pass bubble sort implementation function (front and rear pointer version)

  • Usethe three-number middle function to obtain a relativelymiddle value index a>, will the middle value and left SubscriptTransposition
    Wedefaultkey value subscript< a i=13> isleft subscript, that is,current interval(< /span>,Position subscript)most Left(start)array
                       
  • returnbuild your fingers--prev( front finger 针)和 curBack finger),
    prevStartPointing starting position a>,curFirst placeFirst place after orientation prev
                           
  • usewhile circulationproceeding “pusher box”:
    Currentcurricular finger还小于等于right下标时入继续pusher box”,
    cur fingertipleft and right arrowhas arrived(" Leaveward"较小值较小值甩到left same timesimilar to similar box ),ward period较大值Exchange your fingers's story, workprev inequality time cur when++prev前手针”,な么入较小值cur fingertip as a result,cur capital++无论这找到”, 较小值




                            
  • rightDekaiSuikakubako”Afterwards, while circulation bundle,
    at this timeprev lower title< a i=9>This is thekey to be released, the reasonkey is to be released
                        
  • FinallyReturn the key value position, that is,the position of the prev pointer at this time
Illustration:

This function executes the logic diagram:

                          

                          
---------------------------------------------------------------------------------------------

                       

QuickSort function -- quick sort (recursive version)

  • Because will perform recursive operations on the array ( Recursive quick sort),
    So must first set the recursive end condition :< /span>Yesreturn the recursive resultwhen this happens, you can So......[0,0] interval or , the subscript of the left element will be greater than the interval of the right element's subscript Similar to , where the array range is unreasonable will occur specified later in the recursion. a> recursively executes to the end when , Tail element subscript and will use the array start
    Because



                            
  • Small area optimization-- Small areaNo more recursive split sorting< a i=4>, reduce the number of recursions :
    in order to further improve quick sort Efficiency,
    in recursive partitioning process , After the interval is relatively small, it is best not to perform recursive split sorting, < a i=17> Becausefull binary tree (quick sort recursive process is similar to full Binary tree)There will be 80% of nodes< in the last three levels< /span>there will be ’s recursive operations80% eliminates the need and is highly efficientdirect insertion sort an array with only 10 elements Performing for , instead of recursion to sort directly Insertion sort can be used ), with only 10 elements (similar to when the array interval is recursed into small intervals so , It is not cost-effective to recurse on multiple small intervals yes, ’s recursive operation80%, when the interval is relatively small So






                            
  • For arrays with elements greater than 10 (Large range)Proceedquick sort (recursively):< a i=9>Call one of the three single-pass quick sort functions written above, and receive the return The key value subscript , at this time the current interval is divided into The left and right intervals , perform recursive operations on the left and right intervals respectively.



                       
  • For arrays with elements less than or equal to 10 (small interval)Carry outdirect insertion sort
Illustration:

This function executes the logic diagram:

This function test (hoare version):

This function test (digging version):

This function tests (front and back pointer versions):

                          

                          
---------------------------------------------------------------------------------------------

                       

QuickSortNonR function -- quick sort (non-recursive version)

  • Quick delivery order non-delivery version Honshiro--Borrowing(“Later first out”) Completion:
    First completed one stepFast evacuation orderCompletekey order of exclusion
    Ranhousho< /span>,currently left and right divisionKey also split into two parts, Left section key exclusion orderCompletion of arrangement Afterwards, Fast evacuation operation Back section section,BackLeft sectionfirst fetchin in ,Right section 间范围ReuseLeft section 间范围Use of party,CharacteristicsLater, first out的“ 因为release下标范围Key opening area(Key opening areaRestorationFirst release下标范围)key entry area(key entry areaPreviously releasedPreviously right section< /span>This area's range is small. >In practice, it is similar to ), but itNon-use of forwarding operation (该过程……heavy top operationseffective area继续认前前中入栈了,Non-future district 间入栈 Servedtimeirrational 范围[2,1]orLastRapid ejection< /span>类similar 递归……(heavy addition to this operationNext line progressPreviously left sectionRe-taken Afterwards, Dorizen left sectionResurrection,









                         
  • UseNumber of configurations in progressCompleteNon-transferableShopping:
    UseTransferring fast order's story, a>transfer operationiscurrentoperation system completeSpaceGoing,
    beingLinux (32nd place) loweronly 8M< /span>Easy to open spaceMost likely Overflowing circumstances in the society,Expense space 2G+) Downward >32nd place ( Linuxempty spacecompositionoperation system completeexistYes story,Non-distribution pleasureCompletenumber arrangement medium and useoverflow, guide,Transfer depth and depth, As a result


First import the stack implementation written before:

Illustration:

This function tests:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

Corresponding code (continued from the previous issue)

Sort.h -- Sorting header file

//快速排序函数(递归版本):
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:接收该数组起始位置下标(0)
//第二个参数:接收该数组尾部位置下标(数组元素个数-1)
void QuickSort(int* a, int begin, int end);


//快速排序函数(非递归版本):
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:接收该数组起始位置下标(0)
//第二个参数:接收该数组尾部位置下标(数组元素个数-1)
void QuickSortNonR(int* a, int begin, int end);

            

            

---------------------------------------------------------------------------------------------

            

Sort.c -- Sorting function implementation file

//三数取中(内部函数):
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:数组最左边(起始)元素下标 -- key值
//第三个参数:数组最右边(尾部)元素下标
//返回中间值的下标
int GetMidi(int* a, int left, int right)
{
	//通过left(左下标)和right(右下标)获得mid(中下标):
	int mid = (left + right) / 2;

	//	left	mid 	right
	if (a[left] < a[mid])
		//"左下标值" < "中下标值"
	{
		if (a[mid] < a[right])
			//又有:"中下标值" < "右下标值"
		{
			return mid; //mid就是三数中的中间值下标
		}
		else if (a[left] > a[right])
			//又有:"左下标值" > "右下标值"
		{
			return left; //left就是三数中的中间值下标
		}
		else
			//其它情况:
		{
			return right; //right就是三数中的中间值下标
		}
	}
	else
		//a[left] > a[mid]
		//"左下标值" > "中下标值"
	{
		if (a[mid] > a[right])
			//又有:"中下标值" > "右下标值"
		{
			return mid; //mid就是三数中的中间值下标
		}
		else if (a[left] < a[right])
			//又有:"左下标值" < "右下标值"
		{
			return left; //left就是三数中的中间值下标
		}
		else
			//其它情况:
		{
			return right; //right就是三数中的中间值下标
		}
	}
}



//一趟快速排序(内部函数)-- 方法一hoare版本(细节较多):
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:数组最左边(起始)元素下标 -- key值
//第三个参数:数组最右边(尾部)元素下标
//返回排序后key值下标
int PartSort1(int* a, int left, int right)
{
	//使用三数取中函数获得一个相对而言的中间值下标:
	int midi = GetMidi(a, left, right);
	//将中间值和数组起始元素进行交换:
	Swap(&a[left], &a[midi]);

	//定义快速排序的key值下标,
	//默认key值下标为数组最左边(起始)下标 -- 0:
	//(通过三数取中后的left值在排序后会在数组相对中间位置)
	//(这有助于提高快速排序的效率 -- 因为后面递归的操作)
	int keyi = left;

	//使用while循环,
	//将数组右边的较小值移至数组左边,
	//将数组左边的较大值移至数组右边:
	while (left < right)
		/*
		* 只要left下标还小于right下标,
		* 说明中间可能还有元素需要被调整。
		*/
	{
		//先让right从右往左找小于key值的元素下标:
		/*
		因为是升序,大值应该在右边,
		所以从右往左找较小值,之后移至数组左边
		*/
		while (left < right && a[right] >= a[keyi])
			//left < right,保证right不越界
			//而且right元素还大于等于key值:
		{
			//那就说明还不是较小值,
			//right往左移一位:
			--right;
			//直到调整到比key小的元素下标为止
		}

		//找到右边的较小值后,再找左边的较大值,
		//让left从左往右找大于key值的元素下标:
		/*
		因为是升序,小值应该在左边,
		所以从左往右找较大值,之后移至数组右边
		*/
		while (left < right && a[left] <= a[keyi])
			//left < right,保证left不越界
			//而且left元素还小于等于key值:
		{
			//那就说明还不是较大值,
			//left往右移一位:
			++left;
			//直到调整到比key大的元素下标为止
		}

		//分别找到比key小和比key大的值后,
		//将两者的值进行调换:
		//(让较小值到数组左边,较大值到数组右边)
		Swap(&a[left], &a[right]);
	}

	//key值左边的较小值和右边的较大值排好后,
	//while循环结束时,left == right,
	//此时left(或right)下标就会是key值在数组中的真正下标,
	//所以要将key值和此时left值(right值)进行交换:
	/*
	* 外层while循环结束后,是right先往左边走,left再往右边走,
	* 较大值都在right后 且 left位置一定会在小于key值的元素上,
	* 所以此时可以将key值和left值(right值)进行交换,找到key真正位置
	*/
	Swap(&a[keyi], &a[left]);

	return left;
	//返回left(或right)下标,即此时key值的下标
	//此时key的左边(较小值)和右边(较大值)还是无序的
}
//单趟快速排序的时间复杂度:O(N)



//一趟快速排序(内部函数)-- 方法二挖坑法:
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:数组最左边(起始)元素下标 -- key值
//第三个参数:数组最右边(尾部)元素下标
//返回排序后key值下标
int PartSort2(int* a, int left, int right)
{
	//使用三数取中函数获得一个相对而言的中间值下标:
	int midi = GetMidi(a, left, right);
	//将中间值和数组起始元素进行交换:
	Swap(&a[left], &a[midi]);

	//此时left下标元素就是中间值:
	int key = a[left]; //直接获取key值(存放中间值)
	
	//保存key值以后,在数组起始(最左边)位置形成第一个"坑":
	int hole = left; //只留"坑"的下标(左边的"坑")

	//使用while循环进行快排过程:
	while (left < right)
		/*
		* 只要left下标还小于right下标,
		* 说明中间可能还有元素需要被调整。
		*/
	{
		//让right从右往左走,找比key小的元素下标,
		while (left < right && a[right] >= key)
			//left < right,保证right不越界
			//而且right元素还大于等于key值:
		{
			//那就说明还不是较小值,
			//right往左移一位:
			--right;
			//直到调整到比key小的元素下标为止
		}

		//找到对应下标后填到左边的"坑",
		a[hole] = a[right];
		//在此时right下标处形成新的"坑":
		hole = right; //右边的"坑"(下标)


		//让left从左往右走,找比key大的元素下标,
		while (left < right && a[left] <= key)
			//left < right,保证left不越界
			//而且left元素还小于等于key值:
		{
			//那就说明还不是较大值,
			//left往右移一位:
			++left;
			//直到调整到比key大的元素下标为止
		}

		//找到对应下标后填到右边的"坑",
		a[hole] = a[left];
		//在此时left下标处形成新的"坑":
		hole = left; //再次形成左边的"坑"(下标)
	}

	//left和right在“坑”下标处相遇后,while循环结束,
	//此时“坑”下标就是key值对应下标,
	//所以最后将key值放到“坑”下标处:
	a[hole] = key;

	//返回当前key值“坑”下标:
	return hole;
}



//一趟快速排序(内部函数)-- 方法三前后指针版本:
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:数组最左边(起始)元素下标 -- key值
//第三个参数:数组最右边(尾部)元素下标
//返回排序后key值下标
int PartSort3(int* a, int left, int right)
{
/*
* 思路(类似推箱子):
* 创建两个指针--prev(前指针)和cur(后指针)
* prev一开始指向起始位置,cur一开始指向prev后一位
* 
* cur:
* 1、++cur从左往右开始“找小(比key小)”,
	 找到后++prev,再交换prev和cur指向的值
*	 cur未找到较小值的话,cur单独++,prev不动 
* 
* prev有两种情况:
* 1、在cur还没遇到比key大的值的时候,prev紧跟着cur
* 2、在cur遇到比key大的值的时候,cur单独++,
*  	 prev会在比key大的一组值的前面(数组左边)
* 
* 【本质:把一段大于key的区间(较大值区间),类似推箱子般往右推,
*   同时将较小值甩到左边(较小值区间)去】
*/
	//使用三数取中函数获得一个相对而言的中间值下标:
	int midi = GetMidi(a, left, right);
	//将中间值和数组起始元素进行交换:
	Swap(&a[left], &a[midi]);

	//定义key值(“中间值”)下标:
	int keyi = left; //默认当前数组起始位置下标

	//先定义prev指针:
	int prev = left; //从当前数组起始位置开始

	//再定义cur指针:
	int cur = prev + 1; //指向prev的后一位

	//使用while循环循环进行"推箱子":
	while (cur <= right)
		/*
		* cur==right时,还要再指向一次,
		* 让cur指针出界,出界时prev指针的位置才是key值位置
		*/
	{
		//如果cur从左往右走后找到了“较小值”:
		if (a[cur] < a[keyi] && ++prev != cur)
			/*
			*			++prev != cur  
			* 每次“推箱子”时,先++prev,
			* 且不等于cur,如果等于cur的话会导致自己跟自己交换,
			* 这操作没有意义,所以直接进行下次“推箱子”操作
			*/
		{
			//再交换当前两指针值:
			Swap(&a[prev], &a[cur]);
		}

		//无论cur是否找到“较小值”,cur指针都需要进行++:
		++cur; //++往后(右)走
	}
	/*
	* 把一段大于key的区间(较大值区间),类似推箱子般往右推,
	* 同时将较小值甩到左边(较小值区间)去
	*/

	//right出界“推完箱子”后,while循环结束,
	//此时prev下标就是key值下标,
	//所以将key值放入该位置:
	Swap(&a[prev], &a[keyi]);
	
	//最后返回key值位置 -- prev :
	return prev;
}


//快速排序函数(递归版本):
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:接收该数组起始位置下标(0)
//第二个参数:接收该数组尾部位置下标(数组元素个数-1)
void QuickSort(int* a, int begin, int end)
{
	/*
	* 因为后面会对数组进行递归操作(递归快速排序),
	* 所以要先设置递归结束条件:
	* 因为后面递归时会用到指定的数组起始和尾部元素下标,
	* 在递归执行到后面时,会出现数组范围不合理的情况,
	* 类似左边元素下标会大于右边元素下标或[0,0]区间……
	* 所以当出现这种情况时就可以返回递归结果了:
	*/
	if (begin >= end)
		//数组起始位置下标 >= 数组尾部位置下标:
	{
		//是不合理的数组范围:
		return; //直接返回上层递归
	}

	/*
	* 小区间优化 -- 小区间不再进行递归分割排序,降低递归次数:
	* 为了进一步提高快速排序效率,
	* 在递归分割过程中,区间比较小了以后,最好不再进行递归分割排序,
	* 因为满二叉树(快速排序递归过程类似满二叉树)倒数三层中会有80%的节点
	* 所以当区间比较小后,这些小区间会有80%的递归操作,
	* 对这么多小区间进行递归不划算,
	* 所以当数组区间递归成小区间(类似只有10个元素)后,
	* 可以使用直接插入排序代替递归进行排序,
	* 对只有10个元素的数组进行直接插入排序效率高而且省去了80%的递归操作
	*/

	//对元素大于10的数组(大区间)进行快速排序(递归进行):
	//“满二叉树倒数三层前的节点”(“20%的节点”)
	if ((end - begin + 1) > 10)
		//区间尾下标 - 区间首下标 + 1 = 区间元素个数
	{
		//调用单趟快速排序函数并接收返回的key值下标:
		//int keyi = PartSort1(a, begin, end); //方法一 -- hoare版本
		//int keyi = PartSort2(a, begin, end); //方法二 -- 挖坑法
		int keyi = PartSort3(a, begin, end); //方法三 -- 前后指针版本

		/*
		* 调用函数进行一趟快速排序后,此时数组下标可表示为:
		* [begin, keyi-1] keyi [keyi+1, end]
		* 经过一趟排序,此时key的左边(较小值)和右边(较大值)还是无序的
		* key的左边(较小值)下标区间:[begin, keyi-1]
		* key的右边(较大值)下标区间:[keyi+1, end]
		* 所以接下来要就这两个区间进行排序:
		*/

		/*
		* 可以把下标区间: [begin, keyi-1] keyi [keyi+1, end]
		* 看成类似二叉树的结构:
		*				keyi -- "根"
		*		[begin, keyi-1] -- “左子树”
		*		[keyi+1, end] -- “右子树”
		* 所以可以使用类似树中类似递归的操作完成排序:
		*/

		//对“左子树”进行递归快速排序:
		QuickSort(a, begin, keyi - 1); //[begin, keyi-1] -- “左子树”

		//对“右子树”进行递归快速排序:
		QuickSort(a, keyi + 1, end); //[keyi+1, end] -- “右子树”
	}
	else
		//对元素小于等于10的数组(小区间)进行直接插入排序:
		//“满二叉树的倒数三层节点”(“80%的节点”)
	{
		//调用直接插入排序对递归后的“小区间”进行排序:
		InsertSort(a + begin, end - begin + 1);
		//被快速排序的递归操作分割后的“小区间”首元素下标:a + begin
		//被快速排序的递归操作分割后的“小区间”元素个数:end - begin + 1
	}
}
/*
* 快速排序时间复杂度(三数取中前):
* 最好的情况(key值总排在靠中间的位置) -- O(N*logN)
* 最坏的情况(数组有序,key值总排在靠起始位置) -- O(N^2)
* 
* 快速排序时间复杂度(三数取中后):
* 最好的情况(数组有序,key值总排在靠中间位置) -- O(N*logN)
* 最坏的情况 -- 在三数取中操作后几乎不会有最坏的情况
*/



//快速排序函数(非递归版本):
//第一个参数:接收要排序的数组首元素地址(a)
//第二个参数:接收该数组起始位置下标(0)
//第二个参数:接收该数组尾部位置下标(数组元素个数-1)
void QuickSortNonR(int* a, int begin, int end)
{
	/*
	*		将快速排序改成非递归版本思路--借助栈完成:
	* 先完成一趟快速排序操作,匹配完成key值的排序,
	* 然后将key值右边的区间(大于key值区间)下标范围先放入栈中,
	* 再将key值左边的区间(小于key值区间)下标范围放入栈中,
	* 因为栈的“后进先出”特性,会先使用左区间范围,再使用右区间范围,
	* 在栈中先取出左区间后,对该区间执行一趟快速排序操作,
	* 然后匹配完成左区间key值的排序,该key值又分割出两个当前左右区间,
	* 同样在栈中先存放当前右区间,再存放当前左区间,
	* 然后再取出当前左区间来进行一趟快速排序,重复执行此操作……(类似递归)
	* 到最后当区间范围缩小到无意义范围[0,0]或不合理范围[2,1]时,
	* 就不将这类区间入栈了,继续取当前栈中的有效区间重复上面的操作……
	* (该过程不使用递归进行操作,但是实际执行过程跟递归相同)
	*/

	/*
	*		使用数据结构中的栈完成非递归快排的好处:
	* 使用递归进行快速排序的话,递归操作是在操作系统中的栈空间进行的,
	* 在Linux(32位)下的栈空间只有8M,如果递归深度太深,
	* 栈空间就容易爆,导致栈溢出
	* 
	* 而使用数据结构中的栈完成非递归快排的话,栈是在操作系统中的堆空间进行的,
	* 在Linux(32位)下的堆空间有2G+,大概率不会存在堆溢出的情况
	*/

	//先创建一个栈类型变量:
	ST st;
	
	//对其进行初始化:
	STInit(&st);

	/*
	* 我们要在栈中存放一个区间,即两个整数,
	* 可以将栈中存放的数据改为区间类型:[begin, end],
	* 而我们之前写的栈的每个元素是存放一个整数的,
	* 所以要将一个区间分两次放入栈中,
	* 先一次存放end(区间右范围),后一次存放begin(区间左范围),
	* 之后要连续取两次栈中的元素,即左右范围,构成一个区间
	*/

	//先存放数组(未进行排序前的整个数组)区间的右范围:
	STPush(&st, end);

	//后存放数组区间的左范围:
	STPush(&st, begin);

	/*
	* 之后使用while循环,只要栈中还有元素,
	* 就说明还有区间要进行快排,
	* while循环中,先获取完整数组的区间(分两次存放)
	* 对其进行一趟快速排序,分割出左右区间,
	* 再将左右区间放入栈中,然后重新循环,
	* (注意“后进先出”,先放右区间,之后会先取到左区间)
	* 再获取左区间或右区间,进行一趟快速排序,
	* 就这样循环下去,直到栈元素为空为止
	* (和递归过程类似)
	*/

	//如果当前栈中还有元素不为空:
	while (STEmpty(&st) != true)
	{
		//这里要连续取两次栈顶元素,即一个区间的两个范围:

		//因为“后进先出”,所以会先获取“后进的”左范围:
		int left = STTop(&st); //获得栈顶元素
		//获取后进行出栈:
		STPop(&st);
			
		//再获取“先进的”右范围:
		int right = STTop(&st);
		//获取后进行出栈:
		STPop(&st);

		//获得一个区间范围后,进行一趟快速排序:
		int keyi = PartSort1(a, left, right); 
		//存放排好序的key值下标

		/*
		*			  这时的区间:
		* [left,keyi-1]  keyi  [keyi+1, right]
		* 此时左区间:[left,keyi-1]
		* 此时右区间:[keyi+1, right]
		* 之后要在栈中放入快排后分割出的左右区间
		* 往栈中放入左右区间时,要判断该区间左右范围是否合理
		*/

		//先在栈中放入当前右区间(“先进会后出(取)”):
		if (keyi+1 < right)
			//只有当前区间的左范围小于右范围才是有效且有意义的范围
		{
			//和前面一样先放入(右)区间的右范围:
			STPush(&st, right);

			//再在栈中放入(右)区间的左范围:
			STPush(&st, keyi+1);

			//之后要“后取”当前右区间的时候,
			//会先取其左范围,再取其右范围
		}

		//再在栈中放入当前左区间(“后进会先出(取)”):
		if (left < keyi-1)
			//只有当前区间的左范围小于右范围才是有效且有意义的范围
		{
			//和前面一样先放入(左)区间的右范围:
			STPush(&st, keyi-1);

			//再在栈中放入(左)区间的左范围:
			STPush(&st, left);

			//之后要“先取”当前左区间的时候,
			//会先取其左范围,再取其右范围
		}
	}
	  
	//最后销毁栈:
	STDestroy(&st);
}

            

            

---------------------------------------------------------------------------------------------

            

Test.c -- Sort test files

//快速排序(递归版本)方法测试:
void QSTest()
{
	//创建要进行插入排序的数组:
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };

	//调用快速排序(递归版本)进行排序:
	QuickSort(a, 0, (sizeof(a)/sizeof(int))-1);
	//(sizeof(a)/sizeof(int))-1  --  元素个数-1==尾元素下标

	//使用自定义打印函数打印排序后数组:
	printf("使用快速排序(递归版本)后的数组:> ");
	PrintArray(a, sizeof(a) / sizeof(int));
}


//快速排序(非递归版本)方法测试:
void QSNRTest()
{
	//创建要进行插入排序的数组:
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };

	//调用快速排序(非递归版本)进行排序:
	QuickSortNonR(a, 0, (sizeof(a) / sizeof(int)) - 1);
	//(sizeof(a)/sizeof(int))-1  --  元素个数-1==尾元素下标

	//使用自定义打印函数打印排序后数组:
	printf("使用快速排序(非递归版本)后的数组:> ");
	PrintArray(a, sizeof(a) / sizeof(int));
}

int main()
{
	//ISTest();
	//SSTest();
	//BSTest();
	//SlSTest();
	//QSTest();
	QSNRTest();

	return 0;
}

Guess you like

Origin blog.csdn.net/weixin_63176266/article/details/133976242