1. 程式人生 > >排序(冒泡,插入,希爾,歸併,快排,選擇,堆排序,桶排序,基數排序,雞尾酒排序)

排序(冒泡,插入,希爾,歸併,快排,選擇,堆排序,桶排序,基數排序,雞尾酒排序)

穩定的排序 時間複雜度 空間複雜度
氣泡排序 最差、平均O(n^2),最好O(N) 1
雞尾酒排序(雙向氣泡排序) 最差、平均O(n^2),最好O(N) 1
插入排序 最差、平均O(n^2),最好O(N) 1
歸併排序 最差、平均、最好O(nlogn) n
桶排序 n k
基數排序 dn n
二叉樹排序 nlogn n
圖書館排序 nlogn (1+e)n
不穩定的排序 時間複雜度 空間複雜度
選擇排序 最差、平均n^2 1
希爾排序 nlogn 1
堆排序 最差、平均、最好O(nlogn) 1
快速排序 平均nlogn,最壞n^2 logn

快速排序

分而治之,快排最好的情況,選主元每次正好中分,T(N) = O(NLogN),最壞情況,O(n*n)

 1.選主元

取頭、中、尾的中位數

//選主元,取頭、中、尾的中位數
int median3(int A[], int left, int right)
{
	int center = (left + right) / 2;
	if(A[left] > A[center])
		swap(&A[left], &A[center]);
	if(A[left] > A[right])
		swap(&A[left], &A[right]);
	if(A[center] > A[right])
		swap(&A[center], &A[right]);
	//交換後,A[left]<=A[center]<=A[right]
	swap(&A[center], &A[right - 1]);//將pivot藏到右邊
	//只需考慮A[left+1] ... A[right-2]

	return A[right - 1];
}

2.子集劃分

特殊情況:如果有元素正好等於pivot,停下來交換。這樣的話可以儘可能將兩邊的子集劃分的均衡

 在小規模資料直接呼叫簡單排序,大規模資料就用快排

快排的過程如下:

快排就是在一趟排序後,主元到了它該去的位置,即,它左邊的數都是比它小的,右邊的數都是比它大的

#include<iostream>

using namespace std;

void swap(int* a, int* b)
{
		int temp = *a;
		*a = *b;
		*b = temp;
}

//選主元,取頭、中、尾的中位數
int median3(int A[], int left, int right)
{
	int center = (left + right) / 2;
	if(A[left] > A[center])
		swap(&A[left], &A[center]);
	if(A[left] > A[right])
		swap(&A[left], &A[right]);
	if(A[center] > A[right])
		swap(&A[center], &A[right]);
	//交換後,A[left]<=A[center]<=A[right]
	swap(&A[center], &A[right - 1]);//將pivot藏到右邊
	//只需考慮A[left+1] ... A[right-2]

	return A[right - 1];
}

void quick_sort(int A[], int left, int right)
{
	if(left >= right)
		return;

	int pivot = median3(A, left, right);	
	int i = left;
	int j = right - 1;
	for(;;)
	{
		while(i<right-1 && A[++i] < pivot){//注意i的範圍
		}

		while(j>-1 && A[--j] > pivot){//注意j的範圍,之前就就是因為i,j的範圍沒限定,導致一直有錯
		}

		if(i < j)
		{
			swap(&A[i], &A[j]);
		}
		else
		{
			break;
		}		
	}
	
	swap(&A[i], &A[right - 1]);

	quick_sort(A, left, i-1 );
	quick_sort(A, i+1 , right);
}

int main()
{
	int n;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];


	quick_sort(A, 0, n-1);
	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}


快排的另一種寫法(主元是陣列第一個數)

#include<iostream>

using namespace std;

int partion(int A[],int low,int high)
{
	int pivot = A[low];//用第一個數作為主元
	while(low < high)
	{
		while(low < high && A[high] >= pivot)
			--high;
		A[low] = A[high];
		while(low < high&&A[low] <= pivot)
			++low;
		A[high] = A[low];
	}
	A[low] = pivot;
	return low;
}

void quick_sort(int A[], int low, int high)
{
	if(low < high)
	{
		int pivot_pos = partion(A, low, high);
		quick_sort(A, low, pivot_pos - 1);
		quick_sort(A, pivot_pos + 1, high);
	}
}

int main()
{
	int n;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];


	quick_sort(A, 0, n - 1);
	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;


	delete[] A;
	return 0;
}

氣泡排序

一趟排序後,將最大的元素放到了最後,然後再對最大元素之前的進行同樣的操作

最好情況,順序,T=O(N),最壞情況,逆序,T=O(N*N)

