思想:插入排序就是从第二个数开始(因为第一个数后面已经没有数了,不用进行比较),往后遍历比较,遇到比它大的则继续往后,当遇到比它小的,则将前面的数全部往后挪动,将这个数插入在比它小的前面,然后继续从第三个数开始,再往后比较
动图演示:
代码演示:
思想:希尔排序是属于插入排序的一种优解,当数组为从大到小排序,而我们需要将这个数组从变成从小到大排序时,插入排序的时间将会达到最大,此时希尔排序则能有效地优化插入排序,我们定义一个gap的数,然后从0开始将间距为gap的数进行插入排序,然后再从0+1开始将间距为gap的数进行插入排序,一直循环gap-1次(也就是从0到gao-1开始的间距为gap的数都进行插入排序),这是第一趟排序,第二趟则将gap减小再次进行排序,第三第四趟也是如此,上述的排序称为“预排序”
那么当gap=1时,数组已经接近有序了,此时再进行一次排序(gap=1时,其实就是将数组的全部元素进行一次插入排序),数组就是有序了。
这是大佬对于希尔排序的解释:
图解:
那么,怎么选择gap的值呢,有几种方法:gap=[n/2]或者gap=[n/3],那么如何让最后一次的gap等于1呢?gap=[n/2]时,gap最终都会=1,gap=[n/3]时,gap有可能会变成0,所以可以令gap=[n/3 +1 ],这样gap最后总是会等于1
所以,gap可以取 [n/2]或者[n/3 +1],这里我们选用后一种 gap=[n/3+1]
代码:
第一种是用两个for循环进行排序,最里面的一层for循环是对间隔为gap的数组进行插入排序,外面一层for循环是从0到gap之间的起始数开始循环,外面的while则是令gap不断减小,直到等于1则停止循环。
第二种则在第一种的情况下优化了一下,只进行了一次for循环,但是外内各有一层while,for循环则控制起始坐标从0到gap之间,里面的while则是让间距为gap之间的数进行插入排序,而这些数最后都会在数组内,所以条件则为当最后一个数的数组越界之后则停止插入排序,最外面的while则同样的对gap进行调整。
思想:选择排序较为简单粗暴,遍历一边数组,选出最小的,放到第一位,从第二位开始遍历,选出最小的,放到第二位,从第三位开始遍历,选出最小的,放到第三位,以此类推
动图演示:
代码:
堆排序在之前的文章中已经顺带介绍过了,下面有链接可以跳转进去。
图解:
这里提醒一下,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
假设需要排升序,那么数组则是从小到大的,如果排的是小堆,那么堆顶的元素则为最小的,然后我们需要忽略掉第一个元素,从第二个元素开始作为堆顶重新做堆,那么此时大家的关系有可能都会乱套,那么就需要重新建堆,每排一次都要建一次堆,这不行,很麻烦且复杂且时间复杂度高
假如我们排的是大堆,那么堆顶的元素则为最大的,我们将堆顶的元素与堆尾的元素进行交换,并且忽略掉倒数第一个元素,然后将交换后的堆顶进行向下调整,那么此时堆则不会被破坏,而最后一个数也为最大的,符合升序,然后再将次大的与倒数第二个数进行交换,反复进行,最后就拍完序了,那么降序也是一样,需要建小堆
所以,结论就是排升序要建大堆,排降序建小堆。
代码思路:假设需要升序,那么将乱序数组建堆,建成大堆,然后令堆顶与堆尾交换,然后忽略掉倒数第一个元素,然后对堆顶进行向下调整,第二次第三次以此类推。
建堆需要用到向上调整算法
而向上调整算法与向下调整算法都在上面给出链接,希望读者能结合两处情况进行考虑
思想:相信冒泡排序很多人都会学过,原理就是不断地选取较大的数与后面的数进行比较,遇到较小数则进行交换,当遇到更大的数则选取更大的数,然后继续和后面的数进行比较
动图演示:
代码:
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
而快速排序从出现到现在,有着三种不同的方法
思想:定义左指针left与右指针right,首先将左边第一个数定为key,然后右边开始往前走,遇到比key小的数,则停止,然后左边开始走,遇到比key大的数,则停止,然后将左右两数进行交换,然后右边继续先走,然后再到左边,一直反复,直到left与right相遇,然后令key与right的值进行交换,然后令此时left与right相遇的值与下标重新定义为key,然后此时由key将数组分割成两份,左数组为[0,key-1],而右数组为[key+1,n-1]此时,第一次排序已经完成,然后再对左数组看成一个新的数组重新排序,以此类推,不断地递归,那么停止的标准就是当最开始的begin≥end,也就是数组的开头≥尾的时候就可以停止分割停止排序了
代码:
需要注意到的问题
要知道,左指针是找比key大的值,右指针是找比key小的值,而左指针先动,那么右指针是一定要跟着动,如果右指针先动,那么左指针也一定要跟着动,也就是说,左右指针无论谁先动,都一定要动完整个周期(左->右或者右->左),
如果左侧先动的话,无法保证相遇时对应的数比key小,那么最后交换key的时候就会产生错误,右侧先动的话,右指针的时候就一定会停在比key小的地方,然后左指针开始移动就一定相遇在比key小的值,这就是为什么一定要右指针先走动
思想:也是定义左指针left与右指针right,key对应的下标为piti,然后将左边第一个元素放到key里面,然后此时左边就有了坑,然后右边开始往前走,遇到比key小的,则与key交换,那么此时右指针的地方则变成新坑,然后左边走,左边遇到比key大的数则交换,然后左边变成新坑,以此类推,当相遇时停止,然后将key放进相遇的坑位,然后分割成两个左右数组,左数组为[0,piti-1],而右数组为[piti+1,n-1],然后就是一样的对左右数组进行重新排序,不断地递归
动图演示:
代码:
思想:定义左边第一个数为key,然后定义前指针cur,与后指针prev=cur+1,首先判断cur指针的值,如果比key小,那么prev往后挪,如果prev等于cur,那么cur往后挪,如果cur的值大于key,那么prev不动,cur往后挪,然后当遇到比key小的值,那么prev的后一位就是比key大的,此时prev往后挪然后将prev的值与cur的值交换即可,一直到最后,cur到达尾部时停止循环,然后将key的值与prev对应的值交换,此时prev的值则为key,prev也将两个数组分割成左右两个数组,然后进行递归
动图演示:
代码:
前面三种的key都不约而同地将左边第一个值当成key,假如最左边的数是一个极端值(为数组中最大或者最小的),那么此时将会是最复杂的一种情况
那么此时我们的key可以使用三数取中法确定
三数取中代码:
当数组递归到较小区间时可以使用插入排序
上述三种方法都需要用到递归,不断地递归到小的数组且接近有序,此时插入排序的优点就体现出来了
思想:归并排序可以看成一种分治的思想,就是对数组不断地分割,分割成不可再分割的数组,然后再进行排序递归
动图演示:
代码:
思想:计数排序就是将出现过的元素使用相对映射统计一边,然后重新放回数组里,这种方法比较适合范围较集中的数组
动图演示:
代码:
复杂度与稳定性
稳定性是指,排序的过程中,对于同样的值不会改变先后位置,比如说a=3,b=3吗,a的下标为1,b的下标为2,a在b的前面,排完序之后,a的下标仍然比b的下标要小,也就是a始终在b的前面,那么就是稳定的情况,如果改变了先后位置,那么就是不稳定