插排 与 快排

插入排序

排序和查找是2个重要的算法领域,两者既有区别又有联系。查找不一定要排序,但是排序必须查找。

因此对于排序算法,记住最好是边查找边排序,不要把查找和排序分开操作。比如下面这个插入排序的例子:

def insert_sort( arr ):
    length = len(arr)
    if length < 2:
        return arr

    for i in xrange( 1, length ):
        for j in xrange(0, i):
            if arr[i] < arr[j]:		#先找出arr[i]的位置
                tmp = arr[i]
                for k in xrange(i, j, -1):	#再重新排序
                    arr[k] = arr[k-1]
                    k -= 1
                arr[j] = tmp

上面这个算法先查找后排序,这和从哪头开始查找有关。有时候查找和排序可以一起进行,下面这个插入排序,当找到curVal的位置时,排序也基本完成,只需要一条语句:arr[ curIndex ] = curVal

def insert_sort_opt( arr ):
    if len(arr) < 2:
        return arr

    for i in xrange(1,len(arr)):
        preIndex = i - 1
        curIndex = i
        curVal = arr[ curIndex ]
        
        #下面的双向扫描的核心代码
        while preIndex >= 0 and arr[preIndex] > curVal:
            arr[ curIndex ] = arr[ preIndex ]		#查找和排序一起
            curIndex = preIndex
            preIndex -= 1  
        arr[ curIndex ] = curVal

    return arr

一、插入排序

https://www.cnblogs.com/lanhaicode/p/11259509.html

1、将待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列;

2、取出下一个元素,在已经排序的元素序列中从后向前扫描;

3、如果该元素(已排序)大于新元素,将该元素移到下一位置;

4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;

5、将新元素插入到该位置后;

6、重复步骤2~5。

复杂度分析:插入排序效率比较低,最好情况也要O(N),最坏情况还要O(n^2)。但对于小规模的数组排序而言,还是不错的。

二、快排

插入排序的变化不太多,但对于快排,可以有许多优化的方法,下面针对top-k问题来谈谈快排的优化。

问题:尽可能快地找到前K小的元素。top-k问题

https://www.cxyxiaowu.com/8647.html

核心思路:每次缩小查找的范围,最后锁定答案。将大问题拆解成小问题,通过对小问题的解决来搞定原本的大问题

做法:每次是设置一个标杆(取其中一个元素作为标杆),然后对数组当中的元素进行调整,保证比标杆小的元素都在它的左边,比它大的都在它的右边。标杆最后在的位置就是数据有序之后它正确的位置。

复杂度分析:理想的话每次规模减半,复杂度为O(logN);最糟糕的话数组本身逆序,每次只能排除掉一个元素,复杂度升到O(n^2)。

version 1.0

# 从数组arr中选出最小的k个数
def quick_select( arr, k ):

    n = len( arr )
    if n <= k:
        return arr


    bufferArr = []
    while arr:
        #选择最后一个元素来作为大小判断的标杆
        mark = arr.pop()	#标杆随意选取,运气不好每次只能排除一个元素,时间复杂度为O(n^2)
        less, greater = [], []	#需要额外的2个队列,空间复杂度上升

        for x in arr:
            if x <= mark :
                less.append(x)
            else:
                greater.append(x)

        if len(less) == k:
            return less + bufferArr

        elif len(less) < k:
            bufferArr += less
            k -= len(less)
            arr = [mark] + greater

        else:
            arr = less

