排序算法

排序算法
  • 什么是排序算法
    • 能将一串数据一找特定顺序进行排列的算法
  • 排序算法的稳定性
    • 稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。
冒泡排序
  • 什么是冒泡排序
    • 是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端
  • 冒泡排序的运作方式
    • 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
    • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
    • 针对所有的元素重复以上的步骤。
    • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
  • 算法实现
    def bubble_sort(li):
                n = len(li)
                # 共冒泡n-1趟
                for j in range(n-1): 
                        # 一趟冒泡,把最大的一个数冒到最右边
                        # 数据两两比较,从0位置到n-1位置
                        flag = False    # 设置一个标志,用来后面来设置列表已经是排序状态的标志
                        for i in range(n-1-j):
                                if  li [i]  >  li [i + 1]:
                                        # 交换
                                        li [i],  li [i + 1]  =  li [i + 1],  li [i]
                                        flag = True
                        if flag is False:
                                break
        if __name__ == '__main__':
                l = [1,1,3,4,5]
                # [5,1,2,3,6]
                bubble_sort(l)
                print(l)
  • 时间复杂度
    • 最优时间复杂度:O(n)
    • 最坏时间复杂度:O(n2)
    • 稳定性:稳定
选择排序
  • 什么是选择排序
    • 是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的后面一个位置上。以此类推,直到所有元素均排序完毕。
  • 选择排序的优点
    • 选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。
  • 选择排序的实现
     def select_sort(li):
                n = len(li)
                # 共n次选择,从0到n-1
                for j in range(n-1):
                        # 第一趟选择,记录0位置,把0位置与后面的数据进行比较
                        # 记录一个比较出来的最小数的下标
                        temp = j
                        for i in range(j+1, n):
                                if li[i] < li[temp]:
                                        temp = i
                        # 循环结束时,temp指向的是最小的数
                        li[temp], li[j] = li[j], li[temp]
        if __name__ == '__main__':
                l = [1, 2, 4, 3]
                select_sort(l)
                print(l)   
  • 时间复杂度
    • 最优时间复杂度:O(n2)
    • 最坏时间复杂度:O(n2)
    • 稳定性:不稳定
插入排序
  • 什么是插入排序
    • 是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
  • 插入排序的实现
       def insert_sort(li):
                    n = len(li)
                    # 依次让后面的数据插入到前面的有序序列中,从第二个数开始到最后一个数据
                    for j in range(1, n):
                            # 默认第一个数据是有序的,从第二个数据依次插入到前面的有序序列中
                            for i in range(j, 0, -1):
                                    if li[i - 1] > li[i]:
                                            li[i - 1], li[i] = li[i], li[i - 1]
                                    else:
                                            break
            if __name__ == '__main__':
                    l = [1, 2, 5, 4, 3]
                    insert_sort(l)
                    print(l)
  • 时间复杂度
    • 最优时间复杂度:O(n)
    • 最坏时间复杂度:O(n2)
    • 稳定性:稳定
希尔排序
  • 什么是希尔排序
    • 是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。
    • 希尔排序是非稳定排序算法。 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
  • 希尔排序的过程
    • 将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了
  • 希尔排序的实现
       def shell_sort(li):
                    n = len(li)
    
                    # 定义初始步长
                    gap = n//2
    
                    # 让步长从大到小,最后必须以1步长排序
                    while gap >= 1:
                            for i in range(gap, n):
                                    # 插入排序
                                    while (i-gap) >= 0:
                                            if li[i] < li[i-gap]:
                                                    li[i], li[i - gap] = li[i-gap], li[i]
                                                    i -= gap
                            gap //= 2
        
            if __name__ == '__main__':
                    l = [7, 6, 5, 4, 3, 2]
                    shell_sort(l)
                    print(l)
  • 时间复杂度
    • 最优时间复杂度:根据步长序列的不同而不同(步长的确定也有其算法)
    • 最坏时间复杂度:O(n2)
    • 稳定性:不稳定
  • 希尔排序比插入排序高效的原因
    • 希尔排序同步对步长的限制,可以让相隔较远的元素进行移动,这样就使得,减少了很多中间移动过程,大大的减少了排序过程中的移动,减少了排序由于移动所浪费的时间
  • 备注:
    • 实际使用希尔排序的时候,步长是通过算法确定的,我们这里只是先将步长设置为一半,一直到1,来进行演示;
