Leetcode 912. 排序数组 —— 总结十大排序

前言

这一篇打算用来写十大排序,尤其是三大很快的排序:快速、堆、归并,应该也是面试的重中之重。

题目

在这里插入图片描述
Leetcode 912. 排序数组


排序算法时间复杂度(平均)时间复杂度(最好)时间复杂度(最差)空间复杂度稳定性
直插排序O(n2)O(n2)O(n2)O(1)稳定
选择排序O(n2)O(n2)O(n2)O(1)不稳定
冒泡排序O(n2)O(n)O(n2)O(1)稳定
希尔排序O(n1.5)O(n)O(n2)O(1)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
快速排序O(nlogn)O(nlogn)O(n^2)O(logn)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
计数排序O(n+k)O(n+k)O(n+k)O(k)稳定
桶排序O(n+k)O(n+k)O(n^2)O(n+k)稳定
基数排序O(n*k)O(n*k)O(n*k)O(n+k)稳定

快乐的做法

class Solution 
{
public:
    vector<int> sortArray(vector<int>& nums) 
    {
        sort(nums.begin(),nums.end());
        return nums;
    }
};

直接插入排序

  • 稳定性:稳定
  • 时间复杂度:O(n2)
  • 空间复杂度:O(1)
    在这里插入图片描述
  • 算法步骤
    1. 从第一个元素开始,该元素认为已经被排序;
    2. 取下一个元素,在已排序的序列中进行二分查找
    3. 根据下标lr找到下标,r的右边一个就是应该插入的位置
    4. 将新元素插入到r后面
    5. 重复步骤2~4

代码(首刷看二分解析,更快)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        for(int i = 1; i < n; i++) {
            if(nums[i] >= nums[i-1])   // 两元素递增则直接插入
                continue;   
            int l = 0, r = i-1;
            // 二分查找,时间复杂度logn
            while(l <= r) { 
                int mid = (r-l)/2 + l;
                if(nums[mid] > nums[i]) {   // r右侧元素均大于 nums[i],即 r 及其左侧元素均小于等于nums[i]
                    r = mid - 1;
                } else {
                    l = mid + 1;
                } 
            }
            int tmp = nums[i];
            for(int j = i-1; j >= r + 1; j--) {
                nums[j+1] = nums[j];
            }
            nums[r+1] = tmp;
        }
        return nums;
    }
};

该算法在LC上会超时,无法通过。


选择排序

  • 稳定性:不稳定
  • 时间复杂度:O(n2)
  • 空间复杂度:O(1)
    在这里插入图片描述
  • 算法步骤
    1. 第一层循环:[0,n-1]
    2. 第二层循环:[i+1,n]
    3. 循环遍历找到最小值ind,将iind交换

代码(首刷自解)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        for(int i = 0; i < n-1; i++) {
            int ind = i;
            for(int j = i+1; j < n; j++) {
                if(nums[j] < nums[ind]) {
                    ind = j;
                }
            }
            swap(nums[i], nums[ind]);
        }
        return nums;
    }
};

该算法在LC上会超时,无法通过。


冒泡排序

  • 稳定性:稳定
  • 时间复杂度:O(n2)
  • 空间复杂度:O(1)

在这里插入图片描述

  • 算法步骤
    1. 第一层循环[0,n-1]
    2. 第二层循环[0,n-1-i]
    3. 两两比较交换

代码(首刷自解)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        for(int i = 0; i < n-1; i++) {
            bool flag = false;
            for(int j = 0; j < n-i-1; j++) {
                if(nums[j] > nums[j+1]) {
                    swap(nums[j], nums[j+1]);
                    flag = true;
                }
            }
            if(!flag)
                break;
        }
        return nums;
    }
};

该算法在LC上会超时,无法通过。


希尔排序

  • 稳定性:不稳定
  • 时间复杂度:O(n1.5)
  • 空间复杂度:O(1)

在这里插入图片描述

  • 算法步骤:
    1. 选择一个增量序列T1,T2,… ,Tk,其中Ti>Tj,Tk=1,i>j;
    2. 每趟排序,根据对应的增量Ti,将待排序列分割成若干子序列,分别对各子序列进行直接插入排序;
    3. 按增量序列个数k,对序列进行k趟排序。

代码(首刷看解析)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        for(int gap = n/2; gap >= 1; gap /= 2) {
            for(int i = gap; i < n; i++) {
                shellSort(nums, gap, i);
            }
        }
        return nums;
    }
    void shellSort(vector<int>& nums, int gap, int i) {
        int j = i-gap;
        int tmp = nums[i];
        for(; j >= 0 && tmp < nums[j]; j -= gap) {
            nums[j+gap] = nums[j];
        }
        nums[j+gap] = tmp;
    }
};

第一个通过的排序,记得将判断条件放入for中,节省时间