优化

  • 一,在空间上进行优化,即在自身数组上直接进行排序(查找的同时进行排序),不额外创建新的数组,其次是双向扫描原数组,这样比单向扫描更快。
  • 二,在选择标杆上进行优化,BFPRT算法
  1. 判断数组元素是否大于 5 ,如果小于 5 ,对它进行插入排序,并返回数组的中位数
  2. 如果元素大于 5 个,对数组进行分组,每 5 个元素分成一组,允许最后一个分组元素不足 5 个。
  3. 对于每个分组,对它进行插入排序
  4. 选择出每个分组排序之后的中位数,组成新的数组
  5. 重复以上操作(算法思路很朴素,其实就是一个不断选择中位数的过程。

version 2.0

#对quick_select进行空间上的优化
#在原数组上直接进行快排操作,不额外增加空间,并且减少了复制数据的操作
#这里使用的是双向扫描,即交替扫描头尾2端数据,具体实现看下面代码
def quick_select_opt( arr, k ):
    left = 0
    right = len( arr )
    
    if k >= (right - 0):	#如果k比数组arr的size还大
        return arr;

    while  (right - 0) != k :

        mark = arr[left]
        markL, markR = left, right-1

        #这是双向扫描的核心代码, 务必记住
        while markL < markR:
            while arr[markR] > mark  and  markL < markR:    #条件markL<markR很重要
                markR -= 1
            arr[markL] = arr[markR]
            while arr[markL] <= mark and markL < markR:
                markL += 1
            arr[markR] = arr[markL]
        arr[markR] = mark       #写回mark


        length = markR + 1	# length算式里的+1,是把mark也算进去
        if length == k  or  length - 1 == k:
            return arr[0:k]     #队列是不包括最后一个元素的

        elif length < k:
            left = length

        else:
            right = length - 1
            

#写代码有时侯为了提高一点效率,选择跳过某些步骤或者提前某些运算,但是除非自己很有把握,不然在紧急情况下还是以稳健性为首要前提。不要为了一点点的效率而随便删减流程,因为可能会有bug,不值得。特别是上机考试的时候,如果没有充足的时间来考虑所有情况,那就更不要对流程随意删减,宁愿选择虽然复杂但是更稳健的办法。


#下面将函数quick_select_opt进行拆分,可能思路会更加清晰       
#将数组arr以arr[l]为标杆划分为2部分,左边元素小于arr[l,右边大于
#arr下标范围[l,r]
#返回arr[l]的有序位置
def partion( arr, l, r ):
    if l >= r:
        return l

    mark = arr[l]
    while l < r:
        while arr[r] > mark and l < r:
            r -= 1
        arr[l] = arr[r]
        while arr[l] <= mark and l < r:
            l += 1
        arr[r] = arr[l]
    arr[l] = mark

    return l


def quick_select_opt2( arr, k ):
    if len(arr) <= k:
        return arr

    left, right = 0, len(arr)
    while (right - 0) != k:
        l, r = left, right-1
        length = partion( arr, l, r) + 1

        if length == k or length - 1 == k:
            return arr[0:k]

        elif length > k:
            right = length

        else:
            left = length
            
#将函数功能细分,编程的时候思路更加清晰一些,代码可读性更高。同时产生bug的几率也小了。

version 3.0

# 插入排序,快排需要用到
def insert_sort( arr ):
    if len(arr) < 2:
        return arr

    for i in xrange(1,len(arr)):
        preIndex = i - 1
        curIndex = i
        curVal = arr[ curIndex ]
        while preIndex >= 0 and arr[preIndex] > curVal:
            arr[ curIndex ] = arr[ preIndex ]
            curIndex = preIndex
            preIndex -= 1
        
        arr[ curIndex ] = curVal

    return arr



# 返回数组arr的一个元素下标,该元素至少比数组里3/10的元素大。
# start是数组下标的起始值,length是数组的长度
def bfprt( arr, start = None, length = None):
    if  start is None  or  length is None:
        start, length = 0, len(arr)

    if length <= 5:
        #这里使用切片,trick
        arr[start:start+length] = insert_sort( arr[start:start+length]  ) 
        return start + length // 2

    idx = start
    mid_num = 0
    while length > 5:
        arr[idx:idx+5] = insert_sort( arr[idx:idx+5] )
        arr[start+mid_num], arr[idx+2] = arr[idx+2], arr[start+mid_num]
        idx += 5
        length -= 5
        mid_num += 1

    if length > 0:  #剩下一些c凑不齐5个的元素
        arr[idx:idx+length] = insert_sort( arr[idx:idx+length] )
        arr[start+mid_num], arr[idx+length//2] = arr[idx+length//2], arr[start+mid_num]
        mid_num += 1
        
    return bfprt( arr, start, mid_num )



#划分数组arr为2部分,左边小于mark,右边大于mark
#数组arr下标范围[left,right]
def partion( arr, left, right ):
    mark = arr[left]
    while right > left:
        while arr[right] > mark  and  right > left:
            right -= 1
        arr[left] = arr[right]
        while arr[left] <= mark  and  right > left:
            left += 1
        arr[right] = arr[left]
    arr[left] = mark

    return left




# 最终优化
def quick_select_opt( arr, k ):

    start = 0
    length = len(arr)

    if length <= k:
        return arr

    topN = length
    left = start
    while topN != k:
        markIdx = bfprt( arr, left, topN )
        arr[markIdx], arr[left] = arr[left], arr[markIdx]
        markIdx = partion( arr, left, left+topN-1  )

        if markIdx == k  or  markIdx + 1 == k:
            return arr[0:k]

        elif markIdx < k:
            left = markIdx + 1

        else:
            topN = markIdx
posted @ 2020-04-02 10:09  friedCoder  阅读(507)  评论(0编辑  收藏  举报