一、概念
1.1、排序
排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
平时的上下文中,如果提到排序,通常指的是排升序(非降序)。 通常意义上的排序,都是指的原地排序(in place sort)
原地排序:就是指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。
1.2、稳定性(重要)
两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法
1.3、应用
1. 各大商城的价格从低到高等
2. 中国大学排名
二、七大基于比较的排序-总览
三、插入排序
3.1、直接插入排序-原理
整个区间被分为
- 有序区间
- 无序区间
每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入
插入排序非常简单,就像打扑克牌一样,假设我们拿到第一张牌,那第一场牌肯定是有序的,然后那第二张牌和第一张牌比较,如果第二张牌比第一张小,那就将第一张牌放在前面,以此类推
根据这个思维:第一个数据是有序的,也就是说:在我们遍历的时候,是从下标1 开始的。
具体的操作见下图:
3.2、实现直接插入排序
/**
* 直接插入排序,按照从小到达的循序
* @param array
*/
public static void insertSortUp(int[] array){
for (int i = 1; i < array.length; i++) {
int tmp = array[i];
int j = i-1;
for ( ;j >= 0; j--) {
if(array[j] > tmp){
array[j+1] = array[j];
}else {
break;
}
}
array[j+1] = tmp;
}
}
3.3、插入排序的性能分析
插入排序的稳定性:
结论
一个稳定的排序,可以实现为 不稳定的排序。 但是,一个本身就不稳定的排序是 无法变成 稳定的排序。
直接插入排序 是 有序的。 它的时间复杂度是 O(N^2);最好情况:O(N【数组有序】 也就是说:对于直接插入排序,数据越有序越快!
由此,不难联想到:直接插入排序有时候 会用于 优化 排序。
【假设:假设我们有一百万个数据需要排序,在排序的过程中,区间越来越小,数据越来越有序。直接插入排序的时间复杂度为 O(N),N
越来越小,那么,使用 直接插入排序是不是越来越快!也就是说:直接插入排序 有时候会 用于 排序优化】 直接插入排序经常使用在
数据量不多,且整体数据趋于有序的。
四、希尔排序
4.1、原理
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数n,把待排序文件中所有记录分成n个组,所有距离为 (数据量/n) 的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达只有一个组时,所有记录在统一组内排好序。
- 希尔排序是对直接插入排序的优化。
- 当gap > 1(gap就是要分成的组数)时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很
快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比
正常情况下我们进行分组的时候都是按照顺序进行分组的
比如说:
那么,问题来了!我们怎去确定分多少组,而且越分越少。
【取自清华大学出版的一本书《数据结构》】
但是科学家的思维不同,他们分组的时候是跳着分组的
比如说:
上图转载于:https://blog.csdn.net/DarkAndGrey/article/details/122792097
4.2、模拟实现 - 希尔排序
/*
* 时间复杂度和增量有关系,所以无法得出准确的时间复杂度
* 但只需要记住:在一定的范围里,希尔排序的时间复杂度为 O(N^1.3 ~ N^1.5)
* 空间复杂度为 O(1)
* 稳定性:不稳定
* 判断稳定性的技巧:如果在比较的过程中 发生了 跳跃式交换。那么,就是不稳定的排序。
* */
public static void shell(int[] array,int group){
for (int i = group; i < array.length; i++) {
int j = i - group;
int tmp = array[i];
for( ; j >=0; j = j-group){
if(tmp < array[j]){
//交换元素
array[j+group] = array[j];
array[j] = tmp;
}else {
break;
}
}
array[j+group] = tmp;
}
}
public static void shellSort(int[] array){
//将整个数组分为5组
int gap = 5;
while(gap > 1){
//将分好组的数进行插入排序
shell(array, gap);
//循环得到分多少组,将分的组数按照 group / 2 来分组
gap = gap / 2;
}
//循环结束,那就说明前面分好组的数都排序好了,只剩下最后的一组 就是 gap = 1的组
shell(array, 1);
}
五、选择排序
5.1、原理
上图转载于:https://blog.csdn.net/DarkAndGrey/article/details/122792097
优化
定义 一个 变量, 用来记录 此时的 i 后面最小值的下标。等 j 遍历完了,最小值的下标也就拿到了。此时,再进行交换。
这样就不必让上面那样,遇到比 i下标元素 小的,就交换。
5.2、模拟实现- 选择排序
/*
* 选择排序
* 稳定性: 不稳定 见附图
* 时间复杂度:O(N^2) 》》 外层循环 n -1,内层循环 n -1
* 空间复杂度:O(1)
* */
public static void selectSort(int[] array){
for (int i = 0; i < array.length; i++) {
for (int j = i+1; j < array.length; j++) {
if(array[i] > array[j]){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
}
}
5.3、双向选择排序 (了解)
每一次从无序区间选出最小 + 最大的元素,存放在无序区间的最前和最后,直到全部待排序的数据元素排完 。
上图转载于:https://blog.csdn.net/DarkAndGrey/article/details/122792097
public static void selectSortOP(int[] array){
int low = 0;
int high = array.length - 1;
// [low,high] 表示整个无序区间
while(low < high){
int min = low;
int max = low;
for (int i = low+1; i <= high; i++) {
if(array[i] < array[min]){
min