时间复杂度 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)//2
mergeSortFunc(data, L, mid) //左部分 merge sort
mergeSortFunc(data, mid+1, R)//右部分 merge sort
merge(data, L, R, mid) // 左右 merge
def merge(data, L, R, M):
help_arr = []
p1 = L
p2 = M+1
while 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_arr
if len(data) < 2:
return
mergeSortFunc(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 5
134 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 = 16
merge 实质:让左侧的数 依次碰到每一个右侧的数。 每一次搞定的左侧 不需要重复求,并且计算右侧有多少个比当前数大的时候 可以通过下标直接计算得到 不需要一个个数,因为左侧和右侧都是有序的,当左侧小于右侧的时候 产生小和。
merge sort 好的地方在于 每次比较结果都是变成有序序列。后续比较 只需要左组 和右组进行比较,不需要组内比较, 只需要跨组比较。
暴利查询:1: 左侧小于1的 无。
3: 左侧小于3的 1,
4: 左侧小于4的1,3,
2: 左侧小于2的1
5: 左侧小于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 0
mid = L+(R-L)//2
return mergeSort(data, L, mid)
+ mergeSort(data, mid+1, R)
+ mergeAndSum(data, L, R, mid)
def mergeAndSum(data, L, R, Mid):
p1 = L
p2 = Mid+1
help_arr = []
ret = 0
while 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_arr
return ret
//leastSum func
if len(data)<2:
return
return mergeSort(data, 0, len(data)-1, ret)
逆序对:在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有的逆序对。(即求每个数 左边多少个比它大的数)
def reverseData(data):
if len(data)< 2:
return
def 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+1
else:
largest = left
if data[largest] < data[index]:
largest = index
break
data[largest], data[index] = data[index], data[largest]
index = largest
left = 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:
return
for i in range(0, len(data)):
heapInsert(data[i],i)
heapSize = len(data)-1
data[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:
return
partition(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-1
more = 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:
return
q_sort(data, 0, len(data)-1)