1. 程式人生 > >資料結構之常見排序演算法

資料結構之常見排序演算法

#include "stdafx.h"
#include <memory>


#define MAXSIZE 10
typedef struct  
{
	int r[MAXSIZE+1];//用於儲存要排序陣列,r[0]用作哨兵或臨時變數
	int nlength;//用於記錄順序表長度
}SqList;

//交換L中i,j位置的資料
void swap(SqList *L, int i, int j)
{
	int temp = L->r[i];
	L->r[i] = L->r[j];
	L->r[j] = temp;
}

/************************************************************************/
/* 一、交換排序                                                         */
/************************************************************************/
//1.1氣泡排序      穩定性:穩定       時間複雜度:O(n^2)       空間複雜度:O(1)
//基本思路:兩兩比較相鄰記錄的關鍵字,如果反序則交換,直到沒有反序的記錄為止
//對順序表L做氣泡排序
void BubbleSort(SqList *L)
{
	int i,j;
	bool bchange;//判斷是否發生交換
	for (i=1;i<L->nlength;i++)
	{
		bchange=false;
		for (j=L->nlength-1;j>=i;j--)
		{
			if (L->r[j]>L->r[j+1])
			{
               swap(L,j,j+1);
			   bchange=true;
			}
			   
		}
		if (!bchange)//如果沒有發生交換說明已經是排好序了
		   return;
	}
}


//1.2快速排序      穩定性:不穩定      時間複雜度:平均O(nlogn)      有序時最差O(n^2)       空間複雜度:O(logn)
//基本思路:通過一趟排序將待排記錄分割成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分記錄的關鍵字小
//則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序的目的

//交換順序表L中子表的記錄,使樞軸到位,並返回其所在位置,此時它前面的值均不大於它,後面的值均不小於它
int Partition(SqList *L, int low, int high)
{
	int pivotkey = L->r[low];//用子表的第一個記錄作樞軸記錄
	while(low<high)//從表的兩端交替向中間掃描
	{
		while(low<high && L->r[high]>=pivotkey)
			high--;
		swap(L,low,high);//將比樞軸記錄小的記錄交換到低端
		while(low<high && L->r[low]<=pivotkey)
			low++;
		swap(L,low,high);//將比樞軸記錄大的記錄交換到高階
	}
	return low;//返回樞軸位置
}

//對順序表L中的子序列L->r[low.....high]做快速排序
void QSort(SqList *L, int low, int high)
{
	int pivot;
	if (low<high)
	{
		pivot = Partition(L,low,high);//將L->r[low.....high]一分為二,算出樞軸值pivot
		QSort(L,low,pivot-1);//對低子表做遞迴排序
		QSort(L,pivot+1,high);//對高子表做遞迴排序
	}
	
}

//對順序表L做快速排序
void QuickSort(SqList *L)
{
	QSort(L,1,L->nlength);
}

//快排的優化策略
//1.優化選擇樞軸:三數取中,九數取中
//2.優化不必要的交換:修改Partition()將替換改為交換   high--,r[low]=r[high];  low++,r[high]=r[low];
//3.優化小陣列時的排序方案:修改QSort()當陣列個數不大於某個常數(7?50?)時用直接插入排序
//4.優化遞迴操作:每次分割槽後先處理較短的部分





/************************************************************************/
/* 二、選擇排序                                                         */
/************************************************************************/
//2.1簡單選擇排序        穩定性:不穩定        時間複雜度:O(n^2)     相比於氣泡排序效能更優,因為交換次數少      空間複雜度:O(1)
//基本思路:通過n-i次關鍵字間的比較,從n-i+1個記錄中選出關鍵字最小的記錄,並和第i個記錄交換之
//對順序表L做簡單選擇排序
void SelectSort(SqList *L)
{
	int i,j,min;
	for (i=1;i<L->nlength;i++)
	{
		min=i;//將當前下標定義為最小值下標
		for (j=i+1;j<=L->nlength;j++)//迴圈比較n-i次
		{
			if (L->r[min]>L->r[j])
			   min=j;//找出n-i+1個記錄中的最小值
		}
		if (min!=i)//若min!=i,則交換
		   swap(L,i,min);
	}
}


//2.2堆排序              穩定性:不穩定        時間複雜度:O(nlogn)        空間複雜度:O(1)
//基本思路:利用堆進行排序,重複構造大頂錐,將根結點與末尾元素交換,再將剩餘的元素重新構造大頂錐

//本函式調整L->r[s]的關鍵字,使L->r[s.....m]成為一個大頂錐
void HeapAdjust(SqList *L, int s, int m)
{
	int temp,j;
	temp=L->r[s];   //s為子根結點
	for (j=s*2;j<=m;j*=2)//沿關鍵字較大的孩子結點向下篩選
	{
		if (j<m && L->r[j]<L->r[j+1])
		   j++;//j為關鍵字中較大的記錄的下標
		if (temp<L->r[j])
		{
			L->r[s]=L->r[j];//更新L->r[s]為最大值
			s=j;
		}
	}
	L->r[s]=temp;//交換
}

//對順序表L做堆排序
void HeapSort(SqList *L)
{
	int i;
	for (i=L->nlength/2;i>0;i--)//把L中的r構造成一個大頂錐   此時的i都為子根結點
	{
		HeapAdjust(L,i,L->nlength);
	}
	for (i=L->nlength;i>1;i--)
	{
		swap(L,1,i);//將大頂錐的根結點與末尾元素交換
		HeapAdjust(L,1,i-1);//將剩餘的結點重新構造成一個大頂錐
	}
}




