O(NlogN)的排序算法——左程云算法与数据结构教程2

2.O(NlogN)的排序算法

  • 1.递归
    • 递归行为
      • 压栈,
      • 压栈结束之后的操作可以看成树的后根遍历
    • 递归时间复杂度估计
      • master公式:T(N)=a*T(N/b)+O(N^d)
        • T(N):母问题
        • a*T(N/b):子问题(需要规模均分)
        • O(N^d):附加操作
      • 使用

       

      • 例子:
        • T(N)=2*T(N/2)+O(1),由参数关系得到时间复杂度为O(N)

         

    • 小技巧(除以二变右移一位)
      • 数字除以二可以写成向右移一位(速度更快)
      • 如(R-L)/2可以写成(R-L)>>1
  • 2.归并排序mergesort
    • 归并排序
      • 概念
        • 创建在归并操作上的一种有效的排序算法。算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。归并排序思路简单,速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列。
      • 1. 基本思想
        • 归并排序是用分治思想,分治模式在每一层递归上有三个步骤:
        • 分解(Divide):将n个元素分成个含n/2个元素的子序列。
        • 解决(Conquer):用合并排序法对两个子序列递归的排序。
        • 合并(Combine):合并两个已排序的子序列已得到排序结果。
      • 2. 实现逻辑
        • 2.1 迭代法
          • ① 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
          • ② 设定两个指针,最初位置分别为两个已经排序序列的起始位置
          • ③ 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
          • ④ 重复步骤③直到某一指针到达序列尾
          • ⑤ 将另一序列剩下的所有元素直接复制到合并序列尾
        • 2.2 递归法
          • ① 将序列每相邻两个数字进行归并操作,形成floor(n/2)个序列,排序后每个序列包含两个元素
          • ② 将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素
          • ③ 重复步骤②,直到所有元素排序完毕
      • 代码
        • 递归法:
          • T(N)=2T(N/2)+O(N)
          • master公式:O(N*logN)

           

    • 复杂度
      • 时间复杂度:O(NlogN)
        • 时间复杂度变小的原因:前面的比较信息没有被浪费,变成了有序的东西在后面利用了,所以时间复杂度减少了
      • 额外空间复杂度:O(N)
    • 实际问题
      • 小和问题
        • 问题:
        •  

         

        • 算法:
          • 每次归并过程中得到右边部分数组中大于左边某个数字的个数,从而确定该左边数字在和右边部分数字比较从而在小和计算中出现的次数
          • 右边数字小于等于左边数字时,先将右边归并到额外位置中
          • 当右边数字大于左边数字时,直接通过右边数组中大于右边数字的个数得到左边数字的小和个数
          • 合并过程中只产生左边部分数组的数字的小和
          • 小和个数=左边部分数组小和个数+右边部分数组小和个数+合并过程中判断出的小和个数
        • 代码:

           

      • 逆序对问题
        • 问题:

  • 3.快速排序
    • 快排1
      • 选择最后一个数字将其余数据分为左边小于等于该数字的部分和右边大于该数字的部分
      • 将该数字放在两部分数据中间
      • 对左边部分数据和右边部分数据分别递归该操作
      • 时间复杂度为O(N²)
    • 快排2(利用荷兰国旗问题)
      • 选择最后一个数字将其余数据分为左边小于该数字的部分、中间等于该数字的部分和右边大于该数字的部分
      • 将该数字放在中间的数据部分
      • 对左边部分数据和右边部分数据分别递归该操作
      • 时间复杂度为O(N²)
    • 快排3
      • 随机选择一个数字放到最后,将其余数据分为左边小于该数字的部分、中间等于该数字的部分和右边大于该数字的部分
      • 将该数字放在中间的数据部分
      • 对左边部分数据和右边部分数据分别递归该操作
      • 时间复杂度为O(NlogN)(由概率求得的期望)
      • 代码:
      •  

    • 时间复杂度:
      • 最差(划分值很偏):O(N²)
      • 最好(划分值在差不多中间的位置):
        • T(N)=2T(N/2)+O(N)
        • 由master公式可以得到时间复杂度为O(NlogN)
    • 荷兰国旗问题:
      • 问题1:
        • 问题:
        •  

        • 算法:
          • 一个指针从左向右依次移动,并和
      • 问题1:
        • 问题:

        • 算法:
          • 1.[i]<num,[i]和小于区的下一个交换,小于区右移,i++
          • 2.[i]==num,i++
          • 3.[i]>num,[i]和大于区的前一个交换,大于区左移,i原地不变
### 最长上升子序列 O(n log n) 算法实现解释 #### 1. 算法核心思想 为了将最长上升子序列(Longest Increasing Subsequence, LIS)问题的时间复杂度从 \(O(n^2)\) 降至 \(O(n \log n)\),可以通过维护一个数组 `tails` 来记录当前可能成为 LIS 的最小末尾元素。具体来说,`tails[i]` 表示长度为 \(i+1\) 的所有上升子序列中最后一个元素的最小值[^2]。 该方法的核心在于利用 **二分查找** 更新 `tails` 数组中的元素位置,从而减少不必要的比较操作。由于每次更新都涉及一次二分查找,因此整体时间复杂度降为 \(O(n \log n)\)[^4]。 --- #### 2. 算法步骤说明 以下是算法的主要逻辑分解: - 初始化一个空列表 `tails` 和变量 `length` 记录当前找到的最长上升子序列长度。 - 遍历输入数组中的每一个元素: - 如果当前元素大于 `tails` 中的所有元素,则将其追加到 `tails` 列表末尾,并增加 `length` 值。 - 否则,使用二分查找定位当前元素应替换的位置(即第一个不小于它的元素),并完成替换操作。 - 遍历结束后,`length` 即为所求的最长上升子序列长度。 需要注意的是,虽然 `tails` 可能并不完全等于最终的 LIS 序列本身,但它能够帮助快速计算出 LIS 的长度[^5]。 --- #### 3. Python 实现代码 下面是基于上述思路编写的 Python 实现代码: ```python from bisect import bisect_left def length_of_LIS(nums): tails = [] # 存储潜在的LIS最小末尾元素 for num in nums: pos = bisect_left(tails, num) # 使用二分查找确定num应该放置的位置 if pos == len(tails): # 当前数比已知所有的大,扩展tails tails.append(num) else: # 替换掉第一个大于等于num的位置上的值 tails[pos] = num return len(tails) # 测试用例 nums = [10, 9, 2, 5, 3, 7, 101, 18] print(length_of_LIS(nums)) # 输出:4 对应于子序列 [2, 3, 7, 101] ``` 此代码片段实现了 \(O(n \log n)\) 时间复杂度下的 LIS 长度计算功能[^3]。 --- #### 4. 关键点分析 - **为何有效?** 维护的 `tails` 数组始终满足严格递增性质,这使得我们可以安全地应用二分查找技术来加速搜索过程。 - **能否还原实际路径?** 虽然本算法仅返回了 LIS 的长度而非具体的子序列内容,但如果需要获取完整的子序列,可通过额外存储索引来追踪每个元素对应的父节点信息[^4]。 --- #### 5. 性能对比 传统 DP 方法需双重嵌套循环逐一尝试每一对组合关系,故其时间开销较高 (\(O(n^2)\))[^1]。而改进后的版本借助二分策略显著降低了单步查询成本至常数级别 (\(\log n\)), 进一步提升了效率[\^2]. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值