排序算法的稳定性
当待排序记录的关键字均不相同时,排序结果是惟一的,否则排序结果不唯一
在待排序记录中,若存在多个关键字相同的记录,若排序前后这些具有相同关键字的记录之间的相对次序保持不变,则该排序方法是稳定的;否则就是不稳定的
算法的就地性
在运行过程中临时占用的存储空间随算法的不同而异,有的算法只需要占用少量的临时工作单元,而且不随问题规模的大小而改变,这是节省存储的算法
冒泡排序
从后往前,通过与相邻元素的比较和交换来把小的数交换到最前面。这个过程类似于水泡向上升一样,因此而得名
时间复杂度为O(n^2)
改进:在某次遍历中如果没有数据交换,说明整个数组已经有序。因此通过设置标志位来记录此次遍历有无数据交换就可以判断是否要继续循环。
选择排序
1.从左至右遍历,找到最小(大)的元素,然后与第一个元素交换。
2.从剩余未排序元素中继续寻找最小(大)元素,然后与第二个元素进行交换。
3.以此类推,直到所有元素均排序完毕。
之所以称之为选择排序,是因为每一次遍历未排序的序列我们总是从中选择出最小(大)的元素,可以看做是对冒泡排序的优化,大大减少了交换的次数
不管初始数据有没有排好序,都需要经历n^2/2
次比较, 这对近似排好序的序列来说并不具有优势
在最好的情况下,即所有数据都已排好序,需要0次交换;最差的情况,倒序,需要n-1
次交换
快速排序
也可以看做是对冒泡排序的优化,这种算法不仅把小数冒泡到上面同时也把大数沉到下面
快速排序是不稳定的,其平均时间复杂度是O(nlgn),最坏时间复杂度是O(n^2)
举个栗子:对5,3,8,6,4这个无序序列进行快速排序,思路是右指针找比基准数小的,左指针找比基准数大的,交换之。
5,3,8,6,4 用5作为比较的基准,最终会把5小的移动到5的左边,比5大的移动到5的右边。
5,3,8,6,4 首先设置i,j两个指针分别指向两端,j指针先扫描(思考一下为什么?)4比5小停止。然后i扫描,8比5大停止。交换i,j位置。
5,3,4,6,8 然后j指针再扫描,这时j扫描4时两指针相遇。停止。然后交换4和基准数。
4,3,5,6,8 一次划分后达到了左边比5小,右边比5大的目的。之后对左右子序列递归排序,最终得到有序序列。
上面留下来了一个问题为什么一定要j指针先动呢?首先这也不是绝对的,这取决于基准数的位置,因为在最后两个指针相遇的时候,要交换基准数到相遇的位置。
一般选取第一个数作为基准数,那么就是在左边,所以最后相遇的数要和基准数交换,那么相遇的数一定要比基准数小。所以j指针先移动才能先找到比基准数小的数。
插入排序
以我们平常打扑克牌为例来说明,假设我们手上的牌都是排好序的,那么插入排序可以理解为我们每一次将摸到的牌,和手中的牌从右到左依次进行对比,如果找到合适的位置则直接插入。具体的步骤为:
1.从第一个元素开始,该元素可以认为已经被排序
2.取出下一个元素,在已经排序的元素序列中从后向前扫描
3.如果该元素小于前面的元素(已排序),则依次与前面元素进行比较如果小于则交换,直到找到大于该元素的就则停止;
4.如果该元素大于前面的元素(已排序),则重复步骤2
5.重复步骤2~4,直到所有元素都排好序。
在最坏的情况下需要n^2/2
次比较和交换
在最好的情况下只需要n-1
次比较和0次交换,对部分有序序列较友好
希尔排序
也叫缩小增量排序,它是对插入排序的改进
通过将待比较的元素划分为几个区域来提升插入排序的效率,这样可以让元素可以一次性的朝最终位置迈进一大步,
然后算法再取越来越小的步长进行排序,最后一步就是步长为1的普通的插入排序的,但是这个时候,整个序列已经是近似排好序的,所以效率高。
归并排序
这是一种典型的分治算法,通过将两个有序的序列合并为一个大的有序的序列的方式来实现排序
当一个数组左边有序,右边也有序,那合并这两个有序数组就完成了排序。如何让左右两边有序了?用递归!递归下去,合并上来就是归并排序
归并排序最大的优点是它的时间复杂度为O(nlgn),这个是我们之前的选择排序和插入排序所达不到的。
它还是一种稳定性排序,也就是相等的元素在序列中的相对位置在排序前后不会发生变化。
它的唯一缺点是,需要利用额外的N的空间来进行排序。
归并排序依赖于合并操作,即将两个已经排序的序列合并成一个序列,具体的过程如下:
1.申请空间,使其大小为两个已经排序序列之和,然后将待排序数组复制到该数组中。
2.设定两个指针,最初位置分别为两个已经排序序列的起始位置
3.比较复制数组中两个指针所指向的元素,选择相对小的元素放入到原始待排序数组中,并移动指针到下一位置
4.重复步骤3直到某一指针达到序列尾
5.将另一序列剩下的所有元素直接复制到原始数组末尾
堆排序
最大堆通常被用来进行”升序”排序,而最小堆通常被用来进行”降序”排序
二叉堆用数组来表示,索引为i的节点的左右孩子索引分别为2i+1
和2i+2
,父节点索引为i/2-1
以升序为例,将n个元素的数组排序的过程如下:
1. 构造最大堆
从索引n/2-1
的节点开始,以此节点为根节点构建最大堆,然后遍历将索引依次减1直至根节点不断构建最大堆,此时数列a[0…n]构造成最大堆
2. 交换数据
将a[0]和a[n]交换,使a[n]是a[0…n]中的最大值,然后按之前的方法将a[0…n-1]重新调整为最大堆
将a[0]和a[n-1]交换,使a[n-1]是a[0…n-1]中的最大值;然后将a[0…n-2]重新调整为最大堆
以此类推,最后整个数组就是升序排列的。
计数排序
前提条件:待排序的数是满足一定范围的整数,且需要比较多的辅助空间。
适合于小范围集合的排序
算法复杂度 O(n)
用待排序的数作为计数数组的下标,统计每个数字的个数。然后依次输出即可得到有序序列
桶排序
类似于HashMap
的结构,其中每个桶中的链表是排好序的,这样依次遍历所有桶就能得到有序序列
基数排序
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零
然后,从最低位开始,依次进行一次排序
这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列
参考资料