数据结构——八大排序算法(python实现)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wzyaiwl/article/details/89457337

排序的方法很多,但就全面性性能而言,很难提出一种被认为是最好的方法。每一种方法都有自己的优缺点,适合在不同的环境下使用。目前评价排序算法的好坏的标准主要有两点,一个是执行时间,另一个是辅助空间。

下面讲解下内部排序的八大算法,它们分别是插入排序的“直接插入排序”、“希尔排序”;交换排序的“冒泡排序”、“快速排序”;选择排序的“简单选择排序”、“堆排序”;“归并排序”;“基数排序”。

插入排序

直接插入排序

直接插入排序是一种最简单的排序算法。其基本操作是将一条记录插入到以排好序的有序表中,从而得到一个新的有序表。

  • 算法流程
  1. for循环第一层遍历除第一个元素之外的所有元素
  2. for循环第二层将选择的元素依次跟排序好的i个元素做比较,如果比它们小,两者就替换。
#直接插入排序
def insert(a):
    for i in range(1,len(a)):
        for j in range(i-1,-1,-1):
            if a[j+1] < a[j]:
                temp = a[j]
                a[j] = a[j+1]
                a[j+1] = temp
            else:
                break
    return a
a = [1,5,8,7,6,4,5,8,9,11]
insert(a)
print(a)
[1, 4, 5, 5, 6, 7, 8, 8, 9, 11]

时间复杂度O(n^2),空间复杂度O(1)。

希尔排序

希尔排序又称“缩小增量排序”,是插入排序的一种。其从“减少记录个数”和“序列基本有序”两个方面对直接插入排序进行了改进。

  • 算法流程
  1. 先对列表元素进行分组,分组步长grap等于列表长度的一半。
  2. 对分组的元素进行直接插入排序
  3. 重复步骤1、2,只是步长grap不断减半,直接grap<1,就迭代中止。
#希尔排序
def insert_shell(a):
    grap = len(a)//2
    while(grap >= 1):
        for i in range(grap):  #第一层循环:依次改变gap值对列表进行分组
            for j in range(i,len(a)-grap,grap):
                if a[j] > a[j+grap]:
                    temp = a[j]
                    a[j] = a[j + grap]
                    a[j + grap] = temp
        grap = grap//2  #grap减半
    return a
a = [1,5,8,7,6,4,5,8,9,11]
insert_shell(a)
print(a)
[1, 5, 4, 5, 6, 7, 8, 8, 9, 11]

时间复杂度O(n^(3/2)),空间复杂度O(1)。

选择排序

简单选择排序

简单选择排序又叫做直接选择排序。

  • 算法流程
  1. 从待排序序列中,找到关键字最小的元素;
  2. 如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;
  3. 从余下的 N - 1 个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。
#第一层循环:依次遍历序列当中的每一个元素
#第二层循环:将遍历得到的当前元素依次与余下的元素进行比较,符合最小元素的条件,则交换。
def select_simple(a):
    for i in range(0,len(a)):
        minnum = a[i]
        for j in range(i+1,len(a)):
            if a[j] < minnum:
                temp = a[j]
                a[j] = minnum
                minnum = temp
        a[i] = minnum
    return a
a = [1,5,8,7,6,4,5,8,9,11]
select_simple(a)
print(a)
[1, 4, 5, 5, 6, 7, 8, 8, 9, 11]

时间复杂度O(n^2),空间复杂度O(1)。

堆排序

堆排序是一种树形选择排序,在排序过程中,将列表看出是一棵完全二叉树的顺序存储结构。

  • 算法流程

1、将列表初始化成大顶堆

2、将根节点的值与最后的叶子结点值交换,并将该叶子结点在之后的计算中屏蔽掉。

3、将交换值后的列表再次调整为大顶堆。

4、重复步骤2、3,直至元素全部被屏蔽掉。

#**********获取左右叶子节点**********
def LEFT(i):
    return 2*i + 1
def RIGHT(i):
    return 2*i + 2
#********** 调整大顶堆 **********
#L:待调整序列 length: 序列长度 i:需要调整的结点
def adjust_max_heap(L,length,i):
#定义一个int值保存当前序列最大值的下标
    largest = i
#执行循环操作:两个任务:1 寻找最大值的下标;2.最大值与父节点交换
    while (1):
#获得序列左右叶子节点的下标
        left,right = LEFT(i),RIGHT(i)
#当左叶子节点的下标小于序列长度 并且 左叶子节点的值大于父节点时,将左叶子节点的下标赋值给largest
        if (left < length) and (L[left] > L[i]):
            largest = left
            # print('左叶子节点')
        else:
            largest = i
#当右叶子节点的下标小于序列长度 并且 右叶子节点的值大于父节点时,将右叶子节点的下标值赋值给largest
        if (right < length) and (L[right] > L[largest]):
            largest = right
            # print('右叶子节点')
