算法难,难就在于对这个解决问题的过程的理解和描述。对于理解困难这个问题,我们可以去找一些辅助工具来帮助理解。这篇问题就是借助于状态机的思想,通过图形化的方法去理解算法过程。
状态机介绍
计算编程中面临的绝大数算法问题、或者现实生活中可计算的问题,都可以使用状态机模型去建模,解决问题。因为这个模型足够抽象,同时模型又能很好的去描述一件事情。
这里先来认识下状态机(有限状态机-变换器)的组成:输入集、输出集、状态集和状态迁移过程。这四个部件构成了一个简单的状态机模型。
输入集为我们需要处理的数据。大部分场景下,输入集当前的状态就为状态机需要处理的初始状态。反之,输出集为我们想要的结果,对应最终状态。初始状态和最终状态都在状态集合中,状态集合内部的状态是我们预期可以处理的,没在这个集合中的状态,是我们建立的状态机不可处理的。
排序状态机
结合状态机的构造,我们将排序问题按照状态机的思想进行抽象,我们可以得到如下的状态机模型。
在这个模型中,最终状态的定义是:输出集是有序的,并且是升序、连续的。而我们需要关心的是,如何将初始集从无序状态迁移成有序状态。需要寻找一个状态迁移过程。当然,这个迁移过程存在无数条路径可以进行,而我们需要的是这个迁移过程尽可能高效。
研究对象:状态迁移过程(δ)
因此就排序这个问题,我们创造了许许多多的排序算法,选择排序、插入排序、冒泡排序、快速排序、归并排序、希尔排序、堆排序、基数排序等等。这些算法的区别在于状态迁移过程的效率的不同。这个效率在算法体系中,叫做复杂度(时间复杂度、空间复杂度、最高/最低xx复杂度)。所以我们通过状态机去研究排序这个问题,重点研究的对象是状态迁移的过程。
下面我针对几个常见的排序算法,用这种理解方式来进行理解。
选择排序
我们先看看最基础的排序方法,选择排序这种排序算法的状态迁移过程。选择排序的状态迁移过程定义是:查找无序集中最小(大)的元素放入有序集合的尾部。
这里我将每一次状态迁移的子过程都表达出来,然后再将序列中不同状态的元素进行对齐,就能得到如下图所示的形状。在左边部分为有序集,在右边部分为无序集。在无序集中主要的操作为:查找最小元素。而在有序集中的操作就是把无序集中查找到的元素按规则放入到有序集中。而状态迁移过程的最大操作空间为图中红色虚线三角形部分。
当然对此还可以做改进:同时选择最大和最小进行排序,提高排序效率。
插入排序
我们再来看看插入排序,插入排序的状态迁移过程定义为:选择无序集中任意元素(一般为无序集首个元素)插入到有序集合对应的位置。把迁移子过程中不同状态的元素进行分类对齐后,其迁移的过程如下图所示。
整体形式上跟选择排序差不多。但是有细微的差别是,有序集中,不再是连续的有序列集,而是一个离散的有序列集。并且,两个集合中的操作发生了转换,插入排序中,有序集中的操作为查找元素对应的位置,无序集为缩小的操作。而状态迁移过程的最大操作空间为图中红色虚线三角形部分,与选择排序相反。
tips:冒泡排序的状态迁移过程图?
选择&插入排序算法区别
快速排序
快速排序的状态迁移过程定义为:确定单个数据的位置,然后根据这个数据位置进行大小分类。它的迁移的过程如下图所示。
红色虚线三角为有序集,之外的为无序集。而状态迁移过程的最大操作空间为图中虚线矩形部分。整个状态迁移过程中,有序集是稳定的,而无序集是不稳定的。
快速排序结合查找数据和查找位置的两种方式进行数据排序,并且是多元素并行排序。
归并排序
快速排序的状态迁移过程定义为:扩充子有序列的方式来达到全局有序。归并排序分为两个阶段,第一阶段是排序任务的分解,第二阶段是进行排序。其有序集和无序集刚刚与快速排序相反。
堆排序
堆排序的状态迁移过程定义:构建一个大(小)顶堆,然后利用的堆的性质进行排序。堆排序,其实引入一个趋于全局有序的状态:最大趋于有序的子序列,每次通过更新这个状态,达到从无序到有序的迁移。
把堆排序的过程横铺,可以发现堆的上一层的数比下一层的数大,这就使得整个过程中,形成了以元素组为序列的序列组,这样就能减少元素的比较。而状态迁移过程的最大操作空间为图中红色虚线三角形部分。
把它的过程进行抽象,如下图所示。
后记
当然还有其他的排序,有兴趣可以,按照状态机的思想去图形化。可能会发现一些特别的东西。
tips:
1.排序算法主要的操作:一种是查找数据,一种是查找位置,另外就是两者的结合。
2.利用图形结构对排序过程进行剖析,可以很直观的去发现不同排序算法的特点。
更多内容请关注公众号:答乎 (QLinkInfo)