十大经典排序算法演示与实现

161 阅读3分钟

image.png 从图中可以很清晰地看到排序法分为比较类和非比较类两大类,其中比较类中又包括:交换类、插入类、选择类、归并类。

开 始 学 习

1、冒泡排序

冒泡排序就是一种通过重复比较和交换相邻两元素的排序法,到最后没有元素需要交换,则代表排序完成,这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端,也被称作起泡排序。算法描述:

  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾最后一对,这样在最后的元素应该会是最大的数
  3. 针对所有的元素重复以上的步骤,除了最后一个
  4. 重复步骤1~3,直到排序完成
    for i in range(len(data) - 1):   # 外循环每一次使得有序的数增加一个
        indicator = False    # 用于优化(没有交换时表示已经有序,结束循环)
        for j in range(len(data) - 1 - i):   #内循环每次将无序部分中的最大值放到最上面
            if data[j] > data[j + 1]:
                data[j], data[j+1] = data[j+1], data[j]
                indicator = True
        if not indicator:   #如果没有交换说明列表已经有序,结束循环
            break

2、快速排序

快速排序利用了冒泡排序的思想基础,首先确定一个基准元素,而后通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。算法描述:

  1. 从数列中确定基准元素
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序
    if first >= last:
        return
    mid_value = list_[first]  #定义基准
    low = first
    high = last

    while low < high:
        while low < high and list_[high] >= mid_value:
            high -= 1
        list_[low] = list_[high]
        while low < high and list_[low] < mid_value:
            low += 1
        list_[high] = list_[low]
    # print(low, high)
    list_[low] = mid_value

    fast_sort(list_, first, low - 1)  #递归左子树
    fast_sort(list_, low + 1, last)    #递归右子树

3、插入排序

插入排序的思想是每次将一个待排序的记录插入到已经排好序的数据区中,直到全部插入完为止。算法描述:

  1. 从第一个元素开始,认为可以被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5
  length = len(list_)
  for i in range(length-1):
    key = list_[i+1]
    j = i 
    while list_[j] > key and j >= 0:
      list_[j+1] = list_[j]
      j -= 1
    list_[j+1] = key
  return list_

4、希尔排序

希尔排序的思想是先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:

  1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列个数k,对序列进行k 趟排序;
  3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
    n = len(list_)  # 初始步长
    gap = n // 2  # gap变换到0之前,插入算法执行的次数 
    while gap >= 1:
        # 插入算法,与普通的插入算法的区别就是gap步长
        # 按步长进行插入排序
        for j in range(gap, n):  # j = [gap,gap+1,gap+2,...,n-1] 
            i = j
            while i >= gap:
                if list_[i] < list_[i-gap]:
                    list_[i], list_[i-gap] = list_[i-gap], list_[i]
                    i -= gap
                else:
                    break       
        gap //= 2  # 缩短gap步长

5、选择排序

选择排序的思想是首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。算法描述:

  1. 初始状态:无序区为R[1…n],有序区为空;
  2. 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
  3. n-1趟结束,数组有序化了。
    for i in range(len(list_)-1):
        min_index = i
        for j in range(i+1, len(list_)):
            if list_[j] < list_[min_index]:
                min_index = j
        if min_index != i:
            list_[i], list_[min_index] = list_[min_index], list_[i]
    return list_

6、堆排序

堆排序的思想是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。算法描述:

  1. 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区
  2. 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n]
  3. 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成
def heap_adjust(L, start, end):
    temp = L[start]
    i = start
    j = 2 * i
    while j <= end:
        if (j < end) and (L[j] < L[j + 1]):
            j += 1
        if temp < L[j]:
            L[i] = L[j]
            i = j
            j = 2 * i
        else:
            break
    L[i] = temp
    
def heap_sort(list_):
    list_length = len(list_) - 1

    first_sort_count = list_length / 2
    for i in range(first_sort_count):
        heap_adjust(list_, first_sort_count - i, list_length)

    for i in range(list_length - 1):
        list_ = swap_param(list_, 1, list_length - i)
        heap_adjust(list_, 1, list_length - i - 1)

    return [list_[i] for i in range(1, len(list_))

7、归并排序

归并排序的思想是将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。算法描述:

  1. 把长度为n的输入序列分成两个长度为n/2的子序列
  2. 对这两个子序列分别采用归并排序
  3. 将两个排序好的子序列合并成一个最终的排序序列
    if(len(list_)<=1):        #递归边界条件
        return list_         #到达边界时返回当前的子数组
    mid = int(len(list_)/2)      #求出数组的中位数
    llist, rlist = MergeSort(list_[:mid]),MergeSort(list_[mid:])#调用函数分别为左右数组排序
    result = []
    i, j = 0, 0
    while i < len(llist) and j < len(rlist): #while循环用于合并两个有序数组
        if rlist[j]<llist[i]:
            result.append(rlist[j])
            j += 1
        else:
            result.append(llist[i])
            i += 1
    result += llist[i:] + rlist[j:]  #把数组未添加的部分加到结果数组末尾
    return result         #返回已排序的数组

8、计数排序

计数排序的思想是其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。算法描述:

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
    min_num = min(s)    # 找到最大最小值
    max_num = max(s)    
    count_list = [0]*(max_num-min_num+1)  # 计数列表    
    for i in s:  # 计数
        count_list[i-min_num] += 1
    s.clear()    
    for ind,i in enumerate(count_list):  # 填回
        while i != 0:
            s.append(ind+min_num)
            i -= 1
(当数值中有非整数时,计数数组的索引无法分配)

9、桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。其思想是假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序。算法描述:

  1. 设置一个定量的数组当作空桶
  2. 遍历输入数据,并且把数据一个一个放到对应的桶里去
  3. 对每个不是空的桶进行排序
  4. 从不是空的桶里把排好序的数据拼接起来

def bucket_sort(list_):
    min_num, max_num = min(list_), max(list_)
    bucket_num = (max_num-min_num)
    buckets = [[] for _ in range(int(bucket_num))]
    for num in list_:
        buckets[int((num-min_num)
    new_list = list()
    for i in buckets:
        for j in sorted(i):
            new_list.append(j)
    return new_list

10、基数排序

基数排序的思想是按照低位先排序,然后收集;再按照高位排序,然后再收集;依此类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。算法描述:

  1. 取得数组中的最大数,并取得位数
  2. arr为原始数组,从最低位开始取每个位组成radix数组
  3. 对radix进行计数排序(利用计数排序适用于小范围数的特点)
def radix_sort(list_):
    max_numlist_
    place = 1
    while max_num >= 10**place:
        place += 1
    for i in range(place):
        buckets = [[] for _ in range(10)]
        for num in list_:
            radix = int(num/(10**i) % 10)
            buckets[radix].append(num)
        j = 0
        for k in range(10):
            for num in buckets[k]:
                list_[j] = num
                j += 1
    return list_

算法复杂度汇总

image.png