排序算法

注意:以下所有的排序都是按照遞增的順序排序
參考鏈接:https://www.cnblogs.com/fnlingnzb-learner/p/9374732.html

1、選擇排序

選擇排序很簡單,就是先遍歷數組中的Size個元素,找到最小值放在第一位,然後在遍歷數組中後Size-1個元素,找到最小值放在第二位,一次類推。

時間複雜度:O(n*n),空間複雜度:O(1),不穩定(舉例說明:序列5,8,5,3,9,第一次會將第一個5和3互換位置,這樣兩個5的相對位置就改變了)

void SelectSort(vector<int>& nums) {
    int Size = (int)nums.size();
	for (int i = 0; i < Size - 1; ++i)
	{
		int Min = i;
		for (int j = i + 1; j < Size; ++j)
			Min = nums[Min] > nums[j] ? j : Min;
		swap(nums[i], nums[Min]);
	}
}

2、冒泡排序

冒泡排序就是每一次比較兩個相鄰的元素的大小,如果不是遞增順序,就調換位置。其實整個過程是和選擇排序相反的過程,每一次大循環,找到未排序元素中最大的元素,放置到最後。

時間複雜度:O(n*n),空間複雜度:O(1),穩定

void BubbleSort(vector<int>& nums) {
	int Size = (int)nums.size();
	for (int i = 0; i < Size; ++i)
    //每循環一次,都可以找到前Size - i個元素中最大的元素,並放到nums[Size - i - 1]的位置上
	{
		for (int j = 0; j < Size - 1 - i; ++j)
			if (nums[j] > nums[j + 1])
				swap(nums[j], nums[j + 1]);
	}
}

3、插入排序

可以理解爲前若干個元素是已經排好序的,將當前元素插入到前面若干個元素中,組成一個新的數組。

時間複雜度:O(n)~O(n*n),空間複雜度:O(1),穩定

時間複雜度隨着數組有序性越好,複雜度越低。

void InsertSort(vector<int>& nums) {
	int Size = (int)nums.size();
	for (int i = 1; i < Size; ++i)
    //每次認爲前i個元素是排好序的子數組,然後將第i個元素插入到前i個元素中,也就是比他大的就交換元素位置
	{
		for (int j = i; j >= 1 && nums[j] < nums[j - 1]; --j)
			swap(nums[j], nums[j - 1]);
	}
}

4、希爾排序

希爾排序其實是優化版的插入排序,主要思想就是將輸入數組用gap(增量)劃分成若干個子數組,也就是0,gap,2×gap…是一組,1,gap+1,2×gap + 1…是一組,以此類推。然後將gap增加,最後子數組就合併成一個數組了。具體可以參考這篇文章,寫的通俗易懂:https://blog.csdn.net/qq_39207948/article/details/80006224

插入排序之所以可以進行優化,是因爲插入排序有這樣一個特性,就是時間複雜度在n~n×n之間,如果數組排序性越好,那麼時間複雜度越低。所以先將數組劃分成小數組,這樣即使是n×n複雜度,也不會很大,因爲數組個數少(也就是n小)。然後不斷合併,最後大數組的排序性比較好了,所以時間複雜度也就降低了。

時間複雜度:O(n)~O(n×n),空間複雜度:O(1),不穩定(舉例說明:序列7,5,5,8在第一次循環時,7和第二個5換位,就改變了兩個5的相對順序)

//這樣寫更容易理解,其實最快的只用一個函數來寫
void InsertSort(vector<int>& nums, int gap, int index)
{
	for (int i = index; i - gap >= 0 && nums[i] < nums[i - gap]; i -= gap)
		swap(nums[i], nums[i - gap]);
}

void ShellSort(vector<int>& nums) {
	int Size = (int)nums.size();
	for (int gap = Size / 2; gap >= 1; gap /= 2)
		for (int i = gap; i < Size; ++i)
		{
			InsertSort(nums, gap, i);
		}
}

5、歸併排序

歸併排序就是先分成小數組,再排序,所以需要記住的是主要函數Merge中是先分,再排序,這個遞歸需要記住。我自己想到在排序的步驟中可以用插入排序的方法,空間複雜度明顯降低,但是時間複雜度不穩定。

