pHP可以做論壇網(wǎng)站嗎如何去除痘痘效果好
912. 排序數(shù)組
注意這道題目所有
O(n^2)
復(fù)雜度的算法都會超過時間限制,只有O(nlogn)
的可以通過
- 快速排序空間復(fù)雜度為
O(logn)
是由于遞歸的棧的調(diào)用
- 歸并排序空間復(fù)雜度為
O(n)
是由于需要一個臨時數(shù)組
(當(dāng)然也需要棧的調(diào)用,但是O(logn)
<O(n)
的忽略了)
基于插入的排序算法
直接插入排序
類似于打撲克牌的操作 直接插入排序(算法過程, 效率分析, 穩(wěn)定性分析)
- 時間復(fù)雜度:最好情況
O(n)
, 最壞情況O(n^2)
- 空間復(fù)雜度:
O(1)
- 穩(wěn)定性:是穩(wěn)定的
class Solution {
public:vector<int> sortArray(vector<int>& nums) {// 插入排序for (int i = 1; i < nums.size(); ++i) {int cur_val = nums[i];int j = i - 1;while (j >= 0 && nums[j] > cur_val) { // 尋找插入位置nums[j + 1] = nums[j];--j;}nums[j + 1] = cur_val;}return nums;}
};
折半插入排序
直接插入排序是使用
順序查找的方法,從后往前尋找插入的位置
同理我們也可以使用二分查找
的方式來尋找插入的位置
折半查找減少了比較的次數(shù)
,將比較操作的時間復(fù)雜度降低為O(logn)
,但沒有減少移動的次數(shù)
,整體時間復(fù)雜度還是O(n^2)
- 時間復(fù)雜度:最好情況
O(n)
, 最壞情況O(n^2)
- 空間復(fù)雜度:
O(1)
- 穩(wěn)定性:是穩(wěn)定的
class Solution {
public:int binarySearch(vector<int> nums, int right, int target) {// 找到第一個大于 target 的值int left = 0;// 使用左閉右閉區(qū)間while (left <= right) { // 區(qū)間不為空int mid = left + (right - left) / 2;// 循環(huán)不變量// nums[left - 1] <= target// nums[right + 1] > targetif (nums[mid] <= target) {left = mid + 1;} else if (nums[mid] > target) {right = mid - 1;}}return left;}vector<int> sortArray(vector<int>& nums) {// 折半插入排序for (int i = 1; i < nums.size(); ++i) {int cur_val = nums[i];int insert_pos = binarySearch(nums, i - 1, cur_val);for (int j = i - 1; j >= insert_pos; --j) {nums[j + 1] = nums[j]; }nums[insert_pos] = cur_val;}return nums;}
};
希爾排序 - 插入排序的改進(jìn) - 縮小增量排序
插入排序在序列基本有序
時效率較高
基于這個特點(diǎn),希爾排序就是對數(shù)組分組進(jìn)行插入排序,分組的組數(shù)就是 d
,也即增量,一種簡單的增量序列就是從 num.size() / 2
開始,一直縮小到 1
,當(dāng)然也可以采用其他的增量序列
- 時間復(fù)雜度:最好情況
O(n)
, 最壞情況O(n^2)
,平均復(fù)雜度O(n^1.3)
(了解即可) - 空間復(fù)雜度:
O(1)
- 穩(wěn)定性:不穩(wěn)定的
class Solution {
public:vector<int> sortArray(vector<int>& nums) {for (int d = nums.size() / 2; d >= 1; --d) {// 分組插入排序for (int k = 0; k < d; ++k) {// 組內(nèi)進(jìn)行插入排序for (int i = k + d; i < nums.size(); i += d) {int cur_val = nums[i];int j = i - d;while (j >= 0 && nums[j] > cur_val) {nums[j + d] = nums[j];j -= d;}nums[j + d] = cur_val;}}}return nums;}
};
基于交換的排序算法
冒泡排序
- 時間復(fù)雜度:最好情況
O(n)
, 最壞情況O(n^2)
- 空間復(fù)雜度:
O(1)
- 穩(wěn)定性:穩(wěn)定
class Solution {
public:vector<int> sortArray(vector<int>& nums) {// 冒泡排序for (int i = nums.size() - 1; i >= 1; --i) {bool swapped = false;for (int j = 0; j < i; ++j) {if (nums[j] > nums[j + 1]) {swap(nums[j], nums[j + 1]);swapped = true;}}if (!swapped) { // 沒有發(fā)生交換,說明代碼已經(jīng)有序break;}}return nums;}
};
快速排序 圖解 - 分治法
步驟:
- 隨機(jī)選取一個位置 nums[i] = x
- 將大于 x 的值都移到 nums[i] 的左邊,小于 x 的值都移動到 nums[i] 的右邊
- 對 nums[0 ~i -1] 和 nums[i + 1 ~ n -1] 分別進(jìn)行快速排序
- …
步驟中的核心問題:如何 將大于 x 的值都移到 nums[i] 的左邊,小于 x 的值都移動到 nums[i] 的右邊
?
- 時間復(fù)雜度:最好情況
O(n)
, 最壞情況O(n^2)
- 空間復(fù)雜度:
O(1)
- 穩(wěn)定性:穩(wěn)定
class Solution {
public:void quickSort(vector<int>& nums, int left, int right) {if (left >= right) return; // 遞歸終止條件int p = partition(nums, left, right);quickSort(nums, left, p - 1);quickSort(nums, p + 1, right);}int partition(vector<int>& nums, int left, int right) {int p = left + rand() % (right - left + 1); // 生成 [left ~ right] 區(qū)間內(nèi)的隨機(jī)數(shù)swap(nums[p], nums[right]); // 將 pivot 和末尾值交換int i = left;// 維護(hù)的區(qū)間: [left, i) 區(qū)間內(nèi)的值小于等于 nums[right]// [j, right) 區(qū)間內(nèi)的值大于 nums[right]for (int j = left; j < right; ++j) {if (nums[j] <= nums[right]) {// 此時不滿足我們對區(qū)間的要求了// 調(diào)整區(qū)間使其滿足要求// {nums[left] ... nums[i-1]} {[nums[i] ... nums[j]]}swap(nums[i], nums[j]);++i;// --> {nums[left] ... nums[i-1] nums[j]} { ... nums[i]]}}}swap(nums[i], nums[right]);return i;}vector<int> sortArray(vector<int>& nums) {srand(time(0)); // 以當(dāng)前時間為隨機(jī)數(shù)種子quickSort(nums, 0, nums.size() - 1);return nums;}
};
但是上面這段代碼提交還是會超過時間限制,由于當(dāng)前的快速排序在處理包含大量相同元素的數(shù)組時,表現(xiàn)不佳??焖倥判蛟谧顗那闆r下的時間復(fù)雜度是 O(n^2)
使用三向切分的快速排序
三向切分是對標(biāo)準(zhǔn)快速排序的一種改進(jìn),特別適用于處理大量重復(fù)元素的情況。它將數(shù)組分為三個部分:
- 小于基準(zhǔn)的部分
- 等于基準(zhǔn)的部分
- 大于基準(zhǔn)的部分
通過三向切分,可以避免在處理大量重復(fù)元素時退化為 O(n2),使得時間復(fù)雜度保持在 O(n log n)。
class Solution {
public:void quickSort3Way(vector<int>& nums, int left, int right) {if (left >= right) return; // 遞歸終止條件int pivot = nums[left + rand() % (right - left + 1)]; // 選取隨機(jī)基準(zhǔn)int lt = left, i = left, gt = right; // 初始化 lt、i、gt 指針// [left ~ lt) 小于 pivot// [lt, gt] 等于 pivot// [gt + 1, right] 大于 pivot while (i <= gt) {if (nums[i] < pivot) {swap(nums[lt], nums[i]);++lt;++i;} else if (nums[i] > pivot) {swap(nums[i], nums[gt]);--gt; // 不能++i,因為換下來的這個數(shù)的值還沒有跟 pivot 比較過} else {++i;}}// 遞歸處理小于和大于基準(zhǔn)的部分quickSort3Way(nums, left, lt - 1);quickSort3Way(nums, gt + 1, right);}vector<int> sortArray(vector<int>& nums) {srand(time(0)); // 只需初始化一次隨機(jī)數(shù)種子quickSort3Way(nums, 0, nums.size() - 1);return nums;}
};
選擇排序
簡單選擇排序
- 時間復(fù)雜度:最好情況
O(n)
, 最壞情況O(n^2)
- 空間復(fù)雜度:
O(1)
- 穩(wěn)定性:不穩(wěn)定
不穩(wěn)定性分析:
假設(shè)有一個數(shù)組 [4a, 2, 4b, 3],其中 4a 和 4b 是兩個相同值的元素,但具有不同的初始順序。
第一輪:選擇 2 作為最小元素,然后與 4a 交換,數(shù)組變?yōu)?[
2
,4a
, 4b, 3]。第二輪:選擇 3 作為最小元素,然后與 4a 交換,數(shù)組變?yōu)?[2,
3
, 4b,4a
]。 注意此時 4a 和 4b 的相對順序已經(jīng)被改變:原本 4a 在 4b 之前,現(xiàn)在 4a被排在了 4b 之后。因此,選擇排序是不穩(wěn)定的,因為它改變了相同值元素的初始順序。
class Solution {
public:vector<int> sortArray(vector<int>& nums) {// 選擇排序for (int i = 0; i < nums.size() - 1; ++i) {int min_idx = i;for (int j = i + 1; j < nums.size(); ++j) {if (nums[j] < nums[i]) {min_idx = j; // 最小值的索引}}swap(nums[i], nums[min_idx]); // 和最小值進(jìn)行交換}return nums;}
};
堆排序 - 堆 - 完全二叉樹 - 順序存儲
class Solution {
public:// 堆化函數(shù):調(diào)整以 i 為根的子樹,n 為堆的大小void heapify(vector<int>& nums, int n, int i) {int largest = i; // 初始化為根節(jié)點(diǎn)int left = 2 * i + 1; // 左孩子int right = 2 * i + 2; // 右孩子// 如果左孩子比根節(jié)點(diǎn)大if (left < n && nums[left] > nums[largest]) {largest = left;}// 如果右孩子比當(dāng)前最大值還大if (right < n && nums[right] > nums[largest]) {largest = right;}// 如果最大值不是根節(jié)點(diǎn),交換并繼續(xù)堆化if (largest != i) {swap(nums[i], nums[largest]);// 遞歸對受影響的子樹進(jìn)行堆化heapify(nums, n, largest);}}vector<int> sortArray(vector<int>& nums) {int n = nums.size();// 從最后一個非葉子節(jié)點(diǎn)開始建堆,調(diào)整整個堆for (int i = n / 2 - 1; i >= 0; --i) {heapify(nums, n, i);}// 逐一將堆頂元素與末尾元素交換,并重新調(diào)整堆for (int i = n - 1; i > 0; --i) {// 將當(dāng)前堆頂(最大值)與末尾元素交換swap(nums[0], nums[i]);// 重新對剩下的部分進(jìn)行堆化heapify(nums, i, 0);}return nums;}
};
歸并排序
可以將排序問題分解成 將左半邊排序 + 將右半邊排序 + 合并左右兩側(cè)
- 時間復(fù)雜度:
O(n log n)
- 空間復(fù)雜度:
O(n) (源于臨時數(shù)組)
- 穩(wěn)定性:穩(wěn)定
class Solution {
public:void mergeArray(vector<int> &nums, vector<int>& tmp, int left, int right) {if (right == left) return; // 遞歸終止條件int mid = left + (right - left) / 2;mergeArray(nums, tmp, left, mid); // 對左半邊進(jìn)行排序mergeArray(nums, tmp, mid + 1, right); // 對右半邊進(jìn)行排序// 重要優(yōu)化:如果左右兩部分已經(jīng)有序,可以跳過合并if (nums[mid] <= nums[mid + 1]) return;// 左右兩側(cè)均已完成排序,對二者進(jìn)行合并int i = left, j = mid + 1, k = left;while (i <= mid && j <= right) {if (nums[i] <= nums[j]) {tmp[k++] = nums[i++]; } else {tmp[k++] = nums[j++];}}while (i <= mid) {tmp[k++] = nums[i++];}while (j <= right) {tmp[k++] = nums[j++];}copy(tmp.begin() + left, tmp.begin() + right + 1, nums.begin() + left);}vector<int> sortArray(vector<int>& nums) {vector<int> tmp(nums.size(), 0);mergeArray(nums, tmp, 0, nums.size() - 1);return nums;}
};