排序算法的时间复杂度

链接:直接插入排序的平均时间复杂度为( )。__牛客网
来源:牛客网
 

一、时间复杂度:

(1)定义:

时间复杂度是用来定性描述算法执行所需要的时间。现假设问题规模为n,解决该问题的算法中基本操作重复执行的次数是T(n)。如果有一个辅助函数f(n)使得T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数,因此有T(n)=O(f(n)),此时O(f(n))称为算法的渐进时间复杂度,简称时间复杂度。时间复杂度越高,算法的执行效率越低。

(2)计算方法:

1)首先找出算法中所有重复执行的基本操作并确定总的的执行次数T(n);

2)然后找出T(n)的同数量级函数f(n);

3)最后时间复杂度是O(f(n))。

技巧:

1)一重循环的时间复杂度为O(n),两重循环的时间复杂度为O(n^2),三重循环的时间复杂度为O(n^3),依次类推;

2)二分的时间复杂度为O(logn);

3)一个循环里套一个二分的时间复杂度为O(nlogn)。

(3)举例说明:

for (i = 1; i <= n; i++)

{

    for(j = 1;j <= n;j++)

    {

        printf(“%d,%d”,i,j);

        for (k =1;k  <= n; k++)

        {

             printf(“%d,%d,%d”,i,j,k);

        }

    }

}

1) 

第一个重复执行的基本操作printf(“%d,%d”,i,j),它的执行次数是n^2;

第二个重复执行的基本操作printf(“%d,%d,%d”,i,j,k),它的执行次数是n^3;

总的执行次数是T(n) = n^2 + n^3。

2)

T(n)的同数量级函数是n^3。

3)

时间复杂度是O(n^3)

(4)影响算法时间复杂度的因素:

1)算法本身

2)问题规模

3)数据的初始状态

二、排序算法:

1、冒泡排序。

(1)思想:

比如现在有n个数据,要求从小到大排列。做法是:需要进行n-1轮排序,第i轮排序中将从第0个元素到第n-i-1个元素进行两两比较将该轮排序中的最大值一步一步移动都最后的位置。

(2)代码:

def bubbleSort(L):

    for i in range(len(L)-1):

        for j in range(0,len(L)-i-1):

            if L[j] > L[j+1]:

                L[j],L[j+1] = L[j+1],L[j]

优化代码:如果某一轮中来没有任何元素进行交换,说明已经都排好序了,不需要再进行下一轮排序,算法应该结束。此时可以使用一个标志来

记录控制。

def bubbleSort_new(L):

    flag = False # 控制标志

    for i in range(len(L)-1):

        for j in range(0,len(L)-1-i):

            if L[j] > L[j+1]:

                L[j],L[j+1] = L[j+1],L[j]

                flag = True

        if not flag:

            break

(3)时间复杂度:

1)最好:如果n个数据已经是排好序的,那么就是最好的情况。此时算法需要进行第一轮排序得知数据是排好序的,那么算法中基本操作if比较语句需要执行n-1次,则时间复杂度为O(n)。

2)最坏:如果n个数据正好是反序的,那么就是最坏的情况。此时算法需要进行n-1轮排序,第i轮排序需要比较n-i-1次,那么算法中基本操作if比较语句需要执行的总的次数T(n) = n-1+n-2+n-3+n-4+...+1 = n(n-1)/2,则时间复杂度为O(n^2)。

3)平均:平均执行的次数 = n-1 +  n(n-1)/2 = 1/2n^2 + 1/2n -1,则平均时间复杂度为O(n^2)。

(4)稳定性:序列中两个相等的元素在排序之后,它们的相对位置不会发生改变。因此冒泡排序算法是稳定的算法。

2、选择排序。

(1)思想:

比如现在有n个数据,要求从小到大排列。做法是:需要进行n-1轮排序,第i轮排序中将第i个元素与剩下的所有元素进行比较一步一步选出该轮排序中最小的元素并放在序列的最左边。

(2)代码:

def selectSort(L):

    for i in range(len(L)-1):

        for j in range(i+1,len(L)):

            if L[j] < L[i]:

                L[i],L[j] = L[j],L[i]

(3)时间复杂度:

1)最好:如果n个数据已经是排好序的,那么就是最好的情况。但与冒泡排序不同的是,它不可以设置标志提早结束循环。两层循环都必须要执行完整。所以时间复杂度为O(n^2)。

2)最坏:如果n个数据正好是反序的,那么就是最坏的情况。此时算法需要进行n-1轮排序,第i轮排序需要比较n-i-1次,那么算法中基本操作if比较语句需要执行的总的次数T(n) = n-1+n-2+n-3+n-4+...+1 = n(n-1)/2,则时间复杂度为O(n^2)。

3)平均:平均执行的次数 = n-1 +  n(n-1)/2 = 1/2n^2 + 1/2n -1,则平均时间复杂度为O(n^2)。

(4)稳定性:序列中两个相等的元素在排序之后,它们的相对位置可能会发生改变。因此快速排序算法是不稳定的算法。

3、插入排序。

(1)思想:

边插入边排序。

1)先假定序列中第0个元素是有序的,

