Python3 数据结构--排序算法

本文详细介绍了多种排序算法,包括直接插入排序、折半插入排序、希尔排序、冒泡排序、快速排序、简单选择排序和堆排序。讲解了它们的空间复杂度、时间复杂度、稳定性以及适用场景。其中,直接插入和折半插入排序适用于小数据量,希尔排序通过增量排序优化性能,冒泡排序每次将一个元素放到最终位置,快速排序平均时间复杂度为o(nlogn),选择排序不稳定,而堆排序通过建堆和调整实现高效排序。
# 7.1.2 直接插入排序
def InsertSort(nums):
    """
    空间复杂度:o(1)
    时间复杂度:o(n^2)。折半插入排序的时间复杂度也是o(n^2),但对于数据量不大时性能比较好。
    稳定性:由于每次是先比较再移动,所以不会产生相同元素相对位置变化的情况,所以是稳定的。
    适用性:直接插入排序适用于顺序存储和链式存储的线性表。当为链式存储时,从后往前查找比较移动。(大部分排序仅适用于顺序存储)
    """

    # 使用哨兵,索引0处不存待排序值
    for i in range(2, len(nums)):   # 0处不存值,假设1有序
        if nums[i] < nums[i - 1]:
            nums[0] = nums[i]
            for j in range(i - 1, -1, -1):
                if nums[j] > nums[0]:
                    nums[j + 1] = nums[j]
                else:
                    break
            nums[j + 1] = nums[0]
    return nums


# 7.2.2 折半插入排序
def InsertSort02(nums):
    """
    直接插入排序在是边查找边移动,在顺序存储下,通过将查找和移动分开来进行优化——通过折半查找加速查找。
    依然有"哨兵"
    空间复杂度:o(1)
    时间复杂度:o(n^2)    相比直接插入排序,仅仅减少了比较次数,约为o(nlogn),与初始状态无关。移动次数没变,且依赖于初始状态。
    稳定性:依然是稳定的。
    适用性:自然不适用于链式存储。
    """
    for i in range(2, len(nums)):   # 0为哨兵
        if nums[i] < nums[i - 1]:
            nums[0] = nums[i]
            low, high = 1, i - 1    # 折半查找的范围
            while low <= high:
                mid = (low + high) // 2
                if nums[mid] > nums[0]:
                    high = mid - 1
                else:
                    low = mid + 1
            for j in range(i - 1, high, -1):
                nums[j + 1] = nums[j]
            nums[high + 1] = nums[0]
    return nums


# 7.2.3 希尔排序
def ShellSort(nums):
    """
    直接插入排序适用于 基本有序、数据量小 的场景。针对这两点提出希尔排序(缩小增量排序)
    底层仍然是直接插入排序。
    空间复杂度:o(1)
    时间复杂度:o(n^1.3)~o(n^2)   dk的划分涉及数学尚未解决的难题
    稳定性:当相同元素划分到不同子序列时,其相对位置可能发生改变。
    仅适用于顺序存储。
    """
	dk = len(nums) // 2
    while dk >= 1:
        for i in range(dk, len(nums), dk):      # 从索引dk开始遍历,因为步长是dk了
            if nums[i] < nums[i - dk]:
                tmp = nums[i]   # 不使用哨兵了
                ind = 0     # IDE报错j未定义使用,因此使用此变量记录j
                for j in range(i - dk, -1, dk):
                    if nums[j] > tmp:
                        nums[j + dk] = nums[j]
                        ind = j
                    else:
                        ind = j
                        break
                    if j - dk < 0:
                        ind = j
                        break
                nums[ind + dk] = tmp
        dk -= 1
    return nums
# 7.3 交换排序
# 7.3.1 冒泡排序
def BubbleSort(nums):
    """
    一趟冒泡的边界要特别注意,我是把大的值冒到后面。
    空间复杂度:o(1)
    时间复杂度:o(n^2)    初始序列有序,则仅需比较n-1次即最好情况为o(n); 初始序列逆序,此为最坏情况,为o(n^2)
    稳定性:只有nums[j]>nums[j+1]时才往后冒泡,两个相同的值不会发生交换,故稳定。
    不同于插入排序,每一次冒泡都将一个元素放到了最终排序的位置上(说的专业一点,冒泡过程产生的有序子序列是全局有序的)
    """
    for i in range(len(nums) - 1):      # 最多需要n-1次冒泡
        flag = False    # 一次冒泡中,移动了元素即为True,若一次冒泡后仍为False,排序结束
        for j in range(len(nums) - i - 1):      # 一趟冒泡
            if nums[j] > nums[j + 1]:
                nums[j], nums[j + 1] = nums[j + 1], nums[j]
                flag = True
        if not flag:
            return nums
    return nums
