国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn))

這篇具有很好參考價值的文章主要介紹了【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn))。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

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

相關代碼gitee自取

C語言學習日記: 加油努力 (gitee.com)

?=========================================================================

接上期

【數(shù)據(jù)結構初階】九、排序的講解和實現(xiàn)(直接插入 \ 希爾 \ 直接選擇 \ 堆 \ 冒泡 -- C語言)-CSDN博客

?=========================================================================

? ? ? ? ? ? ? ? ? ? ?

常見排序算法的實現(xiàn)(續(xù)上期)

(詳細解釋在圖片的注釋中,代碼分文件放下一標題處)

? ? ? ? ? ? ? ? ?

三、交換排序

基本思想:

所謂交換,就是根據(jù)序列中兩個記錄鍵值的比較結果交換這兩個記錄在序列中的位置

? ? ? ? ? ? ?

交換排序的特點

鍵值較大的記錄序列的尾部移動鍵值較小的記錄序列的前部移動

? ? ? ? ? ? ? ? ? ? ??

? ? ? ? ? ? ? ? ? ? ??

交換排序 -- 快速排序
? ? ? ? ? ? ? ? ? ? ?

該算法基本思想:
  • 快速排序Hoare(霍爾)于1962年提出的一種二叉樹結構的交換排序方法
    ? ? ? ? ? ? ? ? ?
  • 其基本思想為:
    任取待排序元素序列中的某元素作為基準值key值),
    按照該基準值key值將待排序集合分割成兩個子序列子區(qū)間),
    左子序列左子區(qū)間)中所有元素均小于基準值(key值),
    右子序列右子區(qū)間)中所有元素均大于基準值(key值),
    然后當前左右子序列子區(qū)間再重復上述過程,
    直到所有元素都排列在相應位置上為止
    ? ? ? ? ? ? ? ? ? ?
  • 上述快速排序遞歸實現(xiàn)的主框架,可以發(fā)現(xiàn)其過程與二叉樹前序遍歷規(guī)則非常像,
    寫遞歸框架時可參考二叉樹前序遍歷規(guī)則,
    后續(xù)只需分析如何按照基準值key值來對區(qū)間中數(shù)據(jù)進行劃分的方式即可
    ? ? ? ? ? ? ? ? ? ? ? ? ?
  • 將區(qū)間按照基準值key值劃分為左右兩半部分的常見方式有:
    hoare版本原始版本)快速排序? 、 挖坑法快速排序? 、前后指針版本快速排序

    ? ? ? ? ? ? ? ? ?
快速排序的特性總結:
  • 快速排序整體的綜合性能使用場景都是比較好的,所以才敢叫快速排序
    ? ? ? ? ? ? ? ??

  • 該算法時間復雜度O(N*logN)
    ? ? ? ? ? ? ? ? ? ?

  • 該算法空間復雜度O(logN)
    ? ? ? ? ? ? ? ? ? ? ?

  • 該算法穩(wěn)定性不穩(wěn)定
    ? ? ? ? ? ? ? ?

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

? ? ? ? ? ? ? ? ? ? ? ?

GetMidi內(nèi)部函數(shù) -- 快速排序“三數(shù)取中”優(yōu)化函數(shù)

  • 使用基準值(key值)進行快速排序時,key值將當前數(shù)組劃分出兩個區(qū)間
    可能key值最小值,會排在數(shù)組最左邊,
    那么實際就只會劃分出一個右區(qū)間,此時右區(qū)間新的key值如果又是最小值的話
    那么又只會劃分出一個右區(qū)間,這樣多趟快速排序的效率并不高,
    所以要進行三數(shù)取中操作取出一個不大不小的中間值下標并返回

    ? ? ? ? ? ? ? ??

  • 有了當前區(qū)間的起始元素下標left)和尾部元素下標right),
    可以獲得一個當前區(qū)間的中間元素下標mid),
    比較判斷這三個下標對應值的大小,取出中間值下標并返回
圖示:

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

? ? ? ? ? ? ? ? ? ? ? ? ??

? ? ? ? ? ? ? ? ? ? ? ? ??
---------------------------------------------------------------------------------------------

? ? ? ? ? ? ? ? ? ? ? ?

PartSort1內(nèi)部函數(shù) --? 一趟冒泡排序?qū)崿F(xiàn)函數(shù)(hoare版本

  • 使用三數(shù)取中函數(shù)獲得一個相對而言的中間值下標,中間值left下標值調(diào)換,
    我們默認key值下標left下標,即當前區(qū)間數(shù)組起始最左邊位置下標
    ? ? ? ? ? ? ? ? ? ?
  • 使用while循環(huán),
    較小值較key小值)到數(shù)組左邊較大值較key大值)到數(shù)組右邊,
    內(nèi)嵌第一個while循環(huán)right從右往左找小于key值的元素下標,
    內(nèi)嵌第二個while循環(huán),left從左往右找大于key值的元素下標,
    分別找到比key小和比key大的值的下標后,將兩者的值進行調(diào)換
    ? ? ? ? ? ? ? ? ? ? ? ?
  • while循環(huán)結束時,left == right,
    此時left(或right)下標就會key值在數(shù)組中的真正下標,
    所以要key值此時left值(right值)進行交換
    ? ? ? ? ? ? ? ? ? ? ? ??
  • 最后返回left或right下標即此時key值的下標
