Python实现十大常用的排序算法(1)

 学习自用,参考链接一参考链接二参考链接三参考链接四博文

          基本的排序算法在经过前人呕心沥血的研究下基本可以分为以下十种,当然除此之外,还有结合多种算法思想基于他们的改进变种。在插入、选择、交换这三大类基于比较的排序算法中,时间复杂度会随着优化程度在O(n^2)~O(nlogn)之间变化,希尔排序、快速排序、堆排序分别代表着杰出的优化策略。基于分治递归思想的归并排序将待排数据像二叉树一样分化至最简单的一个数排序问题,子问题合并时间复杂度可控制在O(n),不难想到整体时间复杂度取决于树的深度,即达到O(nlogn)。计数排序、桶排序、基数排序三种线性时间排序算法本质上运用了相同的思想:先将数据按一定映射关系分组(桶),然后桶内排序,顺序输出。三种姑且称为‘桶’排序算法在分组函数使用上不同,导致分组粒度不同,带来的额外空间开销出现差异。这三种排序算法适用于数据满足一定的条件,否则额外的空间开销将无法承受。

        稳定性算法:插入排序,冒泡排序,归并排序,基数排序,计数排序,桶排序;(简称:插冒归基/计桶)

        不稳定的排序:简单选择排序,希尔排序(增量排序),堆排序,快速排序

冒泡排序(Bubble Sort)


算法思想

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
算法描述

1.比较相邻的元素。如果第一个比第二个大,就交换它们两个
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数
3.针对所有的元素重复以上的步骤,除了上次的最后一位
4.重复步骤1~3,直到排序完成

动画演示

代码实现

def bubble_sort(arr):
    """冒泡排序"""
    # 第一层for表示循环的遍数
    for i in range(len(arr) - 1):
        # 第二层for表示具体比较哪两个元素
        for j in range(len(arr) - 1 - i):
            if arr[j] > arr[j + 1]:
                # 如果前面的大于后面的,则交换这两个元素的位置
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr


#-------------------------------------------------------
def BubbleSort(ls):
    n=len(ls)
    if n<=1:
        return ls
    for i in range (0,n):
        for j in range(0,n-i-1):
            if ls[j]>ls[j+1]:
                (ls[j],ls[j+1])=(ls[j+1],ls[j])
    return ls
x=input("请输入待排序数列:\n")
y=x.split()
arr=[]
for i in  y:
    arr.append(int(i))
arr=BubbleSort(arr)
#print(arr)
print("数列按序排列如下:")
for i in arr:
    print(i,end=' ')

算法分析

冒泡排序是一种简单直接暴力的排序算法,为什么说它暴力?因为每一轮比较可能多个元素移动位置,而元素位置的互换是需要消耗资源的,所以这是一种偏慢的排序算法,仅适用于对于含有较少元素的数列进行排序。

  1. 稳定性:我们从代码中可以看出只有前一个元素大于后一个元素才可能交换位置,所以相同元素的相对顺序不可能改变,所以它是稳定排序
  2. 比较性:因为排序时元素之间需要比较,所以是比较排序
  3. 时间复杂度:因为它需要双层循环n*(n-1)),所以平均时间复杂度为O(n^2)
  4. 空间复杂度:只需要常数个辅助单元,所以空间复杂度为O(1),我们把空间复杂度为O(1)的排序成为原地排序(in-place)
  5. 记忆方法:想象成气泡,一层一层的往上变大

选择排序(Selection Sort)

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,所以称为:选择排序

算法思想

  1. 设第一个元素为比较元素,依次和后面的元素比较,比较完所有元素找到最小的元素,将它和第一个元素互换
  2. 重复上述操作,我们找出第二小的元素和第二个位置的元素互换,以此类推找出剩余最小元素将它换到前面,即完成排序

动画演示

代码实现

def selection_sort(arr):
    """选择排序"""
    # 第一层for表示循环选择的遍数
    for i in range(len(arr) - 1):
        # 将起始元素设为最小元素
        min_index = i
        # 第二层for表示最小元素和后面的元素逐个比较
        for j in range(i + 1, len(arr)):
            if arr[j] < arr[min_index]:
                # 如果当前元素比最小元素小,则把当前元素角标记为最小元素角标
                min_index = j
        # 查找一遍后将最小元素与起始元素互换
        arr[min_index], arr[i] = arr[i], arr[min_index]
    return arr


#---------------------------------------------------
def  SelectSort(ls):
    n=len(ls)
    if n<=1:
        return ls
    for i in range(0,n-1):
        minIndex=i
        for j in range(i+1,n):          #比较一遍,记录索引不交换
            if ls[j]<ls[minIndex]:
                minIndex=j
        if minIndex!=i:                     #按索引交换
            (ls[minIndex],ls[i])=(ls[i],ls[minIndex])
    return ls
 
