白話講解排序算法

我是自動化專業的應屆研究生,最終拿到了tplink、華爲、vivo等公司的ssp的offer,分享自己學習過的計算機基礎知識(C語言+操作系統+計算機網絡+linux)以及數據結構與算法的相關知識,保證看完讓你有所成長。
歡迎關注我,學習資料免費分享給你哦!還有其他超多學習資源,都是我自己學習過的,經過過濾之後的資源,免去你還在因爲擁有大量資源不知如何入手的糾結,讓你體系化學習。
在這裏插入圖片描述

內部排序

排序算法因爲數據量的不同,就是一次是否可以全部讀入內存分爲外部排序和內部排序。內部排序就是數據可以一次全部讀入內存的情況。

插入排序

插入排序就是從頭至尾逐漸有序的過程。對於一個數組A,從下標爲i=1開始,首先將下標爲1的元素取出A[1],暫時存儲起來tmp。然後逐漸與i前面的元素比較,只要元素比tmp大,那麼就需要將這個元素後移,這也是爲什麼需要暫時把A[1]存儲起來,因爲防止把前面元素後移的時候把這個值覆蓋。這樣直到比較到比tmp小的,此時說明tmp應該插入到這個位置上。這就是插入排序的基本思路。來看一下具體的過程。

在這裏插入圖片描述

首先i等於1,將A[1]=15存入tmp,然後比較A[0]與tmp,發現A[0]大於tmp,所以將A[0]後移到A[1],然後A[0]已經到了數組的界限,不能在比較了,所以tmp插入到A[0]處。然後此時i加1,等於2,將A[2]的元素取出放在tmp中。

在這裏插入圖片描述

重複之前的步驟,比較A[1]和tmp,發現40大於21,那麼將40後移。
在這裏插入圖片描述

在比較A[0]和tmp,發現A[0]小於tmp,那麼此時說明A[0]前面的所有元素都是比tmp小的(如果A[0]前面還有元素的話,因爲我們是從首開始進行的插入排序,說明A[i]以前的元素都是排序好的,如果有一個元素比A[i]小的話,那麼它前面的所有元素都是比tmp小的。)此時將tmp插入到A[1].然後在將i+1,此時i等於3,將A[3]放入tmp。
在這裏插入圖片描述

此時比較A[2]與tmp,發現A[2]比tmp小,說明tmp還是插入在A[3]處,還是前面所說A[2]前面都是排好序的,如果A[2]比tmp小,那麼A[2]之前的所有元素都比tmp小。這就是插入排序的基本過程。下面來看實現代碼。

//A是數據的數組,n是數組中元素的個數
void insert_sort(int *A,int n)
{
   int i,j,tmp;
   for(i=1;i<n;i++)
   {
	   tmp=A[i];
	   for(j=i;j>0&&A[j-1]>tmp;j--)
	   {
		   A[j]=A[j-1];
	   }
	   A[j]=tmp;
   }
}
冒泡排序

冒泡排序的思路與插入排序不同,它是將已經排好序的數據放在最後,逐漸縮小需要排序的範圍,最開始是整個數組A的大小n,從數值的開始比較j=0,如果A[j]>A[j+1],那麼就交換兩個元素,這樣就保證了A[j+1]是兩者之間最大的,然後j=j+1,然後繼續比較A[j]與A[j+1],如果A[j]>A[j+1],那麼繼續交換兩個元素。直到比較到j+1==(n-1)爲止,因爲每一次都是相當於把j之前的元素最大值與A[j+1]比較,所以這一輪下來之後,就是把數組中的最大值放在了數組的最後一個位置。然後下一輪的排序就會比較到n-2停止,就是把數組中第二大的元素放在數組的倒數第二個位置了,如此反覆,實現了排序。

在這裏插入圖片描述

首先比較21和15,21大於15,所以交換,然後將j加1.

在這裏插入圖片描述

然後比較21和40,21小於40,不交換。將j加一。

在這裏插入圖片描述

比較40與100,40小於100,不交換。將j加一。

在這裏插入圖片描述
比較100與24,100大於24,交換兩個元素。j加一。可以看到j所指向的元素,一定是j掃描過的最大元素,但j掃描過的不一定有序,下標0-3都是掃描過的,但是並不滿足遞增的順序。
在這裏插入圖片描述
最後比較100與31,100大於31,將100和31交換。完成了一輪排序。此時最後一個元素就是數組中最大的元素,然後將比較界限前移一個位置,j從0重新開始掃描,直到比較界限達到1,這就完成了排序。

