初篇(一)--- 排序算法之Low B 三人组

    如果不考虑这是算法,仅仅只是凭借你的日常的排序方法,是否简单地说:比如一堆杂乱的牌子,上面写着阿拉伯数字,让我们排序,比如从最大的开始排,是否先在里面寻找最大的一块数的牌子,然后再放到其他的地方,依次排列,最后形成从大到小数字的牌子顺序。
    如果让程序实现怎么办:一个列表有n个数,排序紊乱,先取里面最大的,是否一个个的比较,每次比较就把更大的那个数赋值给变量max_value,然后每次的max_value再append到一个新的列表,再把原列表的max_value的数给delete。
    这就分为了两步,第一步比较最大值,比较列表的长度为n-1次。第二部放到一个新的列表中。最后就排序完成,大概是1到n-1的求和次计算。

算法实现:

new_li = []
def primary_sort(li):
    if len(li) > 0:
        max_value = (0, li[0])
        for index, i in enumerate(li):
            if i > max_value[1]:
                max_value = (index, i)
        new_li.append(li.pop(max_value[0]))
        primary_sort(li)
  • 时间复杂度:
    ∑ i = 1 n n − i \sum\limits_{i=1}^{n} n-i i=1nni
    = n ∗ n − ( ∑ n = 1 n n ) =n*n-(\sum\limits_{n=1}^{n} n) =nn(n=1nn)
    = n 2 − ( n ∗ ( n + 1 ) 2 ) =n^2-(\frac{n*(n+1)}{2}) =n2(2n(n+1))
    = n 2 − n 2 2 − n 2 =n^2-\frac{n^2}{2}-\frac{n}{2} =n22n22n
    = n 2 2 − n 2 =\frac{n^2}{2}-\frac{n}{2} =2n22n
    可知时间复杂度 O ( n 2 ) O(n^2) O(n2)
    每次循环折半才是 l o g ( n ) log(n) log(n),而不是 n 2 \frac{n}{2} 2n 可以换为 l o g ( n ) log(n) log(n)
  • 空间复杂度为:
    只有一个长度为n的新列表,则: O ( n ) O(n) O(n)
  • 其实这个和选择排序的基本思路是一样的,只不过选择排序是原地排序,所以分了有序区和无序区,这可以说明算法是想法的包装、优化,有的时候一个划时代的想法很重要,那才是从无到有的过程

1.冒泡排序

冒泡排序

  • 冒泡排序,顾名思义,从低到高,从下到上,一步步的比较,一步步的从无序趋于有序。
    -* 将列表相邻的两个数比较,如果前面的比后面的大,则交换这两个数
    -* 每一趟排序完成,则左边的无序数就少一个,右边的有序数就多一个
  • 代码实现:
def bubble_sort(li):
	for i in range(len(li)-1):  # 因为每一个数都要进行冒泡排序,除了最后剩的一个数,故循环n-1次
		for j in range(len(li)-i-1):  # 当前这个数进行冒泡排序的时候,只用在无序区排序,故n-i-1次,i表示已经排序好了几个数。
			if li[j] > li[j+1]:
				li[j], li[j+1] = li[j+1], li[j]
  • 时间复杂度:
    两层for循环,则为 O ( n 2 ) O(n^2) O(n2)
  • 优化:
        其实到了这里基本也就差不多这样,但是总感觉哪里还可以优化一下,请看:
        如果冒泡排序中的一趟排序没有发生交换,则表示你的列表已经有序了,可以结束了。只是尽量的减少了不必要的次数,但量级没变!

则算法实现为:

def bubble_sort(li):
	for i in range(len(li)-1):
		end_consition = False
		for j in range(len(li)-i-1): # 都是把无序区大的数尽量往后排,只要比后一个数大的。故如果这里的无序区一次排序都为发生的话,代表次区的每一个数都比后一个数小,则代表排序已经完成了,则end_consition就是False,便结束排序。
			if li[j] > li[j+1]:
				li[j], li[j+1] = li[j+1], li[j]
				end_consition = True
		if not end_consition:
			return

2.选择排序

  • 选择排序的思想为:
    -* 分为有序区和无序区
    -* 在无序区里遍历找到最小数,放到有序区的第一个位置去。这样从最小开始排的话,则列表的左边为有序区,右边为无序区(原地排序!)
    -* 接着在无序区里寻找最小值放到有序区的最后面,直到,有序区大小从0变为n,无序区大小从n变为0

  • 代码实现:

def select_sort(li):
	for i in range(len(li)-1):  # 每个数都排列,除了最后剩的一个,故为n-1
		min_value = i
		for j in range(i+1, len(li)):  # 寻找无序区里除了第一个数li[i]以外的最小值
			if li[j] < li[min_value]:
				min_value = j
		if min_value != i:  # 把无序区的第一个数li[i]和无序区后面的最小值比较
			li[min_value], li[i] = li[i], li[min_value]  # 不稳定性的关键一步,在于跨大步的交换
  • 因为是选取无序区的最小值,每一个数都要排序,故无法像冒泡排序一样可以优化。
  • 时间复杂度:
    两层for循环,为 O ( n 2 ) O(n^2) O(n2)

3.插入排序

插入排序

  • 插入排序的思想为:
    -* 开始的时候手里(有序区)只有一张牌
    -* 每次(在无序区里)摸一张牌,插入到已有牌的正确位置
    -* 代码思路:从列表开头开始,列表的第二个数和列表的第一个数比较,大的放它的后面。有序区有规模了之后,无序区的第一个数,和有序区的最大的数比较,小于它则换位,直到比前一个数大,则排完序。
  • 代码实现:
def insert_sort(li):
	for i in range(1, len(li)):
		tmp = li[i]  # 无序区的第一个数赋值给临时变量
		j = i - 1  # 有序区最大的一个数的索引
		while j >= 0 and li[j] > tmp:  # 如果有序区的最后一个数开始大于无序区的第一个数
			li[j+1] = li[j]  # 把大于无序区第一个数的这个有序区的数往后排一下
			j -= 1  # 这里减了1,用于前一个数来判断
		li[j+1] = tmp  # 把无序区的第一个数放到合适的位置

上面代码的说明可能有点乱,现在简单地说,要点在于 j 这个列表的下标指向的是什么数:它一直指向无序区的第一个数在有序区里产生变动后的前一个数,用于和无序区第一个数来判断的那个数的下标。

  • 时间复杂度
    两层for循环,为 O ( n 2 ) O(n^2) O(n2)

4.小结

  • 都是原地排序(不产生新的列表),减少了空间的占用,空间复杂度都为 O ( n ) O(n) O(n),时间复杂度都为 O ( n 2 ) O(n^2) O(n2)
  • 计算机的运行效率大约为每秒一千万次(与电脑有关),如果排序一万的数据大概要10秒。
  • 从思维上来讲,都涉及到了有序区和无序区的区分,都是简单的排序方法和数据结构。但是我们能从里面学到分区和指针的思想。

    “指针”是编程语言中的一个对象,它存储着一个内存空间的地址,计算机可以通过这个地址找到变量的值,这个特定的地址指向这个特定的值。指针最大的优点在于它可以有效的利用零碎的内存空间。
    但是在本文中的指针只是代表前面的意思:通过这个地址指向特定的值。以上算法的原地排序的两个值互换位置就是这个意思,指针的移动也代表着所指向的值的变化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值