圖示:

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

該函數(shù)執(zhí)行邏輯圖:

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

? ? ? ? ? ? ? ? ? ? ? ? ??

? ? ? ? ? ? ? ? ? ? ? ? ??
---------------------------------------------------------------------------------------------

? ? ? ? ? ? ? ? ? ? ? ?

PartSort2內(nèi)部函數(shù) --? 一趟冒泡排序?qū)崿F(xiàn)函數(shù)(挖坑法版本

  • 同樣使用三數(shù)取中函數(shù)獲得一個相對而言的中間值下標,中間值left下標值調(diào)換,
    我們默認key值下標left下標,即當前區(qū)間數(shù)組起始最左邊位置下標,
    這次我們直接獲取key值,而不是其下標keyi
    ? ? ? ? ? ? ? ?
  • 保存key值以后,在數(shù)組起始最左邊位置形成第一個""
    獲取或調(diào)整一個元素值后,將該元素值下標處定義為"")
    ? ? ? ? ? ? ? ? ? ?
  • 之后大體思路和hoare版本類似
    只是在right找到"較小值"后,就直接將其放入上次定義的""中,
    "較小值"下標處形成新的"";
    left找到"較大值"后,也直接將其放入上次定義的""中,
    "較小值"下標處形成新的""
    ? ? ? ? ? ? ? ? ? ? ? ?
  • right left 依次完成"挖坑填坑",
    最終會有一個空的"",""key值真正的下標位置
    所以最后將key值放入""
    ? ? ? ? ? ? ? ? ? ? ? ??
  • 最后返回最終的"坑位"下標,即此時key值的下標
圖示:

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

該函數(shù)執(zhí)行邏輯圖:

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

? ? ? ? ? ? ? ? ? ? ? ? ??

? ? ? ? ? ? ? ? ? ? ? ? ??
---------------------------------------------------------------------------------------------

? ? ? ? ? ? ? ? ? ? ? ?

PartSort3內(nèi)部函數(shù) --? 一趟冒泡排序?qū)崿F(xiàn)函數(shù)(前后指針版本)

  • 使用三數(shù)取中函數(shù)獲得一個相對而言的中間值下標中間值left下標值調(diào)換
    我們默認key值下標left下標,即當前區(qū)間數(shù)組起始最左邊位置下標,
    ? ? ? ? ? ? ? ? ? ?
  • 然后創(chuàng)建兩個指針--prev前指針)和?cur后指針),
    prev一開始指向起始位置,cur一開始指向prev后一位
    ? ? ? ? ? ? ? ? ? ? ? ?
  • 使用while循環(huán)進行“推箱子”:
    cur后指針還小于等于right下標時就繼續(xù)推箱子”,
    cur指針從左往右找較小值”,無論是否找到cur都++
    如果cur指針找到了較小值”,那么就++prev前指針,
    這時prev不等于cur的話,就交換兩指針值
    【把一段大于key的區(qū)間(“較大值區(qū)間),類似推箱子般往右推
    同時將較小值甩到左邊("較小值"區(qū)間
    ? ? ? ? ? ? ? ? ? ? ? ??
  • right出界推完箱子”后,while循環(huán)結束,
    此時prev下標就是key值下標,所以將key值放入該位置
    ? ? ? ? ? ? ? ? ? ??
  • 最后返回key值位置,即此時prev指針的位置
圖示:

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

該函數(shù)執(zhí)行邏輯圖:

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

? ? ? ? ? ? ? ? ? ? ? ? ??

? ? ? ? ? ? ? ? ? ? ? ? ??
---------------------------------------------------------------------------------------------

? ? ? ? ? ? ? ? ? ? ? ?

QuickSort函數(shù) -- 快速排序(遞歸版本)

  • 因為后面會對數(shù)組進行遞歸操作遞歸快速排序),
    所以要先設置遞歸結束條件
    因為后面遞歸時會用到指定的數(shù)組起始尾部元素下標
    遞歸執(zhí)行到后面時,會出現(xiàn)數(shù)組范圍不合理的情況,
    類似左邊元素下標會大于右邊元素下標的區(qū)間[0,0]區(qū)間……
    所以當出現(xiàn)這種情況時就可以返回遞歸結果
    ? ? ? ? ? ? ? ? ? ? ? ??
  • 小區(qū)間優(yōu)化 -- 小區(qū)間不再進行遞歸分割排序,降低遞歸次數(shù)
    為了進一步提高快速排序效率
    遞歸分割過程中,區(qū)間比較小了以后,最好不再進行遞歸分割排序,
    因為滿二叉樹快速排序遞歸過程類似滿二叉樹倒數(shù)三層中會有80%的節(jié)點
    所以當區(qū)間比較小后,這些小區(qū)間會有80%的遞歸操作
    這么多小區(qū)間進行遞歸不劃算,
    所以當數(shù)組區(qū)間遞歸成小區(qū)間(類似只有10個元素)后,
    可以使用直接插入排序代替遞歸進行排序
    只有10個元素的數(shù)組進行直接插入排序效率高而且省去了80%的遞歸操作
    ? ? ? ? ? ? ? ? ? ? ? ??
  • 對元素大于10的數(shù)組大區(qū)間進行快速排序遞歸進行):
    調(diào)用上面寫的三種單趟快速排序函數(shù)的其中一種,并接收返回的key值下標
    此時當前區(qū)間就被分成了左右兩個區(qū)間,
    左右兩個區(qū)間分別進行遞歸操作
    ? ? ? ? ? ? ? ? ? ?
  • 對元素小于等于10的數(shù)組小區(qū)間進行直接插入排序
