算法實現-排序相關

#pragma once
#include <vector>
#include <string>
#include <iostream>

using namespace std;

namespace Sort
{
	// 基本思想:通過一趟排序將數分割爲左,右兩部分,並選擇出基數,使得左邊的都得基數小,右邊的都比基數大,並依此遞歸操作。
	// 1. 快速排序是分治思想的又一典型代表,是應用最廣的排序算法。
	// 2. 分治思想就是把原問題的解分解爲兩個或多個子問題解,求解出子問題的解之後再構造出原
	// 問題的解。
	// 3. 在快速排序算法中,它的思想是把一個待排序的數組分成前半部分和後半部分,並且要求
	// 前半部分的值都大於等於或都小於等於後半部分的解, 當前半部分與後半部分都變成有序(通
	// 過遞歸調用快速排序來實現)後,我們就不需要合併兩個子問題的解就已經得到了原問題的解。
	// 這也是爲什麼要求前半部分都大於等於或都小於等於後半部分的原因。
	// 4.所以呢,快速排序的核心在於如何把一個待排序的數組分成兩部分!
	//
	// 核心點:
	// 1. 如何把待排序的數組劃分爲符合要求的兩部分!
	// 2. 期望的時間複雜度爲O(NlogN), 最壞的時間複雜度爲O(N*N)
	// 3. 快速排序爲原址排序,不需要額外的內存空間.
	// 4. 快速排序不是穩定排序, 在交換過程中會破壞穩定性。
	// 快速排序算法
	int GetIndex(vector<int>& arr, int left, int right);
	void QuickSort(vector<int>& arr, int left, int right)
	{
		if (left < right)
		{
			// 獲取基準
			int index = GetIndex(arr, left, right);
			QuickSort(arr, left, index - 1); // 遞歸排序基準右邊
			QuickSort(arr, index + 1, right); // 遞歸排序基準右邊
		}
	}

	// 快速排序獲取基準
	int GetIndex(vector<int>& arr, int left, int right)
	{
		int pivot = arr[left];
		while (left < right)
		{
			while (left < right && arr[right] > pivot) // 先找基準左邊較小的一個數
				right--;
			arr[left] = arr[right]; // 先找基準左邊較小的一個數,並和基準位置交換

			while (left < right && arr[left] <= pivot) // 先找基準右邊較大的一個數
				left++;
			arr[right] = arr[left]; // 先找基準右邊較大的一個數,並和上一次空閒的基準右邊的較大值位置交換
		}
		arr[left] = pivot; // 最後將基準放在合適的位置

		return left;
	}


	/*歸併排序:
	算法思想:分而治之(分三步驟)
	第一, 分解: 把待排序的 n 個元素的序列分解成兩個子序列, 每個子序列包括 n/2 個元素.
	第二, 解決: 對每個子序列分別調用歸併排序MergeSort, 進行遞歸操作
	第三, 合併: 合併兩個排好序的子序列,生成排序結果.
	時間複雜度分析:
	第一步:分解,時間複雜度爲θ(1)
	第二步:解決,對每個子序列進行遞歸操作。時間複雜度爲:2T(n/2)
	第三步:合併,類似於插入操作,子序列有n個元素,最多操作n次,時間複雜度爲θ(n)
	總的時間複雜度爲:T(n)=2T(n/2)+θ(n).由遞歸樹得時間複雜度爲O(nlog n).
	————————————————*/
	// 歸併排序
	void Merge(vector<int>& arr, int left, int mid, int right);
	void MergeSort(vector<int>& arr, int left, int right)
	{
		if (left < right)
		{
			int mid = (right + left) / 2;
			MergeSort(arr, left, mid);		// 對中間位置右邊進行遞歸歸併
			MergeSort(arr, mid + 1, right); // 對中間位置左邊進行遞歸歸併
			Merge(arr, left, mid, right);	// 合併左右兩個已經排序的序列
		}
	}

