• 算法学习【第五篇】:排序算法与二分法


    排序算法稳定性

    冒泡排序

    思路:首先,列表每两个相邻的数比较大小,如果前边的比后边的大,那么这两个数就互换位置。就像是冒泡一样

    代码关键点

    趟数:n-1趟
    无序区

    依次类推就会得到排序结果。冒泡排序的效率还是很低的

    代码示例    (这是基于顺序表实现的,链表还要关注一下节点)

    # 思路:列表中两个相邻的数比较大小,如果前边的比后边的大,那么这两个就互换位置
    def bubblr_sort(li):
        for i in range(1,len(li)-1):#表示趟数
            for j in range(len(li)-i):  #表示无序区,无序区的范围为0,len(li)-i
               if li[j] > li[j+1]:
                   li[j],li[j+1] = li[j+1],li[j]
    
    
    li = list(range(10))
    import random
    random.shuffle(li)
    print(li)
    bubblr_sort(li)
    print(li)

    最坏时间复杂度:O(n2)

    最优时间复杂度:O(n)

    冒泡优化

    # 思路:列表中两个相邻的数比较大小,如果前边的比后边的大,那么这两个就互换位置
    def bubblr_sort(li):
        for i in range(1,len(li)-1):#表示趟数
            change = False
            for j in range(len(li)-i):  #表示无序区,无序区的范围为0,len(li)-i
                if li[j] > li[j+1]:
                    li[j],li[j+1] = li[j+1],li[j]    
                    change = True    如果从头到尾(内层循环一遍)都没有交换过,就不需要走第二趟了
            if not change:
                return
    
    li = list(range(10))
    import random
    random.shuffle(li)
    print(li)
    bubblr_sort(li)
    print(li)

    选择排序

    思路:一趟遍历完记录最小的数,放到第一个位置;在一趟遍历记录剩余列表中的最小的数,继续放置

    代码关键点

    无序区
    最小数的位置

    问题:怎么选出最小的数

    import random
    def select_sort(li):
        for i in range(len(li)-1):
           #i 表示趟数,也表示无序区开始的位置
           min_loc = i  #最小数的位置
           for j in range(i+1,len(li)):  #i  ,i+1,就是后一个位置的范围
               if li[j] <li[min_loc]:    #两个位置进行比较,如果后面的一个比最小的那个位置还小,说明就找到最小的了
                    min_loc = j          #找到最小的位置下标
           li[i],li[min_loc] = li[min_loc],li[i]  #吧找到的两个值进行互换位置
    li = list(range(10))
    random.shuffle(li)
    print(li)
    select_sort(li)
    print(li)

    时间复杂度

    最优时间复杂度:O(n^2)
    最坏时间复杂度:O(n^2)
    稳定性:不稳定(考虑升序每次选择最大的情
    况)

    插入排序

    思路:元素被分为有序区和无序区两部分。最初有序区只有一个元素。每次从无序区中选择一个元素,插入到有序区的位置,直到无序区变空。

    代码关键点

    摸到的牌
    手里的牌

    代码示例一

    import random
    def insert_sort(li):
        for i in range(1,len(li)):
           #i 表示无序区的第一个数
           tmp = li[i]      #摸到的牌
           j = i-1       #指向有序区最后一个位置
           while li[j] >tmp and j>=0:
                        #循环终止条件 li[j]<=tmp  and j==-1
               li[j+1] = li[j]  #向后移动
                j-=1
           li[j+1] = tmp
            
    li = list(range(10))
    random.shuffle(li)
    print(li)
    insert_sort(li)
    print(li)
    

    代码示例二

    代码示例三

    快速排序

    思路

    1、取一个元素p(第一个元素),是元素p归位(去它该去的地方);
    2、列表被p分成两部分,左边的都比p小,右边的都比p大;
    3、递归完成排序

    算法关键点

    归位
    递归

    图示说明

    怎么归并呢?先把5取出来,这时候就会有一个空位,从右边找比5小的数填充过来,现在右边有一个空位了,从左边找比5大的放到右边的空位上。依次类推,

    只要left和right碰在一起,这样就找打5的位置了

    如图示:

    图一

    图二

     图三

    图四

     这样在把找到的5的位置放进去去ok了

    代码示例一

    import time
    def wrapper(func):
       def inner(*args,**kwargs):
           start = time.time()
           ret = func(*args,**kwargs)
           end = time.time()
           print('%s running time :%s'%(func.__name__,start-end))
               return ret
       return inner
    
    
    def partition(li,left,right):
       '''归位函数'''
       tmp = li[left]  #先把5取出来
       while left < right:
            while left < right and li[right] >= tmp:  #如果降序排列修改li[right] <= tmp
                   right -= 1 #从右边找比5小的数,填充到5的位置
           li[left] = li[right]
           while left < right and li[left] <= tmp:  #如果降序排列修改li[right] >= tmp
                   left += 1# 从左边找比5大的数字放在右边的空位
           li[right] = li[left]
       li[left] = tmp  #当跳出循环条件的时候说明找到了,并且把拿出来的5在放进去
        return left
    
    
    def _quick_sort(li,left,right):
        '''快速排序的两个关键点:归位,递归'''
        if left < right:  #至少有两个元素,才能进行递归
            mid = partition(li,left,right)  #找到归位的位置
            _quick_sort(li,left,mid-1)  #递归,右边的-1
             _quick_sort(li,mid+1,right) #递归,左边的+1
    
    @wrapper
    def quick_sort(li):
        return _quick_sort(li, 0, len(li)-1)
    
    @wrapper
    def sys_sort(li):
        '''系统排序'''
        li.sort()
    
    import random
    li = list(range(100000))
    random.shuffle(li)
    # print(li)
    quick_sort(li)
    # print(li)
    
    sys_sort(li)  
     
    #结论:系统的排序要比快排的时间快的多
    # quick_sort running time :-0.6240355968475342
    # sys_sort running time :-0.002000093460083008 

    快速排序的时间复杂度O(nlogn)

     

     代码示例二

    代码示例三

    #quick sort
    def quickSort(array):
        if len(array) < 2:  # 基线条件(停止递归的条件)
            return array
        else:  # 递归条件
            baseValue = array[0]  # 选择基准值
            less, equal, greater = [], [baseValue], []
            for m in array[1:]:    
                if m < baseValue:        
                    # 由所有小于基准值的元素组成的子数组
                    less.append(m)         
                elif m > baseValue:        
                    # 由所有大于基准值的元素组成的子数组
                    greater.append(m)    
                else:        
                    # 包括基准在内的同时和基准相等的元素,在上一个版本的百科当中,并没有考虑相等元素
                    equal.append(m)
            return quickSort(less) + equal + quickSort(greater)
    # 示例:
    array = [2,3,5,7,1,4,6,15,5,2,7,9,10,15,9,17,12]
    print(quickSort(array))
    # 输出为[1, 2, 2, 3, 4, 5, 5, 6, 7, 7, 9, 9, 10, 12, 15, 15, 17]
    

      

    堆排序

    有关对的了解:http://www.cnblogs.com/haiyan123/p/8400537.html

    堆排序过程

    1、建立堆
    2、得到堆顶元素,为最大元素
    3、去掉堆顶,将堆最后一个元素放在堆顶,此时可通过一次调整重新使堆有序
    4、堆顶元素为第二大元素
    5、重复步骤3,直到堆变空

    代码示例

    import random
    
    def _sift(li, low, high):
        """
        :param li:
        :param low: 堆根节点的位置
        :param high: 堆最有一个节点的位置
        :return:
        """
        i = low  # 父亲的位置
        j = 2 * i + 1  # 孩子的位置
        tmp = li[low]  # 原省长
        while j <= high:
            if j + 1 <= high and li[j + 1] > li[j]:  # 如果右孩子存在并且右孩子更大
                j += 1
            if tmp < li[j]:  # 如果原省长比孩子小
                li[i] = li[j]  # 把孩子向上移动一层
                i = j
                j = 2 * i + 1
            else:
                li[i] = tmp  # 省长放到对应的位置上(干部)
                break
        else:
            li[i] = tmp  # 省长放到对应的位置上(村民/叶子节点)
    
    
    def sift(li, low, high):
        """
        :param li:
        :param low: 堆根节点的位置
        :param high: 堆最有一个节点的位置
        :return:
        """
        i = low         # 父亲的位置
        j = 2 * i + 1   # 孩子的位置
        tmp = li[low]   # 原省长
        while j <= high:
            if j + 1 <= high and li[j+1] > li[j]: # 如果右孩子存在并且右孩子更大
                j += 1
            if tmp < li[j]: # 如果原省长比孩子小
                li[i] = li[j]  # 把孩子向上移动一层
                i = j
                j = 2 * i + 1
            else:
                break
        li[i] = tmp
     
     
    
    def heap_sort(li):
        n = len(li)
        # 1. 建堆
        for i in range(n//2-1, -1, -1):
            sift(li, i, n-1)
        # 2. 挨个出数
        for j in range(n-1, -1, -1):    # j表示堆最后一个元素的位置
            li[0], li[j] = li[j], li[0]
            # 堆的大小少了一个元素 (j-1)
            sift(li, 0, j-1)
    
    
    li = list(range(10))
    random.shuffle(li)
    print(li)
    heap_sort(li)
    print(li)
    
    # li=[2,9,7,8,5,0,1,6,4,3]
    # sift(li, 0, len(li)-1)
    # print(li)

    归并排序

    假设现在的列表分两段有序,如何将其合成为一个有序列表。这种操作称为一次归并

    思路:

    • 分解:将列表越分越小,直至分成一个元素
    • 终止条件:一个元素是有序的
    • 合并:将两个有序列表归并,列表越来越大

    图实示例:https://www.cnblogs.com/chengxiao/p/6194356.html

    代码示例

    import random
    def merge(li, low, mid, high):
       # 一次归并
       '''
        :param li: 列表
        :param low: 起始位置
       :param mid: 按照那个位置分
      :param high: 最后位置
       :return:
        '''
        i = low
        j = mid + 1
        ltmp = []
        while i <= mid and j <= high:
            if li[i] < li[j]:
                ltmp.append(li[i])
                i += 1
           else:
               ltmp.append(li[j])
                j += 1
        while i <= mid:
           ltmp.append(li[i])
            i += 1
       while j <= high:
            ltmp.append(li[j])
           j += 1
        li[low:high+1] = ltmp
    
    
    def _merge_sort(li, low, high):
        if low < high:  # 至少两个元素
            mid = (low + high) // 2
            _merge_sort(li, low, mid)
           _merge_sort(li, mid+1, high)
            merge(li, low, mid, high)
           print(li[low:high+1])
    
    
    def merge_sort(li):
        return _merge_sort(li, 0, len(li)-1)
    
    
    li = list(range(16))
    random.shuffle(li)
    print(li)
    merge_sort(li)
    
    print(li)

    时间复杂度

    ·最优时间复杂度:O(nlogn)
    ·最坏时间复杂度:O(nlogn)
    ·稳定性:稳定

    基数排序

    import random
    from timewrap import *
    
    def list_to_buckets(li, iteration):#这个是用来比较每个位置的大小的数字
    
        """
        因为分成10个本来就是有序的所以排出来就是有序的。
        :param li: 列表
        :param iteration: 装桶是第几次迭代
        :return:
        """
        buckets = [[] for _ in range(10)]
        print('buckests',buckets)
        for num in li:
            digit = (num // (10 ** iteration)) % 10
            buckets[digit].append(num)
        print(buckets)
        return buckets
    
    def buckets_to_list(buckets):#这个是用来出数的
        return [num for bucket in buckets for num in bucket]
        # li = []
        # for bucket in buckets:
        #     for num in bucket:
        #         li.append(num)
    
    @cal_time
    def radix_sort(li):
        maxval = max(li) # 10000
        it = 0
        while 10 ** it <= maxval:#这个是循环用来,在以前一次排序的基础上在排序。
            li = buckets_to_list(list_to_buckets(li, it))
            it += 1
        return li
    
    # li = [random.randint(0,1000) for _ in range(100000)]
    li = [random.randint(0,10) for _ in range(10)]
    li=[5555,5525,9939,9999,6,3,8,9]
    s=radix_sort(li)
    print(s)

    希尔排序

    思路

    希尔排序是一种分组插入排序算法
    首先取一个整数d1=n/2,将元素分为d1个组,每组相邻量元素之间距离为d1,在各组内进行直接插入排序;
    取第二个整数d2=d1/2,重复上述分组排序过程,直到di=1,即所有元素在同一组
    希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序。

    代码实现

    def insert_sort(li):      #插入排序
        for i in range(1, len(li)):
                      # i 表示无序区第一个数
            tmp = li[i]   # 摸到的牌
            j = i - 1     # j 指向有序区最后位置
            while li[j] > tmp and j >= 0:
                      #循环终止条件: 1. li[j] <= tmp; 2. j == -1
                li[j+1] = li[j]
                j -= 1
            li[j+1] = tmp
    
    def shell_sort(li):    #希尔排序 与插入排序区别就是把1变成d d = len(li) // 2 while d > 0: for i in range(d, len(li)): #通过一个for循环把所有子序列全部比较了 tmp = li[i] j = i - d while li[j] > tmp and j >= 0: li[j+d] = li[j] j -= d li[j+d] = tmp d = d >> 1 li=[5,2,1,4,5,69,20,11] shell_sort(li) print(li)

      

     

    希尔排序的复杂度特别复杂,取决于d,分组的长度二、位移运算符

    最坏o(n^2)

    最好大概O(n^1.3)

    计数排序

    统计每个数字出现了几次

    #计数排序
    # 0 0 1 1 2 4 3 3 1 4 5 5
    import random
    import copy
    from timewrap import *
    
    @cal_time
    def count_sort(li, max_num = 100):
        count = [0 for i in range(max_num+1)]
        for num in li:
            count[num]+=1
        li.clear()
        for i, val in enumerate(count):
            for _ in range(val):
                li.append(i)
    
    @cal_time
    def sys_sort(li):
        li.sort()
    
    li = [random.randint(0,100) for i in range(100000)]
    li1 = copy.deepcopy(li)
    count_sort(li)
    sys_sort(li1)

    计数排序这么快,为什么不用计数排序呢?因为他是有限制的,你要知道列表中的最大数

    如果一下来了一个很大的数,比如10000,那么占的空间就的这么大,

    计数排序占用的空间和列表的范围有关系

    解决这种问题的方法,可以用桶排序,都放进去可以在进行其他的排序。比如插入排序。

    桶排序

    在计数排序中,如果元素的范围比较大(比如在1到1亿之间),如何改造算法?

    桶排序,首先将将元素分在不同的桶中,在对每个桶中的元素排序。

    多关键字排序

    先对十位进行排序,再根据 十位进行排序

    要用两个函数,一个用来装桶,一个用来出桶

    默认10个桶,找到个位,十位,分别放在对应的桶里的位置

    桶排序的表现取决于数据的分布。也就是需要对不同数据排序时采取不同的分桶策略。

    平均情况时间复杂度:O(n+k)

    最坏情况时间复杂度:O(n+k)

    空间复杂度:O(nk)

    分成若干个桶,桶内用插入排序

    搜索

    搜索是在一个项目集合中找到一个特定项目的算法过程。搜索通常的答案是真的或假的,因为该项目是否存
    在。搜索的几种常见方法:顺序查找、二分法查找、二又树查找、哈希查找

    二分法查找

    二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升
    序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进
    一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功
    

      

    时间复杂度

    ·最优时间复杂度:O(1)
    ·最坏时间复杂度:Ologn)

    总结

    Low B 三人组

    冒泡排序,选择排序,直接插入排序他们的时间复杂度都是O(n^2),空间复杂度是O(1)

    NB 三人组

    快速排序,归并排序,堆排序他们的时间复杂度都是O(nlogn)
    三种排序算法的缺点
    快速排序:极端情况下排序效率低
    归并排序:需要额外的内存开销
    堆排序:在快的排序算法中相对较慢

    挨着换的稳定,不挨着换的不稳定

  • 相关阅读:
    python实现读取并显示图片的两种方法
    如何在jupyter中安装R
    FPGA设计千兆以太网MAC(3)——数据缓存及位宽转换模块设计与验证
    FPGA设计千兆以太网MAC(2)——以太网协议及设计规划
    FIFO深度计算
    【不定期更新】FPGA/IC岗位常见笔试面试题总结(基础知识)
    【原创】建立与保持时间裕量详细分析
    利用ZYNQ SOC快速打开算法验证通路(6)——利用AXI总线实时配置sysGen子系统
    【转载】泰勒展开式
    利用ZYNQ SOC快速打开算法验证通路(5)——system generator算法IP导入IP integrator
  • 原文地址:https://www.cnblogs.com/596014054-yangdongsheng/p/10228703.html
Copyright © 2020-2023  润新知