无论是开发还是架构,是java还是人工智能,算法都是基础。每种算法的关键点,与其他算法的区别联系,都要宏观理解掌握。计算机算法排序包括内排序(内存)和外排序(磁盘IO),下文主要针对内排序,对比分析。
不同的排序算法有着不同的时间开销和空间开销,比如常用的快速排序和插入排序,需要对序列中的数据进行比较,这样的统称为“基于比较的排序”,它的时间下限是不能超越 O(NlogN)的,证明略。对应的有时空权衡的“非基于比较的排序”:桶排序、基数排序等,它可以突破O(NlogN)时间下限,但注意的是,分配排序的使用是有条件限制的,例如桶排序是受元素大小限制,和元素分布限制等。在特定场合下,非基于比较的排序的运算速度很快。
直接选择排序—蛮力法
思想:假设选择的第一个是最小值,作为有序数组。依次和后边的元素比较,如果后边的比假设最小的还小,那就交换,每次都能选出最小的。核心特点是假设最小值,记录下标索引。
时间复杂度:O(n²)
public static void simpleSelect(int[] a){
for (int i =0; i
直接插入排序—减治法
思想:每次插入都与邻近记录逐个比较,直到找到第一个不大于新纪录的值停止。所以它是稳定的,遇到值相等的,插入排序仍然会维持原始顺序,直到排序结束二者的相对顺序也没有改变。特点是短数据、基本有序。
时间复杂度:O(n²)
public static void sort(int[] arr){
for (int i=1; i=0&&temp
冒泡排序--蛮力法
思想:两两比较,如果逆,交换,直至最大or最小。
时间复杂度:O(n²)
private static void sort(int[] a){
for (int i=0; i a[j+1]){
int temp = a[j];
a[j] = a[j+1];
a[j+1] =temp;
}
}
System.out.println("第"+i+"遍:"+ Arrays.toString(a));
}
}
堆排序--变治法 --直接选择排序的升级版
思想:构建堆(最大堆/最小堆),减少重复的比较。
时间复杂度:O( nlogn)
public class HeapSort {
public static void sort(int[] a){
for (int i =a.length/2-1; i>=0; i--){
adjustHeap(a,i,a.length);
}
for (int j=a.length-1; j>0; j--){
swap(a,0,j);
adjustHeap(a,0,j);
}
}
public static void adjustHeap(int[] a, int i, int length){
for (int k = 2*i+1; k a[i]){
a[i] =a[k];
a[k] = temp;
i =k;
}else {
break;
}
System.out.println( Arrays.toString(a));
}
}
public static void swap(int[] a, int root, int last){
int temp = a[root];
a[root] = a[last];
a[last] =temp;
}
public static void main(String[] args) {
int[] arr ={4,6,8,5,9};
System.out.println("原数据:"+Arrays.toString(arr));
sort(arr);
}
}
希尔排序—变治+分治+减治法 --插入排序的升级版
思想:分组版的插入排序,类似折半方法,先分组排序,使数据基本有序。
时间复杂度:接近 O( nlogn)
public class ShellSort {
public static void sort(int[] arr) {
int len = arr.length;
int gap;//步长
int istIndex;//插入位置索引
int tmp;
System.out.println("原始顺序: "+ Arrays.toString(arr));
//按照步长来分组
for(gap = len / 2; gap >= 1; gap /= 2) {
//类似插入排序的方法
for (int i = gap; i < len; i++) {
tmp = arr[i];//取出暂存
istIndex = i;//插入的位置
while ((istIndex > (gap-1) && tmp < arr[istIndex - gap])) {
//插入位置往前移,寻找合适位置
arr[istIndex] = arr[istIndex - gap];
istIndex -= gap;
}
arr[istIndex] = tmp;
}
System.out.println("步长为"+gap+"的排序: "+ Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr = new int[10];
//初始化数组
for (int i = 0; i < 10; i++) {
arr[i] = (int) (Math.random() * (100 + 1));
}
ShellSort.sort(arr);
}
}
快速排序--分治法 --冒泡排序的升级版
思想:二分折半的思想,找出基准值 (start+end)/2;L从左边找比基准值大的数,R从右边找比基准值小的数,然后交换,直到L和R相遇。
时间复杂度:O( nlogn) 尤其是数据量大的时候,更明显。
public class QuickSort {
public static void sort(int[] arr,int low, int hith){
int i = low;
int j = hith;
int key = arr[low];
if (i>=j){
return;
}
while (ikey){
j--;
}
while (i
归并排序--分治法
思想:分而治之,将问题分为子问题递归求解,再“治”合并子问题。是稳定的排序算法。
时间复杂度:O( nlogn) 仅次于快速排序
public class MergeSort {
public static void main(String []args){
int []arr = {9,8,7,6,5,4,3,2,1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int []arr){
int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
sort(arr,0,arr.length-1,temp);
}
private static void sort(int[] arr,int left,int right,int []temp){
if(left
关于算法是否稳定,可以总结为是否是相邻比较,如果都是相邻比较,那肯定是稳定的排序算法;如果有不相邻之间的比较,那就是不稳定的排序算法,因为不相邻的比较,会打乱原序列相同数据的相对有序性。
桶排序和计数排序,很多资料表明二者是一种排序,都是对号入座。至于说是否稳定,得看实现方法。我知道的有2种实现方法,一种是稳定的,一种是不稳定。参考这篇文章《计数排序、桶排序》,文章里说的计数排序,就是稳定的排序,它的排序结果是从初始序列的尾部开始往前扫描,以保证算法的稳定性。而文章里的桶排序,是直接顺序地扫描数组C(此时C[i]可以表示A中值为i的元素的个数),就可以求出排序后的结果,所以它不能求出稳定的order,不稳定。