圖示:

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

該函數(shù)執(zhí)行邏輯圖:

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

該函數(shù)測試(hoare版本):

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

該函數(shù)測試(挖坑法版本):

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

該函數(shù)測試(前后指針版本):

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

? ? ? ? ? ? ? ? ? ? ? ? ??

? ? ? ? ? ? ? ? ? ? ? ? ??
---------------------------------------------------------------------------------------------

? ? ? ? ? ? ? ? ? ? ? ?

QuickSortNonR函數(shù) -- 快速排序(非遞歸版本)

  • 快速排序非遞歸版本思路--借助(“后進先出”)完成:
    先完成一趟快速排序操作,匹配完成key值的排序,
    然后將key值右邊的區(qū)間大于key值區(qū)間下標范圍先放入
    再將key值左邊的區(qū)間小于key值區(qū)間下標范圍放入,
    因為的“后進先出特性會先使用左區(qū)間范圍,再使用右區(qū)間范圍,
    先取出左區(qū)間,對該區(qū)間執(zhí)行一趟快速排序操作,
    然后匹配完成左區(qū)間key值的排序,該key值又分割出兩個當前左右區(qū)間
    同樣在先存放當前右區(qū)間,再存放當前左區(qū)間,
    然后再取出當前左區(qū)間來進行一趟快速排序,重復執(zhí)行此操作……(類似遞歸
    到最后當區(qū)間范圍縮小到無意義范圍[0,0]不合理范圍[2,1],
    不將這類區(qū)間入棧了,繼續(xù)取當前棧中的有效區(qū)間重復上面的操作……
    (該過程不使用遞歸進行操作,但是實際執(zhí)行過程跟遞歸相同
    ? ? ? ? ? ? ? ? ? ? ?
  • 使用數(shù)據(jù)結構中的棧完成非遞歸快排的好處
    使用遞歸進行快速排序的話,遞歸操作操作系統(tǒng)中的空間進行的,
    Linux32位)下的??臻g只有8M,如果遞歸深度太深,
    ??臻g就容易爆,導致棧溢出
    而使用數(shù)據(jù)結構中的棧完成非遞歸快排的話,操作系統(tǒng)中的空間進行的,
    Linux32位)下的堆空間有2G+,大概率不會存在堆溢出的情況
先導入之前寫的棧實現(xiàn):

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

圖示:

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

該函數(shù)測試:

【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn)),CCC全是C,數(shù)據(jù)結構,1024程序員節(jié),算法,深度優(yōu)先,排序算法,c語言

? ? ? ? ?

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

? ? ? ? ? ? ?

對應代碼(續(xù)上期)

Sort.h -- 排序頭文件

//快速排序函數(shù)(遞歸版本):
//第一個參數(shù):接收要排序的數(shù)組首元素地址(a)
//第二個參數(shù):接收該數(shù)組起始位置下標(0)
//第二個參數(shù):接收該數(shù)組尾部位置下標(數(shù)組元素個數(shù)-1)
void QuickSort(int* a, int begin, int end);


//快速排序函數(shù)(非遞歸版本):
//第一個參數(shù):接收要排序的數(shù)組首元素地址(a)
//第二個參數(shù):接收該數(shù)組起始位置下標(0)
//第二個參數(shù):接收該數(shù)組尾部位置下標(數(shù)組元素個數(shù)-1)
void QuickSortNonR(int* a, int begin, int end);

? ? ? ? ? ??文章來源地址http://www.zghlxwxcb.cn/news/detail-715256.html

? ? ? ? ? ??

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

? ? ? ? ? ??

Sort.c -- 排序函數(shù)實現(xiàn)文件

//三數(shù)取中(內(nèi)部函數(shù)):
//第一個參數(shù):接收要排序的數(shù)組首元素地址(a)
//第二個參數(shù):數(shù)組最左邊(起始)元素下標 -- key值
//第三個參數(shù):數(shù)組最右邊(尾部)元素下標
//返回中間值的下標
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就是三數(shù)中的中間值下標
		}
		else if (a[left] > a[right])
			//又有:"左下標值" > "右下標值"
		{
			return left; //left就是三數(shù)中的中間值下標
		}
		else
			//其它情況:
		{
			return right; //right就是三數(shù)中的中間值下標
		}
	}
	else
		//a[left] > a[mid]
		//"左下標值" > "中下標值"
	{
		if (a[mid] > a[right])
			//又有:"中下標值" > "右下標值"
		{
			return mid; //mid就是三數(shù)中的中間值下標
		}
		else if (a[left] < a[right])
			//又有:"左下標值" < "右下標值"
		{
			return left; //left就是三數(shù)中的中間值下標
		}
		else
			//其它情況:
		{
			return right; //right就是三數(shù)中的中間值下標
		}
	}
}