時間複雜度:O(nlog2n),空間複雜度:O(n),穩定

void Sort(vector<int>& nums, int start, int middle, int end)
{
    //插入排序方法,空間複雜度爲1,但是時間複雜度不穩定
	/*for (int i = middle + 1; i <= end; ++i)
		for (int j = i - 1; j >= start && nums[j + 1] < nums[j]; --j)
			swap(nums[j + 1], nums[j]);*/
    //一般的歸併排序的方法,時間複雜度穩定
	vector<int> v1;
	vector<int> v2;
	for (int i = start; i <= middle; ++i)
		v1.push_back(nums[i]);
	for (int i = middle + 1; i <= end; ++i)
		v2.push_back(nums[i]);
	v1.push_back(INT_MAX);
	v2.push_back(INT_MAX);
	int m = 0, n = 0;
	for (int i = start; i <= end; ++i)
	{
		if (v1[m] > v2[n])
			nums[i] = v2[n++];
		else nums[i] = v1[m++];
	}
}

void Merge(vector<int>& nums, int start, int end)
{
	if (start < end)
	{
		int middle = start + (end - start) / 2;
		Merge(nums, start, middle);
		Merge(nums, middle + 1, end);
		Sort(nums, start, middle, end);
	}
}

void MergeSort(vector<int>& nums)
{
	int Size = nums.size();
	Merge(nums, 0, Size - 1);
}

6、快速排序

快速排序和歸併排序是反的,歸併排序是先分,然後再排序,快速排序是先按照中間那個數,將數組分成前後兩個數組,相當於先排序,再分。

時間複雜度:O(nlog2n)~O(n×n),空間複雜度:O(1)

int Sort(vector<int>& nums, int start, int end, int val)
{
	int j = start;
	for (int i = start + 1; i <= end; ++i)
		if (nums[i] < val)
		{
			j++;
			swap(nums[i], nums[j]);//這裏用了swap避免多餘的空間佔用
		}
	swap(nums[start], nums[j]);//這裏是將中間值nums[start]放到兩個子數組的中間。
	return j;
}

void Merge(vector<int>& nums, int start, int end)
{
	if (start < end)
	{
		int val = nums[start];//這裏做了簡化,每次用來劃分子數組的值選取start那個,爲了方便後面的劃分
		int middle = Sort(nums, start, end, val);
		Merge(nums, start, middle - 1);
		Merge(nums, middle + 1, end);
	}
}

void FastSort(vector<int>& nums)
{
	int Size = nums.size();
	Merge(nums, 0, Size - 1);
}

快排的變種:

①當快排分割到尺寸較小的子數組時,使用插入排序更快,在STL中的排序就是這樣優化的。

②三數取中,在快排中,時間複雜度主要取決於選取的切分值是否合適,這個值如果是較大或者較小值,那麼切分得到的兩個子數組會形成一個較大,一個較小的尺寸,不利於後續分割。所以如果切分值選取中位數是最好的,但是這樣比較困難,所以可以在數組中選取三個值,比如開頭,中間和末尾,比較選取這三個數中中間的那個值作爲切分值,更合理。

③三向切分,當數組中有大量重複元素時,可以使用這種優化,就是將一個數組分成三個,小於切分值,等於切分值,大於切分值這三種情況來看。

代碼如下:

pair<int, int> Sort(vector<int>& nums, int start, int end, int val)
{
    //j可以理解爲0~j-1都是小於val的,k+1~end都是大於val的
	int j = start, i = start + 1, k = end;
	while (i <= k)
		if (nums[i] < val)//小於切分值的放左邊
		{
			swap(nums[i++], nums[j++]);
		}
		else if (nums[i] > val)//大於切分值的放右邊
		{
			swap(nums[i], nums[k--]);
		}
		else i++;//等於的不動
	return pair<int, int>(j, k);
}