	// 合併兩個子序列,mid是兩個子序列中間邊界,mid索引歸左邊
	// 合併子序列需要藉助最大right-left+1長度的輔助空間
	void Merge(vector<int>& arr, int left, int mid, int right)
	{
		int i = 0; // 輔助空間的索引
		vector<int> vt_temp(right - left + 1, 0);

		// 比較兩個子序列,將較小值一一拷貝到輔助空間,直到某一個序列先被比較完
		int l = left, r = mid + 1;
		while (l <= mid && r <= right)
		{
			if (arr[l] > arr[r])
				vt_temp[i++] = arr[r++];
			else
				vt_temp[i++] = arr[l++];
		}

		// 假設左邊的序列沒比完,將剩下的拷貝到輔助空間
		while (l <= mid)
		{
			vt_temp[i++] = arr[l++];
		}

		// 假右邊的序列沒比完,將剩下的拷貝到輔助空間
		while (r <= right)
		{
			vt_temp[i++] = arr[r++];
		}

		// 將輔助空間的序列拷貝到arr
		i = 0;
		while (left <= right)
		{
			arr[left++] = vt_temp[i++];
		}
	}
	

	//交換兩個元素的值
	void Swap(int&i, int& j)
	{
		int temp = i;
		i = j;
		j = temp;
	}

	/*
	堆排序是指利用堆這種數據結構所設計的一種選擇排序算法。
	堆是一種近似完全二叉樹的結構(通常堆是通過一維數組來實現的),並滿足性質:以最大堆(也叫大根堆、大頂堆)爲例,其中父結點的值總是大於它的孩子節點。
	實際的物理結構是數組。堆排序可以用到上一次的排序結果,所以不像其他一般的排序方法一樣,每次都要進行n-1次的比較,複雜度爲O(nlogn)。
	堆排序的時間複雜度O(N*logN),額外空間複雜度O(1),是一個不穩定性的排序。

	在第i個元素的索引爲i的情形中:
	性質一:父結點索引爲(i-1)/2,整型時會省略小數部分
	性質一:左孩子索引爲 (2*i+1);
	性質二:右孩子索引 (2*i+2);

	堆排序思想:
	1)利用給定數組創建一個堆,輸出堆頂元素
	2)以最後一個元素代替堆頂,調整成堆,輸出堆頂元素
	3)把堆的尺寸縮小1
	4)重複步驟2,直到堆的尺寸爲1
	*/
	// 堆排序的核心是建堆,傳入參數爲數組,根節點位置,數組長度
	void HeapBuild(vector<int>& vect, int root, int len)
	{
		int lChild = 2 * root + 1; //根節點的左子結點下標
		if (lChild < len) //左子結點下標不能超出數組的長度
		{
			int maxIndex = lChild; // 保存左右節點中較大值的下標
			int rChild = lChild + 1; // 根節點的右子結點下標
			if (rChild < len) //右子結點下標不能超出數組的長度(如果有的話)
			{
				if (vect[lChild] < vect[rChild])
				{
					maxIndex = rChild;
				}
			}

			if (vect[root] < vect[maxIndex]) // 父節點的值小於子節點中較大的,需要交互父節點與該節點的值
			{
				// 交換父結點和比父結點大的最大子節點
				Swap(vect[root], vect[maxIndex]);

				//從此次最大子節點的那個位置開始遞歸建堆
				HeapBuild(vect, maxIndex, len);
			}
		}
	}

	// 堆排序-大根堆
	void HeapSort(vector<int>& vect)
	{
		int len = vect.size();
		if (len < 2)
			return;

		// 1. 無序數組建堆
		// 上升調整堆結構
		// 從(n/2-1) --> 0逐次遍歷。遍歷之後,得到的數組實際上是一個(最大)二叉堆。
		// len-1 爲數組最後一個元素下標,((len-1)-1)/2滿足最後一個非葉子節點的索引,從最後一個非葉子節點的父結點開始建堆
		for (int i = ((len - 1) - 1) / 2; i >= 0; --i)
		{
			HeapBuild(vect, i, len);
		}

		for (int j = len - 1; j >= 0; --j) //j表示數組此時的長度,因爲len長度已經建過了,從len-1開始
		{
			Swap(vect[0], vect[j]);		// 2.交換首尾元素,將最大值交換到數組的最後位置保存
			HeapBuild(vect, 0, j);		// 3.尺寸縮小,去除最後位置的元素重新建堆,此處j表示數組的長度,第一次調整時,最後一個位置下標變爲len-2
		}
	}

	// 打印
	void PritSortStr(const vector<int>& vect, string fun)
	{
		printf("%s: ", fun.c_str());
		for (auto it : vect)
		{
			printf("%d, ", it);
		}
		printf("\n");
	}
}

 

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