//一趟快速排序(內(nèi)部函數(shù))-- 方法一hoare版本(細節(jié)較多):
//第一個參數(shù):接收要排序的數(shù)組首元素地址(a)
//第二個參數(shù):數(shù)組最左邊(起始)元素下標 -- key值
//第三個參數(shù):數(shù)組最右邊(尾部)元素下標
//返回排序后key值下標
int PartSort1(int* a, int left, int right)
{
	//使用三數(shù)取中函數(shù)獲得一個相對而言的中間值下標:
	int midi = GetMidi(a, left, right);
	//將中間值和數(shù)組起始元素進行交換:
	Swap(&a[left], &a[midi]);

	//定義快速排序的key值下標,
	//默認key值下標為數(shù)組最左邊(起始)下標 -- 0:
	//(通過三數(shù)取中后的left值在排序后會在數(shù)組相對中間位置)
	//(這有助于提高快速排序的效率 -- 因為后面遞歸的操作)
	int keyi = left;

	//使用while循環(huán),
	//將數(shù)組右邊的較小值移至數(shù)組左邊,
	//將數(shù)組左邊的較大值移至數(shù)組右邊:
	while (left < right)
		/*
		* 只要left下標還小于right下標,
		* 說明中間可能還有元素需要被調(diào)整。
		*/
	{
		//先讓right從右往左找小于key值的元素下標:
		/*
		因為是升序,大值應該在右邊,
		所以從右往左找較小值,之后移至數(shù)組左邊
		*/
		while (left < right && a[right] >= a[keyi])
			//left < right,保證right不越界
			//而且right元素還大于等于key值:
		{
			//那就說明還不是較小值,
			//right往左移一位:
			--right;
			//直到調(diào)整到比key小的元素下標為止
		}

		//找到右邊的較小值后,再找左邊的較大值,
		//讓left從左往右找大于key值的元素下標:
		/*
		因為是升序,小值應該在左邊,
		所以從左往右找較大值,之后移至數(shù)組右邊
		*/
		while (left < right && a[left] <= a[keyi])
			//left < right,保證left不越界
			//而且left元素還小于等于key值:
		{
			//那就說明還不是較大值,
			//left往右移一位:
			++left;
			//直到調(diào)整到比key大的元素下標為止
		}

		//分別找到比key小和比key大的值后,
		//將兩者的值進行調(diào)換:
		//(讓較小值到數(shù)組左邊,較大值到數(shù)組右邊)
		Swap(&a[left], &a[right]);
	}

	//key值左邊的較小值和右邊的較大值排好后,
	//while循環(huán)結束時,left == right,
	//此時left(或right)下標就會是key值在數(shù)組中的真正下標,
	//所以要將key值和此時left值(right值)進行交換:
	/*
	* 外層while循環(huán)結束后,是right先往左邊走,left再往右邊走,
	* 較大值都在right后 且 left位置一定會在小于key值的元素上,
	* 所以此時可以將key值和left值(right值)進行交換,找到key真正位置
	*/
	Swap(&a[keyi], &a[left]);

	return left;
	//返回left(或right)下標,即此時key值的下標
	//此時key的左邊(較小值)和右邊(較大值)還是無序的
}
//單趟快速排序的時間復雜度:O(N)



//一趟快速排序(內(nèi)部函數(shù))-- 方法二挖坑法:
//第一個參數(shù):接收要排序的數(shù)組首元素地址(a)
//第二個參數(shù):數(shù)組最左邊(起始)元素下標 -- key值
//第三個參數(shù):數(shù)組最右邊(尾部)元素下標
//返回排序后key值下標
int PartSort2(int* a, int left, int right)
{
	//使用三數(shù)取中函數(shù)獲得一個相對而言的中間值下標:
	int midi = GetMidi(a, left, right);
	//將中間值和數(shù)組起始元素進行交換:
	Swap(&a[left], &a[midi]);

	//此時left下標元素就是中間值:
	int key = a[left]; //直接獲取key值(存放中間值)
	
	//保存key值以后,在數(shù)組起始(最左邊)位置形成第一個"坑":
	int hole = left; //只留"坑"的下標(左邊的"坑")

	//使用while循環(huán)進行快排過程:
	while (left < right)
		/*
		* 只要left下標還小于right下標,
		* 說明中間可能還有元素需要被調(diào)整。
		*/
	{
		//讓right從右往左走,找比key小的元素下標,
		while (left < right && a[right] >= key)
			//left < right,保證right不越界
			//而且right元素還大于等于key值:
		{
			//那就說明還不是較小值,
			//right往左移一位:
			--right;
			//直到調(diào)整到比key小的元素下標為止
		}

		//找到對應下標后填到左邊的"坑",
		a[hole] = a[right];
		//在此時right下標處形成新的"坑":
		hole = right; //右邊的"坑"(下標)


		//讓left從左往右走,找比key大的元素下標,
		while (left < right && a[left] <= key)
			//left < right,保證left不越界
			//而且left元素還小于等于key值:
		{
			//那就說明還不是較大值,
			//left往右移一位:
			++left;
			//直到調(diào)整到比key大的元素下標為止
		}

		//找到對應下標后填到右邊的"坑",
		a[hole] = a[left];
		//在此時left下標處形成新的"坑":
		hole = left; //再次形成左邊的"坑"(下標)
	}

	//left和right在“坑”下標處相遇后,while循環(huán)結束,
	//此時“坑”下標就是key值對應下標,
	//所以最后將key值放到“坑”下標處:
	a[hole] = key;

	//返回當前key值“坑”下標:
	return hole;
}



