算法总结:排序

1、选择排序

# 1、选择排序
def selectSort(L):
    for i in range(len(L)):
        minIndex = i
        for j in range(i, len(L)): # 选出剩下序列中最小的数
            if(L[j]<L[minIndex]):
                minIndex = j
        if(minIndex != i):
            temp = L[i]
            L[i] = L[minIndex]
            L[minIndex] = temp
    print(L)
selectSort(list) # list为待排序列表

选择排序思路:

从左到右依次选出剩余序列中最小的数放在当前位置

选择排序分析:

1.额外空间消耗: o(1)
2.平均时间复杂度: o(n2)
3.最差时间复杂度: o(n2

选择排序的思路非常简单直观,然而使用了两重循环,时间复杂度很高,并且无论序列情况如何都需要进行同样的遍历过程。

2、冒泡排序

# 2、冒泡排序
def bubbleSort(L):
    last = len(L)-1
    for i in range(len(L)):
        sign = last
        for j in range(last):  # 比较相邻的两个数,把较大的数换到后面
            if(L[j+1]<L[j]):
                temp = L[j]
                L[j] = L[j+1]
                L[j+1] = temp
                last = j  # 将最后一个交换过位置的数字标记为last
        if(sign == last):  # 若这次遍历没有交换位置,则表明数组已经有序
            break
    print(L)
bubbleSort(list)

冒泡排序思路:

和选择排序相似,冒泡排序需要两重循环,每次遍历将一个最大的数放在当前位置,但遍历时冒泡排序只比较相邻两个数来进行交换,若此次遍历没有发生交换则代表已经有序。

冒泡排序分析:

1.额外空间消耗: o(1)
2.最好时间复杂度: o(n)
3.平均时间复杂度: o(n2)
4.最差时间复杂度: o(n2

当数组已经有序时,只需要遍历一次便终止排序,此时时间复杂度为o(n)。

3、快速排序

# 3、快排
def quickSort(L):
    if(len(L) == 0):
        return L
    low = 0
    high = len(L)-1
    partition(L, low, high)
    print(L)
def partition(L, low, high):
    if(low >= high):
        return
    l = low
    h = high
    temp = L[low]  # 将low位置上的数作为比较标准
    while(l<h):
        while(h>l and L[h]>=temp):  # 找到右边起第一个比temp小的数
            h -= 1
        L[l] = L[h]  # 把这个数放到左边位置
        while(h>l and L[l]<=temp):  # 找到左边起第一个比temp大的数
            l += 1
        L[h] = L[l]  # 把这个数放到右边位置
    L[l] = temp  # 将temp放在正确位置上,此时temp左边的数小于等于temp,右边的数大于等于temp
    partition(L, low, l-1)
    partition(L, l+1, high)
quickSort(list)  # list为待排序列表
# 非递归
def quickSort(L):
    s = [(0, len(L)-1)]  # 用栈来代替递归过程
    while(len(s)>0):
        w = s.pop(-1)
        low = w[0]
        high = w[1]
        index = patition(L, low, high)
        if(index>low):
            s.append((low, index-1))
        if(index<high):
            s.append((index+1, high))
    print(L)
def patition(L, low, high):
    temp = L[low]
    while(low < high):
        while(low<high and L[high]>=temp):
            high -= 1
        L[low] = L[high]
        while(low<high and L[low]<=temp):
            low += 1
        L[high] = L[low]
    L[low] = temp
    return low
quickSort(list)

快排思路:

先在数组中选择一个数字,然后将数组中所有数字语气进行比较,比它小的放左边,比它大的放右边,这样就能找到该数字在排序中的正确位置。接下来用递归思路对每次选中数字的左右两边排序。

快排分析:

1.额外空间消耗: o(nlog2n)
2.平均时间复杂度: o(nlog2n)
3.最差时间复杂度: o(n2

什么情况下会导致最差的时间复杂度呢,当数组已经是排好序,并且每一轮排序的时候都以第一或者最后一个数字作为比较标准,此时时间复杂度为o(n2),即每一次遍历后将该数字放回原处,并继续遍历剩下的序列,遍历次数为n次。也可以将快速排序看作一棵二叉树,它的深度最大是n。因为遍历一次的时间复杂度为o(n),则整个排序过程的最差时间复杂度为o(n2)。

4、插入排序

# 4、插入排序
def insertSort(L):
    for i in range(1, len(L)):
        temp = L[i]
        pre = i-1
        while(pre>=0 and L[i]<L[pre]):  # 找到当前数字在已排序部分的正确位置
            L[pre+1] = L[pre]  # 将大于该数的元素右移
            pre -= 1
        L[pre+1] = temp
insertSort(list)

插入排序思路:

从第二个数开始,先把当前数字标记,将这个数与前面的所有数进行比较,比它大的往后移,直到找到正确位置放置该数字。

插入排序分析:

1.额外空间消耗: o(1)
2.最好时间复杂度: o(n)
3.平均时间复杂度: o(n2)
4.最差时间复杂度: o(n2

若数组为有序的,则不需要进行移位就能完成排序,时间复杂度为o(n)。

选择排序和冒泡排序是基于位置的遍历,每次选择一个正确的数字放在该位置。
快速排序和插入排序是基于元素的遍历,每次选择一个正确的位置来放置该元素。

5、希尔排序

# 希尔排序
def shellSort(L):
    gap = len(L)//2
    while(gap>0):
        for i in range(len(L)):
            pre = i - gap
            temp = L[i]
            while(pre>=0 and L[pre]>temp):
                L[pre+gap] = L[pre]
                pre = pre - gap
            L[pre+gap] = temp
        gap = gap//2
    print(L)
shellSort(list)

希尔排序思路:

希尔排序的主要思想是基于插入排序的,都是在有序的序列中找到当前数字的正确位置,但希尔排序的序列不是简单地从左往右产生的,而是间隔一定距离的跳跃式的序列,每一次间隔为上次的一半。

归并排序分析:

1.额外空间消耗: o(1)
2.最好时间复杂度: o(n)
3.平均时间复杂度: o(n1.3)
4.最差时间复杂度: o(n2

希尔排序的平均时间复杂度比插入排序低。

6、归并排序

# 6、归并排序
def mergeSort(L):
    if (len(L) < 2):
        return L
    medium = int(len(L)/2)
    left = L[:medium]
    right = L[medium:]
    return merge(mergeSort(left), mergeSort(right))

def merge(left, right):
    i = 0
    j = 0
    result = []
    for index in range(len(left) + len(right)):
        print(result)
        if(i>=len(left)):  # 若左边已排序完成,则将右边直接append到result中
            result.append(right[j])
            j += 1
        elif(j>=len(right)):  # 若右边已排序完成,则将左边直接append到result中
            result.append(left[i])
            i += 1
        elif(left[i]<=right[j]):  # 选择左右当前数字中较小的append到result中
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    return result
mergeSort(list)

归并排序思路:

归并排序的主要思路是将两个排序的数组进行重新排序合并成一个,实现过程是通过将待排序数组二分递归,使每个步骤中的左右两边的数组排序,最后实现整个数组有序。

归并排序分析:

1.额外空间消耗: o(n)
2.平均时间复杂度: o(nlog2n)
3.最差时间复杂度: o(nlog2n)

归并排序平均时间复杂度和快排一样,但不存在快排中对已排序数组排序时时间复杂度为o(n2)的情况。

7、堆排序

# 7、堆排序
def swap(L, i, j):
    temp = L[i]
    L[i] = L[j]
    L[j] = temp
def heap_adjust(L, i, size):
    left = i*2
    right = i*2+1
    m = i
    if(left<size and L[left]>L[m]):
        m = left
    if(right<size and L[right]>L[m]):
        m = right
    if(m != i):
        swap(L, i, m)
        heap_adjust(L, m, size)
# 从堆的倒数第二行往上遍历进行最大堆调整
def heap_build(L):
    n = len(L)
    for i in range(n//2-1, -1, -1):
        heap_adjust(L, i, n)
def heap_sort(L):
    heap_build(L)
    for i in range(len(L)):
        swap(L, 0, len(L)-i-1)  # 将堆顶的最大的数放在数组最后
        heap_adjust(L, 0, len(L)-i-1)  # 堆调整,将n-1个数中的最大数放在堆顶
heap_sort(list)

堆排序思路:

通过建最大堆来逐个找到最大的数,最大堆是指每个结点的数值都大于其左右孩子。

堆排序分析:

1.额外空间消耗: o(1)
2.平均时间复杂度: o(nlog2n)
3.最差时间复杂度: o(nlog2n)

初始化建堆过程时间:o(n)。推算过程循环 n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 o(log2n)。总时间为o(nlog2n)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值