算法学习(3)常用排序算法合集(2)

摘要

这篇博客继续上一篇的算法介绍,介绍简单选择排序法和堆排序法,这两个都是选择排序法。堆排序法是这里的重点,建议简单了解下完全二叉树,有助于理解堆排序法。虽然堆排序不容易理解,但真的很有趣,加油,你也可以掌握的~

简单选择排序法

特点

一种非常直观的排序方法,每次都从数据里面挑出最小(或最大)的数,依次排到数组前面。扫描次数是数组个数减1.

实现代码

def SelectSort(array):
    n = len(array)
    for i in range(n-1):
        k = i  # 储存最小数的索引,一开始都是认为待排的第一个为最小
        for j in range(i+1, n):
            if array[k] > array[j]:
                k = j  # 把最小那个数的索引存过去,这样避免了频繁交换
        # 结束 j 的循环后,就找到了数组里面最小的数
        array[i], array[k] = array[k], array[i]  # 交换两个数,完成位置i的排序
        print("第", i+1, '次:', array)

运行效果
简单选择排序法

注意点

  1. 第i次排序,只是把第i个位置跟待排数据中的最小值交换位置,如果没有发生交换,不代表数组就是有序的,这点可以跟冒泡法进行对比,想明白为什么了,也就能区分这两种算法了。
  2. 掌握存储索引的思路,避免程序频繁的数据交换

堆排序法

准备知识:完全二叉树

完全二叉树,我们一个一个名词来解释。
树,是数据的组织形式,一个抽象的模型,相对于数组的线性连接,所谓的线性,就是数据只跟它前面和后面的一个元素有直接联系。所以非线性,就是数据与前后的多个数据产生联系的。树,看下图,就是每个数据与后面多于一个数据有联系。如果一个数据与前后都是多个数据有联系,就是图了,这里不展开讲,有兴趣可以查阅数据结构的书籍。
那二叉树就不难理解了,就是一个数据与后面的两个数据有联系(看图直观一点)。
二叉树的插图
纵使树有很多种分类,但最常用,也是最容易使用的就是二叉树,因为性质真的太好了。你肯定会头大的问,树怎么复杂,怎么存储啊?说出来你可能不信,依然放数组!看图!
树的存储
上图的介绍文字,强烈推荐看看,很有启发意义。这个例子告诉我们,数组还是那个数组,但只要我们换个方式去看,它就会变得不一样!请特别关注二叉树父节点和子节点的位置编号的关系(两个相连的节点,上面那个叫父节点,下面是子节点,是不是很生动~)图3.3 a),有个调皮的小哥,把各元素在数组中的位置,标在了上面,结合着图3.4文字说明,真的很好懂 ~ 最后再附一张图,防止你还是无法理解,这符合我啰嗦的性格。
仔细解答
到这里,对于堆排序所需要的二叉树的知识,已经足够了,因为我学的时候,也是把算法书往前翻,找到二叉树这里,看懂了存储方式,就能理解下面的程序了。再次强调,我这里不是写教程,会有不详细的地方,基础知识务必自己查书!!!

最后,堆,就是一个完全二叉树,然后,多了一个条件,就是父节点的元素,一定要大于或等于下面两个子节点。(当然也可以反过来,关键是父子节点间数据的大小要有序。)

特点

利用堆的特性——最大的数出现在根节点,所以从构成堆的数据中,每次抽取最大值(根节点)另外放好(这里是放到数组的末尾),然后再对剩下的数据进行筛运算(即重新构成堆),反复循环,数据就变成有序了。
筛运算,具体的意思就是,依次比较每个节点与其两个子节点,如果节点比它的任意一个子节点小,就进行交换,这样进行下来,小的数,就会沉到下面去。筛是个贴切的形容词。

实现代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2019-03-25 20:12:17
# @Author  : Promise (promise@mail.ustc.edu.cn)
# @Link    : ${link}
# @Version : $Id$

import CreateRandomNumber