/************************************************************************/
/* 三、插入排序                                                         */
/************************************************************************/
//3.1直接插入排序         穩定性:穩定       時間複雜度:O(N^2)       是簡單排序中效能最好的        空間複雜度:O(1)
//基本思路:將一個記錄插入到已經排好序的有序表中,從而得到一個新的,記錄數增1的有序表
//對順序表L做直接插入排序
void InsertSort(SqList *L)
{
	int i,j;
	for (i=2;i<=L->nlength;i++)
	{
		if (L->r[i]<L->r[i-1])//如果當前元素小於之前的元素,則需要把它插入到前面
		{
			L->r[0]=L->r[i];//設定哨兵
			for (j=i-1;L->r[j]>L->r[0];j--)//與前面的元素分別比較,將大的記錄往後移
			{
				L->r[j+1]=L->r[j];//將記錄後移
			}
			L->r[j+1]=L->r[0];//將元素插入到正確的位置
		}
	}
}

 
//3.2希爾排序              穩定性:不穩定(跳躍式的移動)       時間複雜度:O(N^(3/2))      空間複雜度:O(1)
//基本思路:將原本具有大量記錄的序列,通過相隔某個增量的記錄組成子序列,對子序列進行直接插入排序
//從而使整個序列基本有序,最後對整個序列做直接插入排序
//對順序表L做希爾排序
void ShellSort(SqList *L)
{
	int i,j,increment;
	increment=L->nlength;
	do 
	{
		increment=increment/3+1;//增量序列
		for (i=increment+1;i<=L->nlength;i++)
		{
			if (L->r[i]<L->r[i-increment])//如果當前元素小於之前的元素,則需要把它插入到前面
			{
				L->r[0]=L->r[i];//設定哨兵
				for (j=i-increment;j>0&&L->r[j]>L->r[0];j-=increment)//與前面的元素分別比較,將大的記錄往後移
				{
					L->r[j+increment]=L->r[j];//將記錄後移
				}
				L->r[j+increment]=L->r[0];//將元素插入到正確的位置
			}
		}
	} while (increment>1);
}




/************************************************************************/
/* 四、歸併排序         穩定性:穩定        時間複雜度:O(nlogn)      空間複雜度O(n)                                             */
/************************************************************************/
//基本思路:兩兩合併,倒置的完全二叉樹

//將有序的SR[i...m]和SR[m+1...n]歸併為有序的TR[i...n]
void Merge(int SR[], int TR[], int i, int m, int n)
{
	int j,k,l;
	for (j=m+1,k=i;j<=n&&i<=m;k++)//將SR中記錄由小到大歸併入TR
	{
		if (SR[i]<SR[j])
		   TR[k]=SR[i++];
		else
		   TR[k]=SR[j++];
	}
	if (i<=m)
	{
		for (l=0;l<=m-i;l++)
		   TR[k+l]=SR[i+l];//將剩餘的SR[i...m]複製到TR
	}
	if (j<=n)
	{
		for (l=0;l<=n-j;l++)
			TR[k+l]=SR[j+l];//將剩餘的SR[j...n]複製到TR
	}
}

//將SR[]中相鄰長度為s的子序列兩兩歸併到TR[]
void MergePass(int SR[], int TR[], int s, int n)
{
	int i=1;
	int j;
	while (i<=n-s*2+1)
	{
		Merge(SR,TR,i,i+s-1,i+2*s-1);//兩兩合併
		i=i+s*2;
	}
	if (i<n-s+1)//歸併<最後>兩個序列(只在最後一次出現)
	   Merge(SR,TR,i,i+s-1,n);
	else//若最後只剩下單個子序列
		for (j=i;j<=n;j++)
			TR[j]=SR[j];
}

//對順序表L做歸併排序
void MergeSort(SqList *L)
{
	int k=1;
	int *TR=(int*)malloc(sizeof(int)*L->nlength);//申請額外空間
	while (k<L->nlength)
	{
		MergePass(TR,L->r,k,L->nlength);
		k=k*2;//子序列長度加倍
		MergePass(L->r,TR,k,L->nlength);
		k=k*2;//子序列長度加倍
	}
}



/************************************************************************/
/* 五、總結                                                             */
/************************************************************************/
//5.1時間複雜度來說:
//(1)平方階(O(n2))排序:各類簡單排序:直接插入、簡單選擇和氣泡排序;
//(2)線性對數階(O(nlog2n))排序:快速排序、堆排序和歸併排序;
//(3)O(n^(1+§)))排序(§是介於0和1之間的常數):希爾排序
//(4)線性階(O(n))排序:基數排序,此外還有桶、箱排序。
//說明:
//當原表有序或基本有序時,直接插入排序和氣泡排序將大大減少比較次數和移動記錄的次數,時間複雜度可降至O(n)
//而快速排序則相反,當原表基本有序時,將蛻化為氣泡排序,時間複雜度提高為O(n2)
//原表是否有序,對簡單選擇排序、堆排序、歸併排序和基數排序的時間複雜度影響不大

//5.2穩定性來說:
//排序演算法的穩定性:若待排序的序列中,存在多個具有相同關鍵字的記錄,經過排序, 這些記錄的相對次序保持不變,則稱該演算法是穩定的
//                 若經排序後,記錄的相對 次序發生了改變,則稱該演算法是不穩定的。 
//穩定性的好處:排序演算法如果是穩定的,那麼從一個鍵上排序,然後再從另一個鍵上排序,
//              第一個鍵排序的結果可以為第二個鍵排序所用。基數排序就是這樣,先按低位排序,
//              逐次按高位排序,低位相同的元素其順序再高位也相同時是不會改變的。另外,如果排序演算法穩定,
//              可以避免多餘的比較;
//穩定的排序演算法:氣泡排序、直接插入排序、歸併排序和基數排序
//不穩定的排序演算法:簡單選擇排序、快速排序、希爾排序、堆排序