数据结构与算法python北大版笔记 - 排序算法

排序

冒泡排序

优势:无需任何额外的存储空间开销。
冒泡排序图示

'''
range(start, stop[, step])
参数说明:
start: 计数从 start 开始。默认是从 0 开始。例如range(5)等价于range(0, 5);
stop: 计数到 stop 结束,但不包括 stop。例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
step:步长,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)
'''
def bubbleSort(alist):
    for passnum in range(len(alist)-1, 0, -1):
        for i in range(passnum):
            if alist[i] > alist[i+1]:
                alist[i], alist[i+1] = alist[i+1], alist[i] # 直接交换

alist = [54, 32, 12, 67, 20]
bubbleSort(alist)
print(alist)

比对次数:O(n^2)
交换次数:O(n^2)

冒泡排序的性能改进:
通过监测每趟比对是否发生过交换,可以提前确定排序是否完成。如果某趟比对没有发生任何交换,说明列表已经排好序,可以提前结束算法。

def shortbubbleSort(alist):
    exchanges = True
    passnum = len(alist) - 1
    while passnum > 0 and exchanges:
        exchanges = False
        for i in range(passnum):
            if alist[i] > alist[i+1]:
                exchanges = True
                alist[i], alist[i+1] = alist[i+1], alist[i] # 直接交换

alist = [54, 32, 12, 67, 20]
shortbubbleSort(alist)
print(alist)

选择排序

选择排序的时间复杂度比冒泡排序稍优

比对次数不变,还是O(n^2)
交换次数则减少为O(n)

选择排序图示

def selectionSort(alist):
    for fillslot in range(len(alist) - 1, 0, -1):
        positionOfMax = 0
        for location in range(1, fillslot + 1):
            if alist[location] > alist[positionOfMax]:
                positionOfMax = location
        temp = alist[fillslot]
        alist[fillslot] = alist[positionOfMax]
        alist[positionOfMax] = temp

alist = [54, 32, 12, 67, 20]
selectionSort(alist)
print(alist)

插入排序

第1趟,子列表仅包含第1个数据项,将第2个数据项作为“新项”插入到子列表的合适位置中,这样已排序的子列表就包含了2个数据项。
第2趟,再继续将第3个数据项跟前2个数据项比对,并移动比自身大的数据项,空出位置来,以便加入到子列表中。
经过n-1趟比对和插入,子列表扩展到全表,排序完成。

插入排序图示
复杂度:

最差情况是每趟都与子列表中所有项进行比对,总比对次数与冒泡排序相同,数量级仍是O(n^2 )
最好情况,列表已经排好序的时候,每趟仅需1次比对,总次数是O(n)

def insertionSort(alist):
    for index in range(1, len(alist)):
        currentvalue = alist[index]  # 插入项
        position = index
        while position > 0 and alist[position - 1] > currentvalue:
            alist[position] = alist[position - 1]  # 对比移动
            position -= 1
        alist[position] = currentvalue  # 插入新项


alist = [54, 32, 12, 67, 20]
insertionSort(alist)
print(alist)

谢尔排序

谢尔排序以插入排序作为基础,对无序表进行“间隔”划分子列表,每个子列表都执行插入排序。
思路:子列表的间隔一般从n/2开始,每趟倍增:n/4, n/8 ……直到1

谢尔排序图示
复杂度:

对谢尔排序的详尽分析比较复杂,大致说是介于O(n) 和O(n^2)
如果将间隔保持在2k -1(1、3、5、7、15、31等等),谢尔排序的时间复杂度约为O(n^(3/2))

def shellSort(alist):
    sublistcount = len(alist) // 2  # 间隔设定
    while sublistcount > 0:
        for startposition in range(sublistcount):  # 子列表排序
            gapInsertionSort(alist,startposition,sublistcount)
        print("After increments of size", sublistcount, "The list is", alist)
        sublistcount = sublistcount // 2  # 间隔缩小


def gapInsertionSort(alist,start,gap):  # 带间隔的插入排序
    for i in range(start + gap, len(alist), gap):
        currentvalue = alist[i]
        position = i
        while position >= gap and alist[position - gap] > currentvalue:
            alist[position] = alist[position - gap]
            position = position - gap
        alist[position] = currentvalue


alist = [54, 32, 12, 67, 20]
shellSort(alist)
print(alist)

运行结果:
运行结果

归并排序

归并排序是递归算法,思路是将数据表持续分裂为两半,对两半分别进行归并排序。

归并排序图示
代码1:

def mergeSort(alist):
    if len(alist) > 1:  # 基本结束条件
        mid = len(alist) // 2
        lefthalf = alist[:mid]
        righthalf = alist[mid:]

        mergeSort(lefthalf)  # 递归调用
        mergeSort(righthalf)

        i = j = k = 0
        while i < len(lefthalf) and j < len(righthalf):  # 拉链式交错吧左右半部从小到大归并到结果列表中
            if lefthalf[i] < righthalf[j]:
                alist[k] = lefthalf[i]
                i = i + 1
            else:
                alist[k] = righthalf[j]
                j = j + 1
            k = k + 1

        while i < len(lefthalf):  # 归并左半部分剩余项
            alist[k] = lefthalf[i]
            i = i + 1
            k = k + 1

        while j < len(righthalf):  # 归并右半部分剩余项
            alist[k] = righthalf[j]
            j = j + 1
            k = k + 1

alist = [54, 32, 12, 67, 20]
mergeSort(alist)
print(alist)

代码2(可读性强):

def merge_sort(lst):
    if len(lst) <= 1:  # 递归结束条件
        return lst

    middle = len(lst) // 2
    left = merge_sort(lst[:middle])  # 递归排序
    right = merge_sort(lst[middle:])

    merged = []
    while left and right:
        if left[0] <= right[0]:
            merged.append(left.pop(0))
        else:
            merged.append(right.pop(0))

    merged.extend(right if right else left)
    return merged


alist = [54, 32, 12, 67, 20]
print(merge_sort(alist))

复杂度:

将归并排序分为两个过程来分析:分裂和归并。分裂的过程,借鉴二分查找中的分析结果,是对数复杂度,时间复杂度为O(log n)。归并的过程,相对于分裂的每个部分,其所有数据项都会被比较和放置一次,所以是线性复杂度,其时间复杂度是O(n)。
综合考虑,每次分裂的部分都进行一次O(n) 的数据项归并,总的时间复杂度是O(nlogn)。

注意:归并排序算法使用了额外1倍的存储空间用于归并,这个特性在对特大数据集进行排序的时候要考虑进去

快速排序

快速排序的递归算法“递归三要素”如下:

  • 基本结束条件:数据表仅有1个数据项,
    自然是排好序的
  • 缩小规模:根据“中值”,将数据表分为
    两半,最好情况是相等规模的两半
  • 调用自身:将两半分别调用自身进行排序
    (排序基本操作在分裂过程中)

算法思想:

分裂数据表的目标:找到“中值”的位置
分裂数据表的手段:

  1. 设置左右标(left/rightmark )

  2. 左标向右移动,右标向左移动

    左标一直向右移动,碰到比中值大的就停止
    右标一直向左移动,碰到比中值小的就停止
    然后把左右标所指的数据项交换

  3. 继续移动,直到左标移到右标的右侧,停止移动

  4. 这时右标所指位置 “ 中值 ” 应处的位置

  5. 将中值和这个位置交换

  6. 分裂完成 ,左半部比中值小,右半部比中值大

快速排序图示

def quickSort(alist):
    quickSortHelper(alist, 0, len(alist) - 1)


def quickSortHelper(alist, first, last):
    if first < last:  # 基本结束条件
        splitpoint = partition(alist, first, last)  # 分裂
        quickSortHelper(alist, first, splitpoint - 1)
        quickSortHelper(alist, splitpoint + 1, last)


def partition(alist, first, last):
    pivotvalue = alist[first]  # 选定中值

    leftmark = first + 1  # 左右标初值
    rightmark = last

    done = False
    while not done:

        while leftmark <= rightmark and alist[leftmark] <= pivotvalue:  # 向右移动左标
            leftmark += 1

        while alist[rightmark] >= pivotvalue and rightmark >= leftmark:  # 向左移动右标
            rightmark -= 1

        if rightmark < leftmark:  # 两标相错就结束移动
            done = True
        else:  # 左右标的值交换
            temp = alist[leftmark]
            alist[leftmark] = alist[rightmark]
            alist[rightmark] = temp

    temp = alist[first]  # 中值就位
    alist[first] = alist[rightmark]
    alist[rightmark] = temp

    return rightmark  # 中值点也是分裂点


alist = [54, 32, 12, 67, 20]
quickSort(alist)
print(alist)

复杂度:

快速排序过程分为两部分:分裂和移动
如果分裂总能把数据表分为相等的两部分,那么就是O(log n) 的复杂度;
而移动需要将每项都与中值进行比对,还是O(n)
综合起来就是O(nlog n)

算法运行过程中不需要额外的存储空间。
极端情况下,时间复杂度退化为O(n^2)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值