1. 程式人生 > >【經典排序演算法】八大排序對比總結

【經典排序演算法】八大排序對比總結

針對前面討論的八大經典排序演算法:氣泡排序插入排序選擇排序堆排序歸併排序快速排序希爾排序桶排序

關於各種什麼時間複雜度,空間複雜度的對比總結,網上一大堆,別人也總結的很好,這裡就不贅述了,這裡主要通過對大量資料進行排序測試,測試各排序演算法的執行時間,來比較各排序之間優劣(時間上)。

先貼出測試程式:最後會給出各資料量的情況下,各演算法的執行時間。

typedef void(*SortFun)(int Arr[], int length);  //函式指標

double SortTest(SortFun fp)
{
	int Arr[LENGTH];
	for (int i = 0; i < LENGTH; ++i)
		Arr[i] = rand() % MAXNUM;
	clock_t beginTime = clock();
	fp(Arr, LENGTH);
	double diffTime = double(clock() - beginTime) / CLOCKS_PER_SEC;

	return diffTime;
}
上面的 LENGTH 和 MAXNUM 巨集定義在另外一個頭檔案中(因為桶排序中需要用到 MAXNUM),LENGTH 為待排資料的量,MAXNUM 為待排資料數值的最大值。

為了方便呼叫函式指標,這裡在原排序程式的基礎上添加了一個通用的函式介面。為方便理解,這裡重新貼出各排序程式

1、選擇排序

思想:每次找一個最小值

特點:不穩定排序,in-place sort

最壞時間情況:O(N^2)

最好時間情況:O(N^2)

空間複雜度:O(1)

void SelectSort(int Array[], int length)
{
	int i, j;
	int Minindex;

	for (i = 0; i < length - 1; ++i)    //最後留下的元素自然是最大的
	{
		Minindex = i;
		for (j = i + 1; j < length; ++j)
		{
			if (Array[j] < Array[Minindex])
				Minindex = j;         //更新最小值的索引
		}
		swap(Array[i], Array[Minindex]);   //將最小值與無序區的第一個元素交換
	}
}

2、桶排序

思想:類似於雜湊表的分離連結串列法,定義一個對映函式,將關鍵字劃入對應的桶中

特點:穩定排序

最壞時間情況:全部分到一個桶中O(N^2),一般情況為O(NlogN)

最好時間情況:每個桶中只有一個數據時最優O(N)

空間複雜度:典型地以空間換時間,和雜湊表類似,看要多少桶

typedef struct Node
{
	int data;
	struct Node *next;
}node;

void AddtoBucket(node *&pNode, int value)
{
	node *pHead = NULL;
	int tempValue;

	node *pNew = new node;
	pNew->data = value;
	pNew->next = NULL;

	if (NULL == pNode)
	{
		pNode = pNew;
	}
	else
	{
		pHead = pNode;
		while ((pNode->data <= value) && (pNode->next != NULL))
			pNode = pNode->next;

		if (pNode->data <= value)
		{
			pNode->next = pNew;
		}
		else
		{
			//插入連結串列,這裡採用的是修改值的方法,即插入指定節點的前面,無需獲得該節點的前一節點  
			//只需修改該節點的值使其等於待插入節點值,然後該節點指向新建節點,新建節點的鍵值則設為前面節點的鍵值  
			tempValue = pNode->data;
			pNode->data = pNew->data;
			pNew->data = tempValue;
			//修改指標
			pNew->next = pNode->next;
			pNode->next = pNew;
		}
		pNode = pHead;  //修正頭節點指標
	}
}

void BucketSort(int Arr[], int length, int MaxNum)
{
	node **list = new node*[length];   //分配指標陣列空間
	for (int i = 0; i < length; ++i)
		list[i] = NULL;

	int index;

	for (int i = 0; i < length; ++i)
	{
		index = (Arr[i] * length) / (MaxNum + 1);  //對映函式,可自定義
		AddtoBucket(list[index], Arr[i]);
	}

	//將桶中的資料放入原來的序列中
	for (int i = 0, j = 0; i < length; ++i)
	{
		while (list[i] != NULL)
		{
			Arr[j++] = list[i]->data;
			list[i] = list[i]->next;
		}
	}
	//銷燬分配的空間,code 略
}

