• leetcode刷题-- 2. 排序(待更新)


    排序

    参考五分钟学算法

    复杂度比较

    时间复杂度

    • O(n2) 各种简单的排序:直接插入、直接选择、冒泡
    • O(nlog2n) 快速排序、堆排序、归并排序
    • O(n1+(lambda)),希尔排序
    • 线性阶O(n)排序,基排序、桶、箱排序

    稳定性

    • 稳定排序:冒泡、插入、归并、基数排序
    • 不稳定:选择、快速、希尔、堆排序

    稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。(相等元素相对位置不变)

    选择排序

    • 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置

    • 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

    • 重复第二步,直到所有元素均排序完毕。

    例题

    面试题45. 把数组排成最小的数

    45

    输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

    示例 1:
    
    输入: [10,2]
    输出: "102"
    示例 2:
    
    输入: [3,30,34,5,9]
    输出: "3033459"
    

    思路

    将数组里数字当作字符串拼接起来,nm > mn那么就交换n与m,将m移动到n的前面。选择排序:

    class Solution:
        def minNumber(self, nums: List[int]) -> str:
            nums = [str(i) for i in nums]
    
            for i in range(len(nums)-1):
                for j in range(i+1, len(nums)):
                    if nums[i] + nums[j] > nums[j] + nums[i]:
                        nums[i],nums[j] = nums[j],nums[i]
            
            return "".join(nums)
    

    注意第一层循环到len(nums)-1因为受到第二层循环i+1到 len(nums)的限制。

    快速排序

    参考

    双向

    两个指针i,j分别从左边和右边往中间扫,当nums[i]>基准nums[j]<基准交换他们,最终可确定基准所在位置。这道题每次都舍弃一部分数组,将符合条件的放入buffer中,减少了运算,也是类似的思想。

    例如:求前k个元素(由小到大)

    def quick_select_without_optimizer(arr, k):
        n = len(arr)
        # 如果k大于n,没啥好说的,直接返回
        if k >= n:
            return arr
    
        # 缓存
        buffer = []
        while arr:
            # 选择最后一个元素作为标杆
            mark = arr.pop()
            less, greater = [], []
            # 遍历数组,将元素分为less和greater
            for x in arr:
                if x <= mark:
                    less.append(x)
                else:
                    greater.append(x)
            # 判断三种情况,如果相等直接返回
            if len(less) == k:
                return less
            # 如果小于,将less存入buffer,因为它一定是答案的一部分,可以简化计算
            elif len(less) < k:
                buffer += less
                # k要减去less的长度
                k -= len(less)
                arr = [mark] + greater
            else:
                # 如果大于,直接舍弃右边
                arr = less
    

    单向


    单向扫描,只有j从左到右扫描一遍,i指向小于基准区间的最右边,j指向大于基准区间的最左边,如上图。

    public void qSort(int[] a,int begin,int end){
        if(begin >= end) return;
        int mid = partition(a, begin, end);
        qSort(a, begin, mid - 1);
        qSort(a, mid + 1, end);
    }
     
    public int partition(int[] a,int p ,int r){
        int x = a[r];
        int i = p - 1;
        int j = p;
        for(j = p; j < r; j++){
            if(a[j] < x){
                i++;
                swap(a, i, j);
            }
        }
        swap(a, i + 1, j);
        return i + 1;
    } 
    

    优化BFPRT算法

    快排的问题在于,最坏情况(逆序)下时间复杂度为O(n^2)。发生这种情况问题在于初始值的选择,也就是每次基准的选择。为了解决这种情况,引入BFPRT算法(Blum、Floyd、Pratt、Rivest、Tarjan五位大牛首字母)。

    步骤

    • 判断数组元素是否大于 5 ,如果小于 5 ,对它进行排序,并返回数组的中位数
    • 如果元素大于 5 个,对数组进行分组,每 5 个元素分成一组,允许最后一个分组元素不足 5 个。
    • 对于每个分组,对它进行插入排序
    • 选择出每个分组排序之后的中位数,组成新的数组
    • 重复以上操作

    因此也叫中位数的中位数算法。具体复杂度分析参考,这个算法复杂度只有O(n),当然只是寻找基准,不是排序,算是快排的准备步骤。

    def bfprt(arr, l=None, r=None):
        if l is None or r is None:
            l, r = 0, len(arr)
        length = r - l
        # 如果长度小于5,直接返回中位数
        if length <= 5:
            arr[l: r] = insert_sort(arr[l: r])
            return l + length // 2
        medium_num = l
        start = l
        # 否则每5个数分组
        while start + 5 < r:
            # 对每5个数进行插入排序
            arr[start: start + 5] = insert_sort(arr[start: start + 5])
            arr[medium_num], arr[start + 2] = arr[start + 2], arr[medium_num] #将所有的中位数都放在数组前面
            medium_num += 1
            start += 5
        # 特殊处理最后不足5个的情况
        if start < r:
            arr[start:r] = insert_sort(arr[start:r])
            _l = r - start
            arr[medium_num], arr[start + _l // 2] = arr[start + _l // 2], arr[medium_num]
            medium_num += 1
        # 递归调用,对中位数继续求中位数
        return bfprt(arr, l, medium_num)
    

    之后再调用这个函数获取基准值即可,这只是避免快排最坏情况发生,选择基准的一种做法。接着差不多:

    def quick_select(arr, k):
        n = len(arr)
        if k >= n:
            return arr
    
        mark = bfprt(arr) # 基准获取
        arr[mark], arr[-1] = arr[-1], arr[mark]
        buffer = []
    
        while arr:
            mark = arr.pop()
            less, greater = [], []
            for x in arr:
                if x <= mark:
                    less.append(x)
                else:
                    greater.append(x)
            if len(less) == k:
                return buffer + less
            elif len(less) < k:
                k -= len(less)
                buffer += less
                arr = [mark] + greater
            else:
                arr = less
    
  • 相关阅读:
    【转载】【贪心】各种覆盖问题
    【转载】【知识点总结】NOIP前夕 2014.11.4
    最大子图形问题
    小知识
    Tyvj——P1864 [Poetize I]守卫者的挑战
    Tyvj——P1952 Easy
    BZOJ——2134: 单选错位
    BZOJ——1620: [Usaco2008 Nov]Time Management 时间管理
    BZOJ——1622: [Usaco2008 Open]Word Power 名字的能量
    洛谷 U3357 C2-走楼梯
  • 原文地址:https://www.cnblogs.com/ivan-blog/p/12344819.html
Copyright © 2020-2023  润新知