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

八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

這篇具有很好參考價(jià)值的文章主要介紹了八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

排序概念

排序:所謂排序,就是使一串記錄,按照其中的某個(gè)或某些關(guān)鍵字的大小,遞增或遞減的排列起來(lái)的操作。

穩(wěn)定性:假定在待排序的記錄序列中,存在多個(gè)具有相同的關(guān)鍵字的記錄,若經(jīng)過(guò)排序,這些記錄的相對(duì)次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,則稱(chēng)這種排序算法是穩(wěn)定的;否則稱(chēng)為不穩(wěn)定的。

八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

內(nèi)部排序:數(shù)據(jù)元素全部放在內(nèi)存中的排序。

外部排序:數(shù)據(jù)元素太多不能同時(shí)放在內(nèi)存中,根據(jù)排序過(guò)程的要求不能在內(nèi)外存之間移動(dòng)數(shù)據(jù)的排序。

注:這部分主要是內(nèi)部排序。排序講解都以升序?yàn)槔?/p>

常見(jiàn)的排序算法

八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

常見(jiàn)排序算法的實(shí)現(xiàn)

直接插入排序

(點(diǎn)擊此處跳轉(zhuǎn),更加詳細(xì)的講解過(guò)程,干貨滿(mǎn)滿(mǎn)?。?br> 動(dòng)圖展示:八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序
插入排序,又叫直接插入排序。實(shí)際中,我們玩撲克牌的時(shí)候,就用了插入排序的思想。

基本思想
?在待排序的元素中,假設(shè)前n-1個(gè)元素已有序,現(xiàn)將第n個(gè)元素插入到前面已經(jīng)排好的序列中,使得前n個(gè)元素有序。按照此法對(duì)所有元素進(jìn)行插入,直到整個(gè)序列有序。

但我們并不能確定待排元素中究竟哪一部分是有序的,所以我們一開(kāi)始只能認(rèn)為第一個(gè)元素是有序的,依次將其后面的元素插入到這個(gè)有序序列中來(lái),直到整個(gè)序列有序?yàn)橹埂?br> ?
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

代碼示例:

//插入排序
void InsertSort(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n - 1; i++)
	{
		int end = i;//記錄有序序列的最后一個(gè)元素的下標(biāo)
		int tmp = a[end + 1];//待插入的元素
		while (end >= 0)
		{
			if (tmp < a[end])//還需繼續(xù)比較
			{
				a[end + 1] = a[end];
				end--;
			}
			else//找到應(yīng)插入的位置
			{
				break;
			}
		}
		a[end + 1] = tmp;
		//代碼執(zhí)行到此位置有兩種情況:
		//1.待插入元素找到應(yīng)插入位置(break跳出循環(huán)到此)。
		//2.待插入元素比當(dāng)前有序序列中的所有元素都小(while循環(huán)結(jié)束后到此)。
	}
}

直接插入排序的特性總結(jié)

1>元素集合越接近有序,直接插入排序算法的時(shí)間效率越高
2>時(shí)間復(fù)雜度:O(N^2)
3>空間復(fù)雜度:O(1),它是一種穩(wěn)定的排序算法
4>穩(wěn)定性:穩(wěn)定

希爾排序

(點(diǎn)擊此處跳轉(zhuǎn),更加詳細(xì)的講解過(guò)程,干貨滿(mǎn)滿(mǎn)?。?br> 動(dòng)圖展示:
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

希爾排序法又稱(chēng)縮小增量法。

基本思想

先選定一個(gè)整數(shù)gap,把待排序文件中所有記錄分成gap個(gè)組,所有距離為gap的記錄分在同一組內(nèi),并對(duì)每一組內(nèi)的元素進(jìn)行排序。

然后將gap逐漸減小重復(fù)上述分組和排序的工作。

當(dāng)?shù)竭_(dá)gap=1時(shí),所有元素在統(tǒng)一組內(nèi)排好序。

靜態(tài)圖展示:
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

該題中,前兩趟就是希爾排序的預(yù)排序,最后一趟就是希爾排序的直接插入排序。

代碼示例:

//希爾排序
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 2;//gap折半
		int i = 0;
		//進(jìn)行一趟排序
		for (i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

希爾排序的特性總結(jié)

1>希爾排序是對(duì)直接插入排序的優(yōu)化。
2>當(dāng)gap > 1時(shí)都是預(yù)排序,目的是讓數(shù)組更接近于有序。當(dāng)gap ==
1時(shí),數(shù)組已經(jīng)接近有序的了,這樣就會(huì)很快。這樣整體而言,可以達(dá)到優(yōu)化的效果。
3>希爾排序的時(shí)間復(fù)雜度不好計(jì)算,因?yàn)間ap的取值方法很多,導(dǎo)致很難去計(jì)算,這里不深究。
4>時(shí)間復(fù)雜度O(N^1.5)
5>空間復(fù)雜度O(1)
6>穩(wěn)定性:不穩(wěn)定。

