排序算法

文字转载于https://blog.csdn.net/weixin_41571493/article/details/81875088
代码转载于https://blog.csdn.net/sensev/article/details/80908776
参考https://blog.csdn.net/qq_39207948/article/details/80006224
参考https://www.cnblogs.com/xiugeng/p/9645972.html
参考https://www.jianshu.com/p/86c2375246d7

十大排序算法

十种常见排序算法可以分为两大类:
非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序;
线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。

相关概念:
稳定:如果a原本在b的前面,而a=b,排序之后a仍然在b的前面
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面
时间复杂度:对排序数据的总的操作次数,反映当n变化时,操作次数呈现什么规律
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数

1、冒泡排序(Bubble Sort)

相邻的两个元素进行比较,然后把较大的元素放到后面(正向排序),
在一轮比较后,最大的元素就放在了最后一个位置,
像鱼在水中吐的气泡在上升过程中不断变大)。

每轮操作O(n)次,共O(n)轮,时间复杂度O(n^2)
额外空间开销出在交换数据时那一个过渡空间,空间复杂度O(1)

def bubble_sort(alist):
	length = len(alist)
	for i in range(length-1):
	# i 表示比较多少轮
		for j in range(length-i-1):
		# j表示每轮比较的元素范围,因为每比较一轮就会排序好一个元素的位置
		# 所以在下一轮比较的时候就少比较了一个元素,所以要减去i
			if alist[j] > alist[j+1]:
				alist[j], alist[j+1]= alist[j+1], alist[j]
	return alist	

2、快速排序(Quick Sort)

1、从数列中挑出一个元素,称为“基准”
2、重新排列数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边),这个称为分区操作(partition)。
3、递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。

快速排序基于选择划分,是简单选择排序的优化。
每次划分将数据选到基准值两边,循环对两边的数据进行划分,类似于二分法。

def quick_sort(alist, start, end):
    if start >= end:
        return
    low = start
    high = end
    mid = alist[low]
    
    while low < high:
        while low < high and mid < alist[high]:
            # 从右边开始找,如果元素小于基准,则把这个元素放到左边
            high -= 1
        alist[low] = alist[high]
        
        while low < high and mid > alist[low]:
            # 从左边开始找,如果元素大于基准,则把元素放到右边
            low += 1
        alist[high] = alist[low]
    
    # 循环退出,low==high,把基准元素放到这个位置
    alist[low] = mid
    
    # 递归调用,重新排列左边的和右边的序列
    quick_sort(alist, start, low-1)
    quick_sort(alist, low+1, end)

因为序列被切分为两部分,如果切分了n次把元素切割完,序列的长度为m,则2^n=m,所以切割完的时间复杂度为log(n),在每次切割都会比较所有的元素,需要n次,所以整体的时间复杂度O(nlogn)。

额外空间开销出在暂存基准值,O(logn)次划分需要O(logn)个,空间复杂度O(logn)。

3、插入排序(Insert Sort)

1、从第一个元素开始,该元素可以认为已经被排序;
2、取出下一个元素,在已经排序的元素序列中从后向前扫描;
3、如果该元素(已排序)大于新元素,就将该元素移到下一位置;
4、重复步骤3,直至找到已排序的元素小于或者等于新元素的位置;
5、将新元素插入到该位置后;
6、重复步骤2~5。

简单插入排序同样操作n-1轮,每轮将一个未排序数插入已排序序列。
每轮操作O(n),共O(n)轮,时间复杂度O(n^2)
额外空间开销出在数据移位时那一个过渡空间,空间复杂度O(1)。

def insert_sort(alist):
    for i in range(1, len(alist)):
        # 从第二个元素开始,每次取出一个元素,插入前面的序列使其有序
        for j in range(i, 0, -1):
            if alist[j] < alist[j - 1]:
                alist[j], alist[j - 1] = alist[j - 1], alist[j]
    return alist

4、希尔排序(Shell Sort)