//統一介面
void BucketSortTest(int Arr[], int length)
{
	BucketSort(Arr, length, MAXNUM);
}

3、希爾排序

思想:分割成子序列,然後用直接插入排序

特點:不穩定排序演算法

平均時間複雜度:O()

空間複雜度:O(1)

void ShellSort(int Array[], int length)
{
	int i, j, gap;
	int temp;

	for (gap = length / 2; gap > 0; gap /= 2)
	for (i = 0; i < gap; ++i)
	{
		//下面為直接插入排序,排序的是數組裡間隔gap的元素
		for (j = i + gap; j < length; j += gap)
		{
			int k = j - gap;
			temp = Array[j];
			while (k >= 0 && Array[k] > temp)
			{
				Array[k + gap] = Array[k];
				k -= gap;
			}
			Array[k + gap] = temp;
		}
	}
}

4、插入排序

思想:將無序區的元素比較插入到有序區適當位置,是移動不是交換,所以是穩定排序演算法

特點:穩定排序演算法,in-place sort

時間複雜度:最壞O(N^2),最優O(N)

void InsertSort(int Array[], int length)
{
	int i, j;
	int temp;

	for (i = 1; i < length; ++i)
	{
		j = i - 1;
		temp = Array[i];          //待插入資料,即無序區的第一個元素

		while ((j >= 0) && (temp < Array[j]))  //跳出迴圈的兩個條件
		{
			Array[j + 1] = Array[j];    //資料後移
			--j;
		}
		Array[j + 1] = temp;
	}
}

5、歸併排序

思想:分治法

特點:穩定排序演算法

時間複雜度:O(NlogN)

空間複雜度:O(N+logN)

void Merge(int unsorted[], int first, int mid, int last, int sorted[])
{
	int i = first, j = mid;
	int k = 0;

	while (i < mid && j < last)
	{
		if (unsorted[i] < unsorted[j])
			sorted[k++] = unsorted[i++];
		else
			sorted[k++] = unsorted[j++];
	}

	while (i < mid)
		sorted[k++] = unsorted[i++];

	while (j < last)
		sorted[k++] = unsorted[j++];

	/*2-路合併的結果,與後面一路是無序的(這兩路不在同一序列中),
	需要將前面合併的結果匯入輸入序列中,再次進行2-路合併,first表徵相對位置*/
	for (int v = 0; v < k; ++v)
		unsorted[v + first] = sorted[v];
}

/*分割及歸併排序*/
void MergeSort(int unsorted[], int first, int last, int sorted[])
{
	if (first + 1 < last)
	{
		int mid = first + ((last - first) >> 1); //mid=(left+right)>>1;當left和right比較大時,有溢位的危險
		MergeSort(unsorted, first, mid, sorted);
		MergeSort(unsorted, mid, last, sorted);
		Merge(unsorted, first, mid, last, sorted);
	}
}

//統一介面
void MergeSortTest(int Arr[], int length)
{
	int *sorted = new int[length];
	MergeSort(Arr, 0, length, sorted);
}

6、堆排序

思想:最大堆(最小堆)

特點:不穩定排序演算法

時間複雜度:O(NlogN)

空間複雜度:O(1)

#define PARENT(x) ((x-1) >> 1)
#define LEFT(x)   ((x << 1) + 1)
#define RIGHT(x)  ((x << 1) + 2)