選擇排序

(點(diǎn)擊此處跳轉(zhuǎn),更加詳細(xì)的講解過(guò)程,干貨滿(mǎn)滿(mǎn))
動(dòng)圖展示:
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

基本思想

第一次從待排序的數(shù)據(jù)元素中選出最小(或最大)的一個(gè)元素,存放在序列的起始(末尾)位置,

然后選出次小(或次大)的一個(gè)元素,存放在最大(最小)元素的下一個(gè)位置,

重復(fù)這樣的步驟直到全部待排序的數(shù)據(jù)元素排完 。

代碼示例1:

//選擇排序(一次選一個(gè)數(shù))
void SelectSort(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)//i代表參與該趟選擇排序的第一個(gè)元素的下標(biāo)
	{
		int start = i;
		int min = start;//記錄最小元素的下標(biāo)
		while (start < n)
		{
			if (a[start] < a[min])
				min = start;//最小值的下標(biāo)更新
			start++;
		}
		Swap(&a[i], &a[min]);//最小值與參與該趟選擇排序的第一個(gè)元素交換位置
	}
}

代碼示例2:
實(shí)際上,我們可以一趟選出兩個(gè)值,一個(gè)最大值一個(gè)最小值,然后將其放在序列開(kāi)頭和末尾,這樣可以使選擇排序的效率快一倍。

//選擇排序(一次選兩個(gè)數(shù))
void SelectSort(int* a, int n)
{
	int left = 0;//記錄參與該趟選擇排序的第一個(gè)元素的下標(biāo)
	int right = n - 1;//記錄參與該趟選擇排序的最后一個(gè)元素的下標(biāo)
	while (left < right)
	{
		int minIndex = left;//記錄最小元素的下標(biāo)
		int maxIndex = left;//記錄最大元素的下標(biāo)
		int i = 0;
		//找出最大值及最小值的下標(biāo)
		for (i = left; i <= right; i++)
		{
			if (a[i] < a[minIndex])
				minIndex = i;
			if (a[i]>a[maxIndex])
				maxIndex = i;
		}
		//將最大值和最小值放在序列開(kāi)頭和末尾
		Swap(&a[minIndex], &a[left]);
		if (left == maxIndex)
		{
			maxIndex = minIndex;//防止最大值位于序列開(kāi)頭,被最小值交換
		}
		Swap(&a[maxIndex], &a[right]);
		left++;
		right--;
	}
}

注意

在進(jìn)行最小值和最大值同時(shí)交換時(shí)也會(huì)出現(xiàn)一個(gè)問(wèn)題,

如果最大值在起始位置的時(shí)候,交換了最小值之后,最大值就被交換到了min的位置,

如果繼續(xù)交換max,就會(huì)將最小值交換到末尾位置。

所以,在每次交換了最小值之后應(yīng)該判斷一下最大值是否在起始位置,如果在需要將max賦值為min。

直接選擇排序的特性總結(jié)

1>直接選擇排序思考非常好理解,但是效率不是很好(不論數(shù)組是否有序都會(huì)執(zhí)行原步驟)。實(shí)際中很少使用
2>時(shí)間復(fù)雜度:O(N^2)
3>空間復(fù)雜度:O(1)
4>穩(wěn)定性:不穩(wěn)定

堆排序

(點(diǎn)擊此處跳轉(zhuǎn),更加詳細(xì)的講解過(guò)程,干貨滿(mǎn)滿(mǎn))
動(dòng)圖展示:
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

堆排序即利用堆的思想來(lái)進(jìn)行排序,總共分為兩個(gè)步驟

  1. 建堆

    升序:建大堆
    降序:建小堆

  2. 利用堆刪除思想來(lái)進(jìn)行排序
    建堆和堆刪除中都用到了向下調(diào)整,因此掌握了向下調(diào)整,就可以完成堆排序

    這里以升序?yàn)槔?/p>

    1.首先應(yīng)該建一個(gè)大堆,不能直接使用堆來(lái)實(shí)現(xiàn)??梢詫⑿枰判虻臄?shù)組看作是一個(gè)堆,但需要將數(shù)組結(jié)構(gòu)變成堆。

    2.我們可以從堆從下往上的第二行最右邊開(kāi)始依次向下調(diào)整直到調(diào)整到堆頂,這樣就可以將數(shù)組調(diào)整成一個(gè)堆,且如果建立的是大堆,堆頂元素為最大值。

    3.然后按照堆刪的思想將堆頂和堆底的數(shù)據(jù)交換,但不同的是這里不刪除最后一個(gè)元素

    4.這樣最大元素就在最后一個(gè)位置,然后從堆頂向下調(diào)整到倒數(shù)第二個(gè)元素,這樣次大的元素就在堆頂,重復(fù)上述步驟直到只剩堆頂時(shí)停止。

代碼示例:

//堆排序
void HeapSort(int* a, int n)
{
	//排升序,建大堆
	//從第一個(gè)非葉子結(jié)點(diǎn)開(kāi)始向下調(diào)整,一直到根
	int i = 0;
	for (i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	int end = n - 1;//記錄堆的最后一個(gè)數(shù)據(jù)的下標(biāo)
	while (end)
	{
		Swap(&a[0], &a[end]);//將堆頂?shù)臄?shù)據(jù)和堆的最后一個(gè)數(shù)據(jù)交換
		AdjustDown(a, end, 0);//對(duì)根進(jìn)行一次向下調(diào)整
		end--;//堆的最后一個(gè)數(shù)據(jù)的下標(biāo)減一
	}
}

直接選擇排序的特性總結(jié):

1>堆排序使用堆來(lái)選數(shù),效率就高了很多。
2>時(shí)間復(fù)雜度:O(N*logN)
3>空間復(fù)雜度:O(1)
4>穩(wěn)定性:不穩(wěn)定

冒泡排序

(點(diǎn)擊此處跳轉(zhuǎn),更加詳細(xì)的講解過(guò)程,干貨滿(mǎn)滿(mǎn))
動(dòng)圖展示:
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

冒泡排序應(yīng)該是我們最熟悉的排序了,在C語(yǔ)言階段我們就學(xué)習(xí)了冒泡排序。

他的思想也非常簡(jiǎn)單:

兩兩元素相比,前一個(gè)比后一個(gè)大就交換,直到將最大的元素交換到末尾位置。這是第一趟

一共進(jìn)行n-1趟這樣的交換將可以把所有的元素排好。

( n-1趟是因?yàn)橹皇蓚€(gè)元素時(shí)只需要一趟就可以完成)

代碼示例:

//冒泡排序
void BubbleSort(int* a, int n)
{
	int end = 0;
	for (end = n - 1; end >= 0; end--)
	{
		int exchange = 0;//記錄該趟冒泡排序是否進(jìn)行過(guò)交換
		int i = 0;
		for (i = 0; i < end; i++)
		{
			if (a[i]>a[i + 1])
			{
				Swap(&a[i], &a[i + 1]);
				exchange = 1;
			}
		}
		if (exchange == 0)//該趟冒泡排序沒(méi)有進(jìn)行過(guò)交換,已有序
			break;
	}
}

冒泡排序的特性總結(jié)

1>冒泡排序是一種非常容易理解的排序
2>時(shí)間復(fù)雜度:O(N^2)
3>空間復(fù)雜度:O(1)
4>穩(wěn)定性:穩(wěn)定

快速排序

(點(diǎn)擊此處跳轉(zhuǎn),更加詳細(xì)的講解過(guò)程,干貨滿(mǎn)滿(mǎn))
這里是排序算法的重點(diǎn)了,非常重要!

快速排序是Hoare于1962年提出的一種二叉樹(shù)結(jié)構(gòu)的交換排序方法,

其基本思想為

任取待排序元素序列中的某元素作為基準(zhǔn)值,按照該排序碼將待排序集合分割成兩子序列,左子序列中所有元素均小于基準(zhǔn)值,右子序列中所有元素均大于基準(zhǔn)值,然后最左右子序列重復(fù)該過(guò)程,直到所有元素都排列在相應(yīng)位置上為止。

遞歸實(shí)現(xiàn)

Hoare版本

動(dòng)圖展示:
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

具體思路是

1.選定一個(gè)基準(zhǔn)值,最好選定最左邊或者最右邊,選中間會(huì)給自己找麻煩。
2.確定兩個(gè)指針left 和right 分別從左邊和右邊向中間遍歷數(shù)組。
3.如果選最右邊為基準(zhǔn)值,那么left指針先走,如果遇到大于基準(zhǔn)值的數(shù)就停下來(lái)。
4.然后右邊的指針再走,遇到小于基準(zhǔn)值的數(shù)就停下來(lái)。
5.交換left和right指針對(duì)應(yīng)位置的值。
6.重復(fù)以上步驟,直到left = right ,最后將基準(zhǔn)值與left(right)位置的值交換。

這樣基準(zhǔn)值左邊的所有數(shù)都比他小,而他右邊的數(shù)都比他大,從而他所在的位置就是排序后的正確位置。

之后再遞歸排以基準(zhǔn)值為界限的左右兩個(gè)區(qū)間中的數(shù),當(dāng)區(qū)間中沒(méi)有元素時(shí),排序完成。

代碼展示:

//快速排序(Hoare版本)
void QuickSort1(int* a, int begin, int end)
{
	if (begin >= end)//當(dāng)只有一個(gè)數(shù)據(jù)或是序列不存在時(shí),不需要進(jìn)行操作
		return;
		
	int left = begin;//L
	int right = end;//R
	int keyi = left;//key的下標(biāo)
	while (left < right)
	{
		//right先走,找小
		while (left < right&&a[right] >= a[keyi])
		{
			right--;
		}
		//left后走,找大
		while (left < right&&a[left] <= a[keyi])
		{
			left++;
		}
		if (left < right)//交換left和right的值
		{
			Swap(&a[left], &a[right]);
		}
	}
	int meeti = left;//L和R的相遇點(diǎn)
	Swap(&a[keyi], &a[meeti]);//交換key和相遇點(diǎn)的值

	QuickSort1(a, begin, meeti - 1);//key的左序列進(jìn)行此操作
	QuickSort1(a, meeti + 1, end);//key的右序列進(jìn)行此操作
}

挖坑法

動(dòng)圖展示:
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

具體思路是

1.先將選定的基準(zhǔn)值(最左邊)直接取出,然后留下一個(gè)坑,
2.當(dāng)右指針遇到小于基準(zhǔn)值的數(shù)時(shí),直接將該值放入坑中,而右指針指向的位置形成新的坑位,
3.然后左指針遇到大于基準(zhǔn)值的數(shù)時(shí),將該值放入坑中,左指針指向的位置形成坑位,
4.重復(fù)該步驟,直到左右指針相等。最后將基準(zhǔn)值放入坑位之中。

代碼展示:

//快速排序(挖坑法)
void QuickSort2(int* a, int begin, int end)
{
	if (begin >= end)//當(dāng)只有一個(gè)數(shù)據(jù)或是序列不存在時(shí),不需要進(jìn)行操作
		return;

	int left = begin;//L
	int right = end;//R
	int key = a[left];//在最左邊形成一個(gè)坑位
	while (left < right)
	{
		//right向左,找小
		while (left < right&&a[right] >= key)
		{
			right--;
		}
		//填坑
		a[left] = a[right];
		//left向右,找大
		while (left < right&&a[left] <= key)
		{
			left++;
		}
		//填坑
		a[right] = a[left];
	}
	int meeti = left;//L和R的相遇點(diǎn)
	a[meeti] = key;//將key拋入坑位

	QuickSort2(a, begin, meeti - 1);//key的左序列進(jìn)行此操作
	QuickSort2(a, meeti + 1, end);//key的右序列進(jìn)行此操作
}

前后指針?lè)?/h6>

動(dòng)圖展示:
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

具體思路是

1.選定基準(zhǔn)值,定義prev和cur指針(cur = prev + 1)
2.cur先走,遇到小于基準(zhǔn)值的數(shù)停下,然后將prev向后移動(dòng)一個(gè)位置
3.將prev對(duì)應(yīng)值與cur對(duì)應(yīng)值交換
4.重復(fù)上面的步驟,直到cur走出數(shù)組范圍
5.最后將基準(zhǔn)值與prev對(duì)應(yīng)位置交換
6.遞歸排序以基準(zhǔn)值為界限的左右區(qū)間

代碼展示:

//快速排序(前后指針?lè)ǎ?/span>
void QuickSort3(int* a, int begin, int end)
{
	if (begin >= end)//當(dāng)只有一個(gè)數(shù)據(jù)或是序列不存在時(shí),不需要進(jìn)行操作
		return;

	//三數(shù)取中
	int midIndex = GetMidIndex(a, begin, end);
	Swap(&a[begin], &a[midIndex]);

	int prev = begin;
	int cur = begin + 1;
	int keyi = begin;
	while (cur <= end)//當(dāng)cur未越界時(shí)繼續(xù)
	{
		if (a[cur] < a[keyi] && ++prev != cur)//cur指向的內(nèi)容小于key
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	int meeti = prev;//cur越界時(shí),prev的位置
	Swap(&a[keyi], &a[meeti]);//交換key和prev指針指向的內(nèi)容

	QuickSort3(a, begin, meeti - 1);//key的左序列進(jìn)行此操作
	QuickSort3(a, meeti + 1, end);//key的右序列進(jìn)行此操作
}

非遞歸實(shí)現(xiàn)

當(dāng)我們需要將一個(gè)用遞歸實(shí)現(xiàn)的算法改為非遞歸時(shí),一般需要借用一個(gè)數(shù)據(jù)結(jié)構(gòu),那就是棧。將Hoare版本、挖坑法以及前后指針?lè)ǖ目焖倥判蚋臑榉沁f歸版本,其實(shí)主體思想一致,只是調(diào)用的單趟排序的算法不同而已。
?于是我們可以先將Hoare版本、挖坑法和前后指針?lè)ǖ膯翁伺判騿为?dú)封裝起來(lái)。然后寫(xiě)一個(gè)非遞歸的快速排序,在函數(shù)內(nèi)部調(diào)用單趟排序的函數(shù)即可。

