美文网首页
排序算法

排序算法

作者: 西西里加西 | 来源:发表于2020-04-23 16:22 被阅读0次

    ## 插入排序

    ### 直接插入排序

    原理:

    • 在未排序序列中,构建一个子排序序列,直至全部数据排序完成
    • 待排序的数,插入已经排序的序列中的合适位置
    • 增加一个哨兵,放入待比较值,让它和后面已经排好序的序列比较,找到合适的切入点

    举例:

    初始值:[1 9 8 5 6]

    第一趟:[0 1 9 8 5 6] [9 1 9 8 5 6] -> [1 9 8 5 6]

    如大家所看到的,和初始值必须,我在第一个位置上增加了一个数字0,这个数字0的位置我们称其为哨兵,接下来我们直接取初始值的第二个索引9和它前面一个索引比较(第一个索引相比较),哨兵9比1大,则不需要交换位置,本轮比较结束。

    第二趟:[8 1 9 8 5 6] [8 1 ? 9 5 6] [8 1 8 9 5 6] -> [1 8 9 5 6]

    拿到上一轮比赛结果,接下来我们直接取初始值的第三个索引8和它前面一个索引比较,本轮哨兵为8,而哨兵8比9小,因此,我们需要把9往右移动一位,此时哨兵被插入8和9交换位置索引变为第二个索引,此时依旧需要和前面的一个索引进行比较,很显然第一个索引为1,而哨兵为8,哨兵8比1大,则不需要交换位置,本轮比赛结束。(简单的说就是如果比本轮哨兵大就相互交换位置,如果比本轮哨兵小则本轮结束)

    第三趟:[5 1 8 9 5 6] [5 1 8 ? 9 6] [5 1 ? 8 9 6] [5 1 5 8 9 6] -> [1 5 8 9 6]

    拿到上一轮比赛结果,接下来我们直接去初始值的第四个索引5和它前面一个索引比较,本轮哨兵为5,而哨兵5比9小,因此他们需要相互交换位置,接着哨兵5再和它前面一个索引比较,哨兵5比8小,继续交换位置,然后继续和它前面一个索引比价,发现哨兵5比1大,终止本轮比赛。

    第四趟:[6 1 5 8 9 6] [6 1 5 8 ? 9] [6 1 5 ? 8 9] [6 1 5 6 8 9] -> [1 5 6 8 9]

    拿到上一轮的结果,继续重复插入排序比较,经过上面3次比较,想必大家也清楚它是如何排序的了,最终经过四趟比较,得到的排序是:1 5 6 8 9

    思路:

    • 增加一个哨兵位,每躺将待比较的值放入其中
    • 哨兵依次和带比较数的前一个比较,大数向右移动,找到哨兵中的值要插入的位置
    • 每一轮结束,得到一个从开始到待比较数位置的一个有序序列
    m_list = [
        [1,9,8,5,6,7,4,3,2],
        [1,2,3,4,5,6,7,7,9],
        [9,8,7,6,5,4,3,2,1],
        [1,1,1,1,1,1,1,1,2]
    ]
    
    nums = [0] + m_list[2] # 添加哨兵位
    print(nums[1:])
    
    length = len(nums)
    count_move = 0
    for i in range(2, length): # 跳过哨兵和第一个元素,从第二个元素开始
        nums[0] = nums[i]      # 将带比较值放入哨兵位
        j = i - 1
        if nums[j] > nums[0]:  # 大数右移,找到插入位置
            while nums[j] > nums[0]:
                nums[j+1] = nums[j] # 右移
                j -= 1         # 继续向左比较
                count_move += 1
            nums[j+1] = nums[0] # 插入哨兵,j本是合适位置,但在退出循环之前被-1(19行),所以要加回来
    
    print(nums[1:], '数据移动的次数:{}'.format(count_move), sep='\n')
    

    总结:

    • 插入排序特点:
      • 最好情况,正好是升序排列,比较迭代n-1次
      • 最差情况,正好是降序排列,比较迭代1,2,...,n-1即 n(n-1)/2,同样也是数据移动的次数,数据移动非常之多
      • 使用两次嵌套循环,时间复杂度O(n^2)
      • 稳定排序算法
      • 用在小规模数据比较

    什么是稳定排序算法

    ​ 待排序序列R中有两个元素相等,即R[i] = R[j],且i < j,那么排序之后这两个元素的先后顺序不变,这种排序算法就称做稳定排序。要判断哪些算法是稳定排序,拿[1, 1, 2]来测试就行

    • 待优化:
      如果比较操作耗时大的话,可以采用二分查找提高效率,即二分查找插入排序

    # 选择排序

    ## 简单选择排序

    简单选择排序属于选择排序的一种算法,原理是两两比较大小,找出极值(极大或者极小)放在固定的位置,这个固定的位置一般指的是某一端,结果可以是升序也可以是降序。

    降序

    n个数从左到右,开始遍历

    第一轮:索引从0开始到n-1,两两相依比较,记录大值索引,此轮所有数比较完毕,将大数和索引0数交换,如果大数就是索引0,不交换。

    第二轮:从索引1开始比较,找到最大值,将其和索引1位置交换,如果他就在索引1位置则位置不交换。

    以此类推,每次左边都会固定下一个大数。

    升序

    和降序相反

    简单选择排序的实现

    m_list = [
        [3, 2, 7, 1, 1, 5, 8, 5, 2],
        [9, 1, 8, 8, 5, 3, 8, 8, 9],
        [7, 9, 8, 6, 3, 8, 9, 7, 1]
    ]
    
    nums = m_list[2]
    print(nums)
    
    length = len(nums)
    count_iter = 0 # 迭代的次数
    count_swap = 0 # 元素位置交换的次数
    for i in range(length):
        maxindex = i # 初始化最大值索引指针
        for j in range(i + 1, length):
            count_iter += 1
            if nums[j] > nums[maxindex]: # 最大值指针指向的值与无序区的元素一一比较,maxindex永远指向更大的值;遍历完一遍,就能找到无序区种的最大值了
                maxindex = j
        if i != maxindex: # 判定最大值指针是否移动过,没移动过就说明i 比无序区里面的数字都大了
            nums[i], nums[maxindex] = nums[maxindex], nums[i] # 将无序区选出来的最大值放在有序区的边界
            count_swap += 1
    print(nums, count_iter, count_swap, sep='\n')
    
    
    #运行结果
    [7, 9, 8, 6, 3, 8, 9, 7, 1]
    [9, 9, 8, 8, 7, 7, 6, 3, 1]
    36
    6
    

    ## 简单选择排序的优化--二元选择排序

    同时选择固定左边最小值和右边最大值

    优点:一次迭代,就找出最大&最小值,这样无序区范围再一次迭代就被再次缩小了 ,<font color=red>需要迭代的次数就少了</font>

    m_list = [
        [3, 2, 7, 1, 1, 5, 8, 5, 2],
        [9, 1, 8, 8, 5, 3, 8, 8, 9],
        [7, 9, 8, 6, 3, 8, 9, 7, 1],
        [1, 1, 1, 1, 2, 1, 1, 1, 1]
    ]
    
    nums = m_list[3]
    print(nums)
    length = len(nums)
    count_iter = 0
    count_swap = 0
    
    for i in range(length // 2):
        '''
        为什么是迭代(length // 2) 次就能完成排序了?
        因为一次迭代就搞定了两个数,
        如果length是偶数,那么len//2次就能确定len个元素的顺序
        如果length是基数,那么len//2次就能确定(len-1)个元素的顺序,因为len//2是向下取整,len//2*2 + 1 = len,
        至于最后剩下那个数就是中间数,因为他左右两边的顺序都是好的了
        '''
        maxindex = i # 初始化最大值指针,指向无序区最左的元素,假定他为无序区里的最大值
        minindex = -i - 1 # 初始化最小值指针,指向无序区最右的元素,假定他为无序区里的最小值
        minorigin = length + minindex # 无序区初始化最小值的正索引,同时也是无序区最右的元素的正索引
        for j in range(i+1, length-i): # 遍历无序区除了一个极值外的数,range(i+1, minorigin+1) => range(i+1, length+minindex+1) => range(i+1, length+(-i - 1)+1)
            count_iter += 1
            if nums[j] > nums[maxindex]: # 找出无序区得最大值,方法是,两两比较,maxindex总是指向更大值的索引,比完一圈就找到最大值索引了
                maxindex = j
            if nums[-j - 1] < nums[minindex]: # 找出无序区得最小值,方法是,两两比较,minindex总是指向更小值的索引,比完一圈就找到最小值索引了
                minindex = -j - 1
        minindex = length + minindex # minindex在前面一直用的是负索引,这里转成正索引更方便
        if i != maxindex:
            nums[i], nums[maxindex] = nums[maxindex], nums[i] # 如果最大值就是无序区最左的元素,那就不用交换位置啦
            count_swap += 1
            if i == minindex: # 因为上面的swap操作使得元素的位置发生变化,如果被移位的i刚好是minindex,那么要更新minindex,因为我们追踪的是值,不是索引
                minindex = maxindex
        if minindex != minorigin: # 如果最小值就是无序区最右的元素,那就不用交换位置啦
            nums[minorigin], nums[minindex] = nums[minindex], nums[minorigin]
            count_swap += 1
    
    print(nums, count_iter, count_swap, sep='\n')
    
    
    
    #运行结果
    [7, 9, 8, 6, 3, 8, 9, 7, 1]
    [9, 9, 8, 8, 7, 7, 6, 3, 1]
    20
    6
    

    可以优化的地方:

    如果一次迭代中,找出来的极大值和极小值的值相等,说明比较序列元素全部相等,没有继续调整顺序的必要了。

    m_list = [
        [3, 2, 7, 1, 1, 5, 8, 5, 2],
        [9, 1, 8, 8, 5, 3, 8, 8, 9],
        [7, 9, 8, 6, 3, 8, 9, 7, 1],
        [1, 1, 1, 1, 2, 1, 1, 1, 1]
    ]
    
    nums = m_list[3]
    print(nums)
    length = len(nums)
    count_iter = 0
    count_swap = 0
    
    for i in range(length // 2):
        maxindex = i 
        minindex = -i - 1 
        minorigin = length + minindex 
        for j in range(i+1, length-i):
            count_iter += 1
            if nums[j] > nums[maxindex]: 
                maxindex = j
            if nums[-j - 1] < nums[minindex]:
                minindex = -j - 1
        if nums[maxindex] == nums[minindex]: break  #优化之处
        minindex = length + minindex 
        if i != maxindex:
            nums[i], nums[maxindex] = nums[maxindex], nums[i] 
            count_swap += 1
            if i == minindex: 
                minindex = maxindex
        if minindex != minorigin: 
            nums[minorigin], nums[minindex] = nums[minindex], nums[minorigin]
            count_swap += 1
    
    print(nums, count_iter, count_swap, sep='\n')
    

    想[1, 1, 1, 1, 1, 1, 1, 1, 2]这种情况,第一轮找到的最大值索引是8,最小值索引是-2,如果按照上面的算法,会交换两次,两个最小值1交换就是无用功,所以增加一个判断。

    说起来都是前面极大值交换位置引起的,minorigin索引指向的值被交换位置也不影响,因为minindex已经是无序区中最小的,换谁来当minorigin都一样,minindex都是最小,但是如果宝贝被换过的minorigin对应的值就有可能和minindex相同(原始的minorigin和minindex肯定是不同的,因为上面选minindex有做判断,minorigin被置换的话就不一样了),调换位置就是徒劳的。

    m_list = [
        [3, 2, 7, 1, 1, 5, 8, 5, 2],
        [9, 1, 8, 8, 5, 3, 8, 8, 9],
        [7, 9, 8, 6, 3, 8, 9, 7, 1],
        [1, 1, 1, 1, 2, 1, 1, 1, 1]
    ]
    
    nums = m_list[3]
    print(nums)
    length = len(nums)
    count_iter = 0
    count_swap = 0
    
    for i in range(length // 2):
        maxindex = i 
        minindex = -i - 1 
        minorigin = length + minindex 
        for j in range(i+1, length-i):
            count_iter += 1
            if nums[j] > nums[maxindex]: 
                maxindex = j
            if nums[-j - 1] < nums[minindex]:
                minindex = -j - 1
        if nums[maxindex] == nums[minindex]: break  #优化1
        minindex = length + minindex 
        if i != maxindex:
            nums[i], nums[maxindex] = nums[maxindex], nums[i] 
            count_swap += 1
            if i == minindex: 
                minindex = maxindex
        if minindex != minorigin and nums[minorigin]!=nums[minindex]:  #优化2
            nums[minorigin], nums[minindex] = nums[minindex], nums[minorigin]
            count_swap += 1
    
    print(nums, count_iter, count_swap, sep='\n')
    

    ## 总结

    • 简单选择排序需要数据一轮轮的比较,并在每一轮中发现机值
    • 没有办法知道当前轮是否已经达到排序要求,但是可以知道极值是否在目标索引上
    • 遍历次数:1,2,3,...,n-1之和,即n(n-1)/2
    • 时间复杂度O(n**2)
    • 减少了交换次数,提高了效率,性能率好过冒泡法

    相关文章

      网友评论

          本文标题:排序算法

          本文链接:https://www.haomeiwen.com/subject/tupaihtx.html