在這裏插入圖片描述

懂了基本思想,就可以將其轉換爲代碼了,如下:

void swap(int *a,int *b)
{
	int tmp;
	tmp=*a;
	*a=*b;
	*b=tmp;
}
void bubble_sort(int *A,int n)
{
	int i,j;
	for(i=n-1;i>0;i--)
	{
		for(j=0;j<i;j++)
		{
			if(A[j]>A[j+1])
			{
				swap(&A[j],&A[j+1]);
			}
		}
	}
}

假如在某一輪循環之後,數組已經是排好序的了,也就是沒有必要在繼續去循環了,那麼應該採用什麼辦法呢?其實很簡單,就是判斷是否發生了交換,如果一輪循環判斷下來,沒有發生交換,說明已經排好序了,所以這個時候停止就可以了。

void swap(int *a,int *b)
{
	int tmp;
	tmp=*a;
	*a=*b;
	*b=tmp;
}
void bubble_sort(int *A,int n)
{
	int i,j;
	int flag=0;
	for(i=n-1;i>0;i--)
	{
		flag=0;
		for(j=0;j<i;j++)
		{
			if(A[j]>A[j+1])
			{
				swap(&A[j],&A[j+1]);
				flag=1;
			}
		}
		if(flag==0)
		{
			break;
		}
	}
}
希爾排序

希爾排序其實就是改進版本的插入排序,它不是直接對於所有數據做插入排序,而是採用了將數據分組,一個組一個組的做插入排序,然後將分組擴大,在插入排序,直到分組就是整個數組,完成排序。

在這裏插入圖片描述

假設有如上的待排序數組,首先選取分組的間隔,將數組分組,首先選取incre=n/2=6/2=3,那麼就有如下分組

在這裏插入圖片描述

然後每個分組分別插入排序,可以得到如下的順序:21,24;15,100;31,40;

在這裏插入圖片描述

然後縮小incre=incre/2=1,這樣就是整個數組進行插入排序,就可得到排序完成的數組了。

在這裏插入圖片描述

寫成代碼如下:

void shell_sort(int *A,int n)
{
	int i,j,ins,tmp;
	for(ins=n/2;ins>0;ins=ins/2)
	{
		for(i=ins;i<n;i++)
		{
           tmp=A[i];
		   for(j=i;j>=ins&&A[j-ins]>tmp;j-=ins)
		   {
			   A[j]=A[j-ins];
		   }
		   A[j]=tmp;
		}
	}
}
堆排序

堆排序的思路其實很簡單,在最大堆中,最大的元素在數組首地址處,那麼只需要將最大值與數組最後一個值交換,然後將堆的大小縮小1,原來的範圍爲0-n-1,現在n-1下標處已經存放了最大值的元素,然後將堆的範圍縮小至0-n-2,就是相當於堆的刪除操作,然後重新將0-n-2的堆重新構造成最大堆,本質就是刪除操作,只是將刪除後的元素放在了數組的後面。

//這裏與堆不同的地方在於沒有哨兵了,父節點下標爲i,那子節點下標爲2*i+1、2*i+2
void perdown(int *A,int d,int n)
{
	int parent,child;
	int data;
	data=A[d];
	for(parent=d;parent*2+1<n;parent=child)
	{
		child=parent*2+1;
		if((child+1)<n&&(A[child]<A[child+1]))
		{
			child++;
		}
		if(data>=A[child])
		{
            break;
		}
		A[parent]=A[child];
	}
	A[parent]=data;
}

void heap_sort(int *A,int n)
{
	int i;
	for(i=(n-1)/2;i>=0;i--) //將數組構建爲最大堆
	{
		perdown(A,i,n);
	}
	for(i=n-1;i>0;i--)
	{
		swap(&A[0],&A[i]);  //將最大交換到堆的最後一個位置
	    perdown(A,0,i);//重新構建最大堆
	}
}
歸併排序

歸併排序是採用了遞歸的思想來逐漸將數組分成兩個部分,比如一個數組有6個元素,首先分成兩個部分,前3個爲一組,後3個爲一組。
在這裏插入圖片描述

然後繼續分組。

在這裏插入圖片描述

將每個元素分到最小之後,然後每兩個進行合併,合併的時候按照順序合併,如下圖15和21合併時交換了位置。

