给你一个整数数组 nums,请你将该数组升序排列。
简单的排序题,顺便总结一下常用的排序算法。
LeetCode快排/堆排/归并题解
快速排序
最常见的排序,面试也比较喜欢问。注意快排中的partition操作可以用来求第K
大的数字。
快排的思路是每次迭代把小于(大于)某个元素的数全部放在它前面,把大于(小于)某个元素的数全部放在它后面。
快速排序是不稳定排序
/*
快速排序
*/
class Solution {
public:
int myPartition(vector<int>& nums, int high, int low){
int pivot = nums[high], local = low - 1;
for (int i = low; i < high; i++){
if (nums[i] < pivot){
swap(nums[i], nums[++local]);
}
}
swap(nums[high], nums[++local]);
return local;
}
void quickSort(vector<int>& nums, int high, int low){
if (low < high){
int middel = myPartition(nums, high, low);
quickSort(nums, high, middel + 1);
quickSort(nums, middel-1, low);
}
}
vector<int> sortArray(vector<int>& nums) {
vector<int> ans(nums);
quickSort(ans, ans.size()-1, 0);
return ans;
}
};
时间复杂度:O(nlogn);(快排的时间复杂度不固定,有最优时间复杂度和最差时间复杂度)
空间复杂度:O(1);(快速排序可以是原地排序算法);
冒泡排序
每次把前面最大的元素放在最后,冒泡排序是稳定排序。
/*
冒泡排序
*/
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
vector<int> ans(nums);
int arrLen = ans.size();
for (int i = arrLen - 1; i > 0; i--){
for (int j = 0; j < i; j++){
if (ans[j] > ans[j+1]){
swap(ans[j], ans[j+1]);
}
}
}
return ans;
}
};
时间复杂度:O(n^2);
空间复杂度:O(1);(冒泡排序可以是原地排序算法)
插入排序
每次把一个新的数插入到一个新的序列中,插入排序是稳定排序。
/*
插入排序
*/
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
vector<int> ans(nums);
int arrLen = ans.size();
for (int i = 1; i < arrLen; i++){
int j;
for (j = 0; j < i; j++){
if (ans[i] <= ans[j]){
break;
}
}
int temp = ans[i];
for (int k = i; k > j; k--){
ans[k] = ans[k-1];
}
ans[j] = temp;
}
return ans;
}
};
时间复杂度:O(n^2);
空间复杂度:O(1);(可以是原地排序算法);
选择排序
每次从剩余的元素选择出一个最大/最小的元素放在当前位置。选择排序不是稳定排序。
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
vector<int> ans(nums);
for (int i = 0; i < ans.size() - 1; i++){
int index = i, minNum = ans[i];
for (int j = i + 1; j < ans.size(); j++){
if (ans[j] < minNum){
index = j;
minNum = ans[j];
}
}
swap(ans[i], ans[index]);
}
return ans;
}
};
时间复杂度:O(n^2);
空间复杂度:O(1);(可以是原地排序算法);
归并排序
归并排序利用了分治的思想来对序列尽心排序。对一个长为n的待排序的序列,先将其分解成两个长度为 的子序列。每次先递归调用函数使两个子序列有序,然后我们再线性合并两个有序的子序列使整个序列有序。归并排序是稳定的排序算法。
class Solution {
vector<int> tmp; // 因为要采用递归,所以把用于归并的tmp数组放在递归函数外部
void mergeSort(vector<int>& nums, int left, int right) {
if (left >= right) return;
int mid = (left + right) / 2;
mergeSort(nums, left, mid);
mergeSort(nums, mid+1, right);
int i = left, j = mid + 1;
int cnt = 0;
while (i <= mid && j <= right){
if (nums[i] < nums[j]) {
tmp[cnt++] = nums[i++];
}
else {
tmp[cnt++] = nums[j++];
}
}
while (i <= mid){
tmp[cnt++] = nums[i++];
}
while (j <= right){
tmp[cnt++] = nums[j++];
}
for (int i = 0; i < right - left + 1; i++){
nums[i+left] = tmp[i];
}
}
public:
vector<int> sortArray(vector<int>& nums) {
tmp.resize((int)nums.size(), 0);
mergeSort(nums, 0, (int)nums.size() - 1);
return nums;
}
};
时间复杂度:O(nlogn);
空间复杂度:O(n);(需要辅助数组tmp);
堆排序
先维护一个最大堆,然后每次把堆顶元素挪出堆,放在当前数字序列末尾,这样它就被放到了排序后正确的位置上。因为堆的维护牵扯交换操作,所以堆排序不是稳定排序。一般来说堆排序需要三个函数:
1.建堆;
2.堆化
3.总的排序函数;
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; //堆化每次只操作一个元素,所以直接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;
}
};
时间复杂度:O(nlogn);
空间复杂度:O(1);(原地排序算法,只需要常数空间保存一些变量);