前言
排序算法在算法與數據結構中很常見,在學習排序算法的時候,會涉及到各種核心算法的概念。例如,分治法、隨機算法、最佳、最差和平均情況分析,時空權衡等,還有數組、堆和二叉樹之類的數據結構。
通常對於一個問題,考察一個算法的好壞取決於它的 算法複雜度
。而算法複雜度又分爲時間複雜度
和空間複雜度
。時間複雜度是指:執行算法所需要的計算工作量
。空間複雜度是指:執行這個算法所需要的內存空間
。
如果一個排序算法不需要額外的存儲空間,可以直接在原來的數組完成排序操作,這個算法可以被稱之爲 原地算法
,空間複雜度是。表示只需要常數量級
的空間,不會隨着數組大小的變化而變化。
參考鏈接:https://leetcode-cn.com/problems/sort-an-array/solution/pai-xu-shu-zu-by-
來源:力扣(LeetCode)
1. 冒泡排序
核心思想:第一次,依次比較相鄰的兩個數,如果後面的數小,就交換位置,直到最大值冒泡到最後面;第二次,依次比較相鄰的兩個數(比到前N-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個元素的位置互換;依次類推,直到完成升序排列。
時間複雜度:, 空間複雜度:
代碼實現:
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個元素。
時間複雜度:, 空間複雜度:
代碼實現:
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. 快速排序
核心思想:通過劃分將待排序的序列分成前後兩部分,其中前一部分的數據都比後一部分的數據要小,然後再遞歸調用函數對兩部分的序列分別進行快速排序,以此使整個序列達到有序。
時間複雜度:, 空間複雜度:最差 ,最優
代碼實現:
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 個元素仍爲大根堆,再重複執行以上操作我們即能得到一個有序的序列。
時間複雜度:, 空間複雜度:,只需要常數的空間存放若干變量
代碼實現:
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 的子序列。每次先遞歸調用函數使兩個子序列有序,然後我們再線性合併兩個有序的子序列使整個序列有序。
時間複雜度:, 空間複雜度:
程序實現:
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。
時間複雜度: ~ , 空間複雜度:
代碼實現:
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;
}
除此之外,還有計數排序、桶排序、基數排序、二叉樹排序等。具體要選擇哪一種排序算法,則要根據實際問題,權衡時間和空間複雜度,儘量選擇綜合性能
更好的排序算法。