2)从序列中第1个元素开始遍历,将遍历取出的元素与其前面已经排好序的元素进行比较将其插入到有序序列中的合适位置。

(2)代码:

def insertSort(L):

    for i in range(1,len(L)):

        x = L[i] #待排数据

        j = i -1 #从待排数据的前一个数据开始进行比较

        while j >=0:

            if L[j] > x:

                L[j+1] = L[j]

                j -= 1 

        L[j+1] = x

(3)时间复杂度:

1)最好:如果序列已经是排好序的,那么就是最好的情况。

此时外层循环执行n-1次,每次中内循环体执行1次,赋值语句执行一次,则T(n) = n-1,所以时间复杂度为O(n)。

2)最坏:如果序列正好是逆序的,那么就是最坏的情况。

此时外层循环执行n-1次,对应的内循环体分别执行n-(n-1),n-(n-2),n-(n-3)...,n-3,n-2,n-1,T(n)= n(n-1)/2,所以时间复杂度为O(n)。

3)平均: 平均执行的次数 = n-1 +  n(n-1)/2 = 1/2n^2 + 1/2n -1,则平均时间复杂度为O(n^2)。

(4)稳定性:序列中两个相等的元素在排序之后,它们的相对位置不会发生改变。因此插入排序算法是稳定的算法。

4、快速排序。

(1)思想:

快速排序法的精髓就是利用了分治的思想。将一个规模为n的问题分解为若干个规模较小的子问题,这些子问题互相独立且与原问题形式相同;递归地解这些子问题;最后将各个子问题的解合并得到原问题的解。那么对于快速排序法来说,

1)先从序列中挑出第一个元素作为后期比较的基准。

2)将所有比基准小的元素摆放在基准的左边,所有比基准大的元素摆放在基准的右边,所有和基准相同的元素摆放在基准的任一边。在这个分区结束之后,该基准就处于序列的中间位置。

3)递归地把左分区的元素和右分区的元素再进行排序。

(2)代码:

def quickSort(L,i,j):

    if i<j:

        k = partion(L,i,j)#第一趟排序

        quickSort(L,i,k-1)

        quickSort(L,k+1,j)

def partion(L,i,j):

   #先从j开始向左走直到找到一个比基准(L[i])小的数执行L[j]与L[i]交换,i += 1,然后停下来

   #再从i开始向右走直到找到一个比基准(L[j])大的数执行L[i]与L[j]交换,j += 1,然后停下来

   #重复上面两个步骤直到i==j

   while True:

       while j>i:

            if L[j] >= L[i]:

                j -= 1

            else:

                L[j],L[i] = L[i],L[j]

                i += 1

                break

        while i < j:

            if L[i] <= L[j]:

                i += 1

            else:

                L[i],L[j] = L[j],L[i]

                j -= 1

                break

        if i == j:

            break

    return  i

(3)时间复杂度:

已知快速排序法的递推关系:T(n)= T(i)+T(n-i-1)+c*n

1)最好:如果每次分割,分得的两个子问题的规模是近似相等的,那么就是最好的情况。

为便于分析,假设两个子区间的元素个数恰好各为原序列个数的一半大小,估计虽然过高,但不影响大O的答案。

此时可使用递推公式T(n) = 2T(n/2)+c*n进行分析。

T(n) = 2T(n/2)+c*n

T(n)= 2(2T(n/4)+c*n/2)+c*n = 4T(n/4)+c*2n

T(n)= 4(2T(n/8)+c*n/4)+c*2n = 8T(n/8)+c*3n

...

T(n)=  nT(1)+c*logn*n = n + cnlogn

所以时间复杂度为O(nlogn)

2)最坏:如果每次分割,分得的两个子问题的规模相差很大其中一个为0,那么就是最坏的情况。

此时可使用递推公式T(n)= T(n-1)+c*n进行分析。

T(n)= T(n-1)+c*n

T(n)= T(n-2)+c*(n-1)+c*n

T(n)= T(n-3)+c*(n-2)+c*(n-1)+c*n

...

T(n)= T(n-(n-1)) + c*(n-(n-2))+c*(n-(n-3))+...+c*(n-2)+c*(n-1)+c*n

            =T(1)+c*2 + c*3 + ...+c*(n-2)+c*(n-1)+c*n

            =1 + c*(2+3+4+...+n)

            =1+c*(n+2)(n-1)/2

           =c/2*(n^2+n-2)+1

所以时间复杂度为O(n^2)

3)平均:分得的两个子区间内的元素个数都有可能是0,1,2,...,n-1,因此是每种情况的概率是1/n。

此时可使用递推公式T(n) = [1/nT(0)+1/nT(1)+...1/nT(n-1)] + [1/nT(0)+1/nT(1)+...1/nT(n-1)] + cn进行分析。

T(n) = [1/nT(0)+1/nT(1)+...1/nT(n-1)] + [1/nT(0)+1/nT(1)+...1/nT(n-1)] + cn

(4)稳定性:序列中两个相等的元素在排序之后,它们的相对位置可能会发生改变。因此快速排序算法是不稳定的算法。

 

  • 9
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值