# 关注这里标注数组长度的重要性,而不使用len(array)
def HeapAdjust(array, node, length):  # 这个函数是排序的关键,如快排里面的找中点一样
    while 2*node+1 < length:  # 说明该节点有左子树,node是索引,天生会比数组长度小1
        sonNode = 2*node + 1
        if sonNode + 1 < length:  # 这个条件是不是多余啊?
            if array[sonNode] < array[sonNode+1]:  # 左右子树比较,右子树大,就把子节点用右子树位置代替
                sonNode += 1
        # 开始比较节点node 和 它子节点的大小了
        if array[node] < array[sonNode]:
            array[node], array[sonNode] = array[sonNode], array[node]
            node = sonNode  # 子树的大小规律可能被破坏,要再进行一次排序
        else:
            break  # 因为如果父节点大于子节点,就不用进行任何操作,因为这个算法的前提是,下面的都是按顺序排好的


# 堆排序主函数
def HeapSort(array, length):
    for i in range(length//2-1, -1, -1):  # 从完全二叉树的最后一个节点开始,每个节点调整
        HeapAdjust(array, i, length)  # 完成for循环后,将构成堆,因为每个节点都考虑到了
    for i in range(length-1, 0, -1):
        array[i], array[0] = array[0], array[i]  # array[0] 在上一步已经是最大值
        # 此时,最后一个元素将不再属于堆了,再重构一次堆
        print('第', len(array)-i, '次:', array)
        HeapAdjust(array, 0, i)  # 此时,只有第一个节点 0 位置可能不对


# 检验程序
myArray = [0]*10
isCreated = CreateRandomNumber.CreateRandomNumer(10, myArray, 1, 100)  # 固定产生10个1到100件的随机数
if isCreated:
    print('原始数据:', myArray)
    HeapSort(myArray, len(myArray))

运行效果
堆排序运行示例
注意点:

  1. 程序里面,编号是从0开始,所以父节点和子节点之间的关系会跟截图教材描述的有一点差别,不是直接的2倍关系,而是2倍加1
  2. HeapAdjust(array, node, length) 是核心,每运行一次,就可以把某个节点以下的数组变成堆,要重点琢磨
  3. HeapSort(array, length) 要重点理解第一个for循环,调用 HeapAdjust(array, node, length),从最后一个父节点开始,把一棵杂乱无章的二叉树,整理成堆,这奠定了排序的根基。
  4. HeapSort(array, length) 第二个for循环,理解了为什么每次都是从0这个节点开始进行筛运算,你就掌握了这个算法

值得敲黑板的干货
回忆其他排序算法,不管是冒泡排序还是选择排序,都是依次比较数据,因此,在函数中,动辄就两个for循环嵌套,而堆排序这里,是某个位置,只跟特定位置的元素比较!!! 着重品味这句话! 这样在数据多的时候,就大大降低了复杂度,因为某种程度上,把问题的规模变小了,跟快排的思想类似。这再次提醒我们,换个角度看一个数组中数据的组织形式,有时会带来意想不到的效果!也正是这里的优雅,让我很偏爱堆排序算法。
直接跳转到快排和冒泡法

缺点

  1. 不直观,每一次排序可以确定的是大数肯定会去到最后,但第一个位置的数,却不是上一次排序结束最后的数,需要逐步跟踪才能判断;观察运行结果,其实在第7次排序的时候,数组已经有序,然而暂时我没想到有什么方法进行判断,从而提前结束循环;这里有点像上面的简单选择排序法,一定要全部比完才能停。
  2. 完全二叉树对于大多数人来说很陌生(虽然超级好用,应用到排序这里。)

小福利:
数据处理过程,部分直观演示,博主亲笔字~
数据排序过程

预告

下一篇博客,会快速比较各种排序法的时间复杂度,以及什么情况下,选择哪种排序算法(抄书)。

小结

在常用的排序法,还有直接插入排序法,希尔(Shell)排序法和合并排序法没接受,不过我觉得不是很有趣,可能不会专门写出来,或是直接贴代码,看时间吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值