因為氣泡排序中,在前一個元素嚴格大於後一個元素時才交換,所以他是穩定的。

#include<iostream>
using namespace std;

void swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

void bubble_sort(int A[], int n)
{
	for(int p = n - 1; p >= 0; p--)
	{
		int flag = 0;//用一個標記,如果沒改變的話,說明已經是排好序的,就不用再迴圈
		for(int i = 0; i < p; ++i)
		{
			if(A[i]>A[i + 1])
			{
				swap(&A[i], &A[i + 1]);
				flag = 1;
			}
		}
		if(0 == flag)
			break;	
	}
}

int main()
{
	int n;
	cout << "氣泡排序: " << endl;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	bubble_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

插入排序

把插入排序想象成摸撲克牌的過程,手裡首先有一張牌,A【0】,然後從A[1]開始摸,如果比前一張小,前一張就後移,直到遇到合適的位置,再將這張牌放入

最好情況,順序,T=O(N),最壞情況,逆序,T=O(N*N)

因為插入排序中,在嚴格大於時才後移,所以他是穩定的。

#include<iostream>

using namespace std;

void insertion_sort(int A[], int n)
{
	for(int p = 1; p < n; p++)
	{
		int tmp = A[p];//現在摸到的牌,要將他放到它該在的位置
		int i = p;
		for(; i>0 && A[i - 1] > tmp; i--)
		{
			A[i] = A[i - 1];
		}
		A[i] = tmp;
	}
}

int main()
{
	int n;
	cout << "插入排序 " << endl;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	insertion_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

希爾排序

希爾排序和插入排序有點像,不同在於插入時比較的數字之間的間隔不一樣

比如:81  94  11  96  12   35  17  95  28  58  41  75  15  

先每隔5間隔的數做插入排序,那麼就是 81  35  41,他們就成了 35 41 81,在陣列中就是:

35  94  11  96  12   41  17  95  28  58  81  75  15  

再對下一組“5-間隔”數 94 17 75

35  17  11  96  12   41  75  95  28  58  81  94  15  

接下來同理,結束後,從【5-間隔】變成【3-間隔】,最後是【1-間隔】

增量序列,Dm > Dm-1 > ... > D1 =1

對每個Dk進行【Dk-間隔】的排序,Dk間隔有序,Dk-1間隔排序後,不會把Dk間隔排好的序給打亂,

原始希爾排序Dm = N/2 (取下界),Dk = (Dk+1) /2,(取下界),最壞時間T = (N^2)

缺陷:增量元素不互質,小增量可能不起作用。

改進:

Hibbard增量序列:Dk = 2^k -1,相鄰元素互質,最壞T = (n^1.5),平均時間O(n^1.25)

Sedgewick增量序列:{1,5,9,41,109,。。。} 9* 4^i - 9*2^i + 1 或 4^i - 3*2^i + 1,平均時間(n^7/6),最壞時間O(n^4/3)

#include<iostream>

using namespace std;

void shell_sort(int A[], int n)
{
	for(int D = n / 2; D > 0; D = D / 2)//增量序列
	{
		for(int p = D; p < n; ++p)//插入排序
		{
			int tmp = A[p];
			int i = p;
			for(; i >= D && A[i - D]>tmp; i = i - D)//和插入排序不同的地方就是換成了D
			{
				A[i] = A[i - D];
			}
			A[i] = tmp;
		}
	}
}

int main()
{
	int n;
	cout << "希爾排序 " << endl;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	shell_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

歸併排序(遞迴和非遞迴)

歸併排序適用於外排序,它要另外開闢一個空間作為臨時的儲存,核心在於兩個有序子序列的合併。

遞迴:

#include<iostream>

using namespace std;

//核心,有序子列的歸併
void merge(int A[], int tmpA[], int l, int r, int rightend)
{
	int leftend = r - 1;
	int tmp = l;
	int nums = rightend - l + 1;//元素個數
	while(l <= leftend && r <= rightend)
	{
		if(A[l] <= A[r])
			tmpA[tmp++] = A[l++];
		else
			tmpA[tmp++] = A[r++];
	}
	while(l <= leftend)
		tmpA[tmp++] = A[l++];
	while(r <= rightend)
		tmpA[tmp++] = A[r++];

	//把元素拷回A陣列
	for(int i = 0; i < nums; i++, rightend--)
	{
		A[rightend] = tmpA[rightend];
	}
}

//遞迴
void Msort(int A[], int tmpA[], int l, int rightend)
{
	int center;
	if(l < rightend){
		center = (l + rightend) / 2;
		Msort(A, tmpA, l, center);
		Msort(A, tmpA, center+1, rightend);
		merge(A, tmpA, l, center + 1, rightend);
	}
}
void merge_sort(int A[], int n)
{
	int *tmpA;
	tmpA =(int*)malloc(n* sizeof(int));
	if(tmpA != nullptr)
	{
		Msort(A, tmpA, 0, n - 1);
		free(tmpA);
	}

}

int main()
{
	int n;
	cout << "歸併排序(遞迴) " << endl;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	merge_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

非遞迴:

#include<iostream>
using namespace std;
//和merge不同在不用從tmpA拷回A
void merge1(int A[], int tmpA[], int l, int r, int rightend)
{
	int leftend = r - 1;
	int tmp = l;
	int nums = rightend - l + 1;//元素個數
	while(l <= leftend && r <= rightend)
	{
		if(A[l] <= A[r])
			tmpA[tmp++] = A[l++];
		else
			tmpA[tmp++] = A[r++];
	}
	while(l <= leftend)
		tmpA[tmp++] = A[l++];
	while(r <= rightend)
		tmpA[tmp++] = A[r++];
}

void merge_pass(int A[], int tmpA[], int n, int length)//length,當前子序列的長度
{
	int i = 0;
	for(; i <= n - 2 * length; i = i + 2 * length)
		merge1(A, tmpA, i, i + length, i + 2 * length - 1);//A元素歸併到tmpA後,不拷回A

	if(i + length < n)//歸併最後兩個序列
		merge1(A, tmpA, i, i + length, n - 1);
	else//只剩一個序列
	{
		for(int j = i; j < n; ++j)
			tmpA[j] = A[j];
	}
}

void merge_sort(int A[], int n)
{
	int length = 1;
	int *tmpA;
	tmpA = (int*)malloc(n*sizeof(int));
	if(tmpA != nullptr)
	{
		while(length < n)
		{
			merge_pass(A, tmpA, n, length);
			length *= 2;
			merge_pass(tmpA, A, n, length);//剛好歸併回A
			length *= 2;
		}
		free(tmpA);
	}

}
int main()
{
	int n;
	cout << "歸併排序(非遞迴) " << endl;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	merge_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

選擇排序

#include<iostream>
using namespace std;

void select_sort(int A[], int n)
{
	
	for(int i = 0; i < n-1; ++i)
	{
		int min_index = i;
		for(int j = i + 1; j < n; ++j)
		{
			if(A[j] < A[min_index])
				min_index = j;
		}
		//從未排序的部分中找出值最小的,然後通過交換放到了有序部分的末尾
		int temp = A[i];
		A[i] = A[min_index];
		A[min_index] = temp;
	}
}

int main()
{
	int n;
	cout << "選擇排序 " << endl;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	select_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

堆排序

首先將陣列建成一個最大堆,然後將堆頂和陣列末尾交換,使最大的元素到末尾,再對剩下的部分調整成最大堆,再把堆頂交換到末尾,重複這個過程。

#include<iostream>
using namespace std;

void swap(int* a, int* b)
{
		int temp = *a;
		*a = *b;
		*b = temp;
}

void adjustheap(int A[], int parent, int n)
{
	int child;
	int temp = A[parent];//當前要調整的元素
	//當前要調整的元素要和他的孩子相比較,但是不能越界,所以就有如下的迴圈條件
	for(; parent*2+1 < n; parent = child)
	{
		child = parent * 2+1;
		//如果有右孩子且右孩子的值大於左孩子
		if((child +1 < n) && (A[child]) < A[child + 1])
			child++;//找到了左右孩子中更大的
		//如果temp更大,那麼現在這個位置他是可以“坐穩”的,跳出,否則就將更大的孩子提上來
		if(temp >= A[child])
			break;
		else
			A[parent] = A[child];
		//一趟結束後,parent就去到更大的孩子的下標的位置
	}
	A[parent] = temp;
}

void heap_sort(int A[], int n)
{
	for(int i = n / 2 - 1; i >= 0; i--)//從最後一個有孩子的結點開始調整,調整出一個最大堆
	{
		adjustheap(A, i, n);
	}
	//調整完之後,根結點就是最大值,將最大值交換到陣列的最後,然後對剩下的元素又重新調整成最大堆,重複這個過程
	for(int i = n - 1; i >= 1; --i)
	{
		swap(&A[0], &A[i]);
		adjustheap(A, 0, i);
	}
}

int main()
{
	int n;
	cout << "堆排序 " << endl;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	heap_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;
}

桶排序

桶排序需要知道輸入資料的範圍。假設N個學生,成績0~100,所以建立101個桶,M=101。掃描一遍所有學生的成績,將它放到對應的桶中。再掃面一遍 桶,輸出結果。時間O(M+N)。

#include<iostream>

using namespace std;

//假如是成績0~100,所以有101個桶
void bucket_sort(int A[], int n)
{
	int grade[101] = { 0 };
	for(int i = 0; i < n; ++i)
	{
		grade[A[i]]++;
	}
	for(int i = 0; i <= 100; ++i)
	{
		while(grade[i])
		{			
			cout << i << " ";
			grade[i]--;
		}

	}
}

int main()
{
	int n;
	cout << "桶排序 " << endl;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	bucket_sort(A, n);
	

	delete[] A;
	return 0;
}

基數排序

當資料範圍很大的時候,桶排序就不合適,所以用基數排序。基數排序分為最低位優先和最高位優先,以下程式碼用最低優先,即,先根據所有數的個位數排序,再對排了序的數根據十位數排序,再對排了序的數根據百位的數值大小排序……

以下程式碼基數為十進位制,最大為三位數

#include<iostream>
using namespace std;

//取一個數的個位,或者十位,或者百位……
//d=1,表示取個位,d=2取十位
int get_digit(int num, int d)
{
	int val;
	while(d--)
	{
		val = num % 10;
		num = num / 10;
	}
	return val;
}

void radix_sort(int A[], int n)
{
	int radix = 10;//十進位制
	int *count = new int[radix];//存放對應數位是0,1,2,3...的數的個數
	int *bucket = new int[n];//一個桶來存放A陣列的數,用於中轉

	for(int d = 1; d <= 3; ++d)//最大的數有多少位,就要迴圈多少次,假設最大的數在這裡是3位數,所以外層迴圈3次
	{
		//初始化為0
		for(int i = 0; i < radix; ++i)
			count[i] = 0;
		//統計數位是0,1,2,3...的數分別有多少個
		for(int i = 0; i < n; ++i)
		{
			int j = get_digit(A[i], d);
			count[j]++;
		}
		//意思是,數位為i的數,在它的前面最多有多少個數
		for(int i = 1; i < radix; ++i)
			count[i] = count[i] + count[i - 1];

		//將陣列中的數從後往前裝入桶中,保證了穩定性
		for(int i = n - 1; i >= 0; --i)
		{
			int j = get_digit(A[i], d);
			bucket[count[j] - 1] = A[i];//根據這個數的數位上是多少,就放到桶的對應的位置
			count[j]--;
		}

		//把桶中的數放回A中,完成一趟排序,下一趟就是比較A陣列各個數的十位/百位
		for(int i = 0; i < n; ++i)
		{
			A[i] = bucket[i];
		}
	}
	delete[] count;
	delete[] bucket;
}

int main()
{
	int n;
	cout << "基數排序 " << endl;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	radix_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i]<<"  ";
	cout << endl;

	delete[] A;

	return 0;
}

雞尾酒排序(參考“程式設計師小灰”)

假設一個序列:

這就是雞尾酒排序的思路。排序過程就像鐘擺一樣,第一輪從左到右,第二輪從右到左,第三輪再從左到右......

雞尾酒排序的優點是能夠在特定條件下,減少排序的回合數,而缺點是程式碼量擴大一倍。

適用於【大部分元素已經有序】。

#include<iostream>

using namespace std;

void cock_tail_sort(int A[], int n)
{
	int tmp = 0;
	for(int i = 0; i < n/2; ++i)
	{
		//有序的標記,每一輪開始時都為true
		bool flag = true;
		
		//奇數輪從左往右進行比較和交換
		for(int j = i; j < n - i - 1; ++j)
		{
			if(A[j]>A[j + 1])
			{
				tmp = A[j];
				A[j] = A[j + 1];
				A[j + 1] = tmp;
				flag = false;
			}
		}
		if(flag)
			break;

		//偶數輪,重新標記
		flag = true;
		//偶數輪,從右往左
		for(int j = n - i - 1; j > i; --j)
		{
			if(A[j] < A[j - 1])
			{
				tmp = A[j];
				A[j] = A[j - 1];
				A[j - 1] = tmp;
				flag = false;
			}
		}
		if(flag)
			break;
	}
}


int main()
{
	int n;
	cout << "雞尾酒排序: " << endl;
	cout << "輸入元素個數: ";
	cin >> n;
	int *A = new int[n];
	for(int i = 0; i < n; ++i)
		cin >> A[i];

	cock_tail_sort(A, n);

	for(int i = 0; i < n; ++i)
		cout << A[i] << "  ";
	cout << endl;

	delete[] A;
	return 0;

}