数据结构学习第四天--希尔排序和快排

1、希尔排序

希尔排序(Shell Sort)是插入排序的一种。也叫做缩小增量排序,是直接插入排序算法的一种改进版,是一个非稳定的排序算法.他的原理是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰好被分成一组,算法便是终止了。

希尔排序的基本思想:
    将数组列在一个表中并对列分别进行插入排序,重复这个过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表只有一列了。将数组转换至表是为了更好的理解这个算法,算法本身还是使用数组进行排序 。

这个算法在时间复杂度上面来说的话:
  1、最优时间复杂度:根据步长序列的不同而不同。
  2、最坏时间复杂度:O(n**2)          (“**” 代表的是平方)
  3、稳定性:不稳定。

这个算法中! 当我们的步长取得好的话!我们的时间复杂度是可以得到提升的 !

希尔排序的实现

#coding=utf8

def Shell_sort(alist):
    """希尔排序"""
    n = len(alist)
    gap = n // 2  #代表的步长
#gap变化到0之前,插入算法执行的步骤
    while gap >=1:
        #插入算法,与普通的插入算法的区别就是gap步长
        for j in  range(gap.n):
            i = j
            while i > 0:
                if alist[i] < alist[i - gap]:
                    alist[i] , alist[i - gap] = alist[i - gap],alist[i]
                    i -= gap
                else:
                    break
        #开始缩短步长

这就是一个shell_sort的实现,

希尔排序的时间复杂度

直接看代码分析时间复杂度,我们没办法分析,那么我们直接取特殊的情况! 就是当我们的gap=1的时候,我们会发现这个希尔排序就是一个插入排序,也就是希尔排序最坏时间复杂度为O(n**2),最优的复杂度是根据gap来决定的!取决于gap的取值,那么到底我们的gap如何取到最优呢!?那么我们就需要数学知识了!

2、快速排序

1、快排第一种运行底层原理

假设我们现在对“6  1  2 7  9  3  4  5 10  8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数(基准数就是拿一个数字做我们的参考数)。为了方便,就让第一个数6作为基准数吧。接下来,需要将这个序列中所有比基准数大的数放在6的右边,比基准数小的数放在6的左边,类似下面这种排列:

3  1  2 5  4  6  9 7  10  8

在初始状态下,数字6在序列的第1位。我们的目标是将6挪到序列中间的某个位置,假设这个位置是k。现在就需要寻找这个k,并且以第k位为分界点,左边的数都小于等于6,右边的数都大于等于6。想一想,你有办法可以做到这点吗?

方法就是使用到我们的快排(快排的执行逻辑):分别别从初始序列“6  1  2 7  9  3  4  5 10  8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边,相当于两个指针。刚开始的时候让 i 指向序列的最左边(即i=1),指向数字6。让  j 指向序列的最右边(即=10),指向数字。

    因为此处设置的基准数是最左边的数,所以需要让  j  先出动,这一点非常重要(请自己想一想为什么)。j  一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来  i   再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。然后两个交换。什么时候停止呢?当我们的i 和 j 重合的时候!两者那个位置就是基数6应该在的位置。
    这个时候基数6的左边都是小于6的数,基数6的右边都是大于6的数字,此时基数6的左右两边分别是两个队列!在持续上面的步骤就行,直到完全结束排序。

2、快排第二种运行原理

假如我们有一组数据, 54  ,26 ,93 , 17 , 77 , 31 , 44 , 55 , 20  那么我们还是采用两个指针,第一个指针指向的是这一组数据的第一个,称为low指针 ,第二个指针指向这一行数的最后一个 元素,我们称为high指针。

第一步:我们找一个中间值! 就第一个元素吧! 我们使用一个变量保存这个值! Mid_value = 54 。 那么low此时指向的是空!第二步:然后往左移动最后的high这个指针,判断这个列表的high这个指针指向的值是比54大?还是比54小呢?,
   如果比这个中间值54小的话,那就将这个值保存在 low 这个指针下,然后让low += 1 ,促使low这个指标向右移动,执行第三步!  此刻high这个指向此刻为空!     
   如果high比这个中间值大!直到找到这个比中间值54小的值。进行交换值以后!在执行下一步。
第三步:然后我们开始往右移动low指针,如果发现 alist[low] 这个值比54小的话,那么我们就让low += 1 ,如果发现下一个值比54这个值大!那么我们就将这个值存储到上一步的high指向的那个位置,然后让high -= 1,促使他向左移动!,然后在重复第二步骤!第三步。直到两个指针重合!第一次结束。
第四步:当找到重合点以后,那么就让重合的位置等于那个中间值54.
此时54左边的都比54小,54右边的都比54小!然后分别对这两个数组再次进行这样的快排操作。

快排实现1

#coding=utf-8


#first代表的是alist这个列表的第一个元素的下标
#last代表的是alist这个列表的最后一个元素的下标
def quick_sort(alist,first,last):
    """快速排序"""
    if first >= last:
        return
    mid_value = alist[first]   #这个是我们的中间值
    low = first           #其中的开始元素的指针
    high = last     #其中这是最后一个元素的指针

    #6、外层循环的判断。 其实就是一个不断调用的情况 !
    #7、这里面出现的情况,low和high的值是相等的! ,我们尽量将54都放在其中的一边去处理。
    #   只需要最后在判断的时候进行判断一下就行!想要放在哪一边都行
    while low < high:
        #开始移动
        #1、第一步首先移动high这个元素。判断low什么时候停止移动
        while low < high and alist[high] >= mid_value:
            high -= 1
        # 2、如果发现其中一个比中间值mid_value小的话,那么就会不满足上面的条件,这时候我们就需要交换两个元素。
        list[low] = alist[high]
        #3、移动玩两个元素之后我们就需要去移动low,然后对low进行判断了
       #9、 low += 1


        #4、这边是在移动low这个元素。
        while low > high and alist[low] > mid_value:
            low += 1
        alist[high] = alist[low]
      #9、 high -= 1
    #10、从循环退出时Low是跟high相等的!
    alist[low] = mid_value
    #11、此时整个循环执行了一次!下面该对循环过后的左右两边的小的队列进行排序了。左边的列表式 alist[:low-1] ,右边的列表式alist[low+1:]
    quick_sort(alist , first , low - 1)  #递归使用左边的列表进行quick_sort进行排序 ,然后产生的两个子序列再次进行quick_sort进行排序 。下面同理。
    quick_sort(alist , low + 1, last)


        #5、然后上面的两个步骤是交叉调用的!这时候我们需要外层有一个循环来控制。
        #8、我们还要考虑一个问题! 当我们在移动的时候!出现low和high相等的情况的时候,我们发现low和high完美的错过了!
        #我们要怎么样保证在low和high相等的情况下保证程序的退出呢??  看我们的第9步的操作,我们需要将这两个指针在相遇的时候不进行操作。
        #那就将两个指针的移动放再循环中!不影响最终的结果。


快速排序的最优时间复杂度是 O(nlogn)   ,最坏的时间复杂度是O(n**2) , 快排是不稳定的! 

如何计算最优的时间复杂度,假设我们的数组有九个元素! 那就相当于, 9 /2/2/2 =1 ,9除以多少个2才能到1呢? 那么相当于!2的多少次方是9呢? 2**n = 9 ?  这个时候对数换算! 也就是n = log以2为底9的对数。  现在我们不知道列表有多少个,那么就是  n = log以2为底n的对象代表我们的复杂度, 那么我们在找的时候也需要执行n次! 所以是O(nlogn)。

猜你喜欢

转载自blog.csdn.net/weixin_41928342/article/details/85635914