zoukankan      html  css  js  c++  java
  • 排序

    排序算法的稳定性:一个稳定的排序算法,如果两个等值键R和S在排序前后次序不变,称其是稳定的

     例如排序之前的序列为:(4, 1) (3, 1) (3, 7)(5, 6)

    (3, 1) (3, 7) (4, 1) (5, 6) 维持次序,稳定

    (3, 7) (3, 1) (4, 1) (5, 6)  次序被改变,不稳定

    1.冒泡排序

    两两相邻记录的关键字,如果反序就交换,直到没有反序的记录为止。

    #  直觉的写法,但实际不能算冒泡
    def bubble_sort(alist):
        for i in range(len(alist)-1):   # 从第一个元素到倒数第二个元素
            for j in range(i+1, len(alist)):   # 如果a[i]比后面的元素更大,交换 
                if alist[i] > alist[j]:
                    alist[i], alist[j] = alist[j], aslit[i]

    因为实际上每次固定的a[i]会和后面的所有元素都比较一遍,并不是冒泡中的比较两两相邻的关键字。

    两两是相邻元素;如果有n个元素那就要比较n-1轮,每一轮都减少一次比较;从下往上两两比较,就像泡泡往上冒一样。

    # 升序
    def bubble_sort(alist):
        n = len(alist)
        for i in range(n-1):   # 当前轮从a[i]开始,两两相邻比较,最后一轮从a[n-2]开始。a[:i]已经有序
            for j in range(n-1, i, -1):   # 从下向上冒
                if alist[j] < alist[j-1]:  # 如果后一个元素比前一个小,就交换
                    alist[j], alist[j-1] = alist[j-1], alist[j]

     做一点优化

    def bubble_sort_pro(alist):
        n = len(alist)
        for i in range(n-1):
            count = 0
            for j in range(n-1, i, -1):
                if alist[j] < alist[j-1]:
                    alist[j], alist[j-1] = alist[j-1], alist[j]
                    count += 1
            if count == 0:  # 如果某一轮没有进行任何交换,说明整个数组已经有序了
                return 
    

      

    2.选择排序

    通过n-i次关键字之间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录交换。1<=i<=n。

      在未排序序列中找到最小(大)元素,放到已排序序列起始位置(一次交换);

      然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾(一次交换);

      以此类推,直到所有元素均排序完毕

    选择排序的优点在于数据移动的操作:如果某个元素位于正确的最终位置上,它就不会被移动。所以至多需要交换 n-1 次(因为第一次就直接认为第一个元素是最小(大))。操作的是无序的那部分。

    def selection_sort(alist):
        n = len(alist)
        for i in range(n-1):  # 要找 n-1 轮最值,第一个首先认为是个最小(大)值
            min_index = i   # 记录当前的最小(大)位置
            for j in range(i+1, n):  # 遍历剩下无序部分的元素,找出最小的,再交换
                if alist[j] < alist[min_index]:
                    min_index = j
            if min_index != i:
                alist[i], alist[min_index] = alist[min_index], alist[i]
    

    3.直接插入排序

    简单排序中性能最好的。将一个记录插入到一个已经排好序的有序表中。

    通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。所以过程中需反复把已经排序的元素向后挪。

    操作的是有序的那部分。

    def insert_sort(alist):
        n = len(alist)
        for i in range(1, n):
            for j in range(i, 0, -1):  # 待插入元素为a[i],有序序列为a[:i]
                if alist[j] < alist[j-1]:
                    alist[j], alist[j-1] = alist[j-1], alist[j]   # a[i]从后往前比较,找到插入位置。就相当于一直交换。

    优化

    def insert_sort_pro(alist):
        n = len(alist)
        for i in range(1, n):
            j = i
            while j > 0:
                # 寻找插入位置,只要没到位置,一直交换,最终插入
                if alist[j] < alist[j-1]:
                    alist[j], alist[j-1] = alist[j-1], alist[j]
                    j -= 1
                else:  # 如果找到要插入的位置了就不用往前再看了,避免了多余的循环
                    break
    

    4.希尔排序

    时间复杂度降到O(nlogn),对直接插入排序进行修改。

    把记录按下标的一定增量(gap)分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个记录恰被分成一组,算法终止。

    例如下图就是第一轮分了5组,第二轮分了2组

    # 按gap进行插入排序
    def shell_sort(alist):
        n = len(alist)
        gap = n // 2
        while gap > 0:
            for i in range(gap, n):  # 从 0+gap 开始往后找元素往有序序列中插入,一直找到最后
                j = i 
                while j >= gap and alist[j] < alist[j-gap]:  # 每个子序列的j元素的前一个元素不再是j-1而是j-gap,因为要在a[j]属于的分组里做插入排序
                    alist[j-gap], alist[j] = alist[j], alist[j-gap]
                    j -= gap
            gap //= 2  # 新步长
    
    def shell_sort(alist):
        n = len(alist)
        gap = n // 2
        while gap > 0:
            for i in range(gap, n):  # 待插元素从a[gap]到最后
                for j in range(i, 0, -gap):  # a[i]找插入位置,每次不再向前挪1而挪gap即可
                    if alist[j] < alist[j-gap]:
                        alist[j], alist[j-gap] = alist[j-gap], alist[j]
            gap //= 2

    5.堆排序

    利用前一趟比较的结果,对选择排序进行改进。堆是具有以下性质的完全二叉树:每个节点的值都大于等于(小于等于)其左右孩子节点的值。

    大顶堆 & 小顶堆

     

      1. 将待排序的序列构造成一个大顶堆(或小顶堆)

      2. 整个序列的最大值就是堆顶的根节点,将它移走(与堆数组的末尾元素交换,此时末尾元素就是最大值)。

      3. 将剩余的n-1个序列重新构造成一个堆,重复上述步骤,获得升序序列。

    升序排序,使用大顶堆。

    堆具有的性质,如果从堆的根节点开始从1往后编号,则节点的值满足:

         ,n/2向下取整。

      树节点编号从1开始而数组下标从0开始。统一成从0开始计数,父节点 i 的左右孩子节点在2i+1、2i+2,子节点 i 的父节点在 floor((i-1)/2)

    def heapify(arr, lst, i):
        """维护最大堆,以i为根节点的子树,最后一个节点lst"""
        largest = i
        left, right = 2*i+1, 2*i+2
        
        # 找到 i、i的左子树、i的右子树中的最大值
        if left <= lst and arr[i] < arr[left]:
            largest = left
        if right <= lst and arr[largest] < arr[right]:
            largest = right
        
        # 如果这三个节点中最大的不是i,则最大节点的值和i的值交换
        if largest != i:
            arr[largest], arr[i] = arr[i], arr[largest]
            heapify(arr, lst, largest)  # 继续对largest节点递归维护堆。如果i是最大的元素,则函数结束
    
    def heap_sort(arr):
        n = len(arr)
        for i in range(int((n-1-1)/2), -1, -1):  # 从最后一个节点n-1的父节点开始往顶上,第一个要维护的子树根节点为 floor(((n-1)-1)/2)
            heapify(arr, n-1, i)
        
        for i in range(n-1, 0, -1):
            arr[i], arr[0] = arr[0], arr[i]  # 堆顶a[0] 和 当前堆的最后一个元素(如果认为每次都把排好的移除。实际就是arr[i])交换
            heapify(arr, i-1, 0)  # 交换后要从顶向下维护堆,堆最后一个节点为下标为i-1
        
    a = [3,4,1,2,9,5,6,8]
    heap_sort(a)
    print(a) 
    

    优化,把heapify函数中的尾部递归用迭代实现,效率更高

    def heapify(arr, lst, i):
        """维护最大堆,以i为根节点的子树,最后一个节点lst"""
        while True:
            largest = i
            left, right = 2*i+1, 2*i+2
    
            # 找到 i、i的左子树、i的右子树中的最大值
            if left <= lst and arr[i] < arr[left]:
                largest = left
            if right <= lst and arr[largest] < arr[right]:
                largest = right
        
            # 如果这三个节点中最大还是i,则函数结束
            if largest == i:
                break
            arr[largest], arr[i] = arr[i], arr[largest]  # 否则的把最大值换到当前根节点i上
            i = largest  # 继续下一轮,对当前的largest为根节点的子树进行维护 
    

      

    6.归并排序

     分治思想,先递归分解数组,再合并数组。

    将数组分解最小之后,把n个记录看成是n个有序的子序列,每个子序列长度为1。然后两两归并,得到ceil(n/2)个长度为2或者1的有序子序列,再两两归并...,如此重复直到得到长度为n的有序序列为止。

    其中两两归并的基本思路是,比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。

    def merge_sort(alist):
        if len(alist) <= 1:
            return alist
        # 二分分解
        num = len(alist)//2
        left = merge_sort(alist[:num])
        right = merge_sort(alist[num:])
        # 合并
        return merge(left,right)
    
    def merge(left, right):
        '''合并操作,将两个有序数组left[]和right[]合并成一个大的有序数组'''
        #left与right的下标指针
        l, r = 0, 0
        result = []
        while l<len(left) and r<len(right):
            if left[l] < right[r]:
                result.append(left[l])
                l += 1
            else:
                result.append(right[r])
                r += 1
    
        if l < len(left): 
            result += left[l:]
        elif r < len(right):
            result += right[r:]
        return result
    

      

    迭代实现,直接从下往上。

    def merge_sort(alist):
        if alist is None or len(alist) <= 1:
            return alist
        # 每次要合并的两组数组为 a[low, low+i]、a[low+i, low+2*i]
        n = len(alist)
        tmp = [0]*n  # 建立临时数组
        
        i = 1  # 步长,也就是合并后数组元素的一半。第一次合并后数组长度为2,所以i初始化为1
        while i < n:  # 最后一次合并之前i不超过n。例如考虑n=5的情况,i=1,2,4,最后i=4合并长度为4,1的两组后结束,下一轮i=8,超过n了
            # 开始一趟,两两合并所有分组数组,每一趟都从头开始
            low = 0
            while low < n:
                mid = low + i
                high = min(low + 2*i, n)  # 如果第二组越界了,high等于n即可。数组a[n]会越界,但a[:n]不会,等价于a[:]
                if mid < high:  # 只有存在第二组才需要merge。而第二组只要有元素,high一定大于等于mid+1
                    # merge
                    merge(alist, low, mid, high, tmp)      
                low += 2*i  # 下两组的起始元素在上两组最后元素之后
            i *= 2  # 每一趟合并全部结束后,下一趟步长翻倍
        
    def merge(alist, low, mid, high, tmp):
        """这里merge要考虑分组数组长度为1的情况,还有high等于n的情况
        所以还是用索引在长度上移动来遍历,比较不容易出错
        """
        l, r, k = 0, 0, 0
        len_l, len_r = len(alist[low: mid]), len(alist[mid: high])  # 两个数组的长度
        
        while l < len_l and r < len_r:  # 只要有一个数组遍历完了,补上没有遍历完的即可
            if alist[low + l] < alist[mid + r]:
                tmp[k] = alist[low + l]
                k += 1
                l += 1
            else:
                tmp[k] = alist[mid + r]
                k += 1
                r += 1
        if l < len_l:
            tmp[k: k + mid - (low + l)] = alist[low + l: mid]
            alist[low: high] = tmp[: k + mid - (low + l)]
            
        if r < len_r:
            tmp[k: k + high - (mid + r)] = alist[mid + r: high]
            alist[low: high] = tmp[: k + high - (mid + r)]
            
            
            
    alist = [54,26,93,17,77,31,44,55,20]
    merge_sort(alist)
    print(alist) 
    

      

    7.快速排序

     通过一趟排序将要排序的数据分割成独立的两部分(选择一个比较基准),其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行。

    步骤:

      挑一个元素作为基准,重排元素,比基准大的放基准后面,比基准小的放基准前面;

      递归的把两个子序列进行用上述方法分别排序

    def quick_sort(alist):
        Qsort(alist, 0, len(alist)-1)
    
    def Qsort(arr, low, high):
        """待排序数组arr, 起始位置low, 末尾位置high
        """
        if low < high:
            point = Partition(arr, low, high)   # 根据基准点a[point]处理数组,小的放基准点左边大的放基准点右边 
            Qsort(arr, low, point-1)
            Qsort(arr, point+1, high)
            
    def Partition(arr, low, high):
        # 起始元素为要寻找位置的基准元素,先把基准点的值拿出来,序列中始终就有一个多余的空位
        point_value = arr[low]  # 基准点初始化为起始元素
        
        # 两个游标,low自左向右,high自右向左
        while low < high:
            while low < high and arr[high] >= point_value:  # high游标自右向左找到比基准元素小的元素位置
                high -= 1
            arr[low], arr[high] = arr[high], arr[low]  # 交换,把比基准点小的放前面
            
            while low < high and arr[low] < point_value:  # low游标自左向右找到比基准元素大的元素位置
                low += 1
            arr[high], arr[low] = arr[low], arr[high]  # 交换,把比基准点大的放后面
            
        alist[low] = point_value  # 当low与high位置重合,这就是基准点在最终序列中的位置
        return low
    

      

    快速排序的优化

    1. 优化基准点的选取,三数取中法,基准点选取三个数中间大小的那个,避免极端情况,提高性能。

    def quick_sort(alist):
        Qsort(alist, 0, len(alist)-1)
    
    def Qsort(arr, low, high):
        """待排序数组arr, 起始位置low, 末尾位置high
        """
        if low < high:
            point = Partition(arr, low, high)   # 根据基准点a[point]处理数组,小的放基准点左边大的放基准点右边 
            Qsort(arr, low, point-1)
            Qsort(arr, point+1, high)
            
    def Partition(arr, low, high):
        # 令基准点介于a[low]和a[high]之间
        mid = low + (high - low) // 2
        if arr[mid] > arr[high]:
            arr[mid], arr[high] = arr[high], arr[mid]
        if arr[low] > arr[high]:
            arr[low], arr[high] = arr[high], arr[low]
        if arr[mid] > arr[low]:
            arr[low], arr[mid] = arr[mid], arr[low]
            
        point_value = arr[low]  # 基准点
        
        # 两个游标,low自左向右,high自右向左
        while low < high:
            while low < high and arr[high] >= point_value:  # high游标自右向左找到比基准元素小的元素位置
                high -= 1
            arr[low], arr[high] = arr[high], arr[low]  # 交换,把比基准点小的放前面
            
            while low < high and arr[low] < point_value:  # low游标自左向右找到比基准元素大的元素位置
                low += 1
            arr[high], arr[low] = arr[low], arr[high]  # 交换,把比基准点大的放后面
            
        alist[low] = point_value  # 当low与high位置重合,这就是基准点在最终序列中的位置
        return low
    

      

    2. 优化不必要的交换,直接改成赋值就行了,因为先把基准元素拿出来了,始终有一个位置是空余的。 

    def quick_sort(alist):
        Qsort(alist, 0, len(alist)-1)
    
    def Qsort(arr, low, high):
        """待排序数组arr, 起始位置low, 末尾位置high
        """
        if low < high:
            point = Partition(arr, low, high)   # 根据基准点a[point]处理数组,小的放基准点左边大的放基准点右边 
            Qsort(arr, low, point-1)
            Qsort(arr, point+1, high)
            
    def Partition(arr, low, high):
        # 令基准点介于a[low]和a[high]之间
        mid = low + (high - low) // 2
        if arr[mid] > arr[high]:
            arr[mid], arr[high] = arr[high], arr[mid]
        if arr[low] > arr[high]:
            arr[low], arr[high] = arr[high], arr[low]
        if arr[mid] > arr[low]:
            arr[low], arr[mid] = arr[mid], arr[low]
            
        point_value = arr[low]  # 基准点
        
        # 两个游标,low自左向右,high自右向左
        while low < high:
            while low < high and arr[high] >= point_value:  # high游标自右向左找到比基准元素小的元素位置
                high -= 1
            arr[low]= arr[high]  # 赋值,把比基准点小的放前面
            
            while low < high and arr[low] < point_value:  # low游标自左向右找到比基准元素大的元素位置
                low += 1
            arr[high] = arr[low]  # 交换,把比基准点大的放后面
            
        alist[low] = point_value  # 当low与high位置重合,这就是基准点在最终序列中的位置,填回去
        return low
    

      

    3. 优化小数组时的排序方案,数组长度小于7的时候用直接插入排序。

    def InsertSort(alist, low, high):  # 修改一下插入排序,只排序alist中low到high这一段
        for i in range(low+1, high+1):
            for j in range(i, low, -1):
                if alist[j] < alist[j-1]:
                    alist[j], alist[j-1] = alist[j-1], alist[j]
                    
    def quick_sort(alist):
        Qsort(alist, 0, len(alist)-1)
    
    def Qsort(arr, low, high, MAX_LENGTH_INSERT_SORT=7):
        """待排序数组arr, 起始位置low, 末尾位置high
        """
        if high - low > MAX_LENGTH_INSERT_SORT:
            point = Partition(arr, low, high)   # 根据基准点a[point]处理数组,小的放基准点左边大的放基准点右边 
            Qsort(arr, low, point-1)
            Qsort(arr, point+1, high)
        
        else:  # 数组小的话就直接插入
            InsertSort(arr, low, high)
            
    def Partition(arr, low, high):
        # 令基准点介于a[low]和a[high]之间
        mid = low + (high - low) // 2
        if arr[mid] > arr[high]:
            arr[mid], arr[high] = arr[high], arr[mid]
        if arr[low] > arr[high]:
            arr[low], arr[high] = arr[high], arr[low]
        if arr[mid] > arr[low]:
            arr[low], arr[mid] = arr[mid], arr[low]
            
        point_value = arr[low]  # 基准点
        
        # 两个游标,low自左向右,high自右向左
        while low < high:
            while low < high and arr[high] >= point_value:  # high游标自右向左找到比基准元素小的元素位置
                high -= 1
            arr[low]= arr[high]  # 赋值,把比基准点小的放前面
            
            while low < high and arr[low] < point_value:  # low游标自左向右找到比基准元素大的元素位置
                low += 1
            arr[high] = arr[low]  # 交换,把比基准点大的放后面
            
        alist[low] = point_value  # 当low与high位置重合,这就是基准点在最终序列中的位置,填回去
        return low

    4. 优化递归操作,只要有可能,就把递归写成尾递归(函数中的递归形式出现在末尾),能够提高运行效率。尾递归可以比较容易的写成迭代。

    def InsertSort(alist, low, high):  # 修改一下插入排序,只排序alist中low到high这一段
        for i in range(low+1, high+1):
            for j in range(i, low, -1):
                if alist[j] < alist[j-1]:
                    alist[j], alist[j-1] = alist[j-1], alist[j]
                    
    def quick_sort(alist):
        Qsort(alist, 0, len(alist)-1)
    
    def Qsort(arr, low, high, MAX_LENGTH_INSERT_SORT=7):
        """待排序数组arr, 起始位置low, 末尾位置high
        """
        if high - low > MAX_LENGTH_INSERT_SORT:
            while low < high:
                point = Partition(arr, low, high)   # 根据基准点a[point]处理数组,小的放基准点左边大的放基准点右边 
                Qsort(arr, low, point-1)
                # 现在 low 已经没用了,后半部分的递归是point+1到high,如果令low=point+1,则可以在用循环在下一轮实现point+1到high的递归
                low = point + 1  # 把尾递归写成while循环
            
        else:  # 数组小的话就直接插入
            InsertSort(arr, low, high)
            
    def Partition(arr, low, high):
        # 令基准点介于a[low]和a[high]之间
        mid = low + (high - low) // 2
        if arr[mid] > arr[high]:
            arr[mid], arr[high] = arr[high], arr[mid]
        if arr[low] > arr[high]:
            arr[low], arr[high] = arr[high], arr[low]
        if arr[mid] > arr[low]:
            arr[low], arr[mid] = arr[mid], arr[low]
            
        point_value = arr[low]  # 基准点
        
        # 两个游标,low自左向右,high自右向左
        while low < high:
            while low < high and arr[high] >= point_value:  # high游标自右向左找到比基准元素小的元素位置
                high -= 1
            arr[low]= arr[high]  # 赋值,把比基准点小的放前面
            
            while low < high and arr[low] < point_value:  # low游标自左向右找到比基准元素大的元素位置
                low += 1
            arr[high] = arr[low]  # 交换,把比基准点大的放后面
            
        alist[low] = point_value  # 当low与high位置重合,这就是基准点在最终序列中的位置,填回去
        return low
    

     

    进一步地,要充分发挥递归的优势,加一个判断,令长度大的那部分用循环代替尾递归

    def InsertSort(alist, low, high):  # 修改一下插入排序,只排序alist中low到high这一段
        for i in range(low+1, high+1):
            for j in range(i, low, -1):
                if alist[j] < alist[j-1]:
                    alist[j], alist[j-1] = alist[j-1], alist[j]
                    
    def quick_sort(alist):
        Qsort(alist, 0, len(alist)-1)
    
    def Qsort(arr, low, high, MAX_LENGTH_INSERT_SORT=7):
        """待排序数组arr, 起始位置low, 末尾位置high
        """
        if high - low > MAX_LENGTH_INSERT_SORT:
            while low < high:
                point = Partition(arr, low, high)   # 根据基准点a[point]处理数组,小的放基准点左边大的放基准点右边 
                if point - low < high - point:  # 后半数组长度大
                    Qsort(arr, low, point - 1)
                    # 现在 low 已经没用了,后半部分的递归是point+1到high,如果令low=point+1,则可以在用循环在下一轮实现point+1到high的递归
                    low = point + 1  # 把尾递归写成while循环
                else:
                    Qsort(arr, point + 1, high)  # 现在high已经没用了
                    high = point - 1
            
        else:  # 数组小的话就直接插入
            InsertSort(arr, low, high)
            
    def Partition(arr, low, high):
        # 令基准点介于a[low]和a[high]之间
        mid = low + (high - low) // 2
        if arr[mid] > arr[high]:
            arr[mid], arr[high] = arr[high], arr[mid]
        if arr[low] > arr[high]:
            arr[low], arr[high] = arr[high], arr[low]
        if arr[mid] > arr[low]:
            arr[low], arr[mid] = arr[mid], arr[low]
            
        point_value = arr[low]  # 基准点
        
        # 两个游标,low自左向右,high自右向左
        while low < high:
            while low < high and arr[high] >= point_value:  # high游标自右向左找到比基准元素小的元素位置
                high -= 1
            arr[low]= arr[high]  # 赋值,把比基准点小的放前面
            
            while low < high and arr[low] < point_value:  # low游标自左向右找到比基准元素大的元素位置
                low += 1
            arr[high] = arr[low]  # 交换,把比基准点大的放后面
            
        alist[low] = point_value  # 当low与high位置重合,这就是基准点在最终序列中的位置,填回去
        return low
    

      

  • 相关阅读:
    JavaScript的兼容小坑和调试小技巧
    前端jQuery实现瀑布流
    angular常用属性大全
    Eclipse易卡死
    工作反思
    半年回忆
    努力做到
    产品经理如何应对技术的「做不了」这样的问题(转)
    优秀的产品经理我还有多远
    简历技巧
  • 原文地址:https://www.cnblogs.com/chaojunwang-ml/p/11296423.html
Copyright © 2011-2022 走看看