排序--面經

視頻學習網址:點擊打開鏈接

參考書籍《算法導論》

Pre比較:

理論時間:

                          堆排序      歸併排序        快速排序
最壞時間        O(nlogn)     O(nlogn)        O(n^2)
最好時間        O(nlogn)     O(nlogn)        O(nlogn)
平均時間        O(nlogn)     O(nlogn)        O(nlogn)

空間複雜度     O(1)             O(n)              ???這裏不對啊,我覺得是O(1)啊(ps:這裏的空間複雜度還包括了遞歸的過程,由於快排是遞歸實現的,所以有log(n)到n的空間複雜度,堆排序如果用遞歸實現也是logn的空間複雜度,不過堆排序可以改成非遞歸實現)


實際測量:

4種非平方級的排序:
希爾排序,堆排序,歸併排序,快速排序

我測試的平均排序時間:數據是隨機整數,時間單位是秒
數據規模    快速排序    歸併排序    希爾排序    堆排序
1000萬       0.75           1.22          1.77          3.57
5000萬       3.78           6.29          9.48         26.54  
1億             7.65          13.06        18.79        61.31

堆排序是最差的。
這是算法硬傷,沒辦法的。因爲每次取一個最大值和堆底部的數據(記爲X)交換,重新篩選堆,把堆頂的X調整到位,有很大可能是依舊調整到堆的底部(堆的底部X顯然是比較小的數,纔會在底部),然後再次和堆頂最大值交換,再調整下來。
從上面看出,堆排序做了許多無用功。


排序算法穩定性定義:假定在待排序的記錄序列中,存在多個具有相同的關鍵字的記錄,若經過排序,這些記錄的相對次序保持不變,即在原序列中,ri=rj,且ri在rj之前,而在排序後的序列中,ri仍在rj之前,則稱這種排序算法是穩定的;否則稱爲不穩定的。

堆排序、快速排序、希爾排序、直接選擇排序   不是穩定的排序算法,(快排和直接選擇排序都是在發生交換的時候破壞的,希爾排序由於步長大於1時開頭就會跳過一些元素,堆排序在維護堆的時候會破壞穩定性)

而基數排序、冒泡排序、直接插入排序、折半插入排序、歸併排序、計數排序   是穩定的排序算法。

參考:百度百科

參考視頻:點擊打開鏈接

1 冒泡排序:兩兩比較,大的放後;關注for循環中的兩個變量的變化範圍;時間複雜度O(n^2)

#include <iostream>

class BubbleSort
{
public:
	BubbleSort(){};//默認構造函數
	BubbleSort(BubbleSort &other){};//拷貝構造函數

	//冒泡排序函數
	int *bubbleSort(int *pa, int n);
private:

};


//冒泡排序函數
int *BubbleSort::bubbleSort(int *pa, int n)
{
	int temp = 0;
	//for循環,兩兩比較排序
	for (int j = 0; j < n - 1; j++)
	{
		for (int k = 0; k < n - 1 - j; k++)
		{
			if (pa[k]>pa[k + 1])
			{
				temp = pa[k];
				pa[k] = pa[k + 1];
				pa[k + 1] = temp;
			}
		}
	}

	//返回
	return pa;
}

2 插入排序:逐個選取數組中元素,與前面元素比較插入;時間複雜度O(n^2)

class InsertionSort {
public:
	int* insertionSort(int* A, int n) 
	{
		// write code here
		int key = 0;
		//遍歷數組中每一個元素
		for (int i = 1; i < n; i++)
		{
			key = A[i];
			int j = i - 1;
			//與前面的元素逐個比較,大於則插入
			while (j>=0 && A[j]>key)
			{
				A[j + 1] = A[j];
				j--;
			}
			A[j + 1] = key;
		}

		return A;
	};

private:

};

3 選擇排序:選擇最大值,放到數組的後面;時間複雜度O(n^2)

class SelectionSort {
public:
	int* selectionSort(int* A, int n) {
		// write code here
		int maxKey = 0;
		int maxInd = 0;
		int temp = 0;
		//每次選擇最大的,放在數組的後面
		for (int i = n - 1; i >= 0; i--)
		{	
			maxKey = A[0];
			maxInd = 0;

			//找出最大的
			for (int j = 0; j <= i; j++)
			{
				if (A[j] > maxKey)
				{
					maxKey = A[j];
					maxInd = j;
				}
			}

			//交換 最大值 和 查找範圍末尾位置的值
			temp = A[i];
			A[i] = A[maxInd];
			A[maxInd] = temp;
		}
		return A;
	}
	
};

4 歸併排序:遞歸分解爲子問題,將子問題合併。時間複雜度O(nlogn),空間上需要動態分配內存輔助,額外的空間需求

class MergeSort
{
public:
	int *mergeSort(int *A,int n)
	{
		mergeSortAC(A, 0, n - 1);
		return A;
	}
	
private:
	//將pa指向的數組,劃分成兩個小數組,排序後再合併
	int mergeSortAC(int *pa, int a, int c);

	//合併函數
	int merge(int *pa, int a, int b, int c);
};


