資料結構第九節(排序(上))

W&B發表於2020-12-13

排序

簡單排序(差勁排序)

下面的幾種排序都是非常簡單容易實現的,不過也有著相應的問題,他們在正常的情況下排序,時間複雜度幾乎都是\(O(N^2)\)

氣泡排序

冒泡冒泡,大的沉底,小的浮起。
基本思路是迴圈n-1次,每次把最大的元素沉底。
從j遍歷到i,如果j比他的下一個元素(j+1)大,就交換他們。還有一個小技巧判斷整個序列是否已經有序,無需繼續執行。首先每次迴圈預設有序,如果便利完了都沒有發生交換,說明已經有序退出即可。
演算法最優時間複雜度\(O(N)\),整個序列完全有序。
演算法最壞時間複雜度\(O(N^2)\),整個序列完全逆序。

//氣泡排序
void Bubble_Sort(int array[],int n) {
	for (int i = n-1; i >= 0; i--)
	{
		bool isend = true;
		for (int j = 0; j < i; j++)
		{
			//swap j and j+1
			if (array[j] > array[j + 1]) {
				int temp = array[j];
				array[j] = array[j+1];
				array[j + 1] = temp;
				isend = false;
			}
		}
		if (isend) {
			break;
		}
	}
}

插入排序

基本的原理類似於我們玩撲克時的插排。
每次從未排序的陣列中選一個,插到前面已經有序的序列裡。初始從array[1]開始,向前掃描,如果array[0]如果比array[1]大,把0移到1的位置上,迴圈n-1次。
演算法最優時間複雜度\(O(N)\),整個序列完全有序。
演算法最壞時間複雜度\(O(N^2)\),整個序列完全逆序。

//插入排序
void Insertion_Sort(int array[], int n) {
	for (int i = 1; i < n; i++)
	{
		int temp = array[i];
		int j = 0;
		for (j = i; j > 0&&array[j-1]>temp; j--)
		{
			array[j] = array[j - 1];
		}
		array[j] = temp;
	}
}

選擇排序

非常沙雕的一個排序,雖然很容易理解,但實在是太糟糕了。
基本思路是迴圈i:0到n-1,每次從陣列中找到最小的放在i的的位置。
演算法時間複雜度始終為\(O(N^2)\)

//選擇排序
void Selection_Sort(int array[], int n) {
	for (int i = 0; i < n-1; i++)
	{
		int point = i;
		for (int j = i + 1; j < n; j++) {
			if (array[j] < array[point]) {
				point = j;
			}
		}
		//swap
		if (point != i) {
			int temp = array[i];
			array[i] = array[point];
			array[point] = temp;
		}
	}
}

高階排序

下面等級重排序相比上面略微難理解一些,不過相應的,他們的時間複雜度都有了質的變化。

希爾排序

作為插入排序的進階版,速度提有了很大的提升,但面對特定的序列時可能會與選擇排序達到同樣的時間複雜。
基本思路是做一個增量序列{\(k_1,k_2,k_3,k_4,k_5...k_n\)}。
迴圈的i:1到k,每次以\(k_i\)為間隔做選擇排序。在初始的版本中,選擇的增量序列為陣列元素個數的一半,每次迴圈變為原來的一半,直到為1時結束。不過這樣的方法因為選定的增量序列不互質,給定特殊的序列例如{\(1,9,2,10,3,11,4,12\)}時,我們發現直到增量變為1時,壓根就沒有起效果,比插入排序還慢。
選擇合理的增量序列有時候會有奇效,選擇不好的增量序列時也會非常頭疼。
傳統的希爾排序演算法最壞時間複雜度\(O(N^2)\)
wiki中有更詳細的其他的增量序列

//希爾排序
void Shell_Sort(int array[], int n) {
	//i為增量序列,每隔i個的元素做插入排序
	for (int i = n/2; i > 0; i /=2)
	{
		for (int j = i; j < n; j++)
		{
			int temp = array[j];
			int k = 0;
			for (k = j; k>=i && array[k-i]>temp; k-=i)
			{
				array[k] = array[k - i];
			}
			array[k] = temp;
		}
	}
}

堆排序

基本思路是先將傳入的陣列調成一個最大堆,每次將堆頂元素拿出,與堆的末尾元素交換,再將堆的容量減1 ,再接著調整堆,直到堆變空。