归并排序

  • 稳定性:稳定
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
    在这里插入图片描述
  • 算法步骤
    1. 把长度为n的输入序列分成两个长度为n/2的子序列;
    2. 对这两个子序列分别采用归并排序(递归);
    3. 将两个排序好的子序列合并成一个最终的排序序列(注意下标)。

代码(首刷调试看解析)

class Solution {
public:
    vector<int> tmp;    // 辅助数组
    vector<int> sortArray(vector<int>& nums) {
        tmp.resize(nums.size(), 0); // 初始化
        MergeSort(nums, 0, nums.size()-1);
        return nums;
    }
    void MergeSort(vector<int>& nums, int l, int r) {
        if(l >= r)
            return; // 出递归
        int mid = (r-l)/2+l;
        MergeSort(nums, l, mid);
        MergeSort(nums, mid+1, r);
        // 下面是归并操作
        int i = l, j = mid+1;
        int k = 0;
        while(i <= mid && j <= r) {
            if(nums[i] <= nums[j]) {
                tmp[k++] = nums[i++];
            } else {
                tmp[k++] = nums[j++];
            }
        }
        while(i <= mid) {
            tmp[k++] = nums[i++];
        }
        while(j <= r) {
            tmp[k++] = nums[j++];
        }
        for(int m = 0; m < r-l+1; m++) {
            nums[m+l] = tmp[m];
        }
    }
};

代码(二刷看解析)

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

代码(9.1 三刷自解)

class Solution {
public:
    vector<int> tmp;
    vector<int> sortArray(vector<int>& nums) {
        tmp.resize(nums.size());
        mergeSort(nums, 0, nums.size()-1);
        return nums;
    }
    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 k = 0;
        
        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++];
        for(int m = 0; m < k; m++) 
            nums[left+m] = tmp[m];
    }
};

代码(9.21 四刷自解)

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

快速排序

  • 稳定性:不稳定
  • 最好时间复杂度:O(nlogn)
  • 最差时间复杂度:O(n2)
  • 空间复杂度:O(logn)
    在这里插入图片描述
  • 算法步骤(左右指针法)
    1. 挑选一个pivot
    2. 每次移动指针再判断和pivot的大小,两两交换
    3. 递归recursive:按照[left,r][r+1,right]递归

快速排序最好情况下:O(nlogn) ,最坏情况下退化为选择排序(例如每次选择的都是最大值,之后遍历左边的 n -1 序列):O(n2

代码(首刷看解析)

rand() % (b-a+1)+ a : a~b 之间的一个随机整数。

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        QuickSort(nums, 0, nums.size()-1);
        return nums;
    }
    void QuickSort(vector<int>& nums, int left, int right) {
        if(left >= right) 
            return;
        int pivot = nums[rand()%(right-left+1)+left];
        int l = left-1, r = right+1;    // 左右指针初始指向边界外侧
        while(l < r) {
            while(nums[++l] < pivot);   // 每次先移动指针,再做判断,这样不会陷入循环
            while(nums[--r] > pivot);
            if(l < r) swap(nums[l], nums[r]);
        }
        QuickSort(nums, left, r);
        QuickSort(nums, r+1, right);
    }
};

代码(二刷看解析)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        QuickSort(nums, 0, nums.size() - 1);
        return nums;
    }
    void QuickSort(vector<int>& nums, int left, int right) {
        if(left >= right)
            return; // 返回条件
        int pivot = nums[rand()%(right - left + 1) + left];
        int l = left - 1, r = right + 1;
        while(l < r) {
            while(nums[++l] < pivot);
            while(nums[--r] > pivot);
            if(l < r) swap(nums[l], nums[r]);
        }
        QuickSort(nums, left, r);
        QuickSort(nums, r+1, right);
    }
};

代码(8.31 三刷自解)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        quickSort(nums, 0, nums.size()-1);
        return nums;
    }
    void quickSort(vector<int>& nums, int left, int right) {
        if(left >= right)
            return;
        int pivot = nums[rand()%(right-left+1)+left];
        int l = left-1, r = right+1;
        while(l < r) {
            while(nums[++l] < pivot);
            while(nums[--r] > pivot);
            if(l < r)
                swap(nums[l], nums[r]);
        }
        quickSort(nums, left, r);
        quickSort(nums, r+1, right);
    }
};

代码(9.21 四刷自解)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        quickSort(nums, 0, nums.size()-1);
        return nums;
    }
    void quickSort(vector<int>& nums, int l, int r) {
        if(l >= r)
            return;
        int pivot = nums[rand()%(r-l)+l];
        int left = l-1, right = r+1;
        while(left < right) {
            while(nums[++left] < pivot);
            while(nums[--right] > pivot);
            if(left < right) 
                swap(nums[left], nums[right]);
        }
        quickSort(nums, l, right);
        quickSort(nums, right+1, r);
    }
};