void Merge(vector<int>& nums, int start, int end)
{
	if (start < end)
	{
		int val = nums[start];//這裏做了簡化,每次用來劃分子數組的值選取start那個,爲了方便後面的劃分
		pair<int, int> middle = Sort(nums, start, end, val);
		Merge(nums, start, middle.first - 1);
		Merge(nums, middle.second + 1, end);
	}
}

void FastSort(vector<int>& nums)
{
	int Size = nums.size();
	Merge(nums, 0, Size - 1);
}

④可以用stl自帶的partition函數來寫快排,但是是差不多的。

7、堆排序

如果想要升序排列,就建立最大堆,然後將堆頂的元素放到數組最後,不斷減小堆的尺寸。

其實整個堆排序的過程是將原數組就看成一個二叉樹,然後不斷使用調整堆結構的函數,使得這個二叉樹具有最大堆的性質

時間複雜度:O(NlogN),空間複雜度:O(1),不穩定。

void AdjustHeap(vector<int>& nums, int index, int Size)//調整堆的結構,將下標爲index的元素放到堆中合理的位置
{
	int lchild = index * 2 + 1;
	while (lchild < Size)
	{
        //找到子節點中較大的那個
		if (lchild + 1 < Size && nums[lchild] < nums[lchild + 1])
			++lchild;
        //將子節點和父節點比較,如果子節點較大,就和父節點互換位置
		if (nums[lchild] <= nums[index]) break;
		else {
			swap(nums[lchild], nums[index]);
			index = lchild;
			lchild = index * 2 + 1;
		}
	}
}

void MakeHeap(vector<int>& nums, int Size)
{
    //注意這裏的起始位置是(Size - 2) / 2,這裏代表最後一個元素(Size - 1)的父節點是
    //((Size - 1) - 1) / 2,所以起始位置就是這個
	for (int i = (Size - 2) / 2; i >= 0; --i)
		AdjustHeap(nums, i, Size);
}

void HeapSort(vector<int>& nums)//堆排序就是每次構建一個最大堆,然後將最大堆的堆頂元素放到堆的最後一個元素,然後堆尺寸減1,繼續構建最大堆。
{
	int Size = nums.size();
	MakeHeap(nums, Size);
	for (int i = Size - 1; i > 0; --i)
	{
		swap(nums[i], nums[0]);	
		MakeHeap(nums, i);
	}
}

8、桶排序

桶排序就是用哈希表來排序,比如如下就是10以內不重複的同排序簡單寫法:

void TongSort(int *score,int num)
{
    int a[11];
    for(int i=0;i<=10;i++)
        a[i]=0;

    for(int i=0;i<num;i++)
    {
        int temp=score[i];
        ++a[temp];
    }

    for(int i=0;i<11;i++)
    {
        int num_print=a[i];
        for(int j=1;j<=num_print;j++)
           cout<<i<<" ";
    }

}

9、計數排序

這種方法我覺得和桶排序差不多,我在桶排序中的例子也算是基數排序的例子,就是用一個足夠大的數組,將元素作爲數組的下標,但是桶排序可以有更復雜的表示方法,所以基數排序更簡單。

10、基數排序

就是隻用一個大小爲10的數組,從各位,十位,百位的順序開始遍歷.如圖所示:
在這裏插入圖片描述
代碼如下:

/*
*求數據的最大位數,決定循環的次數
*/
int maxbit(vector<int> data)
{
	int Size = data.size();
	int bit = 1, div = 10;
	for (int i = 0; i < Size - 1; ++i)
	{
		while (data[i] > div)
		{
			bit++;
			div *= 10;
		}
	}
	return div;
}
void radixsort(vector<int> &arr){
	const int buckets_number = 10;
	vector<vector<int> > buckets(10);//10個桶,每個桶是vector<int>
	int len = maxbit(arr);
	int r = 1;
	for (int i = 0; i < len; i++){
		for (int x : arr){
			int tmp = x / r;
			int index = tmp%buckets_number;
			buckets[index].push_back(x);
		}
		int j = 0;
		for (int k = 0; k < 10; ++k){
			for (int x : buckets[k]){
				arr[j] = x;
				j++;
			}
			buckets[k].clear();
		}
		r = r * 10;
	}
}

11、總結

在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章