//調整堆
void Adjustment(int array[],int n,int k) {
	//下標K代表陣列元素array[k]空缺
	int temp = array[k];
	for (int j = k * 2 + 1; j < n; j = 2 * j + 1)
	{
		if (j + 1 < n && array[j + 1] > array[j]) {
			j++;
		}
		if (array[j] > temp) {
			array[k] = array[j];
			k = j;
		}
		else {
			break;
		}
	}
	array[k] = temp;
}
//堆排序
void Heap_Sort(int array[], int n) {
	//使陣列變成最大堆
	for (int i = n/2-1; i >= 0; i--)
	{
		Adjustment(array, n, i);
	}
	//每次交換最大元素array[0]和堆的最後一位,每次堆大小減1
	for (int i = n-1; i > 0; i--)
	{
		int temp = array[i];
		array[i] = array[0];
		array[0] = temp;
		Adjustment(array, i, 0);
	}
}

歸併排序

歸併排序是一種典型的分而治之思想,基本思路是遞迴的將一個陣列一分為2,將左右排好序後,合併他們。對於每個一分為2的陣列又可以接著遞迴,直到要分的陣列大小已經為1便不再去分。
我們都知到呼叫地規並不是一個好方法,可能會系統爆棧,這裡通過迴圈實現。
思路是,一個大小為N的陣列,把他想成N個大小為1的有序陣列,每次迴圈兩個兩個合併起來成為大小為2的有序陣列 ,再接著合併大小為4的有序陣列,直到合併為一整個陣列,也實現了整個陣列的排序。該方法坑略多,具體操作在程式碼中加了註釋。

//合併2個有序子序列
void Merge(int array[],int temparray[],int head1,int head2,int rightend) {
	int leftend = head2 - 1;
	int i = head1, j = head2, k = head1;
	//迴圈條件為要合併的兩個小陣列都不空
	while (i<=leftend&&j<=rightend)
	{
		if (array[i] <= array[j]) {
			temparray[k++] = array[i];
			i++;
		}
		else {
			temparray[k++] = array[j];
			j++;
		}
	}
	//將剩餘元素填入
	while (i <= leftend)
	{
		temparray[k++] = array[i++];
	}
	while (j <= rightend)
	{
		temparray[k++] = array[j++];
	}
	//將臨時陣列中的元素移回原陣列
	for (i = head1; i <= rightend; i++)
	{
		array[i] = temparray[i];
	}
}
//按長度length歸併
void Merge_Pass(int array[], int temparray[],int n,int length) {
	int i;
	//完全的兩兩合併
	for (i = 0; i <= n-2*length; i += 2 * length)
	{
		Merge(array, temparray, i, i + length, i + 2 * length - 1);
	}
	//殘缺的兩兩合併
	if (i + length < n) {
		Merge(array, temparray, i, i + length, n-1);
	}
	//殘的一個都沒有,直接倒回去
	else {
		for (int j = 0; j < n; j++)
		{
			temparray[j] = array[j];
		}
	}
}
//歸併排序
void Merge_Sort(int array[], int n) {
	int length = 1;
	int* temparray = (int*)malloc(n * sizeof(array));
	if (temparray != NULL) {
		while (length<n)
		{
			Merge_Pass(array, temparray, n, length);
			length *= 2;
			Merge_Pass(temparray, array, n, length);
			length *= 2;
		}
		free(temparray);
	}
}

三個小題

09-排序1 排序 (25point(s))

給定N個(長整型範圍內的)整數,要求輸出從小到大排序後的結果。

本題旨在測試各種不同的排序演算法在各種資料情況下的表現。各組測試資料特點如下:

資料1:只有1個元素;
資料2:11個不相同的整數,測試基本正確性;
資料3:103個隨機整數;
資料4:104個隨機整數;
資料5:105個隨機整數;
資料6:105個順序整數;
資料7:105個逆序整數;
資料8:105個基本有序的整數;
資料9:105個隨機正整數,每個數字不超過1000。

輸入格式:
輸入第一行給出正整數N(≤10
​5
​​ ),隨後一行給出N個(長整型範圍內的)整數,其間以空格分隔。

輸出格式:
在一行中輸出從小到大排序後的結果,數字間以1個空格分隔,行末不得有多餘空格。

輸入樣例:

11
4 981 10 -17 0 -20 29 50 8 43 -5

輸出樣例:

-20 -17 -5 0 4 8 10 29 43 50 981

題解:
沒什麼好說的,自行嘗試上面的各種排序(部分排序因為過於慢,會導致長時間判斷中)

#include<algorithm>
#include<cstdbool>
#include<cstdio>
using namespace std;

int arr[100000];
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &arr[i]);
	}
	sort(arr, arr + n);
	bool isf = true;
	for (int i = 0; i < n; i++)
	{
		if (isf) {
			printf("%d", arr[i]);
			isf = false;
		}
		else {
			printf(" %d", arr[i]);
		}
	}
}

09-排序2 Insert or Merge (25point(s))

According to Wikipedia:

Insertion sort iterates, consuming one input element each repetition, and growing a sorted output list. Each iteration, insertion sort removes one element from the input data, finds the location it belongs within the sorted list, and inserts it there. It repeats until no input elements remain.