快速排序的非遞歸算法基本思路
?1、先將待排序列的第一個(gè)元素的下標(biāo)和最后一個(gè)元素的下標(biāo)入棧。
?2、當(dāng)棧不為空時(shí),讀取棧中的信息(一次讀取兩個(gè):一個(gè)是L,另一個(gè)是R),然后調(diào)用某一版本的單趟排序,排完后獲得了key的下標(biāo),然后判斷key的左序列和右序列是否還需要排序,若還需要排序,就將相應(yīng)序列的L和R入棧;若不需排序了(序列只有一個(gè)元素或是不存在),就不需要將該序列的信息入棧。
?3、反復(fù)執(zhí)行步驟2,直到棧為空為止。

代碼示例:

//快速排序(非遞歸實(shí)現(xiàn))
void QuickSortNonR(int* a, int begin, int end)
{
	Stack st;//創(chuàng)建棧
	StackInit(&st);//初始化棧
	StackPush(&st, begin);//待排序列的L
	StackPush(&st, end);//待排序列的R
	while (!StackEmpty(&st))
	{
		int right = StackTop(&st);//讀取R
		StackPop(&st);//出棧
		int left = StackTop(&st);//讀取L
		StackPop(&st);//出棧
		//該處調(diào)用的是Hoare版本的單趟排序
		int keyi = PartSort1(a, left, right);
		if (left < keyi - 1)//該序列的左序列還需要排序
		{
			StackPush(&st, left);//左序列的L入棧
			StackPush(&st, keyi - 1);//左序列的R入棧
		}
		if (keyi + 1 < right)// 該序列的右序列還需要排序
		{
			StackPush(&st, keyi + 1);//右序列的L入棧
			StackPush(&st, right);//右序列的R入棧
		}
	}
	StackDestroy(&st);//銷(xiāo)毀棧
}

Hoare版本

代碼示例:

//Hoare版本(單趟排序)
int PartSort1(int* a, int left, int right)
{
	int keyi = left;//key的下標(biāo)
	while (left < right)
	{
		//right走,找小
		while (left < right&&a[right] >= a[keyi])
		{
			right--;
		}
		//left先走,找大
		while (left < right&&a[left] <= a[keyi])
		{
			left++;
		}
		if (left < right)
		{
			Swap(&a[left], &a[right]);//交換left和right的值
		}
	}
	int meeti = left;//L和R的相遇點(diǎn)
	Swap(&a[keyi], &a[meeti]);//交換key和相遇點(diǎn)的值
	return meeti;//返回相遇點(diǎn),即key的當(dāng)前位置
}

挖坑法

代碼示例:

//挖坑法(單趟排序)
int PartSort2(int* a, int left, int right)
{
	int key = a[left];//在最左邊形成一個(gè)坑位
	while (left < right)
	{
		//right向左,找小
		while (left < right&&a[right] >= key)
		{
			right--;
		}
		//填坑
		a[left] = a[right];
		//left向右,找大
		while (left < right&&a[left] <= key)
		{
			left++;
		}
		//填坑
		a[right] = a[left];
	}
	int meeti = left;//L和R的相遇點(diǎn)
	a[meeti] = key;//將key拋入坑位
	return meeti;//返回key的當(dāng)前位置
}

前后指針?lè)?/h6>

代碼示例:

//前后指針?lè)ǎ▎翁伺判颍?/span>
int PartSort3(int* a, int left, int right)
{
	int prev = left;
	int cur = left + 1;
	int keyi = left;
	while (cur <= right)//當(dāng)cur未越界時(shí)繼續(xù)
	{
		if (a[cur] < a[keyi] && ++prev != cur)//cur指向的內(nèi)容小于key
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	int meeti = prev;//cur越界時(shí),prev的位置
	Swap(&a[keyi], &a[meeti]);//交換key和prev指針指向的內(nèi)容
	return meeti;//返回key的當(dāng)前位置
}

快速排序倆個(gè)優(yōu)化

但是上面的程序還有一些缺陷

在基準(zhǔn)值的選擇上,如果選擇的基準(zhǔn)值為恰好為最小值,會(huì)進(jìn)行不必要的遞歸。

在排序大量有序數(shù)據(jù)或者接近有序數(shù)據(jù)時(shí),效率會(huì)比較低,甚至可能會(huì)出現(xiàn)程序崩潰的情況。

這是因?yàn)樵谂判蛴行驍?shù)據(jù)時(shí),快速排序的遞歸調(diào)用次數(shù)過(guò)多,會(huì)導(dǎo)致棧溢出的情況。

為了解決這些問(wèn)題,這里有兩種優(yōu)化方法:

1> 三數(shù)取中法選基準(zhǔn)值
2> 遞歸到小的子區(qū)間時(shí),可以考慮使用插入排序

三數(shù)取中法取基準(zhǔn)值:即在在起始位置,中間位置,末尾位置中選出中間值,作為基準(zhǔn)值。

代碼示例:

//三數(shù)取中
int GetMidIndex(int* a, int left, int right)
{
	int mid = left + (right - left) / 2;
	if (a[mid] > a[left])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[left]>a[right])
			return left;
		else
			return right;
	}
	else
	{
		if (a[mid] > a[right])
			return mid;
		else if (a[left] > a[right])
			return right;
		else
			return left;
	}
}

