Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

发布时间:2020-08-12 15:24:28 作者:进击的Coder
来源:ITPUB博客 阅读:164

阅读本文大概需要 9 分钟。


阅读本文可以帮助你解开以下疑惑:算法是什么?算法难不难?怎么才能够在短时间内熟悉业内的经典算法呢?这些算法用 Python 实现会是什么样的?它们的耗时会跟时间复杂度相关吗?

神马是算法?

Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。

算法中的指令描述的是一个计算,当其运行时能从一个初始状态和(可能为空的)初始输入开始,经过一系列有限而清晰定义的状态,最终产生输出并停止于一个终态。一个状态到另一个状态的转移不一定是确定的。随机化算法在内的一些算法,包含了一些随机输入。

算法的几大特征

一个算法应该具有 “有穷性”、“确切性”、“输入项”、“输出项”、“可行性” 等重要的特征。这些特征对应的含义如下:

Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

算法两大要素

算法的好坏评定

你说这个算法好、他却说这个算法不好,两人争论不休。那么好与不好应该怎么评定呢?

同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法分析的目的在于选择合适算法和改进算法。一个算法的评价主要从时间复杂度和空间复杂度来考虑。

以上的理论知识可以让我们对算法有个大致的理解和认知,接下来我们将使用 Python 实现几个经典的 排序算法,并在文末对比 Java 的实现。

算法的内外之分

除了《唐门》弟子之外(斗罗大陆中的唐门),排序算法也有内外之分。

比较经典的排序算法如下图所示:

Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

有冒泡排序、归并排序、插入排序、希尔排序、选择排序、快速排序等。

它们各自的时间复杂度如下图所示:

Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

注意:今天先讲冒泡、选择和插入排序

在开始之前,首先要感谢公众号《五分钟学算法》的大佬 “程序员小吴” 授权动态图片和排序思路。

冒泡排序

Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

冒泡排序的过程如上图所示,对应的算法步骤为:

Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

根据动态图和算法步骤, Python 实现冒泡排序的代码如下:

data = [54832]
def bubble(data):
    for i in range(len(data)-1):    # 排序次数
        for s in range(len(data)-i-1):  # s为列表下标
            if data[s] > data[s+1]:
                data[s], data[s+1] = data[s+1], data[s]
    return data
print(bubble(data))

程序运行后输出结果为:

[23458]

这是一种时间复杂度上限比较高的方法,它的排序时间会随着列表长度的增加而增加。

选择排序

Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

选择排序的过程和步骤如上图所示,根据动态图和算法步骤, Python 实现选择排序的代码如下:

data = [3416297085]
def selections(nums):
    for i in range(len(nums)):
        min_index = min(nums)  # 最小值
        for j in range(len(nums) - i):
            if nums[min_index] < nums[j]:
                min_index = j
        nums[min_index], nums[len(nums) - i - 1] = nums[len(nums) - i - 1], nums[min_index]
    return nums
print(selections(data))



其中 min() 方法可以获得列表中的最小值,运行结果为:

[0123456789]

既然 min() 有这个特性 (备注:max() 方法可以获得列表中最大值),我们可以将它利用起来,骚一点的代码为:

data = [3416297085]
res = []
for i in range(0len(data)):
    aps = min(data)
    data.remove(aps)
    res.append(aps)
print(res)

运行后得到的输出结果为:

[0123456789]

假如将 min() 换成 max() 方法的,得到的输出结果为:

[9876543210]

这种只选择列表最大元素或最小元素的行为,是否也能称为选择性排序呢?

虽然这种写法的代码比较短,也更容易理解。但是它的时间复杂度是如何的呢?

首先要确认 min 和 max 的时间复杂度。有人给出了 list 各项操作的时间复杂度:

Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

可以看到 min 和 max 都是随着列表长度而增长,再加上本身需要 for 循环一次,所以这种写法的时间复杂度为

Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

真的是这样吗?

代码中有一个 remove 操作,将原列表的元素删除,但是 remove 的时间复杂度也是O(n),这岂不是变成了 O(n*n + n),如何解决这个问题呢。

观察到 pop 的时间复杂度是 O(1),那么是否可以利用 pop 来降低时间复杂度呢?list 提供了获取元素下标的方法,我们尝试将代码改为:

data = [3416297085]
res = []
for i in range(0, len(data)):
    aps = max(data)
    result = data.pop(data.index(aps))
    print(result)
    res.append(aps)
print(res)

运行后得到的输出结果为:

9
8
7
6
5
4
3
2
1
0
[9876543210]

由此可见确实能够根据索引删除掉 list 元素,在删除元素这里降低了复杂度。

慢着,上述 pop 的时间复杂度是 O(1),但是 pop(data.index(i)) 这种操作的时间复杂度呢?也是 O(1) 吗?我们可以做个实验来验证一下:

# 崔庆才丨静觅、韦世东丨奎因 邀请你关注微信公众号【进击的Coder】
from datetime import datetime
data = [i for i in range(500000)]

start_time = datetime.now()
for i in range(len(data)):
    data.pop(data.index(i))
print(data)
print(datetime.now() - start_time)

这是 pop(data.index(i)) 的代码,运行结果如下:

[]
0:00:40.151812

而如果使用 pop()

from datetime import datetime
data = [i for i in range(500000)]

start_time = datetime.now()
for i in range(len(data)):
    data.pop()
print(data)
print(datetime.now() - start_time)

运行后的结果为:

[]
0:00:00.071441