x=input("请输入待排序数列:\n")
y=x.split()
arr=[]
for i in  y:
    arr.append(int(i))
arr=SelectSort(arr)
#print(arr)
print("数列按序排列如下:")
for i in arr:
    print(i,end=' ')

算法分析

选择排序和冒泡排序很类似,但是选择排序每轮比较只会有一次交换,而冒泡排序会有多次交换,交换次数比冒泡排序少,就减少cpu的消耗,所以在数据量小的时候可以用选择排序,实际适用的场合非常少。

  1. 比较性:因为排序时元素之间需要比较,所以是比较排序
  2. 稳定性:因为存在任意位置的两个元素交换,比如[5, 8, 5, 2],第一个5会和2交换位置,所以改变了两个5原来的相对顺序,所以为不稳定排序。
  3. 时间复杂度:我们看到选择排序同样是双层循环n*(n-1)),所以时间复杂度也为:O(n^2)
  4. 空间复杂度:只需要常数个辅助单元,所以空间复杂度也为O(1)
  5. 记忆方法:选择对象要先选最小的,因为嫩,哈哈

插入排序(Insertion Sort)

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

算法思想

  1. 从第二个元素开始和前面的元素进行比较,如果前面的元素比当前元素大,则将前面元素 后移,当前元素依次往前,直到找到比它小或等于它的元素插入在其后面
  2. 然后选择第三个元素,重复上述操作,进行插入
  3. 依次选择到最后一个元素,插入后即完成所有排序

动画演示

代码实现

def insertion_sort(arr):
    """插入排序"""
    # 第一层for表示循环插入的遍数
    for i in range(1, len(arr)):
        # 设置当前需要插入的元素
        current = arr[i]
        # 与当前元素比较的比较元素
        pre_index = i - 1
        while pre_index >= 0 and arr[pre_index] > current:
            # 当比较元素大于当前元素则把比较元素后移
            arr[pre_index + 1] = arr[pre_index]
            # 往前选择下一个比较元素
            pre_index -= 1
        # 当比较元素小于当前元素,则将当前元素插入在 其后面
        arr[pre_index + 1] = current
    return arr

#---------------------------------------------------------
def InsertSort(ls):
    n=len(ls)
    if n<=1:
        return ls
    for i in range(1,n):
        j=i
        target=ls[i]            #每次循环的一个待插入的数
        while j>0 and target<ls[j-1]:       #比较、后移,给target腾位置
            ls[j]=ls[j-1]
            j=j-1
        ls[j]=target            #把target插到空位
    return ls
 
x=input("请输入待排序数列:\n")
y=x.split()
arr=[]
for i in  y:
    arr.append(int(i))
arr=InsertSort(arr)
#print(arr)
print("数列按序排列如下:")
for i in arr:
    print(i,end=' ')

算法分析

插入排序的适用场景:一个新元素需要插入到一组已经是有序的数组中,或者是一组基本有序的数组排序。

  1. 比较性:排序时元素之间需要比较,所以为比较排序
  2. 稳定性:从代码我们可以看出只有比较元素大于当前元素,比较元素才会往后移动,所以相同元素是不会改变相对顺序
  3. 时间复杂度:插入排序同样需要两次循坏一个一个比较,故时间复杂度也为O(n^2)
  4. 空间复杂度:只需要常数个辅助单元,所以空间复杂度也为O(1)
  5. 记忆方法:想象成在书架中插书:先找到相应位置,将后面的书往后推,再将书插入

希尔排序(Shell Sort)

希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量(间隔)排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本,它与插入排序的不同之处在于,它会优先比较距离较远的元素,该方法因D.L.Shell于1959年提出而得名。

算法思想

希尔排序的整体思想是将固定间隔的几个元素之间排序,然后再缩小这个间隔。这样到最后数列就成为了基本有序数列,而前面我们讲过插入排序对基本有序数列排序效果较好。

  1. 计算一个增量(间隔)值
  2. 对元素进行增量元素进行比较,比如增量值为7,那么就对0,7,14,21…个元素进行插入排序
  3. 然后对1,8,15…进行排序,依次递增进行排序
  4. 所有元素排序完后,缩小增量比如为3,然后又重复上述第2,3步
  5. 最后缩小增量至1时,数列已经基本有序,最后一遍普通插入即可

已知的最增量式是由 Sedgewick 提出的 (1, 5, 19, 41, 109,…),该步长的项来自 9 * 4^i - 9 * 2^i + 1 和 4^i - 3 * 2^i + 1 这两个算式。这项研究也表明 "比较在希尔排序中是最主要的操作,而不是交换。 用这样增量式的希尔排序比插入排序和堆排序都要快,甚至在小数组中比快速排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。

动画演示

代码实现

