排序算法
1、插入排序
1-1直接插入排序
->思想:把待排序的数据按其值的大小插入到已经排好序的有序数组中,直到所有数据插完为止,即可得到一个有序数列。
->时间复杂度:最坏情况下:O(N^2) ->数据逆序时
最好情况下:O(N) ->数据有序时
->稳定排序
->使用场景:数据越有序越快,适用于趋于有序的数组
1-2希尔排序【也称:缩小增量排序】
->思想:先选定一个整数n【增量】,把待排序的数据分成n个组,这些数据中所有距离为n的为一组,组内进行插入排序;然后缩小增量【n变小】,继续上述操作,直至n为1,就可排好数据。
->希尔排序是对直接插入排序的优化;当增量>1时,相当于是预排序。
->gap的取值方法很多,导致很难去计算,因此在一些资料中给出的希尔排序的时间复杂度不固定
暂时的时间复杂度:O(N^1.3 ~N^1.5)
->不稳定排序
2、选择排序
2-1 直接选择排序
->思想:每次从待排的数据中选择出一个最小(降序,就选最大)的元素,放到序列的起始位置,直至所有数据被排完。
每次只排一个。
直接选择排序的进阶:
每次排两个。思想:与直接选择排序类似,假设要升序的数据,每次从待排的数据中选择出一个最小的元素放到序列的起始位置,同时选择一个最大的元素放于末位置,直至所有数据被排完。
实现如下:
上面标※的内容,是考虑到最大值就在left位置时,此时要将找到的最小值与其进行交换,会导致最大值就跑到了交换后最小值的位置,因此需要修正一下。
2-2堆排序
降序:建立小根堆
升序:建立大根堆;每次让最后一个数据与堆顶元素进行交换;交换完之后向下调整;直到所有数据交换完成
->堆排序的时间复杂度:O(NlogN)
->空间复杂度:O(1)
->不稳定的排序
3、交换排序
3-1 冒泡排序
i表示趟数,每次排一个;
j每次交换找到最大的/最小的
->没优化的时间复杂度:O(N^2)
->优化后的时间复杂度:O(N)
->稳定的排序
3-2 快速排序
思想:
主框架如下图:让待排序元素的某个值作为基准值,将序列按照该基准值分为两个部分,左边是小于它的,右边是大于它的;然后左右子序列重复这个过程;直到该序列排好序即可。
针对每种与基准值交换的方法【分割成两个序列的方法不同】:
法1:hoare
->最坏情况:顺序时,O(N^2)
->时间复杂度:理想情况,O(NlogN)
->使用场景:无序的情况。在有序的情况时,容易栈溢出。【递归】
法2:挖坑法
把第一个位置挖出来作为基准值,第一个位置就为坑了;从后头开始,将小于基准值的放到坑中,此时,该位置为新的坑;在从左边开始,将大于基准值的放到新坑中,此刻这个位置就是新的坑,以此类推。
->快排时间复杂度:理想情况下O(NlogN);有序或者逆序时O(N^2)
->快排空间复杂度:最好O(N);最坏:O(N),当N足够大时,递归的深度就大。
解决方案【让序列尽可能均匀分割】:
1、随机选取基准法
2、三数取中法【找前中后三个位置的数,进行比较,把第二大的数作为基准】
针对有序的数据:
如果待排数据较少时,可以采用直接插入排序。
->快速排序不稳定。
非递归实现快速排序:
public static void quickSort2(int[] array){
Stack<Integer> stack = new Stack<>();
int left = 0;
int right = array.length-1;
int pivot = patrition(array,left,right);
//前边超过一个数字了就入栈
if (pivot > left+1){
stack.push(left);
stack.push(pivot-1);
}
if (pivot < right-1){
stack.push(pivot+1);
stack.push(right);
}
while (!stack.isEmpty()){
right = stack.pop();
left = stack.pop();
pivot = patrition(array,left,right);
if (pivot > left+1){
stack.push(left);
stack.push(pivot-1);
}
if (pivot < right-1){
stack.push(pivot+1);
stack.push(right);
}
}
}
4、归并排序
先分解,再合并。
->不论什么数组,时间复杂度:O(NlogN)
->空间复杂度:O(N)
->稳定的排序