堆排序

  • 稳定性:不稳定
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
    在这里插入图片描述
  • 算法步骤
    1. 建立堆
    2. 把堆首(最大值)和堆尾互换
    3. 把堆的尺寸减一,重新构建堆,目的是把新的数组顶端数据调整到相应位置
    4. 重复步骤2和3,直到堆的尺寸为1

代码(首刷调试看解析)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        buildHeap(nums);
        int n = nums.size();
        for(int i = n-1; i > 0; i--) {
            swap(nums[0], nums[i]);
            heapAdjust(nums, 0, i);
        }
        return nums;
    }
    void buildHeap(vector<int>& nums) {
        int n = nums.size();
        for(int i = (n-1)/2; i >= 0; i--) { // 从下往上建立
            heapAdjust(nums, i, n);
        }
    }
    void heapAdjust(vector<int>& nums, int i, int n) {  // 调整为从上往下
        while(i*2+1 < n) {  // n为个数,当还有儿子的时候
            int child = i*2+1;
            if(child+1 < n && nums[child+1] > nums[child])
                child++;
            if(nums[child] > nums[i]) {
                swap(nums[child], nums[i]);
                i = child;
            } else {
                break;
            }
        }
    }
};

代码(二刷调试看解析)

三个注意点:

  1. heapAdjust的参数需要指明长度,因为最后面的都是已经排好序的
  2. 一开始构建堆的时候,for需要i>=0
  3. 堆排序时heapAdjust的参数:长度需要-1
class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        buildHeap(nums);
        for(int i = nums.size() - 1; i; i--) {
            swap(nums[0], nums[i]);
            heapAdjust(nums, 0, i-1);
        }
        return nums;
    }
    void buildHeap(vector<int>& nums) {
        int n = nums.size() - 1;
        for(int i = n/2; i >= 0; i--) {
            heapAdjust(nums, i, n);
        }
    }
    void heapAdjust(vector<int>& nums, int k, int len) {
        // int n = nums.size() - 1;
        for(int i = k*2+1; i <= len; i = i*2+1) {
            if(i+1 <= len && nums[i+1] > nums[i]) i++;
            if(nums[i] > nums[k]) {
                swap(nums[k], nums[i]);
                k = i;
            } else {
                break;
            }
        }
    }
};

代码(9.1 三刷自解)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        buildHeap(nums);
        int n = nums.size();
        for(int i = 0; i < n; i++) {
            swap(nums[0], nums[n-i-1]);
            heapAdjust(nums, 0, n-i-2);
        }
        return nums;
    }
    void buildHeap(vector<int>& nums) {
        int n = nums.size();
        for(int i = (n-1)/2; i >= 0; i--) {
            heapAdjust(nums, i, n-1);
        }
    }
    void heapAdjust(vector<int>& nums, int k, int n) {
        for(int i = k*2+1; i <= n; i = i*2+1) {
            if(i+1 <= n && nums[i+1] > nums[i])
                i++;
            if(nums[k] < nums[i]) {
                swap(nums[k], nums[i]);
                k = i;
            } else {
                break;
            }
        }
    }
};

代码(9.21 四刷自解)

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
       int n = nums.size()-1;
       biuldHeap(nums);
       for(int i = n; i; i--) {
           swap(nums[i], nums[0]);
           heapAdjust(nums, i-1, 0);
       }
       return nums;
    }
    void biuldHeap(vector<int>& nums) {
        int n = nums.size()-1;
        for(int i = (n-1)/2; i >= 0; i--)
            heapAdjust(nums, n, i);
    }
    void heapAdjust(vector<int>& nums, int n, int k) {
        for(int i = k*2+1; i <= n; i = i*2+1) {
            if(i+1 <= n && nums[i+1] > nums[i])
                i++;
            if(nums[k] < nums[i]) {
                swap(nums[k], nums[i]);
                k = i;
            }
        }
    }
};

计数排序

时间复杂度:O(n+k)
空间复杂度:O(k)
稳定性:稳定

先假设 20 个数列为:{9, 3, 5, 4, 9, 1, 2, 7, 8,1,3, 6, 5, 3, 4, 0, 10, 9, 7, 9}。

让我们先遍历这个无序的随机数组,找出最大值为 10 和最小值为 0。这样我们对应的计数范围将是 0 ~ 10。然后每一个整数按照其值对号入座,对应数组下标的元素进行加1操作。
在这里插入图片描述
eg:大小写都包含的字符串转成全小写且排好序

void countingSort(std::vector<int>& arr) {
    int maxVal = *max_element(arr.begin(), arr.end()); // 获取最大值

    // 创建计数数组并初始化为0
    std::vector<int> count(maxVal + 1, 0);

    // 计算每个元素的出现次数
    for (int val : arr) {
        count[val]++;
    }

    // 重新生成排序后的数组
    int index = 0;
    for (int i = 0; i <= maxVal; i++) {
        for (int j = 0; j < count[i]; j++) {
            arr[index++] = i;
        }
    }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值