归并排序
  • 归并排序概念
    • 归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组
  • 归并排序过程
    • 将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
  • 代码实现
        def merge_sort(li):
                        # 当列表的长度为1时,不需要再拆分
                        if len(li) == 1:
                                return li
                        # 取一个中间位置,把数据拆分成左右两半
                        mid = len(li)//2
                        left = li[:mid]
                        right = li[mid:]

                        # 递归继续拆分
                        left_res = merge_sort(left)
                        right_res = merge_sort(right)
                        # 合并下层方法返回的结果,并且进行排序
                        res = merge(left_res,right_res)
                       return res
                def merge(left,right):
                        """把两个有序的序列组成有序序列"""
                        # 定义两个下标,分别指向两个序列的0位置
                        left_index = 0
                        right_index = 0
                        # 创建临时列表
                        res = []
                        while left_index < len(left) and right_index < len(right):
                                if left[left_index] <= right[right_index]:
                                        res.append(left[left_index])
                                        left_index += 1
                                else:
                                        res.append(right[right_index])
                                        right_index += 1
                        res = res + left[left_index:]
                        res = res + right[right_index:]
                        return res
                if __name__ == '__main__':
                        l =  [6,5,4,3,2,1]
                        print(merge_sort(l))
  • 时间复杂度
    • 最优时间复杂度:O(nlogn)
    • 最坏时间复杂度:O(nlogn)
    • 稳定性:稳定
  • 缺点
    • 归并排序虽然失效复杂度较低,但是每一层进行归并的时候,都需要创建一个临时空间,使用的临时空间很多,所以归并排序的空间复杂度比较高,所以在使用的时候,需要考虑对空间占用的情况
快速排序
  • 快速排序概念
    • 快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
  • 快速排序步骤
    • 从数列中挑出一个元素,称为"基准"(pivot),一般挑选第一个元素。
    • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
    • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
  • 代码实现
        def quick_sort(li,start,end):
            # 当start=end时,证明只对一个数排序,不需要排序
                        # 当start>end时,证明没有需要排序的数据
            if start >= end:
                return
            # 定义两个下标,一个是头,一个是尾
            left = start
            right = end
            # 把第一个数当作中间值
            mid = li[left]
            # 不停的让右边下标往左移动,左边下标往右移动,直到左右下标相等
            while left < right:
               # 右边下标往左移动,找到一个小于mid的数据
                while left < right and li[right] >= mid:
                    right -= 1
                # 循环结束时,right指向的数据小于mid,把数据放到左边下标位置
                li[left] = li[right]
                # 左边下标往右移动,找到一个大于mid的数据
                while left < right and li[left] <= mid:
                    left += 1
                # 循环结束时,left指向的数据大于mid,把数据放到右边下标位置
                li[right] = li[left]
            # 循环结束时,left=right,把mid放到此位置
            li[left] = mid
            # 递归让左边的数据继续快排
            quick_sort(li,start,left-1)
            # 递归让右边的数据继续快排
            quick_sort(li,left+1,end)
        if __name__ == '__main__':
            l = [7,6,5,4,3,2]
            quick_sort(l,0,len(l)-1)
            print(l)
  • 时间复杂度
    • 最优时间复杂度:O(nlogn)
    • 最坏时间复杂度:O(n2)
    • 稳定性:不稳定
  • 时间复杂度说明
    • 在最好的情况,每次我们运行一次分区,我们会把一个数列分为两个几近相等的片段。这个意思就是每次递归调用处理一半大小的数列。因此,在到达大小为一的数列前,我们只要作log n次嵌套的调用。这个意思就是调用树的深度是O(log n)。但是在同一层次结构的两个程序调用中,不会处理到原来数列的相同部分;因此,程序调用的每一层次结构总共全部仅需要O(n)的时间(每个调用有某些共同的额外耗费,但是因为在每一层次结构仅仅只有O(n)个调用,这些被归纳在O(n)系数中)。结果是这个算法仅需使用O(n log n)时间。
常见排序算法的比较



刘小恺(Kyle) wechat
如有疑问可联系博主