一、排序算法概念
1. 排序算法分类
A. 基于比较的内部排序算法根据比较策略的不同的分类

B. 外部排序(大多需要借助外存)的排序分类:

2. 内部排序算法的稳定性:待排序的序列中,若存在值相等的元素,经过排序后,相等元素之间的顺序保持不变,这种特性称为排序算法的稳定性.
A. 例子如下

B. 保持稳定性的作用:对一组数据进行多轮的排序(对不同属性),如果使用不稳定的排序算法,则可能做很多次重复的排序
例如一组学生的数据包含学生的成绩和年龄,使用不稳定的排序算法,先根据成绩排序,再根据年龄排序,则第二次排序相当于覆盖了第一次排序
C. 稳定性算法的应用场景:

若是对同一属性,则稳定和不稳定算法没什么影响
二、选择排序算法实现思想和实现代码(默认结果为升序)
1. 基于选择的思想:每次从无序区间中选择最大/最小值,放在无序区间的最开始/最末尾(作为有序区间),直到整个数组有序
2. 堆排序:每次将堆顶元素(最大值/最小值)放到无序区间的最末尾,直到整个数组有序,因此堆排序是基于选择思想的排序,堆排序具体实现思路和代码在优先级队列那一块(DAY 17)
堆排序性能分析:
3. 直接选择排序实现思路和实现代码
(1)实现思路
A. 在整个无序区间中选取最小值放在无序区间的最开始。每次都将当前区间的最小值放在数组最开始的位置,每次选一个元素,这个元素就放到了正确的位置
B. 无序元素为n,每一趟取最小值和放值的过程,则有序区间元素 + 1 ,无序区间元素 - 1 ,则需要n-1趟(注:因为每次取最小值放到争取的位置,则剩余的最后一个不用排序就自动是有序的了)能使得有序区间元素为n
(2)稳定性分析:不稳定,因为涉及和最小值交换,因此没办法保证相对位置
(3)有序区间和无序区间,其中i是外层循环遍历,初始为0
A. 有序区间:[0,i)
B. 无序区间:[i,arr.length - 1]
(4)性能分析
A. 时间复杂度O(n^2) :如下图代码所示,时间复杂度为交换次数和比较次数
a. 最好情况:数组完全有序:不用交换,只用比较,比较次数为:n - 1 + n - 2 +... + 1 = O(n^2)
b. 最坏情况:数组完全逆序:交换n - 1次,比较次数为:n - 1 + n - 2 +... + 1 = O(n^2)
C. 平均情况:因此,综上规则,平均时间复杂度也是O(n^2)

B. 空间复杂度O(1):一个min变量
C. 稳定性:不稳定
(5)实现代码和测试


三、插入排序算法实现思想和实现代码(默认结果为升序)
1. 基于插入的排序的思想:每次在无序区间中选择一个元素,插入在有序区间的合适位置,直到整个数组有序
2. 直接插入排序:
(1)无序区间和有序区间:
A. 无序区间:[i,arr.length)
B. 有序区间:[0,i)。初始默认第一个元素有序
循环伪代码:
//注意:这里的i不能取arr.length - 1,因为如果是i < arr.length - 1,则最后一次只能保证得到的结果是两个有序的序列的拼接,不能保证整个数组是有序的
//每走一次内层循环,就将无序区间的第一个元素插入到有序区间的合适的位置,直到整个数组有序,也是走n - 1趟,因为默认有序区间初始有一个元素且有序
for(int i = 1; i < arr.length; i ++){
//不断向索引为i的元素插入到前面的有序区间中合适的位置
}
(2)图文例子:

(2)实现代码和测试


(3)内层循环的简化写法:因为只有当arr[j] < arr[j - 1]才交换,因此可以将arr[j] >= arr[j - 1]写在循环的继续条件中,则循环的继续条件为 j >= 1 && arr[j] >= arr[j - 1]
(4)算法性能分析:
A. 时间复杂度
a. 最好情况:数组完全有序:每一躺只比较一次,比较n - 1躺,因此时间复杂度为O(n)

b. 最坏情况:数组完全逆序:n - 1躺,第一躺内层走1次,第二躺内层走2次....第n - 1躺内层走n - 1次,因此等差数列求和,则时间复杂度为O(n^2)
c. 平均情况:也是O(n^2)
B. 空间复杂度:O(1)
C. 稳定性:稳定,相等的时候不交换能够保证稳定性
(5)使用场景:当数组近乎有序(只有几个元素乱序,整体基本上已经有序;或者是元素个数比较少的情况)的时候,直接插入排序的时间复杂度可以看作为O(n)(已经有序的部分,内层循环只用比较一次,因此近似为O(n)),因此插入排序经常作为其他高阶排序算法的辅助优化手段。
原理:当待排序数组的区间(实验论证为16个以内;JDK是规定64个以内)很小,这样的数组都可以认为是近乎有序的数组,使用插入排序进行排序即可效率较高。

注:可以把这个东西看作是混乱都一样的东西,元素越少,则混乱的可能性越小
3. 希尔排序:针对直接插入排序的优化
(1)希尔排序的核心思想:直接插入排序在近乎有序或者在小数据规模的数组上性能非常的好。因此希尔排序采用先将数组调整为近乎有序,然后使用直接插入排序。
(2)希尔排序的宏观思路:先将数组调整为近乎有序,然后在整个有序的数组上来一次直接插入排序
(3)希尔排序的微观操作:
A. 先选定一个整数gap,gap初始值一般为设置为arr.length / k(k为除数,此处k设置为2,一般也就设置为2,3或者5,实验论证)
B. 将待排序的集合中分为gap组:所有距离为gap的元素为同一组,对同一组的元素进行直接插入排序。不断缩小这个gap的长度(gap = gap / 2),然后进行直接插入排序
C. 直到gap == 1,则说明距离为1元素为同一组,实际上也就是整个数组为同一组,对其来一次直接插入排序即可
注意:根据B和C的特性,因此可以合并B和C,即:循环的终止条件写为gap == 0
(4)图文例子

(5)分组的直接插入排序方法思想

四、实际排序测试辅助类(包含下列方法实现)
1. 生成一个大小为n的,区间在[l,r]的随机数数组
补充知识:如果我们想要生成一个随机数,通常会使用Random类。但是在并发情况下Random生成随机数的性能并不是很理想,在这种情况下一般使用ThreadLocalRandom。固定套路为:创立一个该类的对象,然后调用相应的方法
例如:

2. 生成一个数组长度为n近乎有序的数组.swapstimes表示交换次数,交换次数越多,概率角度上讲,越不有序
思路:生成一个有序的数组,然后选取随机的值进行几次交换,即为近乎有序的数组
3. 测试不同的排序算法,输出排序时间(利用反射和计时器)
4. 验证数组是否有序(默认升序)
思路:因为是默认升序,因此遍历数组比较前一个和后一个的值,如果出现反例(前一项大于后一项)则返回false,遍历完整个数组没返回则说明已经有序,返回true即可
5. 得到原先arr数组的深拷贝:通过Arrays.copyOf方法
上述实现代码

456

被折叠的 条评论
为什么被折叠?



