[算法] 关于十大排序——你真的了解吗

排序算法作为一项需求,足够简单,是学习基础算法思想(分治算法、减治算法、递归)很好的学习材料。面试遇到写排序算法,先问清楚数据的特点,有些时候可能还会给具体的业务场景。

时间复杂度

  • 时间复杂度统计的是算法的计算操作数量,而不是运行的绝对时间。两者呈正相关,并不相等。算法运行时间受编程语言、计算机处理速度、运行环境等多种因素的影响。

  • 体现的是计算操作随数据大小N变化时的变化情况。

算法基础知识铺垫

  • 稳定排序:如果a原本在b的前面,且a == b,排序之后a仍然在b的前面,则为稳定排序。冒泡排序、插入排序、归并排序
  • 非稳定排序:如果a原本在b的前面,且a == b,排序之后a可能不在b的前面,则为非稳定排序。快速排序、选择排序、希尔排序、堆排序
  • 原地排序:在排序过程中不申请多余的存储空间,只利用原来的存储空间进行比较和交换的数据排序。
  • 非原地排序:需要利用额外的数组来辅助排序。
  • 时间复杂度:一个算法执行所消耗的时间。
  • 空间复杂度:运行完一个算法所需的内存大小。

1、冒泡排序(了解)

冒泡排序就是把小的元素往前调或者把大的元素往后调,冒泡排序是一种稳定排序算法

  • 一般思路:

    • 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
    • 对每一对相邻元素做同样的工作,从开始的第一对到最后一对。这步做完以后,最后的元素会是最大的数。
    • 针对所有元素重复以上步骤,除了最后一个。
    • 持续每次对越来越少的元素重复上面的步骤。
    • 时间复杂度o(n^2),空间复杂度o(1)。

    实现代码

    void bubbleSort(vector<int>& a) {
        int n = a.size();
        for (auto i = 0; i < n; ++i) {
            for (int j = 0; j < n - i - 1; ++j) {
                if (a[j] > a[j + 1])
                    swap(a[j], a[j + 1]);
            }
        }
    }
    
  • 冒泡排序优化

    • 假如从开始第一对到结尾最后一对,相邻元素之间都没有发生交换的操作,这意味着右边的元素总是大于等于左边的元素,此时的数组已经是有序的了

2、选择排序(了解)

选择排序是给每个位置选择当前元素最小值。

  • 从待排序的数据中寻找最小值,将其与序列最左边起始位置的数字进行交换。
  • 从剩余未排序元素中继续寻找最小值,然后放到序列最左边第二个位置。
  • 以此类推,直到所有元素均排序完毕。
  • 时间复杂度o(n^2),空间复杂度o(1)。
void selectSort(vector<int>& nums) {
    int len = nums.size();
    int minIndex = 0;
    for (int i = 0; i < len; ++i) {
        minIndex = i;
        for (int j = i + 1; j < len; ++j) {
            if (nums[j] < nums[minIndex]) minIndex = j;
        }
        swap(nums[i], nums[minIndex]);
    }
}

3、归并排序(重点)

待补充

4、快速排序(重点)

  • 快速排序原理

    两个核心,分别是哨兵划分和递归

    • 哨兵划分:以数组某个元素(一般选取首元素)为基准数,将所有小于基准数的元素移动至其左边,大于其基准数的元素移动至其右边
    • 递归
  • 快速排序和二分法的原理类似

    class Solution {
     public:
      vector<int> getLeastNumbers(vector<int>& arr, int k) {
        quickSort(arr, 0, arr.size() - 1);
        vector<int> res;
        // 将arr容器中的[begin, end)中的元素赋值给res
        res.assign(arr.begin(), arr.begin() + k);
        return res;
      }
    
     private:
      void quickSort(vector<int>& arr, int l, int r) {
        // 子数组长度为 1 时终止递归
        if (l >= r) return;
        // 哨兵划分操作(以 arr[l] 作为基准数)
        int i = l, j = r;
        while (i < j) {
          while (i < j && arr[j] >= arr[l]) j--;
          while (i < j && arr[i] <= arr[l]) i++;
          swap(arr[i], arr[j]);
        }
        swap(arr[i], arr[l]);
        // 递归左(右)子数组执行哨兵划分
        quickSort(arr, l, i - 1);
        quickSort(arr, i + 1, r);
      }
    };
    

5、插入排序(熟悉)

插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。

当然,刚开始这个有序的小序列只有一个元素,就是等于第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起。如果碰到一个和插入元素相等的,那么把想要插入的元素放在相等元素的后面。

  • 从第一个元素开始,该元素可以认为已经被排序。
  • 取出下一个元素,在已经排序元素中从后向前排序。
  • 如果该元素(已排序)大于新元素,将该元素移到下一个位置。
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。
  • 将新元素插入到该位置后。
  • 重复步骤2~5.
  • 时间复杂度o(n^2),空间复杂度o(1)。
void print(vector<int>& a, int n, int i) {
    cout << "step"<< i << ": ";
    for (int j = 0; j < n; j++) {
        cout << a[j] << " ";
    }
    cout << endl;
}

void insertionSort(vector<int>& a, int n) {//{ 9,1,5,6,2,3 }
    for (int i = 1; i < n; ++i) {
        if (a[i] < a[i - 1]) { //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
            int j = i - 1;
            int x = a[i]; //复制为哨兵,即存储待排序元素
            //a[i] = a[i - 1]; //先后移一个元素,可以不要这一句,跟循环里面的功能重复了
            while (j >= 0 && x < a[j]) { //查找在有序表的插入位置,还必须要保证j是>=0的因为a[j]要合法
                a[j + 1] = a[j];
                j--; //元素后移
            }
            a[j + 1] = x; //插入到正确位置
        }
        print(a, n, i); //打印每趟排序的结果
    }
}

6、堆排序(堆很重要,堆排序看个人情况)

代补充

7、桶排序(了解)

待补充

8、基数排序(了解)

待补充

9、计数排序(了解)

待补充

10、希尔排序(不建议多花时间)

  • 快速排序首先会在序列中随机选择一个基准值(pivot),然后将除了基准值以外的数分为“比基准值小的数”和“比基准值大的数”这两个类别,再将其排列成==【比基准值小的数】基准值【比基准值大的数】==形式。
  • 快速排序是一种分治法。它将原本的问题分成两个子问题(比基准值小的数和比基准值大的数),然后再分别解决这两问题。
  • 解决子问题的时候会再次使用快速排序,甚至这个快速排序里面仍然要使用快速排序。
  • 分割子序列时候需要选择基准值,如果每次选择的基准值都能使两个子序列的长度为原来的一半,那么快速排序的运行时间为nlogn。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值