小區(qū)間優(yōu)化:類(lèi)似于二叉樹(shù),每個(gè)子樹(shù)都會(huì)進(jìn)行一次遞歸調(diào)用,越到下面遞歸調(diào)用會(huì)越多。為了減少遞歸調(diào)用,當(dāng)?shù)竭f歸到下層時(shí),我們可以使用其他的排序來(lái)替代。這里我們使用插入排序。

代碼示例:

//優(yōu)化后的快速排序
void QuickSort0(int* a, int begin, int end)
{
	if (begin >= end)//當(dāng)只有一個(gè)數(shù)據(jù)或是序列不存在時(shí),不需要進(jìn)行操作
		return;

	if (end - begin + 1 > 20)//可自行調(diào)整
	{
		//可調(diào)用快速排序的單趟排序三種中的任意一種
		//int keyi = PartSort1(a, begin, end);
		//int keyi = PartSort2(a, begin, end);
		int keyi = PartSort3(a, begin, end);
		QuickSort(a, begin, keyi - 1);//key的左序列進(jìn)行此操作
		QuickSort(a, keyi + 1, end);//key的右序列進(jìn)行此操作
	}
	else
	{
		//HeapSort(a, end - begin + 1);
		ShellSort(a, end - begin + 1);//當(dāng)序列長(zhǎng)度小于等于20時(shí),使用希爾排序
	}
}

快速排序的特性總結(jié)

1>快速排序整體的綜合性能和使用場(chǎng)景都是比較好的,所以才敢叫快速排序
2>時(shí)間復(fù)雜度:O(N*logN)
3>空間復(fù)雜度:O(logN)
4>穩(wěn)定性:不穩(wěn)定

歸并排序

(點(diǎn)擊此處跳轉(zhuǎn),更加詳細(xì)的講解過(guò)程,干貨滿(mǎn)滿(mǎn))
動(dòng)圖展示:
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

基本思想

歸并排序(MERGE-SORT)是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法(Divide andConquer)的一個(gè)非常典型的應(yīng)用。

將已有序的子序列合并,得到完全有序的序列;即先使每個(gè)子序列有序,再使子序列段間有序。

若將兩個(gè)有序表合并成一個(gè)有序表,稱(chēng)為二路歸并。

圖片展示:
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

按照其算法思想:

首先應(yīng)在數(shù)組中找出有序的序列,但數(shù)組是否有序編譯器可不知道。

所以可以將數(shù)組劃分,一分二,二分四…直到每個(gè)序列中只有一個(gè)數(shù)字。

一個(gè)數(shù)字總可以認(rèn)為他是有序的吧。

最后將同時(shí)劃分的序列合并。

遞歸實(shí)現(xiàn)

歸并排序,從其思想上看就很適合使用遞歸來(lái)實(shí)現(xiàn),并且用遞歸實(shí)現(xiàn)也比較簡(jiǎn)單。其間我們需要申請(qǐng)一個(gè)與待排序列大小相同的數(shù)組用于合并過(guò)程兩個(gè)有序的子序列,合并完畢后再將數(shù)據(jù)拷貝回原數(shù)組。

代碼示例:

//歸并排序(子函數(shù))
void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)//歸并結(jié)束條件:當(dāng)只有一個(gè)數(shù)據(jù)或是序列不存在時(shí),不需要再分解
	{
		return;
	}
	int mid = left + (right - left) / 2;//中間下標(biāo)
	_MergeSort(a, left, mid, tmp);//對(duì)左序列進(jìn)行歸并
	_MergeSort(a, mid + 1, right, tmp);//對(duì)右序列進(jìn)行歸并
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	//將兩段子區(qū)間進(jìn)行歸并,歸并結(jié)果放在tmp中
	int i = left;
	while (begin1 <= end1&&begin2 <= end2)
	{
		//將較小的數(shù)據(jù)優(yōu)先放入tmp
		if (a[begin1] < a[begin2])
			tmp[i++] = a[begin1++];
		else
			tmp[i++] = a[begin2++];
	}
	//當(dāng)遍歷完其中一個(gè)區(qū)間,將另一個(gè)區(qū)間剩余的數(shù)據(jù)直接放到tmp的后面
	while (begin1 <= end1)
		tmp[i++] = a[begin1++];
	while (begin2 <= end2)
		tmp[i++] = a[begin2++];
	//歸并完后,拷貝回原數(shù)組
	int j = 0;
	for (j = left; j <= right; j++)
		a[j] = tmp[j];
}
//歸并排序(主體函數(shù))
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);//申請(qǐng)一個(gè)與原數(shù)組大小相同的空間
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	_MergeSort(a, 0, n - 1, tmp);//歸并排序
	free(tmp);//釋放空間
}

非遞歸實(shí)現(xiàn)

非遞歸實(shí)現(xiàn)的思想與遞歸實(shí)現(xiàn)的思想是類(lèi)似的。