Merge sort works as follows: Divide the unsorted list into N sublists, each containing 1 element (a list of 1 element is considered sorted). Then repeatedly merge two adjacent sublists to produce new sorted sublists until there is only 1 sublist remaining.

Now given the initial sequence of integers, together with a sequence which is a result of several iterations of some sorting method, can you tell which sorting method we are using?

Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤100). Then in the next line, N integers are given as the initial sequence. The last line contains the partially sorted sequence of the N numbers. It is assumed that the target sequence is always ascending. All the numbers in a line are separated by a space.

Output Specification:
For each test case, print in the first line either "Insertion Sort" or "Merge Sort" to indicate the method used to obtain the partial result. Then run this method for one more iteration and output in the second line the resuling sequence. It is guaranteed that the answer is unique for each test case. All the numbers in a line must be separated by a space, and there must be no extra space at the end of the line.

Sample Input 1:

10
3 1 2 8 7 5 9 4 6 0
1 2 3 7 8 5 9 4 6 0

Sample Output 1:

Insertion Sort
1 2 3 5 7 8 9 4 6 0

Sample Input 2:

10
3 1 2 8 7 5 9 4 0 6
1 3 2 8 5 7 4 9 0 6

Sample Output 2:

Merge Sort
1 2 3 8 4 5 7 9 0 6

題解:
根據插入排序的性質,可以很容易判斷出插入排序。
首先從0開始掃描,直到掃描到逆序的點,接著從逆序點p開始向後掃描到n-1,如果array1和array2都一樣,說明一定是插入排序,否則為歸併排序。
對插入排序,再對逆序的位置插一次。
對歸併排序,我們先分析一下要能分清楚插入還是歸併,肯定至少經過一步,但是也不能完全排序,所以歸併至少每2個元素組成的陣列有序,那麼,如何更高的判斷4個的情況呢,就是取中間值,如果長度為n的小陣列中點小於他的前節點,就說明是無序的,且排到有序長度正好為該長度的一半。
所以從2開始,每次加2倍的2,判斷是否達到了4。{2,6,10,14......}。
如果4成功通過,接著{4,12,20......},依次類推。

#include<algorithm>
#include<cstdbool>
#include<cstdio>
#define MAXSIZE 101
using namespace std;

//調整堆
void Adjustment(int array[], int n, int k) {
	//下標K代表陣列元素array[k]空缺
	int temp = array[k];
	for (int j = k * 2 + 1; j < n; j = 2 * j + 1)
	{
		if (j + 1 < n && array[j + 1] > array[j]) {
			j++;
		}
		if (array[j] > temp) {
			array[k] = array[j];
			k = j;
		}
		else {
			break;
		}
	}
	array[k] = temp;
}
//列印陣列
void PrintArray(int arr[], int n) {
	bool isf = true;
	for (int i = 0; i < n; i++)
	{
		if (isf) {
			printf("%d", arr[i]);
			isf = false;
		}
		else {
			printf(" %d", arr[i]);
		}
	}
}

//合併2個有序子序列
void Merge(int array[], int temparray[], int head1, int head2, int rightend) {
	int leftend = head2 - 1;
	int i = head1, j = head2, k = head1;
	//迴圈條件為要合併的兩個小陣列都不空
	while (i <= leftend && j <= rightend)
	{
		if (array[i] <= array[j]) {
			temparray[k++] = array[i];
			i++;
		}
		else {
			temparray[k++] = array[j];
			j++;
		}
	}
	//將剩餘元素填入
	while (i <= leftend)
	{
		temparray[k++] = array[i++];
	}
	while (j <= rightend)
	{
		temparray[k++] = array[j++];
	}
	//將臨時陣列中的元素移回原陣列
	for (i = head1; i <= rightend; i++)
	{
		array[i] = temparray[i];
	}
}
//按長度length歸併
void Merge_Pass(int array[], int temparray[], int n, int length) {
	int i;
	//完全的兩兩合併
	for (i = 0; i <= n - 2 * length; i += 2 * length)
	{
		Merge(array, temparray, i, i + length, i + 2 * length - 1);
	}
	//殘缺的兩兩合併
	if (i + length < n) {
		Merge(array, temparray, i, i + length, n - 1);
	}
}

