1. 程式人生 > >各種排序演算法(In-place sort)

各種排序演算法(In-place sort)

常用排序演算法的時間複雜度和空間複雜度表格


1.選擇排序

思想:每次找一個最小值。
#include <iostream>
using namespace std;
//從小到大排序
void SelectSort(int a[], int n)
{
	int index, temp;
	for (int i = 0; i < n - 1; i++)	//執行(n-1) 次
	{
		index = i;
		for (int j = i + 1; j<n; j++)//執行(n-1)次  每個a[i]都要與a[i+1]至a[n-1]做比較
		{
			if (a[index] > a[j])	//記錄序列中最小值的位置  
			{
				index = j;
			}
		}
		if (index != i)		//如果無序序列中第一個記錄不是最小值,則進行交換  
		{
			temp = a[index];
			a[index] = a[i];
			a[i] = temp;
		}
	}
}
int main()
{
	int a[10], i, n = 10, num = 10;
	for (i = 0; i < n; i++)
		a[i] = num--;
	cout << "原序列:\n";
	for (i = 0; i < n; i++)
		cout << a[i] << "  ";
	SelectSort(a, n); cout << "排序後:\n"; for (i = 0; i < n; i++)cout << a[i] << " "; 
	return 0;
}
//優化排序
//如果在每一次查詢最小值的時候,也可以找到一個最大值,然後將兩者分別放在它們應該出現的位置,這樣遍歷的次數就比較少了,下邊
//給出程式碼實現:
void SelectSort2(int a[], int n)
{
	int left = 0; int right = n - 1; int min = left;//儲存最小值的下標
	int max = left;//儲存最大值的下標
	while (left <= right)
	{
		min = left;
		max = left; 
		for (int i = left; i <= right; ++i)
		{
			if (a[i] < a[min])
				min = i;
if (a[i] > a[max])
max = i; 
		}
		swap(a[left], a[min]); 
		if (left == max)
			max = min; 
		swap(a[right], a[max]); 
		++left; 
		--right;
	}
}
	//遞迴版
void RecursiveSelectSort(int a[], int start, int end)
{
	if (start < end)
	{
		int temp = a[start];
		int index = start;
		for (int i = start + 1; i < end; i++)
		{
			if (a[index] > a[i])
			{
				index = i;
			}
		}
		if (start != index)
		{
			temp = a[start];
			a[start] = a[index];
			a[index] = temp;
		}
		start++;
		RecursiveSelectSort(a, start, end);
	}
}


2.堆排序
思想:一是建立堆,二是堆頂與堆的最後一個元素交換位置。所以堆排序有兩個函式組成。一是建堆的滲透函式,

二是反覆呼叫滲透函式實現排序的函式。


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

void HeapAdjust(int *a, int i, int size)  //調整堆 
{
	int lchild = 2 * i;         //i的左孩子節點序號 
	int rchild = 2 * i + 1;     //i的右孩子節點序號 
	int max = i;                //臨時變數 
	if (i <= size / 2)          //如果i不是葉節點就不用進行調整 
	{
		if (lchild <= size&&a[lchild] > a[max])
		{
			max = lchild;
		}
		if (rchild <= size&&a[rchild] > a[max])
		{
			max = rchild;
		}
		if (max != i)
		{
			swap(a[i], a[max]);
			HeapAdjust(a, max, size);    //避免調整之後以max為父節點的子樹不是堆 
		}
	}
}

void BuildHeap(int *a, int size)    //建立堆 
{
	int i;
	for (i = size / 2; i >= 1; i--)    //非葉節點最大序號值為size/2 
	{
		HeapAdjust(a, i, size);
	}
}

void HeapSort(int *a, int size)    //堆排序 
{
	int i;
	BuildHeap(a, size);
	for (i = size; i >= 1; i--)
	{
		swap(a[1], a[i]);           //交換堆頂和最後一個元素,即每次將剩餘元素中的最大者放到最後面 
		HeapAdjust(a, 1, i - 1);      //重新調整堆頂節點成為大頂堆
	}
}


3.氣泡排序

思想:通過兩兩交換,像水中的泡泡一樣,小的先冒出來,大的後冒出來,每一次都有一個相對的最大值沉底。


