基础算法-2: 时间复杂度为O(N*logN)的排序算法
时间复杂度 O(N*logN):
归并排序,堆排序(大根堆,小根堆,heapInsert/heapify),快速排序(荷兰国旗问题)。
- 归并排序
L — Mid — R
先让 左有序,右有序。
归并 谁小copy谁
def mergeSort(data):def mergeSortFunc(data, L, R):if L==R:return mid = L+(R-L)//2mergeSortFunc(data, L, mid) //左部分 merge sortmergeSortFunc(data, mid+1, R)//右部分 merge sortmerge(data, L, R, mid) // 左右 mergedef merge(data, L, R, M):help_arr = []p1 = Lp2 = M+1while p1<=M and p2<=R:if data[p1] <= data[p2]:help_arr.append(data[p1])p1++else:help_arr.append(data[p2])p2++while p1<=M:help_arr.extend(data[p1:M+1])while p2<R:help_arr.extend(data[p2:R+1])data[L:R+1] = help_arrif len(data) < 2:returnmergeSortFunc(data, 0, len(data)-1)
T(N) = 2T(N/2) +O(N) 所以 时间复杂度就是 O(N*logN), 额外空间复杂度是 O(N)
题目:小和问题 和 逆序对问题
小和问题:在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和,求一个数组的小和。
//解法: 暴利查询 每个数查询左侧比他小的值 并且求和, 时间复杂度O(N^2)//解法2: 归并。
一个数a 左边比他小的数加和 产生小和 与 右边有多少个比他大产生 n*a 小和 等价.
'''
例: 1 3 4 2 5
划分左右 1 3 4 2 5
划分左右 1 3 4 2 5
merge 时 右侧比 左侧大的时候 产生小和。1 3 merge:1<3 , 右侧有1个比1 大。所以 1* 1
13 4 merge: 1<4,右侧有1个比1大, 产生1*1, 3和4比较, 3<4,右侧有1个比3大,产生1*3.
得到 1 3 4 2 5 merge: 2 < 5, 右侧有1个比2大,所以产生1*2
得到2 5134 25 merge:
--> 1 < 2: 右侧有2个比1大, 产生2*1. 新的数组得到 1. 原数组为 34 25
--> 3 > 2: 不产生小和。新的数组为 1 2,原数组为 34 5
--> 3 < 5: 右侧有1个比3大, 产生1*3. 新数组得到 1 2 3, 原数组为4 5
--> 4 < 5: 右侧有1个比4大, 产生1*4. 新数组为 1 2 3 4 5. 最后产生的小和: 1*1 + 1*1 + 1*3 + 1*2 + 2*1 + 1*3 + 1*4 = 16merge 实质:让左侧的数 依次碰到每一个右侧的数。 每一次搞定的左侧 不需要重复求,并且计算右侧有多少个比当前数大的时候 可以通过下标直接计算得到 不需要一个个数,因为左侧和右侧都是有序的,当左侧小于右侧的时候 产生小和。
merge sort 好的地方在于 每次比较结果都是变成有序序列。后续比较 只需要左组 和右组进行比较,不需要组内比较, 只需要跨组比较。暴利查询:1: 左侧小于1的 无。 3: 左侧小于3的 1, 4: 左侧小于4的1,3,2: 左侧小于2的15: 左侧小于5的1,2,3,4 加和: 1+1+3+1+1+2+3+4 = 16
'''
def leastSum(data):def mergeSort(data, L, R):if L==R:return 0mid = L+(R-L)//2return mergeSort(data, L, mid) + mergeSort(data, mid+1, R) + mergeAndSum(data, L, R, mid)def mergeAndSum(data, L, R, Mid):p1 = Lp2 = Mid+1help_arr = []ret = 0while p1 <= Mid and p2 <= R:if data[p1]< data[p2]:help_arr.append(data[p1])ret += (R-p2+1)*data[p1]p1++else data[p1]>=data[p2]://相等的时候 copy 右部分。help_arr.append(data[p2])p2++while p1<=Mid:help_arr.extend(data[p1:Mid+1])while p2<=R:help_arr.extend(data[p2:R+1])data[L:R+1] = help_arrreturn ret//leastSum func if len(data)<2:returnreturn mergeSort(data, 0, len(data)-1, ret)
逆序对:在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有的逆序对。(即求每个数 左边多少个比它大的数)
def reverseData(data):if len(data)< 2:returndef mergeSort
堆
堆:用数组实现的完全二叉树结构(默认下标从0开始)
0 1 2 3 4 5 6 7
–> 0
1 2
3 4 5 6
完全二叉树: 结点i, 父节点 (i-1)/2 左孩子: 2i +1, 右孩子: 2i+2大根堆:对于每颗子树的最大值 就是 父节点
小根堆:对于每颗子树的最小值 就是父节点heapsize 是堆的一个重要参数。
任何一个数组 都可以按照大根堆的方式进行排序。 时间复杂度为O(Log_2 N)
def heapInsert(data, index): //数字 来到了 index 位置,如何往上移动while data[index] > data[(index-1)//2]:data[index], data[(index-1)//2] = data[(index-1)//2],data[index]index = (index-1)//2// 即包含了index在0位置 没有父节点的时候 也包含 index在其他位置 有父节点的时候// index = 0 时, (index-1)//2 也等于0
如果用户跟我要数的时候,比如要大根堆的堆顶,但是剩下的还是希望是大根堆结构。如何做?
用最后一个位置填堆顶,heapsize 减1,相当于把最后一个位置在堆里面移除。此时需要调整为大根堆,也就是把堆顶往下调整。
时间复杂度为O(Log_2 N)
def heapify(data, index, heapSize):left = index * 2 + 1 //左孩子下标while left < heapSize: //下方还有孩子的时候if left+1 < heapSize and data[left+1] > data[left]:largest = left+1else:largest = leftif data[largest] < data[index]:largest = indexbreakdata[largest], data[index] = data[index], data[largest]index = largestleft = index*2 + 1
如果某个位置的值发生了变化,则可以heapInsert 和heapify 各来一遍,最多只可能发生一个函数操作。
堆排序
给定一个数组,先把数组整体变成大根堆,此时heapsize=N。
比如: 9 8 3 7 6 2 1
step-1: 建立大根堆
step-2:把堆顶 和最后一个位置交换。把最后一个位置去掉。heapSize-1
step-3: 调整当前的数组为大根堆。如果数组放那边,调整成大根堆 可以自顶往上(时间复杂度为O(logN)),也可以自底往上(时间复杂度度O(N))。
自底往上把数组调整成大根堆。一开始认为数组为一个完全二叉树,从最后一个数开始 调整为大根堆(看他是否比每一个子孩子大,如果是的话 就往下走)heapify堆排序:时间复杂度为O(NlogN), 额外空间复杂度O(1)
//从顶往下
def heapSort(data):if len(data)<2:returnfor i in range(0, len(data)):heapInsert(data[i],i)heapSize = len(data)-1data[0],data[heapSize] = data[heapSize],data[0]while heapSize>0:heapify(data,0,heapSize)data[0],data[heapSize] = data[heapSize],data[0]
堆扩展
已知一个几乎有序的数组,几乎有序是指的是 如果把数组排好顺序的话,每个元素一定的距离可以不超过K, 并且K相对于数组来说比较小。请选择一个合适的排序算法针对这个数组排序。
答: 使用堆。 为什么?如何用?
先把0-(K+1)个数 设置为小根堆,也就是0位置放最小的。
然后再把1-(K+2)个数 设置为小根堆,得到1的位置。
。。。
时间复杂度O(N * log K) (小根堆调整是log K 级别的)快速排序
5-1:快排的partition的思想
问题1: 给定一个数a,和数组A,请先做到 把小于等于a的放到左边,大于a的放到右边。要求时间复杂度O(N),额外空间复杂度O(1)。
方法:
设一个小于等于 区域。最开始为空。
当前数 <= a, 当前数和小于等于区域下一个数 交换,然后小于等于区域阔一个位置,当前数跳到下一个。
当前数 >a, 当前数直接跳下一个。
需要有的参数:小于等于区域指针,当前数指针。问题1拓展:荷兰国旗问题。小于a的放到左边,等于a放中间,大于a的放右边。
方法:
当前数=划分值, 当前数跳到下一个
当前数<划分值,当前数 与小于区域 下一个数 交换,小于区域 往后扩一个,当前数跳到下一个
当前数>划分值,当前数与大于区域 前一个数 交换,大于区域往前扩一个,当前不变。
当前数与大于区域撞上时,停止。小于划分值区域 等于区域 当前数 …(待定区域) 大于区域
def NetherLandsFlag(data,p):def partition(data, L, R, p):// p是划分值,要把L-R区间调整less = L-1 //小于区域的右边界more = R + 1//大于区域的左边界while L< more: // L是当前数下标if data[L] < p://小于划分值data[L], data[less+1] = data[less+1], data[L]//交换less++L++else if data[L]> p:data[L], data[more-1] = data[more-1], data[L]more--else:L++return [less+1, more-1] //等于区域的下标if len(data)<1:returnpartition(data, 0, len(data)-1, p)
5-2:快排
不改进的快速排序:
1) 把数组范围中的最后一个数作为划分值,然后把数组通过荷兰国旗问题分成3个部分:左侧<划分值,中间==划分值,右侧>划分值。
2)对左侧范围和右侧范围,递归执行。
分析:
1)划分值越靠近两侧,复杂度越高,划分值越靠近中间,复杂度越低.
2)可以轻而易举地举出最差的例子,所以不改进的快排时间复杂度是O(N^2) (比如:1,2,3,4,5,6 划分值是6)。最好的情况是partition 的位置几乎在中点,时间复杂度O(NlogN)。
3)为了防止每次都是最差的情况出现,则通过L-R上随机选择一个数和N-1位置交换,这样降低复杂度。 (因为随机选择一个数,所以各种情况都是均等的,最后平均复杂度为O(NlogN))
经典快排:
(L-R上随机选一个数字和N-1位置上的数进行交换)X 在N-1位置上,对0-N-2上的数进行partition <=x, >x (<=x 左侧,>x 右侧,然后把x和>x区域的第一个数交换),保证小于等于区域最后一个数是x.这样对于<=区域继续递归,然后对>x区域递归。 Base case: 区域只有一个值时 不需要排序。
(最差的情况 时间复杂度O(N^2),空间复杂的O(N),最好的情况是时间复杂度O(N*logN),空间复杂度O(logN))
改进的快排:
(L-R上随机选一个数字和N-1位置上的数进行交换)X是N-1上的划分值,在0 - (N-2)上进行荷兰国旗加速 <x, =x, >x(得到3个区域),然后把X和大于区域第一个值交换,并且大于区域往后移一位。然后在<x 和>x 区域分别做递归.
改进VS 经典快排:
改进的快排是每次排序 可以把所有等于x的都搞定 ,而经典的快排是每次都只能搞定一个数(也就是只有最后那个划分值 放到正确的位置)
def quickSort(data):def partition(data, L, R)://less = L-1more = R // 最后一个作为划分值while L < more:if data[L] < data[R]:data[L], data[less+1] = data[less+1],data[L]L++less++else if data[L] > data[R]:data[L], data[more-1] = data[more-1],data[L]more-- else:L++ data[more-1], data[R] = data[R],data[more-1]return (less+1, more)def q_sort(data, L, R)://排好L-R之间的数if L<R:index = random(L, R)data[R], data[index] = data[index], data[R]p = partition(data, L, R)q_sort(data, L, p[0]-1)q_sort(data, p[1]+1, R)if len(data)<2:returnq_sort(data, 0, len(data)-1)
基础算法-2: 时间复杂度为O(N*logN)的排序算法相关推荐
- php常用算法的时间复杂度,php的几个经典排序算法及时间复杂度和耗时
$arr = []; for ($i=0; $i 测试结果:排序用时(秒):5.2821290493011 php排序里的经典算法首先想到的就是冒泡排序法, 排序思想:两两交换小数上浮大数下沉每轮浮出 ...
- O(N*logN)的排序算法
时间复杂度为:O(N*logN)的排序算法 注:图来自于网络 归并排序(Merge Sort) 归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法.该算法是采用分治法(Divide ...
- <<算法很美>>——(三)十大排序算法(上)
目录 前言 冒泡排序 图解冒泡 代码实现 冒泡优化 选择排序 图解选排 代码实现 插入排序 图解插入 代码实现 希尔排序 图解希尔 代码实现: 归并排序 图解归并 代码实现 快速排序 图解快排 ...
- 【数据结构与算法】之深入解析十大常用排序算法的原理分析和算法实现
一.十大排序算法的对比 排序算法 平均时间复杂度 最好情况 最坏情况 空间复杂度 排序方式 稳定性 冒泡排序 O(n2) O(n) O(n2) O(1) In-place 稳定 选择排序 O(n2) ...
- 【轻松学排序算法】眼睛直观感受几种常用排序算法(转)
1 快速排序 介绍: 快速排序是由东尼·霍尔所发展的一种排序算法.在平均状况下,排序 n 个项目要Ο(n log n)次比较.在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见.事实上,快速排序通 ...
- 算法学习02:认识O(logN)的排序
01归并排序 归并排序就是先将一个数组的左侧与右侧都有序,然后用两个指针分别比较两个数组元素的大小,将比较结果复制到辅助数组中. 整体采用递归方法,确定递归基是需要排序的序列只有一个数的时候.其余情况 ...
- 计数排序:时间复杂度仅为 O(n) 的排序算法
一.简介 计算排序假设 n 个输入元素都是 0 到 k 区间内的一个整数,其中 k 为某个整数. 基本原理: 创建一个长度为 k+1 的数组 count[],它的 count[i] 的值对应输入数组中 ...
- 算法设计与分析 自创O(n)排序算法 适用于任何有理数
受桶排序思想的启发,想到了这个算法.不同之处在于,桶排序是对于每个bucket各自排序,而本算法桶内部不需要排序. 功能 本算法可以在有限空间内,将任意有理数排序,平均时间复杂度为O(n) 输入:需要 ...
- <<算法很美>>——(三)十大排序算法(下)
目录 1. 奇数在左偶数在右 2. 最快效率求出乱序数组中第k小的数 3. 数组中有一个数字出现次数超过数组长度一半 4. 合并两个有序数组 5. 数组中的逆序对 6. 排序数组中两个数字之和 7. ...
最新文章
- Google 公司的 Java 语言编写规范
- 央行:货币政策稳健并不意味着一成不变
- 单脉冲雷达的相干干扰的研究文章_什么是量子纠缠和量子退相干?这个比喻太绝了!...
- Servlet课程0425(五) sendRedirect实现不同页面共享数据
- boost::hana::make_range用法的测试程序
- Android Library projetcts cannot be exported.
- php for循环 循环奇数,php – Foreach,每个n项的特殊处理(奇数,甚至例如)
- Mentor PADS 9.5下载安装及破解指南
- 【无码专区12】子集和(背包dp)
- 调整 Windows系统参数网址
- iphone最新款手机_苹果用户不换安卓手机的8点原因,最后一点最关键
- 湖人签阿泰是双赢之举 防罗伊打压火箭是两大关键
- python文字转图片_技能:如何使用Python将文本转为图片
- 网易回应暴力裁员事件并道歉了!连发两声明:当事人绩效确不合格...刘强东说了这句话,意外上热搜...
- log4j2关闭debug日志
- 从零開始搭建微信硬件开发环境全过程——1小时掌握微信硬件开发流程
- ps图层高级扩展知识
- Android笔记:多开/分身检测
- 熬夜总结的2022java面试题
- 区块链智能合约----Solidity状态修饰符view、pure
热门文章
- php mysql安装包_apache php mysql集成开发环境
- 看齐iOS砍掉祖传功能,Android 16G内存也危险了
- python中x+=y和x=x+y的区别
- android布局置顶_[置顶] Android系统五大布局详解Layout
- 局域网ip冲突的时候,释放ip重新获取
- 《安富莱嵌入式周报》第317期:开源60W小型UPS电源,0.1Hz - 200MHz 频率计,纯C实现的SokolGFX渲染库, FreeRTOS Trace
- git 警告: LF will be replaced by CRLF in 解决办法
- android和ios适配_针对iOS和Android设计时“并非总是”情况的8个UX设计技巧
- Linux网络-IP协议
- 漏洞扫描以及常用扫描工具总结