不同的是,這里的序列劃分過(guò)程和遞歸是相反的,不是一次一分為二,而是先1個(gè)元素一組,再2個(gè)元素一組,4個(gè)元素一組…直到將所有的元素歸并完。
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

代碼示例:

//歸并排序(子函數(shù))
void _MergeSortNonR(int* a, int* tmp, int begin1, int end1, int begin2, int end2)
{
	int j = begin1;
	//將兩段子區(qū)間進(jìn)行歸并,歸并結(jié)果放在tmp中
	int i = begin1;
	while (begin1 <= end1&&begin2 <= end2)
	{
		//將較小的數(shù)據(jù)優(yōu)先放入tmp
		if (a[begin1] < a[begin2])
			tmp[i++] = a[begin1++];
		else
			tmp[i++] = a[begin2++];
	}
	//當(dāng)遍歷完其中一個(gè)區(qū)間,將另一個(gè)區(qū)間剩余的數(shù)據(jù)直接放到tmp的后面
	while (begin1 <= end1)
		tmp[i++] = a[begin1++];
	while (begin2 <= end2)
		tmp[i++] = a[begin2++];
	//歸并完后,拷貝回原數(shù)組
	for (; j <= end2; j++)
		a[j] = tmp[j];
}
//歸并排序(主體函數(shù))
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);//申請(qǐng)一個(gè)與待排序列大小相同的空間,用于輔助合并序列
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	int gap = 1;//需合并的子序列中元素的個(gè)數(shù)
	while (gap < n)
	{
		int i = 0;
		for (i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			if (begin2 >= n)//最后一組的第二個(gè)小區(qū)間不存在或是第一個(gè)小區(qū)間不夠gap個(gè),此時(shí)不需要對(duì)該小組進(jìn)行合并
				break;
			if (end2 >= n)//最后一組的第二個(gè)小區(qū)間不夠gap個(gè),則第二個(gè)小區(qū)間的后界變?yōu)閿?shù)組的后界
				end2 = n - 1;
			_MergeSortNonR(a, tmp, begin1, end1, begin2, end2);//合并兩個(gè)有序序列
		}
		gap = 2 * gap;//下一趟需合并的子序列中元素的個(gè)數(shù)翻倍
	}
	free(tmp);//釋放空間
}

外排序

上面介紹的排序算法均是在內(nèi)存中進(jìn)行的,對(duì)于數(shù)據(jù)量龐大的序列,上面介紹的排序算法都束手無(wú)策,而歸并排序卻能勝任這種海量數(shù)據(jù)的排序。

假設(shè)現(xiàn)在有10億個(gè)整數(shù)(4GB)存放在文件A中,需要我們進(jìn)行排序,而內(nèi)存一次只能提供512MB空間,歸并排序解決該問(wèn)題的基本思路如下:
?1、每次從文件A中讀取八分之一,即512MB到內(nèi)存中進(jìn)行排序(內(nèi)排序),并將排序結(jié)果寫(xiě)入到一個(gè)文件中,然后再讀取八分之一,重復(fù)這個(gè)過(guò)程。那么最終會(huì)生成8個(gè)各自有序的小文件(A1~A8)。
?2、對(duì)生成的8個(gè)小文件進(jìn)行1 1合并,最終8個(gè)文件被合成為4個(gè),然后再1 1合并,就變成2個(gè)文件了,最后再進(jìn)行一次1 1合并,就變成1個(gè)有序文件了。

注意:這里將兩個(gè)文件進(jìn)行1 1合并,并不是先將兩個(gè)文件讀入內(nèi)存然后進(jìn)行合并,因?yàn)閮?nèi)存裝不下。這里的11合并是利用文件輸入輸出函數(shù),從兩個(gè)文件中各自讀取一個(gè)數(shù)據(jù),然后進(jìn)行比較,將較小的數(shù)據(jù)寫(xiě)入到一個(gè)新文件中去,然后再讀取,再比較,再寫(xiě)入,最終將兩個(gè)文件中的數(shù)據(jù)全部寫(xiě)入到另一個(gè)文件中去,那么此時(shí)這個(gè)文件又是一個(gè)有序的文件了。

八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

歸并排序的特性總結(jié)

1>歸并的缺點(diǎn)在于需要O(N)的空間復(fù)雜度,歸并排序的思考更多的是解決在磁盤(pán)中的外排序問(wèn)題。
2>時(shí)間復(fù)雜度:O(N*logN)
3>空間復(fù)雜度:O(N)
4>穩(wěn)定性:穩(wěn)定

計(jì)數(shù)排序

(點(diǎn)擊此處跳轉(zhuǎn),更加詳細(xì)的講解過(guò)程,干貨滿(mǎn)滿(mǎn))
動(dòng)圖展示:
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

思想