def shell_sort(arr):
    """希尔排序"""
    # 取整计算增量(间隔)值
    gap = len(arr) // 2
    while gap > 0:
        # 从增量值开始遍历比较
        for i in range(gap, len(arr)):
            j = i
            current = arr[i]
            # 元素与他同列的前面的每个元素比较,如果比前面的小则互换
            while j - gap >= 0 and current < arr[j - gap]:
                arr[j] = arr[j - gap]
                j -= gap
            arr[j] = current
        # 缩小增量(间隔)值
        gap //= 2
    return arr

#--------------------------------------------------------------------
def ShellSort(ls):
    def shellinsert(arr,d):
        n=len(arr)
        for i in range(d,n):
            j=i-d
            temp=arr[i]             #记录要出入的数
            while(j>=0 and arr[j]>temp):    #从后向前,找打比其小的数的位置
                arr[j+d]=arr[j]                 #向后挪动
                j-=d
            if j!=i-d:
                arr[j+d]=temp
    n=len(ls)
    if n<=1:
        return ls
    d=n//2
    while d>=1:
        shellinsert(ls,d)
        d=d//2
    return ls
 
 
x=input("请输入待排序数列:\n")
y=x.split()
arr=[]
for i in  y:
    arr.append(int(i))
arr=ShellSort(arr)
#print(arr)
print("数列按序排列如下:")
for i in arr:
    print(i,end=' ') 

算法分析

  1. 比较性:排序时元素之间需要比较,所以为比较排序
  2. 稳定性:因为希尔排序是间隔的插入,所以存在相同元素相对顺序被打乱,所以是不稳定排序
  3. 时间复杂度: 最坏时间复杂度O(n2)平均复杂度为O(n1.3)
  4. 空间复杂度:只需要常数个辅助单元,所以空间复杂度也为O(1)
  5. 记忆方法:插入排序是每轮都是一小步,希尔排序是先大步后小步,它第一个突破O(n2)的排序算法。联想起阿姆斯特朗登月之后说:这是我个人一小步,却是人类迈出的一大步。

归并排序(Merge Sort)

归并排序(Merge-Sort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。归并排序适用于子序列有序的数据排序。

算法思想

从上图看分解后的数列很像一个二叉树。

  1. 使用递归将源数列使用二分法分成多个子列
  2. 申请空间将两个子列排序合并然后返回
  3. 将所有子列一步一步合并最后完成排序

动图演示

代码实现

def merge_sort(arr):
    """归并排序"""
    if len(arr) == 1:
        return arr
    # 使用二分法将数列分两个
    mid = len(arr) // 2
    left = arr[:mid]
    right = arr[mid:]
    # 使用递归运算
    return marge(merge_sort(left), merge_sort(right))


def marge(left, right):
    """排序合并两个数列"""
    result = []
    # 两个数列都有值
    while len(left) > 0 and len(right) > 0:
        # 左右两个数列第一个最小放前面
        if left[0] <= right[0]:
            result.append(left.pop(0))
        else:
            result.append(right.pop(0))
    # 只有一个数列中还有值,直接添加
    result += left
    result += right
    return result

#---------------------------------------------------------------
def MergeSort(ls):
    #合并左右子序列函数
    def merge(arr,left,mid,right):
        temp=[]     #中间数组
        i=left          #左段子序列起始
        j=mid+1   #右段子序列起始
        while i<=mid and j<=right:
            if arr[i]<=arr[j]:
                temp.append(arr[i])
                i+=1
            else:
                temp.append(arr[j])
                j+=1
        while i<=mid:
            temp.append(arr[i])
            i+=1
        while j<=right:
            temp.append(arr[j])
            j+=1
        for i in range(left,right+1):    #  !注意这里,不能直接arr=temp,他俩大小都不一定一样
            arr[i]=temp[i-left]
    #递归调用归并排序
    def mSort(arr,left,right):
        if left>=right:
            return
        mid=(left+right)//2
        mSort(arr,left,mid)
        mSort(arr,mid+1,right)
        merge(arr,left,mid,right)
 
    n=len(ls)
    if n<=1:
        return ls
    mSort(ls,0,n-1)
    return ls
 
x=input("请输入待排序数列:\n")
y=x.split()
arr=[]
for i in  y:
    arr.append(int(i))
arr=MergeSort(arr)
#print(arr)
print("数列按序排列如下:")
for i in arr:
    print(i,end=' ') 

算法分析

  1. 比较性:排序时元素之间需要比较,所以为比较排序
  2. 稳定性:我们从代码中可以看到当左边的元素小于等于右边的元素就把左边的排前面,而原本左边的就是在前面,所以相同元素的相对顺序不变,故为稳定排序
  3. 时间复杂度: 复杂度为O(nlog^n)
  4. 空间复杂度:在合并子列时需要申请临时空间,而且空间大小随数列的大小而变化,所以空间复杂度为O(n)
  5. 记忆方法:所谓归并肯定是要先分解,再合并

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值