[leetcode]排序算法(冒泡排序,选择排序,插入排序,快速排序,计数排序)

Posted 是安澜啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[leetcode]排序算法(冒泡排序,选择排序,插入排序,快速排序,计数排序)相关的知识,希望对你有一定的参考价值。

目录

1.冒泡排序

原理

 代码(python&cpp)

拓展:timeit()用法

2.选择排序

原理

3.插入排序

原理

代码(python&cpp)

4.归并排序

原理

代码

 5.快速排序

原理

代码(python&cpp)

6.计数排序

原理

 代码(python&cpp)

总结

参考


1.冒泡排序

冒泡排序(Bubble Sort)是一种很原始的排序方法,就是通过不断地交换“大数”的位置达到排序的目的。因为不断出现“大数”类似于水泡不断出现,因此被形象地称为冒泡算法。

原理

冒泡算法的基本原理就是比较相邻两个数字的大小。将两数中比较大的那个数交换到靠后的位置。不断地交换下去就可以将最大的那个数放到队列的尾部。然后重头再次交换,直到将数列排成有序数列。

一个 n 个数的数列需要排序 n -1轮。这样可以确保得到一个有序的数列。因此冒泡排序的时间复杂度是

 代码(python&cpp)

#!/user/bin/env/python3
#-*-conding:utf-8 -*-
# data:20211210
# author:yang

import random
import timeit

def randomList(n):
    """返回一个长度为n的整数列表,数据范围为[0,1000)"""
    iList = []
    for i in range(n):
        iList.append(random.randrange(1000))
    return iList

def bubbleSort(iList):
    """冒泡排序"""
    if len(iList) <= 1:
        return iList
    for i in range(1,len(iList)):
        for j in range(0,len(iList)-i):
            if iList[j] >= iList[j+1]:
                iList[j],iList[j+1] = iList[j+1],iList[j]
        print("第%d轮排序结果"%i,end=" ")
        print(iList)
    # return iList             


if __name__=="__main__":
    iList = randomList(10)
    print("iList:",iList)
    print(bubbleSort(iList))
    # bubbleSort(iList)
    # print(timeit.timeit("bubbleSort(iList)","from __main__ import bubbleSort,iList", number=100))
       

cpp待补充 

拓展:timeit()用法

python中的timeit()方法, 它用于获取代码的执行时间。该库将代码语句默认运行一百万次,并提供从集合中花费的最短时间,可以用来检查代码的性能。

语法:

timeit.timeit(stmt, setup,timer, number)

参数:

stmt:这将采用您要测量其执行时间的代码。默认值为“pass”。

setup:这将包含需要在stmt之前执行的设置详细信息。这个参数可以将stmt的环境传进去。比如各种import和参数什么的。默认值为“ pass”。

timer:它将具有计时器值,timeit()已经设置了默认值,我们可以忽略它。

number:stmt将按照此处给出的编号执行。默认值为1000000。

注:

要使用timeit(),我们需要导入模块,如下:

import timeit

另外需要补充一点是,如果你想直接 stmt 那里执行函数。可以把函数申明在当前文件中,然后在 stmt = ‘func()’ 执行函数。然后使用 setup = ‘from __main__ import func’ 即可,如果要import 多个需要使用 setup = from __main__ import func; import simplejson'

2.选择排序

原理

简单地说,选择排序只做了一件事,就是从数列中选择最大(最小)的那个数,将这个数放到合适的位置。然后在抛开这个数的子数列中找最大(最小)的那个数放到合适的位置。然后……一直到子数列为空为止。与冒泡排序稍有不同的是,它不是相邻的两个数比较,而是某个数和数列中其他所有的数比较。

第一轮排序:以数列中第1个数为基数,遍历数列中其他的数字,找到最小的那个数,然后交换这两个数的位置。 本轮排序的结果将数列中最小的那个数放到了数列的第一位。

 第二轮排序:然后以数列中的第2个数为基数,遍历数列中剩余的其他数字,找到最小的那个数,交换这两个数的位置。第二个数字3已经是剩余数列中最小的数了。因此本轮无须交换数字,

 第N轮排序:按照这个规律,不断地找剩余数列中最小的数字,交换位置,直到将原始数列排成有序数列。

选择排序数列完毕。共6个数,排序了5轮。理论上来说,选择排序的时间复杂度是 O ( n 2 )。但在Python中稍微有点特殊,因为Python 列 表 中 寻 找 最 小 的 那 个 数 不 需 要 逐 个 比 较 , 使 用min(iList[i:])就可以得到最小的那个数,所以使用Pythonic风格版本的选择排序,时间复杂度是 O ( n )。因此,理论上在Python版本的排序算法中选择排序算法是最快的。

 代码(python&cpp)

#!/user/bin/env/python3
#-*-conding:utf-8 -*-
# data:20211210
# author:yang

from randomList import randomList
import timeit