结果显而易见,pop(i) 的时间复杂度依旧是跟元素个数有关,而不是预想中的 O(1)。由于列表元素不断减少,所以它的时间复杂度也不是 O(n),假设当前列表元素数量为 k,那么这个部分的时间复杂度则是 O(k)。说明简短的 min max写法能够一定程度的降低时间复杂度。

验证一下,两次 for 循环的选择排序写法和 mix max 的简短写法耗时情况如何:

from datetime import datetime
data = [i for i in range(30000)]


def selections(nums):
    for i in range(len(nums)):
        min_index = min(nums)  # 最小值
        for j in range(len(nums) - i):
            if nums[min_index] < nums[j]:
                min_index = j
        nums[min_index], nums[len(nums) - i - 1] = nums[len(nums) - i - 1], nums[min_index]
    return nums


start_time = datetime.now()
selections(data)
print(datetime.now() - start_time)

这里以 3 万个元素为例,两次 for 循环的运行时间为 47 秒左右。而同样的数量,用 min max 方式排序:

from datetime import datetime
data = [i for i in range(30000)]

start_time = datetime.now()
res = []
for i in range(0, len(data)):
    aps = max(data)
    # del data[data.index(aps)]
    data.pop(data.index(aps))
    res.append(aps)
print(datetime.now() - start_time)

所花费的时间为 12 秒,代码中用 del 和 pop 方法得到的结果一样。

还……还有这种操作?

选择排序也是一种时间复杂度上限比较高的方法,它的排序时间同样会随着列表长度的增加而增加。

插入排序

Python 排序算法[一]:令你茅塞顿开,却又匪夷所思
Python 排序算法[一]:令你茅塞顿开,却又匪夷所思

插入排序的过程和步骤如上图所示,根据动态图和算法步骤, Python 实现插入排序的代码如下:

from datetime import datetime
data = [i for i in range(30000)]
data.insert(605)

# 崔庆才丨静觅、韦世东丨奎因 邀请你关注微信公众号【进击的Coder】
def direct_insert(nums):
    for i in range(1, len(nums)):
        temp = nums[i]  # temp变量指向尚未排好序元素(从第二个开始)
        j = i-1  # j指向前一个元素的下标
        while j >= 0 and temp < nums[j]:
            # temp与前一个元素比较,若temp较小则前一元素后移,j自减,继续比较
            nums[j+1] = nums[j]
            j = j-1
            nums[j+1] = temp  # temp所指向元素的最终位置
    return nums


start_time = datetime.now()
res = direct_insert(data)
print(datetime.now() - start_time)
print(len(res), res[:10])

生成列表后在列索引为 60 的地方插入一个值为 5 的元素,现在数据量为 3 万零 1。代码运行得到的输出结果为:

0:00:00.007398
30001 [0, 1, 2, 3, 4, 5, 5, 6, 7, 8]

可以看到 3 万零 1 个元素的列表排序耗时很短,而且通过切片可以看到顺序已经经过排列。

然后测试一下选择,代码如下:

from datetime import datetime
data = [i for i in range(30000)]
data.insert(605)


def selections(nums):
    for i in range(len(nums)):
        min_index = min(nums)  # 最小值
        for j in range(len(nums) - i):
            if nums[min_index] < nums[j]:
                min_index = j
        nums[min_index], nums[len(nums) - i - 1] = nums[len(nums) - i - 1], nums[min_index]
    return nums


start_time = datetime.now()
res = selections(data)
print(datetime.now() - start_time)
print(len(res), res[:10])

代码运行后得到的输出结果为:

0:00:47.895237
30001 [0, 1, 2, 3, 4, 5, 5, 6, 7, 8]

可以看到 3 万零 1 个元素的列表排序耗并不短,耗费了 47 秒钟,通过切片可以看到顺序已经经过排列。

接着试一下 max min 型选择排序的写法,得到的结果为:

0:00:14.150992
30001 [29999, 29998, 29997, 29996, 29995, 29994, 29993, 29992, 29991, 29990]

这简直了,为什么这种操作就能够节省这么多时间呢?

最后测试一下冒泡:

# 崔庆才丨静觅、韦世东丨奎因 邀请你关注微信公众号【进击的Coder】
from datetime import datetime
data = [i for i in range(30000)]
data.insert(605)


def bubble(data):
    for i in range(len(data)-1):    # 排序次数
        for s in range(len(data)-i-1):  # s为列表下标
            if data[s] > data[s+1]:
                data[s], data[s+1] = data[s+1], data[s]
    return data


start_time = datetime.now()
res = bubble(data)
print(datetime.now() - start_time)
print(len(res), res[:10])

代码运行后得到的输出结果为:

0:00:41.392303
30001 [0, 1, 2, 3, 4, 5, 5, 6, 7, 8]

可以看到 3 万零 1 个元素的列表排序耗并不短,耗费了 41 秒钟,通过切片可以看到顺序已经经过排列。

得到的结果为:

问题:实在是令人匪夷所思,插入排序的速度居然比其他两种排序方式耗时少那么多。这是为什么呢?

事实上插入排序只用了 1 层 for 循环,并非像冒泡和选择那样使用 2 层 for 循环,是不是由此可以刷新上图中对于时间复杂度的介绍呢?

问题:而两种不同的选择排序法的结果差异这么大,这又是为什么???

请在评论区发表你的看法

推荐阅读:
  1. Python3 备份 MySQL/MariaDB(本地+FTP)
  2. python-sqlalchemy

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

python 令你 却又

上一篇:vue npm install安装某个指定版本的方法

下一篇:Git中分支是什么

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》