//調整最大堆
void MaxHeapify(int Array[], int Index, int HeapSize)
{
	int L = LEFT(Index);   //Index為父節點索引
	int R = RIGHT(Index);
	int Largest;

	//選擇三(兩個)個元素(“血緣關係”)的最大值
	if (L <= HeapSize && Array[L] > Array[Index])
		Largest = L;
	else
		Largest = Index;

	if (R <= HeapSize && Array[R] > Array[Largest])
		Largest = R;

	if (Largest != Index)
	{
		swap(Array[Largest], Array[Index]);  //最大值與父節點位置交換
		MaxHeapify(Array, Largest, HeapSize);
		/*這裡的MaxHeapify()遞迴是用於最大堆的調整,從上至下調整,且只需調整一棵子樹
		構建最大堆是從陣列尾端往前,並不需要遞迴,實際上構建時也不會出現遞迴呼叫*/
	}
}

//構建最大堆
void BuildHeapify(int Array[], int HeapSize)
{
	for (int i = PARENT(HeapSize); i >= 0; --i)
		MaxHeapify(Array, i, HeapSize);       //必須單步向前
}

void HeapSort(int Array[], int length)
{
	int HeapSize = length - 1;

	BuildHeapify(Array, HeapSize);

	for (int i = HeapSize; i >= 0; --i)
	{
		swap(Array[0], Array[i]);
		--HeapSize;
		MaxHeapify(Array, 0, HeapSize);
		//刪除根節點後,,有一棵子樹滿足最大堆,有一棵不滿足,只需調整一棵子樹
	}
}

7、快速排序

思想:分治,化整為零,與基準比較

特點:不穩定排序演算法

時間複雜度:O(NlogN),如果資料本來是排序好的,最壞O(N^2)

空間複雜度:O(logN)

int Partition(int Array[], int left, int right)
{
	int L = left, R = right;
	int pivot = Array[L];

	while (L < R)
	{
		while ((L < R) && (Array[R] > pivot))
			--R;
		if (L < R)
		{
			Array[L] = Array[R];   //補數的時候,要知道空位在哪
			++L;
		}

		while ((L < R) && (Array[L] < pivot))
			++L;
		if (L < R)
		{
			Array[R] = Array[L];
			--R;
		}
	}

	Array[L] = pivot;    //補上最後的空位
	return L;
}

void QuickSort(int Array[], int left, int right)
{
	if (left < right)
	{
		int pos = Partition(Array, left, right);
		QuickSort(Array, left, pos - 1);           //注意邊界
		QuickSort(Array, pos + 1, right);
	}
}

//統一介面
void QuickSortTest(int Arr[], int length)
{
	QuickSort(Arr, 0, length - 1);
}

8、氣泡排序

思想:冒泡

特點:穩定排序演算法

時間複雜度:O(N^2)

void BubbleSort(int Array[], int length)
{
	for (int i = 0; i < length - 1; ++i)
	for (int j = 1; j < length - i; ++j)    //單次之後最大資料在最後面  
	{
		if (Array[j - 1] > Array[j])
			swap(Array[j - 1], Array[j]);
	}
}
主程式:

避免長久的等待,將氣泡排序置最後。

int main()
{
	cout << "BucketSort:  " << SortTest(BucketSortTest) << endl;
	cout << "HeapSort  :  " << SortTest(HeapSort) << endl;
	cout << "InsertSort:  " << SortTest(InsertSort) << endl;
	cout << "MergeSort :  " << SortTest(MergeSortTest) << endl;
	cout << "QuickSort :  " << SortTest(QuickSortTest) << endl;
	cout << "SelectSort:  " << SortTest(SelectSort) << endl;
	cout << "ShellSort :  " << SortTest(ShellSort) << endl;
	cout << "BubbleSort:  " << SortTest(BubbleSort) << endl;

	return 0;
}
結果:
#define LENGTH 20000   //資料量
#define MAXNUM 10000   //資料的最大值

#define LENGTH 10000   //資料量
#define MAXNUM 10000   //資料的最大值

#define LENGTH 5000   //資料量
#define MAXNUM 10000   //資料的最大值

#define LENGTH 50000   //資料量
#define MAXNUM 100000   //資料的最大值
這裡的氣泡排序需要等很久


上面執行時間的計算,受很多因素的影響,這裡只是對比各大排序的排序時間。毫無疑問快速排序是最快的。