def selectionSort(iList):
    if len(iList) <= 1:
        return iList
    for i in range(0,len(iList)-1):
        if iList[i] != min(iList[i:]):
            minIndex = iList.index(min(iList[i:]))  # 最小数的下标
            iList[i],iList[minIndex] = iList[minIndex],iList[i]
    print("第%d轮排序结果:"%(i+1),end=" ")
    print("ilist:",iList)
    # return iList

if __name__ == "__main__":
    iList = randomList(10)
    print(iList)
    print(selectionSort(iList))
    # print(timeit.timeit("selectionSort(iList)","from __main__ import selectionSort,iList",number = 1)) 

cpp待补充 

3.插入排序

插入排序(Insertion Sort)可能是最容易理解的排序了,插入排序方法与打扑克取牌的排序很相似。在打扑克时,每取一张新牌,都会与手上已有的牌进行比较,将新牌插入到比自己小的牌后面。在取完所有的牌后,手上已有的牌就是个有序的序列。

原理

插入排序首先将数列分成两部分。数列的第一个数为left部分,其他的数为right部分。然后将right部分中的数逐一取出,插入left部分中合适的位置。当right部分为空时,left部分就成为了一个有序
数列。

举例:

 第一轮:

首先要做的是将数列分成左、右两部分,left部分是第一个数字,right部分是数列剩余的部分。首先在牌堆中取出第一张牌,牌面是7,

 第二轮排序

然后从牌堆中取出第二张牌,牌面是3。将牌面是3的牌放入手中合适的位置。将right部分的第一个数字3插入left部分合适的位置。3比7要小,插入到7的前面。

第N轮排序 

循环将right部分的数字插入left部分,插入排序的时间复杂度是 O ( n 2 )。

代码(python&cpp)

#!/user/bin/env/python3
#-*-conding:utf-8 -*-
# data:20211210
# author:yang

from randomList import randomList
import timeit

def insertSort(iList):
    if len(iList) <= 1:
        return iList
    for right in range(1,len(iList)):
        target = iList[right]
        for left in range(0,right):
            if target <= iList[left]:
                # 使用python切片赋值 
                iList[left+1:right+1] = iList[left:right] 
                iList[left] = target
                break
    return iList  

if __name__ == "__main__":
    iList = randomList(10)
    print(iList)
    print(insertSort(iList))
    # print(timeit.timeit("selectionSort(iList)","from __main__ import selectionSort,iList",number = 1))
    
        

cpp待补充 

4.归并排序

归并排序(Merge Sort)是一种典型的递归法排序。它把复杂的排序过程分解成一个简单的合并子序列的过程。至于怎么得到这个子序列,就得自己调用自己了。

原理

归并排序首先要做的就是将数列分成左右两部分(最好是等分),然后将左、右两个子数列排序完毕后再合并到一起就成了一个有序数列。左、右两个子数列怎么变成有序数列呢?那就回头调用自
己,再把子数列分成左、右两部分,直到将所有的子数列的长度分为1为止。然后把子子数列排序完毕后合并成子数列……有点像那个著名的故事,山上有座山,山里有座庙,庙里有两个和尚……。和尚讲故事是无穷无尽的,幸运的是数列的长度即使再大也不会是无尽的。所以当子子子……序列分到不可再分割的时候(最小的序列长度为1时),就可以返回开始合并数列了。逐步合并子子子子……数列,到最后就得到了一个新的有序数列。

第一次分组

第二次分组

 

经过三次分组,已经将所有的子子子数列都变成了长度为1的数列。现在可以确定这些长度为1的数列必定是有序数列了,然后开始反向合并这些数列。

第一次合并

这里还需要考虑left和right长度不一致的情况。先合并数列[3]、[5]以及[9]、[4]。

第二次合并 

 第三次合并

归并排序完毕。经过3次合并,最终得到了一个有序数列。归并排序的时间复杂度是 O ( n Log n) )。

代码

#!/user/bin/env/python3
#-*-conding:utf-8 -*-
# data:20211210
# author:yang

from randomList import randomList
import timeit

def mergeSort(iList):
    if len(iList) <= 1:
        return iList
    middle = len(iList)//2
    left,right = iList[0:middle],iList[middle:]
    return mergeList(mergeSort(left),mergeSort(right))

def mergeList(left,right):
    """合并序列"""
    mList = []
    while left and right:
        if left[0] >= right[0]:
            mList.append(right.pop(0))
        else:
            mList.append(left.pop(0))
            
    while left:
        mList.append(left.pop(0))
    while right:
        mList.append(right.pop(0))
    return mList   
    
if __name__ == "__main__":
    iList = randomList(10)
    print(iList)
    print(mergeSort(iList))
    # print(timeit.timeit("selectionSort(iList)","from __main__ import selectionSort,iList",number = 1))
               

 5.快速排序

