【算法之美】不要再用冒泡、選擇、插入排序了,丟不起這人!


前言
排序算法在算法與數據結構中很常見,在學習排序算法的時候,會涉及到各種核心算法的概念。例如,分治法、隨機算法、最佳、最差和平均情況分析,時空權衡等,還有數組、堆和二叉樹之類的數據結構。
通常對於一個問題,考察一個算法的好壞取決於它的 算法複雜度。而算法複雜度又分爲時間複雜度空間複雜度。時間複雜度是指:執行算法所需要的計算工作量。空間複雜度是指:執行這個算法所需要的內存空間
如果一個排序算法不需要額外的存儲空間,可以直接在原來的數組完成排序操作,這個算法可以被稱之爲 原地算法,空間複雜度是O(1)O(1)O(1)O(1)表示只需要常數量級的空間,不會隨着數組大小的變化而變化。

參考鏈接:https://leetcode-cn.com/problems/sort-an-array/solution/pai-xu-shu-zu-by-
來源:力扣(LeetCode)


1. 冒泡排序

核心思想:第一次,依次比較相鄰的兩個數,如果後面的數小,就交換位置,直到最大值冒泡到最後面;第二次,依次比較相鄰的兩個數(比到前N-1個數爲止),如果後面數小,就交換位置,直到次大值冒泡到數列倒數第二的位置。依次類推,兩個數比較大小,使得較大的數向後沉,較小的數向前冒。
時間複雜度On2O(n^2)空間複雜度O(1)O(1)
代碼實現

vector<int> sortArr(vector<int>& arr)
{
	for(int i=0;i<arr.size();i++)
		for(int j=0;j<arr.size()-i-1;j++)
		{
			if(arr[j]>arr[j+1])
				swap(arr[j],arr[j+1]);
		}
	return arr;
}

2. 選擇排序

核心思想:首先找到最大值,與最右邊的數位置互換;再找前n-1項的最大值,與第n-1個元素的位置互換;依次類推,直到完成升序排列。
時間複雜度:On2O(n^2)空間複雜度O(1)O(1)
代碼實現:

vector<int> sortArr2(vector<int>& arr)
{
	for(int i=0;i<arr.size()-1;i++)
	{
		int max_index=0;
		for(int j=0;j<arr.size()-i;j++)
		{
			if(arr[j]>arr[max_index])
				max_index=j;
		}
		swap(arr[max_index],arr[arr.size()-1-i]);
	}
	return arr;
}

3. 插入排序

核心思想:先把前 i-1 個元素按順序排好,再插入第i個元素。
時間複雜度On2O(n^2)空間複雜度O(1)O(1)
代碼實現

vector<int> sortArr3(vector<int>& arr)
{
	for(int i=1;i<arr.size();i++)  //從二個元素開始遍歷 
		for(int j=i;j>0&&arr[j]<arr[j-1];j--)
			swap(arr[j],arr[j-1]);
	return arr;	
} 

4. 快速排序

核心思想:通過劃分將待排序的序列分成前後兩部分,其中前一部分的數據都比後一部分的數據要小,然後再遞歸調用函數對兩部分的序列分別進行快速排序,以此使整個序列達到有序。
時間複雜度OnlognO(n log n)空間複雜度:最差 O(n)O(n),最優 O(logn)O(logn)
代碼實現:

class Solution {
    int partition(vector<int>& nums, int l, int r) {
        int pivot = nums[r];
        int i = l - 1;
        for (int j = l; j <= r - 1; ++j) {
            if (nums[j] <= pivot) {
                i = i + 1;
                swap(nums[i], nums[j]);
            }
        }
        swap(nums[i + 1], nums[r]);
        return i + 1;
    }
    int randomized_partition(vector<int>& nums, int l, int r) {
        int i = rand() % (r - l + 1) + l; // 隨機選一個作爲我們的主元
        swap(nums[r], nums[i]);
        return partition(nums, l, r);
    }
    void randomized_quicksort(vector<int>& nums, int l, int r) {
        if (l < r){
            int pos = randomized_partition(nums, l, r);
            randomized_quicksort(nums, l, pos - 1);
            randomized_quicksort(nums, pos + 1, r);
        }
    }
public:
    vector<int> sortArray(vector<int>& nums) {
        srand((unsigned)time(NULL));
        randomized_quicksort(nums, 0, (int)nums.size() - 1);
        return nums;
    }
};

5. 堆排列

核心思想:先將待排序的序列建成 大根堆,使得每個父節點的元素大於等於它的子節點。此時整個序列最大值即爲堆頂元素,我們將其與末尾元素交換,使末尾元素爲最大值,然後再調整堆頂元素使得剩下的 n-1 個元素仍爲大根堆,再重複執行以上操作我們即能得到一個有序的序列。
時間複雜度O(nlogn)O(nlogn)空間複雜度O(1)O(1),只需要常數的空間存放若干變量
代碼實現