//一趟快速排序(內(nèi)部函數(shù))-- 方法三前后指針版本:
//第一個參數(shù):接收要排序的數(shù)組首元素地址(a)
//第二個參數(shù):數(shù)組最左邊(起始)元素下標 -- key值
//第三個參數(shù):數(shù)組最右邊(尾部)元素下標
//返回排序后key值下標
int PartSort3(int* a, int left, int right)
{
/*
* 思路(類似推箱子):
* 創(chuàng)建兩個指針--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大的一組值的前面(數(shù)組左邊)
* 
* 【本質(zhì):把一段大于key的區(qū)間(較大值區(qū)間),類似推箱子般往右推,
*   同時將較小值甩到左邊(較小值區(qū)間)去】
*/
	//使用三數(shù)取中函數(shù)獲得一個相對而言的中間值下標:
	int midi = GetMidi(a, left, right);
	//將中間值和數(shù)組起始元素進行交換:
	Swap(&a[left], &a[midi]);

	//定義key值(“中間值”)下標:
	int keyi = left; //默認當前數(shù)組起始位置下標

	//先定義prev指針:
	int prev = left; //從當前數(shù)組起始位置開始

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

	//使用while循環(huán)循環(huán)進行"推箱子":
	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的區(qū)間(較大值區(qū)間),類似推箱子般往右推,
	* 同時將較小值甩到左邊(較小值區(qū)間)去
	*/

	//right出界“推完箱子”后,while循環(huán)結束,
	//此時prev下標就是key值下標,
	//所以將key值放入該位置:
	Swap(&a[prev], &a[keyi]);
	
	//最后返回key值位置 -- prev :
	return prev;
}