快速排序(Quick Sort)算法也是一种递归排序,用简单的方法来解决复杂的问题,唯一不太好的地方就是稍微有点浪费空间。

原理

以列表中的任意一个数为基准(一般选择列表中的第一个数),将列表分为左、右(前、后)两个子列表:左边子列表的数要比基准数小,右边子列表的数要比基准数大。然后继续把左边子列表和右边子列表按同样的方法继续分解、比较,一直到分无可分为止。然后按照左边子列表(比基准数小)+基准数+右边子列表(比基准数大)的方式连接起来。最后得到一个有序的数列。

以数列[7,3,5,1,9,4]为例。

第一次分组

以“7”为基准,比7小的分在左边的子数列中,比7大的分在右边的子数列中。

 此时左边的子序列有4个数[3, 5, 1, 4],还需要继续分组。右边的子序列中只有一个数9,不需要再分了。第二次只需要将左边的部分分组排序就可以了。

第二次分组

左边的子序列还剩下[3, 5, 1, 4],现在以第一个数3为基准数,将后面部分的[5, 1, 4]分成两部分。同样还是比基准数(3)小的放到左边子序列,比基准数(3)大的放到右边子序列。

 数分组完毕后。只要按照一定的顺序重新组合起来就可以了。组合很简单,就是小的数(左边部分)+中间数(基准数)+大的数(右边部分)。

第一次合并

将子序列合并,具体规则是left + [base] + right。这里需要稍微注意一下,left和right都是序列(列表),而base部分是单独的一个数,所以需要将它序列化。

 第二次合并

 第三次合并

 经过3次分组、合并后,得到了有序数列。快速排序的时间复杂度最坏的情况下是 O ( n 2 )。

代码(python&cpp)

#!/user/bin/env/python3
#-*-conding:utf-8 -*-
# data:20211210
# author:yang

from randomList import randomList
import timeit

def quickSort(iList):
    if len(iList) <= 1:
        return iList
    left = []
    right = []
    for i in iList[1:]:
        if i <= iList[0]:
            left.append(i)
        else:
            right.append(i)
    return quickSort(left) + [iList[0]] + quickSort(right)
        

if __name__ == "__main__":
    iList = randomList(10)
    print(iList)
    print(quickSort(iList))
    # print(timeit.timeit("selectionSort(iList)","from __main__ import selectionSort,iList",number = 1))

注:此方法速度快。

6.计数排序

计数排序(Counting Sort)算法是一种比较特殊的算法,因为它不是一个基于比较的算法。将两个数进行比较,将大的数放后面、小的数放前面,这样的算法都是基于比较的算法。

原理

它采用了一个巧妙的方法,选择一个数为基数,然后统计整个数列中有多少个数比基数小。如果有 n 个数比基数小,那么基数就放到新数列的第n +1的位置上(rList[n])。以数列[7, 3, 5, 1, 9, 4]为例,首先要做的是先创建一个与[7, 3, 5, 1, 9, 4]相同长度的空数列。

 第一个数的位置

第二个数的位置

 

第三个数的位置

 

依次类推

将所有的数字都插入到新数列后,排序就完成了。计数排序的时间复杂度是 O ( n + k ),其中 k 是整数的范围。

 代码(python&cpp)

#!/user/bin/env/python3
#-*-conding:utf-8 -*-
# data:20211210
# author:yang

from randomList import randomList
import timeit

def countingSort(iList):
    if len(iList) <= 1:
        return iList
    iLen = len(iList)
    rList = [None]*iLen
    for i in range(iLen):
        small = 0   # 比基数小的
        same = 0    # 与基数相等的
        for j in range(iLen):
            if iList[j] < iList[i]:
                small += 1
            elif iList[j] == iList[i]:  # 相同的数
                same += 1
        for k in range(small,small+same):
            rList[k] = iList[i]
    return rList
           

if __name__ == "__main__":
    iList = randomList(10)
    print(iList)
    print(countingSort(iList))
    # print(timeit.timeit("selectionSort(iList)","from __main__ import selectionSort,iList",number = 1))
    
                 

总结

 从表格上来看,排序100次用时最少,速度最快的应该是快速排序。果然是名副其实。选择排序的速度与快速排序相差无几。用时最多、速度最慢的是插入排序,其次就是冒泡排序。当然,根据输入数列的不同,这个排名可能会有所变动。

参考

图解leetcode初级算法(python版)--胡松涛

以上是关于[leetcode]排序算法(冒泡排序,选择排序,插入排序,快速排序,计数排序)的主要内容,如果未能解决你的问题,请参考以下文章

leetcode刷题-- 2. 排序(待更新)

排序算法之冒泡选择插入排序(Java)

排序算法之冒泡选择插入排序(Java)

冒泡排序算法和选择排序算法比较

三大基础排序算法(冒泡排序,选择排序,插入排序)

排序算法-冒泡排序(改),选择排序