# 7.3.2 快速排序
def QuickSort(nums, low, high):
    """
    空间复杂度:最好o(logn), 最坏o(n). 最好的情况基准选择均衡,递归栈深度log(n+1);最坏情况下基准在边界,递归n-1次,栈深度为o(n)
    时间复杂度:最坏o(n^2)
    稳定性:在划分算法中,若右端存在两个小于基准的数,交换后其相对位置改变,故不稳定。
    不产生有序子序列,但每次排序后基准会放在最终排序位置上。
    """
    if low < high:
        k = Paritition(nums, low, high)
        QuickSort(nums, low, k - 1)
        QuickSort(nums, k + 1, high)
    return nums


def Paritition(nums, low, high):
    pivot = nums[low]
    while low < high:
        while low < high and nums[high] >= pivot:
            high -= 1
        nums[low] = nums[high]

        while low < high and nums[low] <= pivot:
            low += 1
        nums[high] = nums[low]
    print("$$$ low, high", low, high)
    nums[low] = pivot   # 换成high行吗——事实证明,可以
    return low


# 7.4 选择排序
# 7.4.1 简单选择排序
def SelectSort(nums):
    """
    空间复杂度:o(1)
    时间复杂度:o(n^2)    移动次数较少,最好的时候移动0次——表已经有序;比较次数与初始序列无关,始终是n(n-1)/2即o(n^2)
    稳定性:不稳定。假如有两个相同元素,其相对位置为 ...A...B...,前一次排序min会先指向B,后一次才会指向A,排序后二者相对位置变成...B...A...
    """
    for i in range(len(nums) - 1):      # n - 1 次即可把整个数组排序
        min = i
        for j in range(i, len(nums)):   # 从这个范围找出最小的数的索引
            if nums[j] < nums[min]:
                min = j
        if min != i:
            nums[min], nums[i] = nums[i], nums[min]
    return nums


# 7.4.2 堆排序
# 这应该是最复杂的排序算法了。细细体会
def HeapSort(nums):
    """
    nums索引0位置不使用,仅仅用来暂存元素。
    算法流程:首先BuildMaxHeap建堆,然后每输出一个堆顶元素就对以堆顶元素为根的树进行一次AdjustDown()
    空间复杂度:o(1),即索引0
    时间复杂度:o(nlogn),o(n)是建堆,算法平均下来时间复杂度是o(nlogn)
    稳定性:不稳定。
    """
    BuildMaxHeap(nums)
    print('$$$建堆后:', nums)
    for i in range(len(nums) - 1, 0, -1):
        print('输出:', nums[1])       # 切记,输出的是堆顶元素,然后与堆底元素交换
        nums[i], nums[1] = nums[1], nums[i]
        AdjustDown(nums, 1, i - 1)      # i - 1更容易理解
        print('$向下调整后:', nums)


def BuildMaxHeap(nums):
    """
    建堆,从最后一个至第一个非叶节点,依次执行AdjustDown()
    注意:索引0是暂存
    """
    i = (len(nums) - 1) // 2
    while i > 0:
        AdjustDown(nums, i, len(nums) - 1)
        i -= 1
    return nums


def AdjustDown(nums, k, length):
    """
    调整算法一定要手动模拟,这样理解才最透彻。
    索引0节点是暂存作用,不使用它存储待排序数据。
    :param nums:
    :param k: 要调节的根节点. [k不能为0,否则会死循环,所以必须按书上的——索引0为暂存]
    :param length: 根节点可以调节移动的范围,包括length
    :return:
    """
    nums[0] = nums[k]
    i = 2 * k
    while i <= length:
        if i < length and nums[i] < nums[i + 1]:    # 这里处理越界非常巧妙,length是包括的,i < length意味着i + 1 <= length
            i += 1

        if nums[i] > nums[0]:
            nums[k] = nums[i]
            k = i

        i *= 2
    nums[k] = nums[0]
    return nums

def MergeSort(nums):
	"""
     思路:分治法、递归
     时间复杂度:O(nlogn) 合并算法O(n) 递归O(logn)
     空间复杂度:O(n) 不只要递归栈深度,还有合并算法的临时数组,所以是O(n)
     稳定性:稳定
    """
	if len(nums) <= 1:
		return nums
	mid = len(nums) // 2
	nums1 = MergeSort(nums[:mid])
	nums2 = MergeSort(nums[mid:])
	return Merge(nums1, nums2)

def Merge(nums1, nums2):
	res = []
	while nums1 and nums2:
		if nums1[0] > nums2[0]:
			res.append(nums2.pop(0))
		else:
			res.append(nums1.pop(0))
	if nums1:
		res += nums1
	elif nums2:
		res += nums2
	return res
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值