希尔排序是插入排序的一种。也称缩小增量排序。
1、先将整个待排序序列按照增量分割成若干子序列,分别进行直接插入排序
(每个分组分别进行插入排序,每个分组就变成有序(整体不一定))
在这里插入图片描述
在这里插入图片描述
2、然后缩小增量为上个增量的一半,对每个分组进行插入排序
在这里插入图片描述
在这里插入图片描述

def shell_sort(alist):
    n = len(alist)
    # 初始步长
    gap = n / 2
    while gap > 0:
        # 按步长进行插入排序
        for i in range(gap, n):
            j = i
            # 插入排序
            while j >= gap and alist[j - gap] > alist[j]:
                alist[j - gap], alist[j] = alist[j], alist[j - gap]
                j -= gap
        # 得到新的步长
        gap = gap / 2

5、选择排序(Select Sort)

第一轮,所有的元素都和第一个元素进行比较,如果比第一个元素大,就和第一个元素进行交换,在这轮比较完成后,就找到了最小的元素;
第二轮,所有的元素都和第二个元素进行比较,找出第二个位置的元素,以此类推

每遍历一次排好一个元素,而每一次都会比较所有的元素,所以时间复杂度为O(n^2)

def selection_sort(alist):
    length = len(alist)
    for i in range(length - 1, 0, -1):
        for j in range(i):
            if alist[j] > alist[i]:
                alist[j], alist[i] = alist[i], alist[j]

6、 堆排序(Heap Sort)

堆,是一种特殊的完全二叉树。
大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大;
小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小。

1、首先,将待排序的数组构造出一个大根堆
2、取出这个大根堆的堆顶节点(最大值),与堆的最下最右的元素进行交换,然后把剩下的元素再构造出一个大根堆
3、重复第2步,直到这个大根堆的长度为1,此时完成排序

堆常应用于topk问题:
现有n个数,设计算法得到前k大的数,(k<n)。常用于实现网站热搜榜等。
思路:
取列表前k个元素建立一个小根堆。堆顶就是目前第k大的数(最小的数)。
依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行依次调整。

7、 归并排序(Merge Sort)

分治法(divide and conquer)
1、把长度为n的输入序列分成两个长度为n/2的子序列;
2、对这两个子序列分别采用归并排序
3.将两个排序好的子序列合并成一个最终的排序序列
在这里插入图片描述
在这里插入图片描述

def merge(left, right):
    l, r = 0, 0
    result = []
    while l < len(left) and r < len(right):
        if left[l] < right[r]:
            result.append(left[l])
            l += 1
        else:
            result.append(right[r])
            r += 1
    result.append(left[l:])
    result.append(right[r:])
    return result
    
def merge_sort(alist):
    if len(alist) <= 1:
        return
    num = len(alist) / 2
    # 划分
    left = merge_sort(alist[:num])
    right = merge_sort(alist[num:])
    # 合并
    return merge(left, right)

8、 桶排序(Bucket Sort)

1、假设数据在[min,max]之间均匀分布,其中min/max分别指数据中的最小值和最大值。那么将区间[min,max]等分成n份,这n个区间便称为n个桶
2、将数据加入对应的桶中,然后每个桶内单独排序
3、由于桶之间有大小关系,因此可以从大到小(或从小到大)将桶中元素放入到数组中

9、 计数排序(Counting Sort)

1、根据待排序集合中最大元素和最小元素的差值范围,申请额外空间;
2、遍历待排序集合,将每一个元素出现的次数记录到元素值对应的额外空间内;
3、对额外空间内数据进行计算,得出每一个元素的正确位置;
4、将待排序集合每一个元素移动到计算得出的正确位置上

10、 基数排序(Radix Sort)

基数排序是桶排序的扩展。
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串和特定格式的浮点数,所以基数排序也不是只能使用于整数。
1、取得数组中的最大数,并取得位数;
2、arr为原始数组,从最低位开始取每个位组成radix数组;
3、对radix数组进行计数排序(利用计数排序适用于小范围数的特点)

基数排序 vs 计数排序 vs 桶排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶;
计数排序:每个桶只存储单一键值;
桶排序:每个桶存储一定范围的数值;

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值