計(jì)數(shù)排序又稱(chēng)為鴿巢原理,是對(duì)哈希直接定址法的變形應(yīng)用。 操作步驟

  1. 統(tǒng)計(jì)相同元素出現(xiàn)次數(shù)
  2. 根據(jù)統(tǒng)計(jì)的結(jié)果將序列回收到原來(lái)的序列中

代碼示例:

//計(jì)數(shù)排序
void CountSort(int* a, int n)
{
	int min = a[0];//記錄數(shù)組中的最小值
	int max = a[0];//記錄數(shù)組中的最大值
	for (int i = 0; i < n; i++)
	{
		if (a[i] < min)
			min = a[i];
		if (a[i] > max)
			max = a[i];
	}
	int range = max - min + 1;//min和max之間的自然數(shù)個(gè)數(shù)(包括min和max本身)
	int* count = (int*)calloc(range, sizeof(int));//開(kāi)辟可儲(chǔ)存range個(gè)整型的內(nèi)存空間,并將內(nèi)存空間置0
	if (count == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	//統(tǒng)計(jì)相同元素出現(xiàn)次數(shù)(相對(duì)映射)
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}
	int i = 0;
	//根據(jù)統(tǒng)計(jì)結(jié)果將序列回收到原來(lái)的序列中
	for (int j = 0; j < range; j++)
	{
		while (count[j]--)
		{
			a[i++] = j + min;
		}
	}
	free(count);//釋放空間
}

注意:計(jì)數(shù)排序在排負(fù)數(shù)時(shí),可將負(fù)數(shù)的類(lèi)型轉(zhuǎn)化成 unsigned int。
數(shù)組中元素有正有負(fù)的情況時(shí)不適用計(jì)數(shù)排序。

計(jì)數(shù)排序的特性總結(jié):

1>計(jì)數(shù)排序在數(shù)據(jù)范圍集中時(shí),效率很高,但是適用范圍及場(chǎng)景有限。 2>時(shí)間復(fù)雜度:O(MAX(N,范圍))
3>空間復(fù)雜度:O(范圍)
4>穩(wěn)定性:穩(wěn)定

常見(jiàn)排序算法的性能分析

最后我們對(duì)各種排序算法進(jìn)行一個(gè)簡(jiǎn)單的測(cè)試:隨機(jī)測(cè)試10000個(gè)數(shù)據(jù)

我們借此來(lái)觀察一下各個(gè)排序算法所用時(shí)長(zhǎng)的差異

void TestOP()
{
    srand(time(0));
    const int N = 100000;
    int* a1 = (int*)malloc(sizeof(int) * N);
    int* a2 = (int*)malloc(sizeof(int) * N);
    int* a3 = (int*)malloc(sizeof(int) * N);
    int* a4 = (int*)malloc(sizeof(int) * N);
    int* a5 = (int*)malloc(sizeof(int) * N);
    int* a6 = (int*)malloc(sizeof(int) * N);
    for (int i = 0; i < N; ++i)
    {
        a1[i] = rand();   //獲得隨機(jī)數(shù)
        a2[i] = a1[i];
        a3[i] = a1[i];
        a4[i] = a1[i];
        a5[i] = a1[i];
        a6[i] = a1[i];
    }
    //直接插入排序
    int begin1 = clock();
    InsertSort(a1, N);
    int end1 = clock();
    //希爾排序
    int begin2 = clock();
    ShellSort(a2, N);
    int end2 = clock();
    //選擇排序
    int begin3 = clock();
    SelectSort(a3, N);
    int end3 = clock();
    //堆排序
    int begin4 = clock();
    HeapSort(a4, N);
    int end4 = clock();
    //快速排序
    int begin5 = clock();
    QuickSort(a5, 0, N - 1);
    int end5 = clock();
    //歸并排序
    int begin6 = clock();
    MergeSort(a6, N);
    int end6 = clock();
    printf("InsertSort:%d\n", end1 - begin1);
    printf("ShellSort:%d\n", end2 - begin2);
    printf("SelectSort:%d\n", end3 - begin3);
    printf("HeapSort:%d\n", end4 - begin4);
    printf("QuickSort:%d\n", end5 - begin5);
    printf("MergeSort:%d\n", end6 - begin6);
    free(a1);
    free(a2);
    free(a3);
    free(a4);
    free(a5);
    free(a6);
}

八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序
由此可見(jiàn),不同的排序算法之間差異性較大,大家應(yīng)該根據(jù)不同的應(yīng)用場(chǎng)景來(lái)選擇合適的排序算法。

最后,給出所有排序算法的時(shí)間復(fù)雜度、空間復(fù)雜度、穩(wěn)定性的對(duì)比表格,希望大家能夠有所收獲。
八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序

如此明了清晰的詳解過(guò)程,希望各位看官能夠一鍵三連哦??文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-493410.html

到了這里,關(guān)于八大排序:直接插入排序、希爾排序、選擇排序、堆排序、冒泡排序、快速排序、歸并排序、計(jì)數(shù)排序的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

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

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

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

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包