//快速排序函數(shù)(遞歸版本):
//第一個參數(shù):接收要排序的數(shù)組首元素地址(a)
//第二個參數(shù):接收該數(shù)組起始位置下標(0)
//第二個參數(shù):接收該數(shù)組尾部位置下標(數(shù)組元素個數(shù)-1)
void QuickSort(int* a, int begin, int end)
{
	/*
	* 因為后面會對數(shù)組進行遞歸操作(遞歸快速排序),
	* 所以要先設置遞歸結束條件:
	* 因為后面遞歸時會用到指定的數(shù)組起始和尾部元素下標,
	* 在遞歸執(zhí)行到后面時,會出現(xiàn)數(shù)組范圍不合理的情況,
	* 類似左邊元素下標會大于右邊元素下標或[0,0]區(qū)間……
	* 所以當出現(xiàn)這種情況時就可以返回遞歸結果了:
	*/
	if (begin >= end)
		//數(shù)組起始位置下標 >= 數(shù)組尾部位置下標:
	{
		//是不合理的數(shù)組范圍:
		return; //直接返回上層遞歸
	}

	/*
	* 小區(qū)間優(yōu)化 -- 小區(qū)間不再進行遞歸分割排序,降低遞歸次數(shù):
	* 為了進一步提高快速排序效率,
	* 在遞歸分割過程中,區(qū)間比較小了以后,最好不再進行遞歸分割排序,
	* 因為滿二叉樹(快速排序遞歸過程類似滿二叉樹)倒數(shù)三層中會有80%的節(jié)點
	* 所以當區(qū)間比較小后,這些小區(qū)間會有80%的遞歸操作,
	* 對這么多小區(qū)間進行遞歸不劃算,
	* 所以當數(shù)組區(qū)間遞歸成小區(qū)間(類似只有10個元素)后,
	* 可以使用直接插入排序代替遞歸進行排序,
	* 對只有10個元素的數(shù)組進行直接插入排序效率高而且省去了80%的遞歸操作
	*/

	//對元素大于10的數(shù)組(大區(qū)間)進行快速排序(遞歸進行):
	//“滿二叉樹倒數(shù)三層前的節(jié)點”(“20%的節(jié)點”)
	if ((end - begin + 1) > 10)
		//區(qū)間尾下標 - 區(qū)間首下標 + 1 = 區(qū)間元素個數(shù)
	{
		//調(diào)用單趟快速排序函數(shù)并接收返回的key值下標:
		//int keyi = PartSort1(a, begin, end); //方法一 -- hoare版本
		//int keyi = PartSort2(a, begin, end); //方法二 -- 挖坑法
		int keyi = PartSort3(a, begin, end); //方法三 -- 前后指針版本

		/*
		* 調(diào)用函數(shù)進行一趟快速排序后,此時數(shù)組下標可表示為:
		* [begin, keyi-1] keyi [keyi+1, end]
		* 經(jīng)過一趟排序,此時key的左邊(較小值)和右邊(較大值)還是無序的
		* key的左邊(較小值)下標區(qū)間:[begin, keyi-1]
		* key的右邊(較大值)下標區(qū)間:[keyi+1, end]
		* 所以接下來要就這兩個區(qū)間進行排序:
		*/

		/*
		* 可以把下標區(qū)間: [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的數(shù)組(小區(qū)間)進行直接插入排序:
		//“滿二叉樹的倒數(shù)三層節(jié)點”(“80%的節(jié)點”)
	{
		//調(diào)用直接插入排序?qū)f歸后的“小區(qū)間”進行排序:
		InsertSort(a + begin, end - begin + 1);
		//被快速排序的遞歸操作分割后的“小區(qū)間”首元素下標:a + begin
		//被快速排序的遞歸操作分割后的“小區(qū)間”元素個數(shù):end - begin + 1
	}
}
/*
* 快速排序時間復雜度(三數(shù)取中前):
* 最好的情況(key值總排在靠中間的位置) -- O(N*logN)
* 最壞的情況(數(shù)組有序,key值總排在靠起始位置) -- O(N^2)
* 
* 快速排序時間復雜度(三數(shù)取中后):
* 最好的情況(數(shù)組有序,key值總排在靠中間位置) -- O(N*logN)
* 最壞的情況 -- 在三數(shù)取中操作后幾乎不會有最壞的情況
*/



//快速排序函數(shù)(非遞歸版本):
//第一個參數(shù):接收要排序的數(shù)組首元素地址(a)
//第二個參數(shù):接收該數(shù)組起始位置下標(0)
//第二個參數(shù):接收該數(shù)組尾部位置下標(數(shù)組元素個數(shù)-1)
void QuickSortNonR(int* a, int begin, int end)
{
	/*
	*		將快速排序改成非遞歸版本思路--借助棧完成:
	* 先完成一趟快速排序操作,匹配完成key值的排序,
	* 然后將key值右邊的區(qū)間(大于key值區(qū)間)下標范圍先放入棧中,
	* 再將key值左邊的區(qū)間(小于key值區(qū)間)下標范圍放入棧中,
	* 因為棧的“后進先出”特性,會先使用左區(qū)間范圍,再使用右區(qū)間范圍,
	* 在棧中先取出左區(qū)間后,對該區(qū)間執(zhí)行一趟快速排序操作,
	* 然后匹配完成左區(qū)間key值的排序,該key值又分割出兩個當前左右區(qū)間,
	* 同樣在棧中先存放當前右區(qū)間,再存放當前左區(qū)間,
	* 然后再取出當前左區(qū)間來進行一趟快速排序,重復執(zhí)行此操作……(類似遞歸)
	* 到最后當區(qū)間范圍縮小到無意義范圍[0,0]或不合理范圍[2,1]時,
	* 就不將這類區(qū)間入棧了,繼續(xù)取當前棧中的有效區(qū)間重復上面的操作……
	* (該過程不使用遞歸進行操作,但是實際執(zhí)行過程跟遞歸相同)
	*/

	/*
	*		使用數(shù)據(jù)結構中的棧完成非遞歸快排的好處:
	* 使用遞歸進行快速排序的話,遞歸操作是在操作系統(tǒng)中的棧空間進行的,
	* 在Linux(32位)下的??臻g只有8M,如果遞歸深度太深,
	* ??臻g就容易爆,導致棧溢出
	* 
	* 而使用數(shù)據(jù)結構中的棧完成非遞歸快排的話,棧是在操作系統(tǒng)中的堆空間進行的,
	* 在Linux(32位)下的堆空間有2G+,大概率不會存在堆溢出的情況
	*/

	//先創(chuàng)建一個棧類型變量:
	ST st;
	
	//對其進行初始化:
	STInit(&st);

	/*
	* 我們要在棧中存放一個區(qū)間,即兩個整數(shù),
	* 可以將棧中存放的數(shù)據(jù)改為區(qū)間類型:[begin, end],
	* 而我們之前寫的棧的每個元素是存放一個整數(shù)的,
	* 所以要將一個區(qū)間分兩次放入棧中,
	* 先一次存放end(區(qū)間右范圍),后一次存放begin(區(qū)間左范圍),
	* 之后要連續(xù)取兩次棧中的元素,即左右范圍,構成一個區(qū)間
	*/

	//先存放數(shù)組(未進行排序前的整個數(shù)組)區(qū)間的右范圍:
	STPush(&st, end);

	//后存放數(shù)組區(qū)間的左范圍:
	STPush(&st, begin);

	/*
	* 之后使用while循環(huán),只要棧中還有元素,
	* 就說明還有區(qū)間要進行快排,
	* while循環(huán)中,先獲取完整數(shù)組的區(qū)間(分兩次存放)
	* 對其進行一趟快速排序,分割出左右區(qū)間,
	* 再將左右區(qū)間放入棧中,然后重新循環(huán),
	* (注意“后進先出”,先放右區(qū)間,之后會先取到左區(qū)間)
	* 再獲取左區(qū)間或右區(qū)間,進行一趟快速排序,
	* 就這樣循環(huán)下去,直到棧元素為空為止
	* (和遞歸過程類似)
	*/

	//如果當前棧中還有元素不為空:
	while (STEmpty(&st) != true)
	{
		//這里要連續(xù)取兩次棧頂元素,即一個區(qū)間的兩個范圍:

		//因為“后進先出”,所以會先獲取“后進的”左范圍:
		int left = STTop(&st); //獲得棧頂元素
		//獲取后進行出棧:
		STPop(&st);
			
		//再獲取“先進的”右范圍:
		int right = STTop(&st);
		//獲取后進行出棧:
		STPop(&st);

		//獲得一個區(qū)間范圍后,進行一趟快速排序:
		int keyi = PartSort1(a, left, right); 
		//存放排好序的key值下標

		/*
		*			  這時的區(qū)間:
		* [left,keyi-1]  keyi  [keyi+1, right]
		* 此時左區(qū)間:[left,keyi-1]
		* 此時右區(qū)間:[keyi+1, right]
		* 之后要在棧中放入快排后分割出的左右區(qū)間
		* 往棧中放入左右區(qū)間時,要判斷該區(qū)間左右范圍是否合理
		*/

		//先在棧中放入當前右區(qū)間(“先進會后出(取)”):
		if (keyi+1 < right)
			//只有當前區(qū)間的左范圍小于右范圍才是有效且有意義的范圍
		{
			//和前面一樣先放入(右)區(qū)間的右范圍:
			STPush(&st, right);

			//再在棧中放入(右)區(qū)間的左范圍:
			STPush(&st, keyi+1);

			//之后要“后取”當前右區(qū)間的時候,
			//會先取其左范圍,再取其右范圍
		}

		//再在棧中放入當前左區(qū)間(“后進會先出(取)”):
		if (left < keyi-1)
			//只有當前區(qū)間的左范圍小于右范圍才是有效且有意義的范圍
		{
			//和前面一樣先放入(左)區(qū)間的右范圍:
			STPush(&st, keyi-1);

			//再在棧中放入(左)區(qū)間的左范圍:
			STPush(&st, left);

			//之后要“先取”當前左區(qū)間的時候,
			//會先取其左范圍,再取其右范圍
		}
	}
	  
	//最后銷毀棧:
	STDestroy(&st);
}

? ? ? ? ? ??

? ? ? ? ? ??

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

? ? ? ? ? ??

Test.c -- 排序測試文件

//快速排序(遞歸版本)方法測試:
void QSTest()
{
	//創(chuàng)建要進行插入排序的數(shù)組:
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };

	//調(diào)用快速排序(遞歸版本)進行排序:
	QuickSort(a, 0, (sizeof(a)/sizeof(int))-1);
	//(sizeof(a)/sizeof(int))-1  --  元素個數(shù)-1==尾元素下標

	//使用自定義打印函數(shù)打印排序后數(shù)組:
	printf("使用快速排序(遞歸版本)后的數(shù)組:> ");
	PrintArray(a, sizeof(a) / sizeof(int));
}


//快速排序(非遞歸版本)方法測試:
void QSNRTest()
{
	//創(chuàng)建要進行插入排序的數(shù)組:
	int a[] = { 9,1,2,5,7,4,8,6,3,5 };

	//調(diào)用快速排序(非遞歸版本)進行排序:
	QuickSortNonR(a, 0, (sizeof(a) / sizeof(int)) - 1);
	//(sizeof(a)/sizeof(int))-1  --  元素個數(shù)-1==尾元素下標

	//使用自定義打印函數(shù)打印排序后數(shù)組:
	printf("使用快速排序(非遞歸版本)后的數(shù)組:> ");
	PrintArray(a, sizeof(a) / sizeof(int));
}

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

	return 0;
}