void BubbleSort(int a[], int n)
{
	int temp;
	for (int i = 0; i < n - 1; i++)   //執行(n-1)次 每一次冒泡 都有一個最大值沉底
	{
		for (int j = 0; j< n - i - 1; j++)  //執行(n-1)次 冒泡的次數  決定冒泡的位置
		{
			if (a[j]>a[j + 1])
			{
				temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
	}
}

//改進的氣泡排序
//最佳執行時間:O(n)
//最壞執行時間:O(n^2)
void BubbleSort2(int a[], int n)
{
	int temp,flag = 0;
	for (int i = 0; i < n - 1; i++)   //每一次冒泡 都有一個最大值沉底
	{
		for (int j = 0; j<n - i - 1; j++)  //冒泡的次數  決定冒泡的位置
		{
			if (a[j]>a[j + 1])
			{
				flag = 1;
				temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
		if (flag == 0)
			break;		//沒有資料交換 已經排好序了
	}
}
//改進2傳統氣泡排序中每一趟排序操作只能找到一個最大值或最小值,我們考慮利用在每趟排序中進行正向和反向兩遍冒泡的方法一次
//可以得到兩個最終值(最大者和最小者) , 從而使排序趟數幾乎減少了一半。
void Bubble_2(int a[], int n)
{
	int low = 0;
	int high = n - 1; //設定變數的初始值  
	int tmp, j;
	while (low < high) 
	{
		for (j = low; j< high; ++j) //正向冒泡,找到最大者  
		if (a[j]> a[j + 1])
		{
			tmp = a[j]; 
			a[j] = a[j + 1];
			a[j + 1] = tmp;
		}
		--high;                 //修改high值, 前移一位  
		for (j = high; j>low; --j) //反向冒泡,找到最小者  
		if (a[j] < a[j - 1]) 
		{
			tmp = a[j]; 
			a[j] = a[j - 1];
			a[j - 1] = tmp;
		}
		++low;                  //修改low值,後移一位  
	}
}
//遞迴氣泡排序
void RecursiveBubbleSort(int a[], int start, int end)
{
	if (start < end) //迴圈結束條件一為start == end
	{
		int temp = 0;
		int length = end - start + 1;
		for (int i = start; i < length - 1; i++)	//迴圈結束條件二為i < length - 1;  
		{
			if (a[i] < a[i + 1]) 
			{
				temp = a[i];
				a[i] = a[i + 1];
				a[i + 1] = temp;
			}
		}
		end--;
		RecursiveBubbleSort(a, start, end);
	}
}

4.快速排序
思想:選擇一個基準元素,通常選擇第一個元素或者最後一個元素,通過一趟排序講待排序的記錄分割成獨立的兩部分,其中一部分記錄的元素值均比基準元素值小。另一部分記錄的 元素值比基準值大。用同樣的方法繼續進行排序,直到整個序列有序。


void Swap(int *p1, int *p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
void QuickSort(int *arr, int ileft, int iright, int length)
{
	int i = ileft;//從左邊開始迴圈  
	int j = iright + 1;//從右邊開始迴圈  

	if (i < j)
	{
		do
		{
			do
			{
				i++;
			} while (arr[i] <= arr[ileft] && i <= iright);
			do
			{
				j--;
			} while (arr[j] >= arr[ileft] && j > ileft);
			if (i < j)
			{
				Swap(&arr[i], &arr[j]);
			}
		} while (i < j);
		Swap(&arr[ileft], &arr[j]);
		QuickSort(arr, ileft, j - 1, 0);
		QuickSort(arr, j + 1, iright, 0);
	}
}
//改進快排
void Swap(int *p1, int *p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

int partition(int a[], int low, int high)
{
	int privotKey = a[low];                 //基準元素  
	while (low < high){                   //從表的兩端交替地向中間掃描  
		while (low < high  && a[high] >= privotKey)
			--high; //從high 所指位置向前搜尋,至多到low+1 位置。將比基準元素小的交換到低端  
		Swap(&a[low], &a[high]);
		while (low < high  && a[low] <= privotKey) 
			++low;
		Swap(&a[low], &a[high]);
	}
	return low;
}


void qsort_improve(int r[], int low, int high, int k)
{
	if (high - low > k)
	{ //長度大於k時遞迴, k為指定的數  
		int pivot = partition(r, low, high); // 呼叫的Partition演算法保持不變  
		qsort_improve(r, low, pivot - 1, k);
		qsort_improve(r, pivot + 1, high, k);
	}
}
void quickSort(int r[], int n, int k)
{
	qsort_improve(r, 0, n, k);//先呼叫改進演算法Qsort使之基本有序  

	//再用插入排序對基本有序序列排序  
	for (int i = 1; i <= n; i++){
		int tmp = r[i];
		int j = i - 1;
		while (tmp < r[j])
		{
			r[j + 1] = r[j]; 
			j = j - 1;
		}
		r[j + 1] = tmp;
	}

}


5.插入排序
思想:假設待排序的記錄存放在陣列R[1..n]中。初始時,R[1]自成1個有序區,無序區為R[2..n]。從i=2起直至i=n為止,依次將R[i]插入當前的有序區R[1..i-1]中,生成含n個記錄的有序區。


void InsertSort(int a[], int n)
{
	for (int i = 2; i <=n; i++)   //外迴圈(n-1)次 
	{
		int j = i - 1;			 // 從下標為1開始
		a[0] = a[i];			 //每個數都要與a[0](相當於key)比較			
		while (a[0]<a[j] && j >0)     //比a[0]大,則替換
		{
			a[j + 1] = a[j];
			j--;			 //向前移動一位,再進行比較
		}
		a[j + 1] = a[0];
	}
	
}
int main()
{
	int a[20], i, n =10;
	int num = 10;
	for (i = 1; i <=10; i++)
		a[i] = num--;
	cout << "原序列:\n";
	for (i = 1; i <=n; i++)
		cout << a[i] << "  ";
	cout << endl;

	InsertSort(a, 10);

	cout << "排序後:\n";
	for (i = 1; i <=n; i++)
		cout << a[i] << "  ";
	return 0;
}


6.希爾排序
思想:先取一個小於n的整數d1作為第一個增量,把檔案的全部記錄分成d1個組。所有距離為d1的倍數的記錄放///在同一個組中。先在各組內進行直接插入排序;然後,取第二個增量d2<d1重複上述的分組和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有記錄放在同一組中進行直接插入排序為止。


void ShellSort(int a[], int n)
{
	int d = n / 2;
	while (d >= 1)
	{
		for (int i = 2 + d; i <= n; i++)
		{
			int j = i - d;
			a[0] = a[i];
			while (j > 0 && a[0] < a[j])
			{
				a[j + d] = a[j];
				j = j - d;
			}
			a[j + d] = a[0];
		}
		d = d / 2;
	}
}
int main()
{
	int a[20], i, n =10;
	int num = 10;
	for (i = 1; i <=10; i++)
		a[i] = num--;
	cout << "原序列:\n";
	for (i = 1; i <=n; i++)
		cout << a[i] << "  ";
	cout << endl;

	ShellSort(a, 10);

	cout << "排序後:\n";
	for (i = 1; i <=n; i++)
		cout << a[i] << "  ";
	return 0;
}


7.歸併排序法
思想:將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分為若干個子序列,每個子序列是有序的。然後再把有序子序列合併為整體有序序列。


//將r[i…m]和r[m +1 …n]歸併到輔助陣列b[i…n]  
void Merge(int *a, int *b, int i, int m, int n)
{
	int j, k;
	for (j = m + 1, k = i; i <= m && j <= n; ++k)
	{
		if (a[j] < a[i])
			b[k] = a[j++];
		else
			b[k] = a[i++];
	}
	while (i <= m)
		b[k++] = a[i++];
	while (j <= n)
		b[k++] = a[j++];
}

void MergeSort(int *a, int *b, int lenght)
{
	int len = 1;
	int *q = a;
	int *tmp;
	while (len < lenght)
	{
		int s = len;
		len = 2 * s;
		int i = 0;
		while (i + len < lenght)
		{
			Merge(q, b, i, i + s - 1, i + len - 1); //對等長的兩個子表合併  
			i = i + len;
		}
		if (i + s < lenght)
			Merge(q, b, i, i + s - 1, lenght - 1); //對不等長的兩個子表合併  

		tmp = q; q = b; b = tmp; //交換q,b,以保證下一趟歸併時,仍從q 歸併到b  
	}
}
int main()
{
	int a[10] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	int b[10];
	cout << "原序列:\n";
	for (int i = 0; i < 10; i++)
		cout << a[i] << "  ";
	cout << endl;
	MergeSort(a, b, 10);
	cout << "排序後:\n";
	for (int i = 0; i < 10; i++)
		cout << a[i] << "  ";
	return 0;
}
//兩路歸併
//思想設兩個有序的子檔案(相當於輸入堆)放在同一向量中相鄰的位置上:R[low..m],R[m+1..high],先將它們合併到
//一個區域性的暫存向量R1(相當於輸出堆)中,待合併完成後將R1複製回R[low..high]中。
//merge two subArray,one is A[i1]~A[j1],another is A[i2]~A[j2]  
void MergeTwoArray(int A[], int i1, int j1, int i2, int j2)
{
	int *tmp = new int[j2 - i1 + 1];
	int i = i1, j = i2, k = 0;
	while (i <= j1 && j <= j2)
	{
		//add samller one into tmp arrary  
		if (A[i] <= A[j])
		{
			tmp[k++] = A[i++];
		}
		else
		{
			tmp[k++] = A[j++];
		}
	}
	while (i <= j1)
		tmp[k++] = A[i++];
	while (j <= j2)
		tmp[k++] = A[j++];


	for (i = 0; i < k; i++)
	{
		A[i1++] = tmp[i];
	}
	delete[]tmp;


}


void MergeSort(int A[], int n)
{
	int i1, j1, i2, j2 = 0;
	int size = 1;
	while (size < n)
	{
		i1 = 0;
		while (i1 + size < n)//存在兩個序列,那就需要合併  
		{
			//確定兩個序列的邊界  
			j1 = i1 + size - 1;
			i2 = i1 + size;
			if (i2 + size - 1 > n - 1)
			{
				j2 = n - 1;
			}
			else
				j2 = i2 + size - 1;
			MergeTwoArray(A, i1, j1, i2, j2);
			//更新i1  
			i1 = j2 + 1;
		}
		size *= 2;
	}
}

int main()
{
	int a[10] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	cout << "原序列:\n";
	for (int i = 0; i < 10; i++)
		cout << a[i] << "  ";
	cout << endl;
	MergeSort(a, 10);
	cout << "排序後:\n";
	for (int i = 0; i < 10; i++)
		cout << a[i] << "  ";
	return 0;
}


8.(桶)基數排序
思想:基數排序是通過“分配”和“收集”過程來實現排序。


int getdigit(int x, int d)
{
	int a[] = { 1, 1, 10 };     //因為待排資料最大資料也只是兩位數,所以在此只需要到十位就滿足
	return ((x / a[d]) % 10);    //確定桶號
}

void msdradix_sort(int arr[], int begin, int end, int d)
{
	const int radix = 10;
	int count[radix], i, j;
	//置空
	for (i = 0; i < radix; ++i)
	{
		count[i] = 0;
	}
	//分配桶儲存空間
	int *bucket = (int *)malloc((end - begin + 1) * sizeof(int));
	//統計各桶需要裝的元素的個數  
	for (i = begin; i <= end; ++i)
	{
		count[getdigit(arr[i], d)]++;
	}
	//求出桶的邊界索引,count[i]值為第i個桶的右邊界索引+1
	for (i = 1; i < radix; ++i)
	{
		count[i] = count[i] + count[i - 1];
	}
	//這裡要從右向左掃描,保證排序穩定性 
	for (i = end; i >= begin; --i)
	{
		j = getdigit(arr[i], d);      //求出關鍵碼的第d位的數字, 例如:576的第3位是5   
		bucket[count[j] - 1] = arr[i];   //放入對應的桶中,count[j]-1是第j個桶的右邊界索引   
		--count[j];                    //第j個桶放下一個元素的位置(右邊界索引+1)   
	}
	//注意:此時count[i]為第i個桶左邊界    
	//從各個桶中收集資料  
	for (i = begin, j = 0; i <= end; ++i, ++j)
	{
		arr[i] = bucket[j];
	}
	//釋放儲存空間
	free(bucket);
	//對各桶中資料進行再排序
	for (i = 0; i < radix; i++)
	{
		int p1 = begin + count[i];         //第i個桶的左邊界   
		int p2 = begin + count[i + 1] - 1;     //第i個桶的右邊界   
		if (p1 < p2 && d > 1)
		{
			msdradix_sort(arr, p1, p2, d - 1);  //對第i個桶遞迴呼叫,進行基數排序,數位降 1    
		}
	}
}
int main()
{
	int a[10] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	cout << "原序列:\n";
	for (int i = 0; i < 10; i++)
		cout << a[i] << "  ";
	cout << endl;
	msdradix_sort(a, 0, 10 - 1, 2);
	cout << "排序後:\n";
	for (int i = 0; i < 10; i++)
		cout << a[i] << "  ";
	return 0;
}