【排序】选择排序和插入排序

排序算法相关接口

排序关注的对象是数组元素(支持随机访问),每个元素都有一个key值,key值是能够按照某种方式排列(即可排序)的。

一个排序算法大概需要实现如下接口:

class SortFunc {
public:
    void sort(Comparables a);

private:
    bool less(const Comparable &a, const Comparable &b);
    void exch(Comparable &a, Comparable &b)
    {
        Comparable t = a;
        a = b;
        b = t;
    }
};

Comparable是单个数组元素的类型,Comparables是支持随机访问的元素集合的类型。

要完成sort,需要提供比较两个元素的方法less以及根据比较结果交换元素的方法exch

在分析某个排序算法时,除了关注lessexch调用次数外,还需要关注是否配置了额外内存空间存储数组的副本

选择排序

  1. 找到数组中最小的那个元素
  2. 将它和数组的第一个元素交换位置
  3. 在剩下的元素中找到最小的元素
  4. 将它和数组中第二个元素交换位置
  5. 如此往复,直到只剩下一个元素

这种算法叫做选择排序,因为它在不断地选择剩余元素中的最小者。

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) 
    {
        int n = nums.size();
        for (int i = 0; i < n; ++i) {
            int minIdx = i;
            for (int j = i + 1; j < n; ++j) {
                if (less(nums, j, minIdx)) {
                    minIdx = j;
                }
            }
            exch(nums, i, minIdx);
        }

        return nums;
    }

private:
    bool less(const vector<int>& nums, int i, int j) 
    {
        return nums[i] < nums[j];
    }

    void exch(vector<int>& nums, int i, int j)
    {
        int t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }
};

选择排序有两个鲜明的特点:

  1. 运行时间和输入无关。一个有序的数组(或key全部相等的数组)和元素随机排列的数组的排序时间一样长。
  2. 元素移动次数少。元素交换的次数和数组的大小是线性关系

插入排序

整理桥牌的时候,将每一张牌插入到其它已经有序的牌中的适当位置

在计算机的实现中,为了给要插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位,这种算法叫做插入排序

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) 
    {
        int n = nums.size();
        for (int i = 1; i < n; ++i) {
            int j = i;
            while (j >= 1 && less(nums, j, j - 1)) {
                exch(nums, j, j - 1);
                --j;
            }
        }

        return nums;
    }

private:
    bool less(const vector<int>& nums, int i, int j) 
    {
        return nums[i] < nums[j];
    }

    void exch(vector<int>& nums, int i, int j)
    {
        int t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }
};

优化版:在内循环中将较大的元素都向右移动而不总是交换元素,这样访问数组的次数能减半。

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) 
    {
        int n = nums.size();
        for (int i = 1; i < n; ++i) {
            int tmp = nums[i];
            int j = i;
            while (j >= 1 && less(tmp, nums[j - 1])) {
                nums[j] = nums[j - 1];
                --j;
            }
            nums[j] = tmp;
        }

        return nums;
    }

private:
    bool less(int i, int j) 
    {
        return i < j;
    }

    void exch(vector<int>& nums, int i, int j)
    {
        int t = nums[i];
        nums[i] = nums[j];
        nums[j] = t;
    }
};

每次内循环时,要保留nums[i]的副本,并使用这个副本做比较和赋值运算。

选择排序和插入排序的共同点:

  1. 当前索引左边的所有元素都是有序的。
  2. 无额外空间消耗。O(1)空间复杂度、O(n^2)时间复杂度。

选择排序和插入排序的不同点:

  1. 选择排序中,每次循环都会确定一个元素的最终位置。但插入排序不会。
  2. 插入排序所需的时间取决于输入元素的初始顺序 。但选择排序不会。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值