到了這里,關于【數(shù)據(jù)結構初階】十、快速排序(比較排序)講解和實現(xiàn)(三種遞歸快排版本 + 非遞歸快排版本 -- C語言實現(xiàn))的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 數(shù)據(jù)結構初階--排序2

    數(shù)據(jù)結構初階--排序2

    本篇文章將繼續(xù)介紹快排,歸并等排序算法以及其變式。 快速排序是Hoare于1962年提出的一種 二叉樹結構 的交換排序方法。 其基本思想為:任取待排序元素序列中的某元素作為 基準值 ,按照該排序碼將待排序集合分割成兩子序列,左子序列中所有元素均小于基準值,右子序

    2024年02月16日
    瀏覽(24)
  • 【數(shù)據(jù)結構初階】八大排序(三)——歸并排序&&計數(shù)排序

    【數(shù)據(jù)結構初階】八大排序(三)——歸并排序&&計數(shù)排序

    大家好我是沐曦希?? 往期博客:【數(shù)據(jù)結構初階】八大排序(一)——希爾排序堆排序直接插入排序直接選擇排序 【數(shù)據(jù)結構初階】八大排序(二)——快速排序冒泡排序 歸并排序(MERGE-SORT)是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法(Divide and Conquer)的一

    2024年02月03日
    瀏覽(89)
  • 數(shù)據(jù)結構初階之排序

    數(shù)據(jù)結構初階之排序

    個人主頁:點我進入主頁 專欄分類:C語言初階? ? ??C語言程序設計————KTV? ? ? ?C語言小游戲? ? ?C語言進階 C語言刷題? ? ? ?數(shù)據(jù)結構初階? ??Linux 歡迎大家點贊,評論,收藏。 一起努力,共赴大廠。 目錄 一.前言 二.選擇排序 2.1選擇排序思想 2.2代碼實現(xiàn) 三.快速

    2024年01月18日
    瀏覽(29)
  • 【數(shù)據(jù)結構初階】一. 復雜度講解

    【數(shù)據(jù)結構初階】一. 復雜度講解

    ========================================================================= 相關代碼gitee自取 : C語言學習日記: 加油努力 (gitee.com) ?========================================================================= 接上期 : 學C的第三十四天【程序環(huán)境和預處理】_高高的胖子的博客-CSDN博客 ?===============================

    2024年02月10日
    瀏覽(25)
  • 數(shù)據(jù)結構初階之插入排序與希爾排序詳解

    數(shù)據(jù)結構初階之插入排序與希爾排序詳解

    個人主頁:點我進入主頁 專欄分類:C語言初階? ? ??C語言程序設計————KTV? ? ? ?C語言小游戲? ? ?C語言進階 C語言刷題? ? ? ?數(shù)據(jù)結構初階? ??Linux 歡迎大家點贊,評論,收藏。 一起努力,共赴大廠。 目錄 一.前言 二.插入排序 2.1插入排序的思想 2.2代碼實現(xiàn) 三.希

    2024年02月02日
    瀏覽(19)
  • 『初階數(shù)據(jù)結構 ? C語言』⑥ - 插入排序&希爾排序

    『初階數(shù)據(jù)結構 ? C語言』⑥ - 插入排序&希爾排序

    學習目標 寫在前面 1.插入排序 2.插入排序?qū)崙?zhàn)? 3.插入排序的實現(xiàn)? 4.插入排序的效率 5.平均情況 6.希爾排序 7.希爾排序的實現(xiàn) 8.希爾排序的效率 9.總結 ? 之前我們衡量一個算法的效率時,都是著眼于它在最壞情況下需要多少步。原因很簡單,連最壞的情況都做足準備了,其

    2024年02月15日
    瀏覽(25)
  • 『初階數(shù)據(jù)結構 ? C語言』④ - 冒泡排序

    『初階數(shù)據(jù)結構 ? C語言』④ - 冒泡排序

    ? 本文內(nèi)容借鑒一本我非常喜歡的書——《數(shù)據(jù)結構與算法圖解》。學習之余,我決定把這本書精彩的部分摘錄出來與大家分享。 ?? ? 本章內(nèi)容 寫在前面 1.冒泡排序 2.冒泡排序?qū)崙?zhàn) 3.冒泡排序的實現(xiàn) 4.冒泡排序的效率 5.二次問題 6.線性解決 7.總結 ? ? 大 O記法能客觀地衡量

    2024年02月16日
    瀏覽(19)
  • 【初階數(shù)據(jù)結構】——堆排序和TopK問題

    【初階數(shù)據(jù)結構】——堆排序和TopK問題

    ?========================================================================= 個人主頁 代碼倉庫 C語言專欄 初階數(shù)據(jù)結構專欄 Linux專欄 ?========================================================================= 接上篇二叉樹和堆的引入 =========================================================================? 目錄 前言 建堆 插

    2024年02月07日
    瀏覽(28)
  • 『初階數(shù)據(jù)結構 ? C語言』⑤ - 選擇排序

    『初階數(shù)據(jù)結構 ? C語言』⑤ - 選擇排序

    本文內(nèi)容借鑒一本我非常喜歡的書——《數(shù)據(jù)結構與算法圖解》。學習之余,我決定把這本書精彩的部分摘錄出來與大家分享。 ??? 目錄 寫在前面 1.選擇排序 2.選擇排序?qū)崙?zhàn) 3.選擇排序的實現(xiàn) 4.選擇排序的效率 5.忽略常數(shù) 6.大O的作用 7.總結 ? ? 大 O 是一種能夠比較算法效

    2024年02月14日
    瀏覽(80)
  • 【數(shù)據(jù)結構初階】八大排序算法+時空復雜度

    【數(shù)據(jù)結構初階】八大排序算法+時空復雜度

    學會控制自己是人生的必修課 1.直接插入排序思想: 假設現(xiàn)在已經(jīng)有一個有序序列,如果有一個數(shù)字插入到這段序列的末尾,我們會選擇拿這個數(shù)和它前面的每個數(shù)字都比較一遍,如果前面的數(shù)字比他大,那我們就讓前面的數(shù)字賦值到這個被插入的數(shù)字位置,依次與前面的數(shù)

    2024年02月01日
    瀏覽(32)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包