一、前言
算法和语言无关、学习路径从线性(排序),树形结构,图形结构慢慢学~
1. 算法思想:
在强调数据结构的重要性后,其中算法思想也是不容忽略:
- 分治算法:归并排序、快速排序……
- 贪心算法:最小生成树……
- 动态规划:最短路径……
- 递归搜索:树形结构……
以上所举的例子可以看出数据结构和算法之间的互相依托程度,例如在学习归并、快速排序时,实则也在探究分治算法……
每个细分领域都是算法,例如以下例子:
- 图形学
- 机器学习
- 人工智能
- 数据挖掘
- 操作系统
- 网络安全
- 高性能计算
二、选择排序(默认都是从小到大排序)
- 在数组中找最小的数字与数组中第一个位置的交换
- 此时数组第一个位子是最小的,那么接着从寻找次小的,与第二个位置交换、以此类推
public static void sort(int[] arr){
int len = arr.length;
for(int i = 0; i < len; i ++){
int minIndex = i;
for(int j = i + 1; j < len; j ++){
if(arr[minIndex] > arr[j])
minIndex = j;
}
swap(arr, minIndex, i);
}
}
三、优化
1. 使用泛型来编写
对于上述代码,只能适用于int类型,而不是能用于double,自定义类型排序。
public static void sort(Comparable[] arr){
int len = arr.length;
for(int i = 0; i < len; i ++){
int minIndex = i;
for(int j = i + 1; j < len; j ++){
if(arr[j].compareTo(arr[minIndex]) < 0)
minIndex = j;
}
swap(arr, minIndex, i);
}
}
2. 在每一个轮中,可以同时找出当前未处理元素的最大值和最小值
3.编写随机生成算法与测试算法性能
- 测试的数据不能太少,可以直接写一个满足随机生成算法测试用例的条件。
- 编写一个赋值函数来测试不同算法之间的性能差异,测试时间运行的长短,通过反射来获取相应的排序算法。
随机生成算法
//生成有n个元素的随机数组,每个元素的随机范围为[rangL, rangR]
public static Integer[] generateRandomArray(int n, int rangL, int rangR){
assert rangL <= rangR;
Integer[] arr = new Integer[n];
for(int i = 0; i < n; i ++){
arr[i] = new Integer((int)(Math.random() * (rangR- rangL + 1) + rangL));
}
return arr;
}
测试性能算法
public static void testSort(String sortClassName, Comparable[] arr){
try {
//通过sortClassName获得排序函数的class对象
Class sortClass = Class.forName(sortClassName);
//通过排序函数Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort", new Class[]{Comparable[].class});
//排序参数只有一个,就是可比较的额数组
Object[] params = new Object[]{arr};
long startTime = System.currentTimeMillis();
sortMethod.invoke(null, params);
long endTime = System.currentTimeMillis();
assert isSorted(arr);
System.out.println(sortClass.getSimpleName() + ":" + (endTime - startTime) + "ms");
}catch (Exception e){
e.printStackTrace();
}
}
测试:
public static void main(String[] args) {
int N = 20000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("wang.sort.SelectionSort03.SelectionSort", arr);
return;
}
四、插入排序
- 保持第一个位置不动,后面位置上的元素比前面小,则交换,换一轮,则第一个位置上的元素就是最小的了
- 继续保持第二个位置上元素不动,接着比较其后面的元素
1. 代码实现注意:
- 插入排序外层循环从下标1开始,因为是从1开始往前比较,一定要保证下标越界问题
- 每次都是往前找,所以j--,而不是j++
public static void sort(Comparable[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
//寻找arr[i]的最佳插入位置
for(int j = i; j > 0 && arr[j].compareTo(arr[j-1]) < 0; j --){
//要是不熟悉泛型的话,直接写arr[i] < arr[j-1]
swap(arr, j, j-1);
}
/* 寻找元素arr[i]合适的插入位置 写法二
for( int j = i ; j > 0 ; j-- )
if( arr[j] < arr[j-1] )
swap( arr, j, j-1 );
else
break;
*/
}
}
2. 对比与代码优化
2.1选择排序和插入排序的根本区别:
插入排序的内循环在满足条件的情况下是可以提前结束的!而选择排序必须遍历每一次循环。所以插入排序理论上比选择排序更快一些。(比如一个值已经比插入点要大了,而它之后的点还比这个值大,那么就不用遍历前面而直接结束了,而选择排序里的for循环必须是len长度,要整个遍历一遍才行~)
2.2 代码优化
(1)减少多次交换swap操作
在实际测试中,插入排序比选择排序稍微慢,原因是插入在执行内循环每次都执行了swap操作,这样会更耗时,其实涉及了三次赋值,更别说数组中索引值访问等消耗时间。
优化: 避免在内循环中使用swap,只进行一次赋值操作,在内循环结束后再进行一次赋值操作。逻辑并没有变,只是将之前的一次次交换修改成赋值操作、性能得到提高。
public static void sort(Comparable[] arr){
int n = arr.length;
for(int i = 1; i < n; i ++){
Comparable e = arr[i];
int j = i;
for( ; j > 0 && arr[j-1].compareTo(e) > 0; j --){
arr[j] = arr[j-1];
}
arr[j] = e;
}
}
所以一般来说,插入排序的优异性是因为在内循环中可以提前结束,所以在部分有序数组中进行排序效果会更好~
在20000个数据,依次是无序,稍微有序,几乎有序的情况下测试、提升到80000个数据继续测试、
可以发现,无序时,查找排序和优化后的插入排序差不多,而在几乎有序的情况下,插入排序的高效性。