class Solution {
    void maxHeapify(vector<int>& nums, int i, int len) {
        for (; (i << 1) + 1 <= len;) {
            int lson = (i << 1) + 1;
            int rson = (i << 1) + 2;
            int large;
            if (lson <= len && nums[lson] > nums[i]) {
                large = lson;
            }
            else {
                large = i;
            }
            if (rson <= len && nums[rson] > nums[large]) {
                large = rson;
            }
            if (large != i) {
                swap(nums[i], nums[large]);
                i = large;
            }
            else break;
        }
    }
    void buildMaxHeap(vector<int>& nums, int len) {
        for (int i = len / 2; i >= 0; --i) {
            maxHeapify(nums, i, len);
        }
    }
    void heapSort(vector<int>& nums) {
        int len = (int)nums.size() - 1;
        buildMaxHeap(nums, len);
        for (int i = len; i >= 1; --i) {
            swap(nums[i], nums[0]);
            len -= 1;
            maxHeapify(nums, 0, len);
        }
    }
public:
    vector<int> sortArray(vector<int>& nums) {
        heapSort(nums);
        return nums;
    }
};

6. 歸併排列

核心思想:利用了分治的思想來對序列進行排序。對一個長爲 n 的待排序的序列,我們將其分解成兩個長度爲 n/2 的子序列。每次先遞歸調用函數使兩個子序列有序,然後我們再線性合併兩個有序的子序列使整個序列有序。
時間複雜度nlong(n)nlong(n)空間複雜度O(n)O(n)
程序實現

class Solution {
    vector<int> tmp;
    void mergeSort(vector<int>& nums, int l, int r) {
        if (l >= r) return;
        int mid = (l + r) >> 1;
        mergeSort(nums, l, mid);
        mergeSort(nums, mid + 1, r);
        int i = l, j = mid + 1;
        int cnt = 0;
        while (i <= mid && j <= r) {
            if (nums[i] < nums[j]) {
                tmp[cnt++] = nums[i++];
            }
            else {
                tmp[cnt++] = nums[j++];
            }
        }
        while (i <= mid) tmp[cnt++] = nums[i++];
        while (j <= r) tmp[cnt++] = nums[j++];
        for (int i = 0; i < r - l + 1; ++i) nums[i + l] = tmp[i];
    }
public:
    vector<int> sortArray(vector<int>& nums) {
        tmp.resize((int)nums.size(), 0);
        mergeSort(nums, 0, (int)nums.size() - 1);
        return nums;
    }
};

7. 希爾排序

核心思想:希爾排序可以看作是一個冒泡排序或者插入排序的變形。希爾排序在每次的排序的時候都把數組拆分成若干個序列,一個序列的相鄰的元素索引相隔固定的距離gap,每一輪對這些序列進行冒泡或者插入排序,然後再縮小gap得到新的序列一一排序,直到gap爲1。
時間複雜度O(n43)O(n^\frac{4}{3}) ~ O(n2)O(n^2), 空間複雜度:O(1)O(1)
代碼實現

vector<int> shellSor(vector<int>& arr) {
    int gap = arr.size() >> 1;
    while (gap > 0) {
        for (int i = 0; i < gap; i++) {                        // 對每個子序列進行排序
            for (int j = i+gap; j < arr.size(); j+=gap) {     // 插入排序的部分
                int temp = j;
                while (temp > i && arr[temp] < arr[temp-gap]) {
                    swap(arr[temp], arr[temp-gap]);
                    temp -= gap;
                }
            }
        }
        gap >>= 1;
    }
    return arr;
}

8. 優化後的快排函數sort()

當然,除了以上排序算法外。有時爲了方便,我們還可以調用標準庫algorithm中的排序函數sort(),該函數在原有快速排序算法上進行了改進,我們可以直接使用它進行排序,默認是升序。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

bool compare(int a, int b){
	return a>b;
}
//使用自帶排序功能sort(),compare決定升序還是降序。
vector<int> sortArr(vector<int>& arr){
	sort(arr.begin(),arr.end());  //升序
	//sort(arr.begin(),arr.end(),compare);  //降序
	return arr; 
} 

int main(){
	int n=0;
	cin>>n;  //輸入序列元素的個數
	vector<int> arr_num(n);
	for(int i=0; i<n;i++)
		cin>>arr_num[i];  //輸入元素
	arr_num=sortArr(arr_num);  //排序
	for(int i=0; i<n; i++){
		cout<<arr_num[i]<<" ";  //打印輸出
	}
	return 0;
} 

除此之外,還有計數排序桶排序基數排序二叉樹排序等。具體要選擇哪一種排序算法,則要根據實際問題,權衡時間和空間複雜度,儘量選擇綜合性能更好的排序算法。

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