C++中stable_sort和sort学习及简单实现

比较

std::stable_sort - cppreference.com

  • 二者使用时都需要引入 <algorithm> 头文件,
  • stable_sort是基于归并排序的,是一种稳定排序。比较适合对比较复杂的数据进行排序,比如一些订单数据,,已经按照订单号排好了,现在想对交易日期再进行排序,就可以使用归并排序,排序后元素之间相对位置没有发生变化。
  • sort是基于快排实现的,是一种不稳排序,理想情况下时间复杂度 O(NlogN),空间复杂度O(logN),极端情况下时间复杂度O(N^2),空间复杂度O(N)
  • 二者都可以自定义comp函数,如果结果为true,则排序为args1, args2,否则args2,args1。所以,
    • 升序 return args1<args2;
    • 降序 return args1>args2

基于二叉树的实现

归并排序

总结:先把左半边数组排好,再把右半边数组排好,再把两个数组合并

这个过程可以在逻辑上抽象成一个二叉树,节点是数组区间,叶子结点的值就是数组元素。和二叉树的后续遍历是一样,

class Merge {
    // 用于辅助合并有序数组
    // 不在merge中new辅助数组,避免在递归中频繁分配内存和释放内存带来的性能问题
private:
    vector<int>  tmp;
public:
    void sort(vector<int> *nums) {
        tmp.clear();
        // 排序整个数组(原地修改)
        sort(nums, 0, nums->size() - 1);
    }
private:
    void sort(vector<int> *nums, int low, int high) {
        // 单个元素不排序
        if (low == high) {
            return;
        }
        int mid = (high - low) / 2 + low;
        sort(nums, low, mid);
        sort(nums, mid+1, high);
        merge(nums, low, mid, high);
    }
    
    void merge(vector<int> *nums, int low, int mid, int high) {
        // 先把num[low, high] 复制到tmp中
        // 以便合并后结果能直接存入nums
        for (int i=low; i<=high; i++) {
            tmp[i] = (*nums)[i];
        }
        // 数组双指针技巧,合并两个有序数组
        int i = low;
        int j = mid + 1;
        for (int idx=low; idx<=high; idx++) {
            if (i == mid+1) {
                (*nums)[idx] = tmp[j++];
            } else if (j == high+1) {
                (*nums)[idx] = tmp[i++];
            } else if (tmp[i] < tmp[j]) {
                (*nums)[idx] = tmp[i++];
            } else {
                (*nums)[idx] = tmp[j++];
            }
        }
    }
}

执行的次数是二叉树节点的个数,每次执行的复杂度就是每个节点代表的子数组的长度,所以总时间复杂度就是整棵树中数组元素的个数。整个树高度是logN,一层元素个数是N,所以总的时间复杂度是O(NlogN)

快速排序

总结:快速排序是先将一个元素排好序(左边元素都小于它,右边元素都大于它),再将剩下的元素排好序(递归左边的也排好,右边的也排好)。

这个过程抽象出来就是一个二叉搜索树的构建过程。为避免极端情况,可以先用洗牌算法将数组乱序,或者随机选择元素作为分界点。

class Quick {
    public:
        void sort(vector<int> *nums) {
        // 避免极端情况,先随机打乱
        shuffle(nums);
        // 排序整个数组,原地修改
        sort(nums, 0, nums.size()-1);
    }
    private:
        void sort(vector<int> *nums, int low, int high) {
        if (low >= high) {
            return;
        }
        int p = partition(nums, low, high);
        sort(nums, low, p);
        sort(nums, p+1, high);
    }
    private:
        void partition(vector<int> *nums, int low, int high) {
            int pivot = (*nums)[low];
            int i = low + 1;
            int j = high;
            while (i <= j) {
                while(i<high && (*nums)[i]<=pivot) {
                    i++;
                }  // 结束时 nums[i] > pivot
                while(j>low && (*nums)[j]>pivot) {
                    j--;
                }  // 结束时,nums[j]<=pivot
                if (i>=j) break;
                swap(nums, i, j);
            }
            // 将pivot放在合适的位置
            swap(nums, low, j);
            return j;
        }
    // 洗牌算法
    private:
        void shuffle(vector<int> *nums) {
        
            int n = nums.size()-1;
            for (int i = 0; i<n; i++) {
                int r = (rand() % (n-i))+ i + 1;
                swap(nums, i, r);
            }

    }
    private:
        void swap(vector<int> *nums, int i, int j) {
        int tmp = (*nums)[i];
        (*nums)[i] = (*nums)[j];
        (*nums)[j] = tmp;
    }
}

partiton 执行次数就是二叉树及诶蛋的个数,每次执行的复杂度就是每个节点代表的子数组nums[low, high]的长度,所以总的时间复杂度就是整棵树中数组元素的个数

假设数组元素个数为N,那么二叉树每一层元素个数之和就是O(N);分界点分布均匀情况下,树的层数是O(logN),所以理想时间复杂度O(NlogN)

由于快排米有使用任何辅助数组,所以空间复杂度就是递归的深度,即树高O(logN)

  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值