#如果largest不等于i 说明当前的父节点不是最大值,需要交换值
        if (largest != i):
            temp = L[i]
            L[i] = L[largest]
            L[largest] = temp
            i = largest
            # print(largest)
            continue
        else:
            break
#********** 初始化大顶堆 **********
def build_max_heap(L):
    length = len(L)
    for x in range((int)((length-1)/2),-1,-1):
        adjust_max_heap(L,length,x)
#********** 堆排序 **********
def heap_sort(L):
#先建立大顶堆,保证最大值位于根节点;并且父节点的值大于叶子结点
    build_max_heap(L)
#i:当前堆中序列的长度.初始化为序列的长度
    i = len(L)
#执行循环:1. 每次取出堆顶元素置于序列的最后(len-1,len-2,len-3...)
#         2. 调整堆,使其继续满足大顶堆的性质,注意实时修改堆中序列的长度
    while (i > 0):
        temp = L[i-1]
        L[i-1] = L[0]
        L[0] = temp
#堆中序列长度减1
        i = i-1
#调整大顶堆
        adjust_max_heap(L,i,0)
a = [1,5,8,7,6,4,5,8,9,11]
heap_sort(a)
print(a)
[1, 4, 5, 5, 6, 7, 8, 8, 9, 11]

时间复杂度O(nlog2n),空间复杂度O(1)。

交换排序

冒泡排序

冒泡排序是一种最简单的交换排序算法,通过两两比较相邻记录的关键字,如果发生逆序,就交换位置,从而使关键字大的记录如石块一样逐渐“坠落”。

  • 算法流程
  1. 第一层循环遍历所有元素,循环(length-1)次,每循环一次,内层遍历的次数就会少一次。因为第一轮结束后,序列最后一个元素一定是当前序列的最大值。
  2. 第二层循环依次比较相邻的两元素,如果逆序就交换位置。
#冒泡排序
def bubble_sort(a):
    length = len(a)
    for i in range(1,length):
        for j in range(0,length-i):
            if a[j] > a[j+1]:
                temp = a[j]
                a[j] = a[j+1]
                a[j+1] = temp
    return a
a = [1,5,8,7,6,4,5,8,9,11]
bubble_sort(a)
print(a)
[1, 4, 5, 5, 6, 7, 8, 8, 9, 11]

时间复杂度O(n^2),空间复杂度O(1)。

快速排序

快速排序是由冒泡排序改进而来,在冒泡排序中,只对相邻的两个记录进行比较,因此每次交换两个记录只能消除一个逆序。如果能通过两个不相邻记录的一次交换,消除多个排序,则会大大加快排序的速度。快速排序方法中的一次叫交换可以消除多个逆序。

  • 算法流程:挖坑填数+分治法
  1. 在待排序列表中选择一个为pivotkey,这里选择第一个值,并将第一个元素的位置挖出来。
  2. 从右往左寻找到第一个值小于pivotkey的,将这个值的位置挖出来并将这个值填入第一个元素挖出的坑中。
  3. 再从左往右找第一个大于pivotkey的值,将这个值位置挖出,并将这个值填入上一个坑中。
  4. 重复步骤2、3(每一次从左往右或是从右往左,都是基于上一个被挖坑的位置开始),直至两个方向挖的坑在同一个位置就迭代中止,即low=high。
def quick_sort(a,start,end):
    if start < end:
        low,high,pivotkey = start,end,a[start]
        while(low<high):
            #从右往左找到第一个小于pivotkey的值
            while(low<high):
                if (a[high] < pivotkey):
                    break
                high -= 1
            #将找到的值填入a[low]中
            if (low <high):
                a[low] = a[high]
                low += 1
            #从左往右找到第一个大于pivotkey的值
            while(low<high):
                if(a[low] > pivotkey):
                    break
                low += 1
            #将这个值赋给上一个留下的坑a[high]
            if (low<high):
                a[high] = a[low]
                high -= 1
        a[low] = pivotkey
        quick_sort(a,start,low-1)
        quick_sort(a,low+1,end)
a = [1,5,8,7,6,4,5,8,9,11]
quick_sort(a,0,len(a)-1)
print(a)
[1, 4, 5, 5, 6, 7, 8, 8, 9, 11]

时间复杂度O(nlog2n),空间复杂度最好是O(log2n),最坏是O(n)。

归并排序

归并排序就是将两个或两个以上的有序表合并成一个有序表。将两个有序表合并的过程叫2-路归并,其最为简单和常用,下面以这个介绍。

  • 算法流程
  1. 归并排序要做的就是两件事:一个是分解,另一个是合并。搜先我们函数递归将列表分解成length个子序列。
  2. 然后递归两两进行合并,合并时是将两个子序列的第一个元素拿出来比较,小的那个放在合并后的第一个位置,然后重复这种取值比较,直至有一个子序列的元素全部取完,这时就把另一个子序列表的元素依次全部添加到合并后的序列中。