//將pa指向的數組,劃分成兩個小數組,排序後再合併
int MergeSort::mergeSortAC(int *pa, int a, int c)
{
	//邊界條件:只有一個元素,返回
	if (a >= c) return 0;

	int b = (a + c) >> 1;

	//對[a,b],[b+1,c]的兩個數組分別排序,遞歸調用
	mergeSortAC(pa, a, b);
	mergeSortAC(pa, b + 1, c);

	//合併兩個已經排好序的數組
	merge(pa, a, b, c);

	return 0;
}
//合併函數
int MergeSort::merge(int *pa, int a, int b, int c)
{
	//分配兩個動態數組
	int *pb = new int[b - a + 2];
	assert(pb != NULL);
	int *pc = new int[c - b + 1];
	assert(pc != NULL);

	//將要排序的部分複製到動態數組中
	int i = a;
	int j = b + 1;

	while (i <= b)
	{
		pb[i - a] = pa[i];
		i++;
	}
	while (j <= c)
	{
		pc[j - b - 1] = pa[j];
		j++;
	}

	//爲動態數組添加末尾無窮大值
	pb[b - a + 1] = INT_MAX;
	pc[c - b] = INT_MAX;

	//逐個比較複製到pa中
	i = j = 0;
	for (int k = a; k <= c; k++)
	{
		if (pb[i] < pc[j])
		{
			pa[k] = pb[i];
			i++;
		}
		else
		{
			pa[k] = pc[j];
			j++;
		}
	}

	//釋放動態內存
	delete[] pb;
	delete[] pc;

	//返回
	return 0;

}

5 快速排序:隨機抽取一個數,小的放左邊,大的放右邊。利用小於等於區間,可以原址排序。使用隨機函數,引入隨機選擇。理論最差情況,時間複雜度O(n^2),期望複雜度是O(nlogn),實際使用中,快排的速度是最快的,而且由於是原址排序,所以空間需求少,不像歸併排序需要O(n)的輔助空間。

#include <stdlib.h>
#include <time.h>

class QuickSort
{
public:
	int *quickSort(int *A, int n)
	{
		quickSortBC(A,0,n-1);
		return A;
	}
	int *quickSortBC(int *A, int b, int c)
	{
		//邊界條件
		if (b >= c) return 0;

		int i = 0;
		i = partition(A, b, c);//劃分

		quickSortBC(A, b, i-1);//對劃分的子集,快速排序
		quickSortBC(A, i+1, c);

		return 0;
	}
	//劃分函數
	int partition(int *A, int b, int c)
	{
		//邊界條件
		if (b >= c) return 0;

		//爲快排加入隨機選擇
		srand((unsigned)time(0));
		int ind = b + rand() % (c - b + 1);//ind取值範圍[b,c]
		int key = A[ind];
		//將ind對應的元素,換到數組末尾
		A[ind] = A[c];
		A[c] = key;

		int temp = key;
		int i = b - 1;
		int j = b - 1;
		while (j < c)
		{
			j++;
			if (A[j] <= key)
			{
				i++;
				temp = A[i];
				A[i] = A[j];
				A[j] = temp;
			}
		}

		//返回key值的索引
		return i;
	}

private:

};


6 堆排序:利用最大堆,每次取堆根,然後維護堆;時間複雜度上界是O(nlogn),堆排在實際中並不快,做了很多無用的比較;

class HeapSort
{
public:
	int *heapSort(int *A, int n);
private:
	int buildHeap(int *A, int size);//建堆
	int maxHeap(int *A, int ind, int size);//維護最大堆
	inline int exchange(int *A, int a, int b);//交換兩個元素
};


int *HeapSort::heapSort(int *A, int n)
{
	//將最小元素換到A[0],因爲A[0]是不參與建堆的
	int minEle = A[0];
	int ind = 0;
	for (int i = 0; i < n; i++)
	{
		if (minEle>A[i])
		{
			minEle = A[i];
			ind = i;
		}
	}
	A[ind] = A[0];
	A[0] = minEle;

	//建堆
	buildHeap(A, n - 1);

	//循環取堆根放到數組A末尾,維護堆
	for (int size = n - 1; size > 1;)
	{
		exchange(A, 1, size);
		size--;
		maxHeap(A, 1, size);
	}

	return A;
}
int HeapSort::buildHeap(int *A, int size)//建堆
{
	//from size/2 to 1
	for (int i = size >> 1; i > 0; i--)
	{
		maxHeap(A, i, size);
	}

	return 0;
}
int HeapSort::maxHeap(int *A, int ind, int size)//維護最大堆
{
	int largest = ind;
	int left = (ind << 1);
	int right = (ind << 1) + 1;

	if (left <= size && A[left] > A[ind])//與左孩子比較
	{
		largest = left;
	}
	if (right <= size && A[right] > A[largest])//與右孩子比較
	{
		largest = right;
	}
	if (largest != ind)
	{
		exchange(A, largest, ind);
		maxHeap(A, largest, size);
	}
	return 0;
}
inline int HeapSort::exchange(int *A, int a, int b)
{
	int temp = A[a];
	A[a] = A[b];
	A[b] = temp;
	return 0;
}


7 希爾排序




















發佈了59 篇原創文章 · 獲贊 112 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章