int main() {
	int array1[MAXSIZE];
	int array2[MAXSIZE];
	int n;
	//read array
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &array1[i]);
	}
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &array2[i]);
	}
	//check insertion or heap sort
	int p;
	for (p = 0; p < n - 1; p++)
	{
		if (array2[p] > array2[p + 1]) {
			p++;
			break;
		}
	}
	bool isinsertion = true;
	for (int i = p; i < n; i++)
	{
		if (array1[i] != array2[i]) {
			isinsertion = false;
			break;
		}
	}
	//get next step sort
	if (isinsertion) {
		int temp = array2[p];
		int j = 0;
		for (j = p; j > 0 && array2[j - 1] > temp; j--)
		{
			array2[j] = array2[j - 1];
		}
		array2[j] = temp;
		printf("Insertion Sort\n");
		PrintArray(array2, n);
	}
	else {
		bool con = true;
		int i;
		for (i = 2; con ; i*=2)
		{
			for (int j = i; j < n; j+=i*2)
			{
				if (array2[j - 1] > array2[j]) {
					con = false;
				}
			}
		}
		Merge_Pass(array2, array1, n, i/2);
		printf("Merge Sort\n");
		PrintArray(array2, n);
	}
}

09-排序3 Insertion or Heap Sort (25point(s))

According to Wikipedia:

Insertion sort iterates, consuming one input element each repetition, and growing a sorted output list. Each iteration, insertion sort removes one element from the input data, finds the location it belongs within the sorted list, and inserts it there. It repeats until no input elements remain.

Heap sort divides its input into a sorted and an unsorted region, and it iteratively shrinks the unsorted region by extracting the largest element and moving that to the sorted region. it involves the use of a heap data structure rather than a linear-time search to find the maximum.

Now given the initial sequence of integers, together with a sequence which is a result of several iterations of some sorting method, can you tell which sorting method we are using?

Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤100). Then in the next line, N integers are given as the initial sequence. The last line contains the partially sorted sequence of the N numbers. It is assumed that the target sequence is always ascending. All the numbers in a line are separated by a space.

Output Specification:
For each test case, print in the first line either "Insertion Sort" or "Heap Sort" to indicate the method used to obtain the partial result. Then run this method for one more iteration and output in the second line the resulting sequence. It is guaranteed that the answer is unique for each test case. All the numbers in a line must be separated by a space, and there must be no extra space at the end of the line.

Sample Input 1:

10
3 1 2 8 7 5 9 4 6 0
1 2 3 7 8 5 9 4 6 0

Sample Output 1:

Insertion Sort
1 2 3 5 7 8 9 4 6 0

Sample Input 2:

10
3 1 2 8 7 5 9 4 6 0
6 4 5 1 0 3 2 7 8 9

Sample Output 2:

Heap Sort
5 4 3 1 0 2 6 7 8 9

題解:
根據插入排序的性質,可以很容易判斷出插入排序。
首先從0開始掃描,直到掃描到逆序的點,接著從逆序點p開始向後掃描到n-1,如果array1和array2都一樣,說明一定是插入排序,否則為堆排序。
對插入排序,再對逆序的位置插一次。
對堆排序,先從後掃描直到小於堆頂,交換2個元素,並且調整堆,即可獲得下一次結果。

#include<algorithm>
#include<cstdbool>
#include<cstdio>
#define MAXSIZE 101
using namespace std;

//調整堆
void Adjustment(int array[], int n, int k) {
	//下標K代表陣列元素array[k]空缺
	int temp = array[k];
	for (int j = k * 2 + 1; j < n; j = 2 * j + 1)
	{
		if (j + 1 < n && array[j + 1] > array[j]) {
			j++;
		}
		if (array[j] > temp) {
			array[k] = array[j];
			k = j;
		}
		else {
			break;
		}
	}
	array[k] = temp;
}
//列印陣列
void PrintArray(int arr[],int n) {
	bool isf = true;
	for (int i = 0; i < n; i++)
	{
		if (isf) {
			printf("%d", arr[i]);
			isf = false;
		}
		else {
			printf(" %d", arr[i]);
		}
	}
}
int main() {
	int array1[MAXSIZE];
	int array2[MAXSIZE];
	int n;
	//read array
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &array1[i]);
	}
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &array2[i]);
	}
	//check insertion or heap sort
	int p;
	for (p = 0; p < n-1; p++)
	{
		if (array2[p] > array2[p + 1]) {
			break;
		}
	}
	bool isinsertion = true;
	for (int i = p+1; i < n; i++)
	{
		if (array1[i] != array2[i]) {
			isinsertion = false;
			break;
		}
	}
	//get next step sort
	if (isinsertion) {
		int temp = array2[p+1];
		int j = 0;
		for (j = p + 1; j > 0 && array2[j - 1] > temp; j--)
		{
			array2[j] = array2[j - 1];
		}
		array2[j] = temp;
		printf("Insertion Sort\n");
	}
	else {
		for (int i = n-1; i >= 0; i--)
		{
			if (array2[0] > array2[i]) {
				int temp = array2[i];
				array2[i] = array2[0];
				array2[0] = temp;
				Adjustment(array2, i, 0);
				break;
			}
		}
		printf("Heap Sort\n");
	}
	PrintArray(array2, n);
}

相關文章