# 这是分组的函数
def merge_sort(L,first,last,temp):
    if first < last:
        mid = (int)((first + last) / 2)
#使左边序列有序
        merge_sort(L,first,mid,temp)
#使右边序列有序
        merge_sort(L,mid+1,last,temp)
#将两个有序序列合并
        mergearray(L,first,mid,last,temp)
#这是合并的函数
# 将序列L[first...mid]与序列L[mid+1...last]进行合并
def mergearray(L,first,mid,last,temp):
#对i,j,k分别进行赋值
    i,j,k = first,mid+1,0
#当左右两边都有数时进行比较,取较小的数
    while (i <= mid) and (j <= last):
        if L[i] <= L[j]:
            temp[k] = L[i]
            i = i+1
            k = k+1
        else:
            temp[k] = L[j]
            j = j+1
            k = k+1
#如果左边序列还有数
    while (i <= mid):
        temp[k] = L[i]
        i = i+1
        k = k+1
#如果右边序列还有数
    while (j <= last):
        temp[k] = L[j]
        j = j+1
        k = k+1
#将temp当中该段有序元素赋值给L待排序列使之部分有序
    for x in range(0,k):
        L[first+x] = temp[x]
# 归并排序的函数
def merge_sort_array(L):
#声明一个长度为len(L)的空列表
    temp = len(L)*[None]
#调用归并排序
    merge_sort(L,0,len(L)-1,temp)
a = [1,5,8,7,6,4,5,8,9,11]
merge_sort_array(a)
print(a)
[1, 4, 5, 5, 6, 7, 8, 8, 9, 11]

时间复杂度O(nlog2n),空间复杂度是O(n)。

基数排序

前述的各种排序算法都是建立在关键字比较的基础上,而分配类排序不需要比较关键字的大小,它是根据关键字中各位的值,通过对待排序记录进行若干趟“分配”和“收集”来实现排序的,是一种借助于多关键字排序的思想。

  • 算法流程
  1. 从低位到高位依次循环
  2. 用count记录出现第pos位数字的个数,如a中有1和11,故各位的1出现了两次,即count[1] = 2。
  3. 通过遍历count[i] = count[i] + count[i-1],得到出现i的最后一个值在原列表的位置。
  4. 遍历原列表所有元素,根据其位置的数值0~9,依次放入桶内。
  5. 将桶内的值赋值给原列表,重复2、3、4、5,直到最大值的位数都遍历了一遍。
#排序的顺序跟序列中最大数的位数相关
def radix_sort_nums(L):
    maxnum = L[0]
    for i in L:
        if maxnum < i:
            maxnum = i
    time = 0
    while(maxnum > 0):
        maxnum //= 10
        time += 1
    return time
#确定一个数第几位的数字
def get_num_pos(num,pos):
    return (num//(10**(pos-1))%10)
#基数排序
def radix_sort(L):
    count = 10 * [None]  # 存放各个桶的数据统计个数,0~9
    bucket = len(L) * [None]  # 暂时存放排序结果
    # 从低位到高位依次执行循环
    for pos in range(1, radix_sort_nums(L) + 1):
        # 置空各个桶的数据统计
        for x in range(0, 10):
            count[x] = 0
        # 统计当前该位(个位,十位,百位....)的元素数目
        for x in range(0, len(L)):
            # 统计各个桶将要装进去的元素个数
            j = get_num_pos(int(L[x]), pos)
            count[j] = count[j] + 1
        #统计第i个桶最后一个数在L中的位置
        for x in range(1, 10):
            count[x] = count[x] + count[x - 1]
        for x in range(len(L) - 1, -1, -1):
            # 求出元素第K位的数字
            j = get_num_pos(L[x], pos)
            # 放入对应的桶中,count[j]-1是第j个桶的右边界索引
            bucket[count[j] - 1] = L[x]
            # 对应桶的装入数据索引-1
            count[j] = count[j] - 1
            # 将已分配好的桶中数据再倒出来,此时已是对应当前位数有序的表
        for x in range(0, len(L)):
            L[x] = bucket[x]
a = [1,5,8,7,6,4,5,8,9,11]
radix_sort(a)
print(a)
[1, 4, 5, 5, 6, 7, 8, 8, 9, 11]

性能比较

  • 下面生成1万个数值,查看其时间性能。
import random
import time
x = time.time()
a = [random.randint(0,9999) for i in range(10000)]
insert(a)
y = time.time()
print('直接插入花费时间:{}秒'.format(y-x))
直接插入花费时间:12.834958791732788秒
希尔排序:0.09348869323730469
简单选择排序:6.617225408554077
堆排序:0.09376239776611328
冒泡排序:16.446390390396118
快速排序:0.06253242492675781

猜你喜欢

转载自blog.csdn.net/wzyaiwl/article/details/89457337