zoukankan      html  css  js  c++  java
  • 插排 与 快排

    插入排序

    排序和查找是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
    
  • 相关阅读:
    Burp
    SQL注入
    网络安全没有“银弹”
    Centos7
    虚拟机的使用流程
    虚拟机安装流程
    nmap指令
    UDP 服务器和客户端实例,实现2个客户端通过UDP服务器打洞穿透
    c++ win32下窗口的最小化到托盘以及还原
    基于百度OCR的图片文字识别
  • 原文地址:https://www.cnblogs.com/friedCoder/p/12617844.html
Copyright © 2011-2022 走看看