数据结构与算法之美 | 排序(1)

排序算法:分析

学会如何评价、分析一个排序算法对于其学习和在实际场景中的运用至关重要。

执行效率

  • 最好情况、最坏情况和平均情况时间复杂度

    对于要排序的数据,有的接近有序,有的完全无序。有序度不同的数据,对于排序的执行时间肯定是有影响的,我们要知道排序算法在不同数据下的性能表现

  • 时间复杂度的系数、常数和低阶

    1)时间复杂度反映的是数据规模N很大的时候的一个增长趋势,所以可以忽略;

    2)但是在实际开发场景中,面对数据规模很小的情况时,在进行同一阶时间复杂度的排序算法性能对比就要将其纳入考虑当中

  • 比较次数和交换(或移动)次数

    基于比较的排序算法的执行过程,会涉及两种操作,一种是元素比较大小,另一种是元素交换或移动。所以,如果我们在分析排序算法的执行效率的时候,应该把比较次数和交换(或移动)次数也考虑进去

内存消耗

  • 原地排序(Sorted in place):空间复杂度为O(1)
  • 冒泡排序、插入排序和选择排序都是原地排序算法

稳定性

  • 稳定性:如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变

  • 稳定排序算法:可以保持值相同的两个对象,在排序之后的前后顺序不变

  • 不稳定排序算法:前后顺序发生变化

在真正软件开发中,我们要排序的往往不是单纯的整数,而是一组对象,我们需要按照对象的某个 key 来排序,一个稳定排序算法可以非常简洁地解决此类问题。

有序度和逆序度

  • 有序度:是数组中具有有序关系的元素对的个数
  • 逆序度:是数组中具有无序关系的元素对的个数
  • 满有序度:完全有序的数组的有序度

满有序度的计算方法: n ∗ ( n − 1 ) / 2 n*(n-1)/2 n(n1)/2

三者之间的关系:逆序度 = 满有序度 - 有序度

冒泡排序(Bubble Sort)

基本思想

  • 冒泡排序只会操作相邻的两个数据
  • 每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。
  • 一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。

使用Python代码实现如下:

def bubble_sort(nums):
    """
    使用冒泡排序对列表进行排序

    参数:
        nums (list): 待排序列表

    返回值:
        list: 排序后列表
    """
	nums_length = len(nums)
    # 检查列表是否为空
    if not nums:
        return []
	
    # 外围循环控制次数
    for i in range(len(nums)):
        flag = True       

        # 两两比较交换位置
        for j in range(nums_length-i-1):            
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
                flag = False
        
        # 如果没有可排序的值,表示排序已完成
        if flag:
            break

    # 返回已完成排序后的列表
    return nums

冒泡排序算法评价

  • 执行效率:最好情况时间复杂度为 O ( n ) O(n) O(n),最坏情况时间复杂度为 O ( n 2 ) O(n^2) O(n2),平均情况时间复杂度为 O ( n 2 ) O(n^2) O(n2)

  • 内存消耗:是一个原地排序算法,空间复杂度为 O ( 1 ) O(1) O(1)

  • 稳定性:是一个稳定的排序算法

插入排序(Insertion Sort)

基本思想

  • 将数组中的数据分为两个区间,已排序区间和未排序区间
  • 初始已排序区间只有一个元素,就是数组的第一个元素。
  • 插入算法的核心思想是**取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。**重复这个过程,直到未排序区间中元素为空,算法结束。

使用Python代码实现如下:

def insertion_sort(nums):
    '''
    使用插入排序算法对列表进行排序
    参数:
        nums(list): 待排序列表

    返回值:
        nums(list): 已完成排序的列表
    '''
    nums_length = len(nums)  
    if not nums:  
        return []
    
	# 从第二个元素开始,依次插入到前面已排序的子列表中
    for i in range(1, nums_length):  
        # 当前要插入的元素
        val = nums[i]  
        # 已排序子列表的最后一个元素的下标
        j = i - 1  
		# 在已排序子列表中找到合适的插入位置
        while j >= 0 and nums[j] > val:  
            nums[j + 1] = nums[j]  # 元素后移
            j -= 1
		# 将当前元素插入到正确的位置
        nums[j + 1] = val  

    return nums       

插入排序算法评价

  • 执行效率:最好情况时间复杂度为 O ( n ) O(n) O(n),最坏情况时间复杂度为 O ( n 2 ) O(n^2) O(n2),平均情况时间复杂度为 O ( n 2 ) O(n^2) O(n2)

  • 内存消耗:是一个原地排序算法,空间复杂度为 O ( 1 ) O(1) O(1)

  • 稳定性:是一个稳定的排序算法

选择排序(Selection Sort)

基本思想

  • 选择排序算法也分已排序区间和未排序区间。
  • 但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾

使用Python代码实现:


def selection_sort(nums):
    '''
    使用选择排序算法对列表进行排序
    参数:
        nums(list): 待排序列表

    返回值:
        nums(list): 已完成排序的列表
    '''
    nums_length = len(nums)  
    if not nums:  
        return []
    
    for i in range(nums_length):
        # 最小值的索引
        min_index = i
        for j in range(i + 1, nums_length):
            # 找到更小的值
            if nums[j] < nums[min_index]:
				min_index = j
        # 将最小值与当前值进行交换
       	nums[j], nums[min_index] = nums[min_index], nums[j]
    return nums

选择排序算法评价

  • 执行效率:最好情况时间复杂度为 O ( n 2 ) O(n^2) O(n2),最坏情况时间复杂度为 O ( n 2 ) O(n^2) O(n2),平均情况时间复杂度为 O ( n 2 ) O(n^2) O(n2)

  • 内存消耗:是一个原地排序算法,空间复杂度为 O ( 1 ) O(1) O(1)

  • 稳定性:是一个不稳定的排序算法

参考文献

  • 王争. 排序(上):为什么插入排序比冒泡排序更受欢迎?极客时间. 2018

你可能感兴趣的:(排序算法,算法,数据结构,排序分析)