在這裏插入圖片描述
在這裏插入圖片描述

然後繼續合併,直達整個數組排好順序。這就是歸併的思路,首先將排序的數組分離,分離之後在逐漸按照順序合併。

void merge(int *A,int *tmp,int left,int right,int right_end)
{
	int left_end,tmpPos=left,lengthofdata;
	left_end=right-1;
	lengthofdata=right_end-left+1;
	while(left<=left_end&&right<=right_end)
	{
		if(A[left]<A[right])
		{
			tmp[tmpPos++]=A[left++];
		}
		else
		{
            tmp[tmpPos++]=A[right++];
		}
	}
	while(left<=left_end)
	{
        tmp[tmpPos++]=A[left++];
	}
	while(right<=right_end)
	{
        tmp[tmpPos++]=A[right++];
	}
	for(lengthofdata;lengthofdata>0;lengthofdata--,right_end--)
	{
		A[right_end]=tmp[right_end];
	}

}
void mergesort(int *A,int *tmp,int left,int right_end)
{
	int right;
	if(left>=right_end)
	{
		return;
	}
	right=(left+right_end)/2;
	mergesort(A,tmp,left,right);
	mergesort(A,tmp,right+1,right_end);
	merge(A,tmp,left,right+1,right_end);
}
void merge_sort(int *A,int n)
{
	int *tmp;
	tmp=(int *)malloc(sizeof(int)*n);
	if(tmp!=NULL)
	{
		mergesort(A,tmp,0,n-1);
		free(tmp);
	}
	else
	{
		printf("內存不足");
	}
}
快速排序

快速排序基本思想與歸併排序類似,歸併排序一個是按照等分的策略,將待排序的數組分成兩部分,然後迭代處理;另一點是需要額外的空間。快速排序不同的地方是不是等分的策略而是選取一個值作爲參考值,將這次排序區域中的比它小的放到左邊,比它大的放在右面,然後將左面和右面分別按照這種辦法進行遞歸求解,直到待排序的值只有一個爲止。
在這裏插入圖片描述
以上面數組爲例,首先選取一個數字作爲分界的參考值,這裏以數組的第一個元素作爲參考值,x=A[0],之後從數組的最前面和最後面開始掃描數組,過程是這樣的,定義i=0,j=9,

在這裏插入圖片描述

從A[j]開始比較,如果A[j]>=x,那麼j就前移,即j–,如果A[j]<x,那麼就將這個值插入到A[i]中,因爲A[i]的值已經保存到了X中,然後i++,在從A[i]開始比較。如果A[i]<x,就i向前移動,即i++。

在這裏插入圖片描述

下圖所示,直到i移動到83這個位置,此時A[i]>=x。

在這裏插入圖片描述

此時將83插入到A[j]的位置,然後j前移,在從A[j]處重複上述過程,直到i>=j結束。
在這裏插入圖片描述

這樣就完成了一次快速排序,紅色部分左側比72小,右側比72大,然後將左側下標爲0-4的數據進行上述過程,右側下標爲7-9的數據進行上述過程,直到只有一個數據。就完成了快速排序,代碼如下:

void q_sort(int *A,int left,int right_end)
{
	int privot,i,j;
	privot=A[left];
    i=left;
    j=right_end;
	if(i<j)
	{
		
		while(1)
		{
			while(i<j&&A[j]>=privot)
			{
				j--;
			}
			if(i<j)
			{
				A[i++]=A[j];
			}
			while(i<j&&A[i]<privot)
			{
				i++;
			}
			if(i<j)
			{
				A[j--]=A[i];
			}
			else
			{
				break;
			}
		}
		A[i]=privot;
		q_sort(A,left,i-1);
		q_sort(A,i+1,right_end);
    }
}
//爲了使排序接口一致
void quick_sort(int *A,int n)
{
    q_sort(A,0,n-1);	
}
排序算法時間複雜度總結
排序方法 平均時間複雜度 最壞時間複雜度 額外空間複雜度 穩定性
插入排序 O(n^2) O(n^2) O(1) 穩定
冒泡排序 O(n^2) O(n^2) O(1) 穩定
歸併排序 O(nlogn) O(nlogn) O(n) 穩定
希爾排序 O(n^d) O(n^2) O(1) 不穩定
堆排序 O(nlogn) O(nlogn) O(1) 不穩定
快速排序 O(nlogn) O(n^2) O(logn) 不穩定
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章