排序是计算机科学的最基础的根基之一。基本的排序方法以O()排序。
存在几种排序算法, 以O(NlogN)为时间界。
任何通用的算法均需要次比较。
插入排序:
对于少量元素的排序,它是一个有效的算法。
插入排序的工作方式像许多人排序一手扑克牌。开始时,我们左手为空并且桌子的牌面向下。然后我们每次从桌子上拿走一张牌并将它插入左手的正确位置。我们从右向左将它与已在手中的每张牌进行比较,拿在左手的牌总是排序好的。而左手的牌依次是初始牌堆顶部的牌。
插入时,确定位置后,使所有其他牌后移一位。
public class InsertS {
public static int[] insertSort(int array[]) {
int tmp;
int j;
for(int i=1;i<array.length;i++){
tmp=array[i];//先放在这里了 因为变换的时候array[i]位会变化
//每个内层循环的j从i的前一位开始走,都走到不大于tmp(当前i),或为0的时候停止循环
for(j=i-1;j>=0&&tmp<array[j];j--){
//找到插入位置前,每一个都后移
array[j+1]=array[j];
}
//因为j停止的时候还--了一次
//此处赋值需要修复,所以要加1
array[j+1]=tmp;
}
return array;
}
public static void main(String[] args){
int[] array={83,4,16,88,34,23,76,55,12,99,8,72,22,3} ;
insertSort(array);
for (int i=0;i<array.length;i++){
System.out.println(array[i]);
}
}
}
希尔排序
也叫缩小增量排序,每次选取子数组进行插入排序,非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
逻辑如图:
1.选择增量 gap = 10/2 ,缩小增量继续以 gap = gap/2 的方式,初始增量为 gap = 10/2 = 5,整个数组分成了 5 组
按颜色划分为【 8 , 3 】,【 9 , 5 】,【 1 , 4 】,【 7 , 6 】,【 2 , 0 】对这分开的 5 组分别使用插入排序。
这五组中的相对小元素都被调到前面了。
2.继续进行缩小增量操作,gap = 5/2 = 2,整个数组分成了 2 组【 3 , 1 , 0 , 9 , 7 】,【 5 , 6 , 8 , 4 , 2 】
对这分开的 2 组分别使用上节所讲的插入排序。
3.再缩小增量 gap = 2/2 = 1,整个数组分成了 1 组【 0, 2 , 1 , 4 , 3 , 5 , 7 , 6 , 9 , 0 】
此时,只需要对以上数列进行简单的微调,不需要大量的移动操作即可完成整个数组的排序。
说明部分转载自:https://www.jianshu.com/p/40dcc3b83ddc 五分钟学算法 作者:程序员小吴师兄
代码如下:
package LinkedList.Sort;
/**
* Created by jiangyayi on 19/7/15.
*/
public class ShellSort {
public static void shellSort(int array[]){
int j;
//缩小增量
for(int gap=array.length/2;gap>0;gap/=2){
//数据从gap点开始取,取gap下标及i-gap,举例来说 gap=5,则取5和0两数进行对比
//下一次循环的时候就是2,进行插入排序的数组就加大了。
for (int i=gap;i<array.length;i++){
int tmp=array[i];
//插入排序过程,每次不是--,而是-gap
// //j从开始往前倒,倒到tmp比他小了,或者等于0了,就不走了。
//例如,首次对比的是array[5]和array[0]
for (j=i;j>=gap&&tmp<array[j-gap];j=j-gap){
//移位过程
array[j]=array[j-gap];
}//循环结束插入过程
array[j]=tmp;
}
}
}
public static void main(String[] args){
int[] array={43,4,16,88,34,23,76,55,12,99,8,72,22,3} ;
shellSort(array);
for (int i=0;i<array.length;i++){
System.out.println(array[i]);
}
}
}
归并排序
归并排序使用递归,把数组递归的拆成左右数组,拆到最后独立操作单元只剩两个元素,往tmp中按顺序填写即可。
其中的mergeSort方法用来组织拆分,和合并。merge方法提供两个数组的合并方式,可以用来提供合并两个单独的数,也可以用来合并两个数组。最后tmp的值赋给array,完成排序。
归并排序的运行时间是O(NlogN),使用线性附加内存。Java中的array.sort使用的是归并排序的优化版本。
package LinkedList.Sort;
/**
* Created by jiangyayi on 19/7/12.
*/
public class MergeS {
public static void mergeSort(int []array,int left,int right,int [] tmp){
if(left<right){
//只有left小于right时候,才递归
int mid=(left+right)/2;
mergeSort(array,left,mid,tmp);
mergeSort(array,mid+1,right,tmp);
merge(array,left,mid,right,tmp);
}
}
//merge是merge每个递归迭代的左子数组和右子数组,所以传入的参数有数组,左指针,右指针,中点指针和tmp数组
private static void merge(int []array,int left,int mid,int right,int[] tmp){
//if是一次迭代 但其实需要循环,同时,mid和mid+1需要定义,还是因为需要循环
//
// if(left<mid&&mid+1<right){
//左边指针需要从left开始
//右边指针需要从mid+1开始
int i=left;
int j=mid+1;
int k=0;
//父母双全,左右都有的时候
//注意等号
//判断的是左指针和mid 判断的是mid+1和右指针
while (i<=mid&&j<=right){
if(array[i]<array[j]){
tmp[k]=array[i];
k++;i++;
}else {
tmp[k]=array[j];
k++;j++;
}
}
//只有左数组了
while (i<=mid){
tmp[k]=array[i];
k++;i++;
}
//只有右数组
while (j<=right){
tmp[k]=array[j];
k++;j++;
}
k=0;
//此处是赋值操作,把tmp赋给array
//赋值注意等号
while (left<=right){
array[left++]=tmp[k++];
}
}
public static void main(String[] args){
int[] array={83,4,16,88,34,23,76,55,12,99,8,72,22,3} ;
int[] tmp=new int [array.length];
mergeSort(array, 0, array.length - 1,tmp);
for (int i=0;i<array.length;i++){
System.out.println(array[i]);
}
}
}
快速排序
快排是一种实践快速的排序算法。平均运行时间是O(NlogN)。
通常编程中,我们选定首位元素为锚点进行对比,但这种选择可能存在问题。
即如果序列基本有序,快速排序的递归过程可能基本没有作用,存在数据倾斜,某一边的数据量几乎等于整个数组长度,如第一位为数组最小值时。所以进一步的锚点选择方式叫“三数值分割”。当然,随机选择也可以基本解决这种问题。
此处我们基于排序算法的解析,暂时省略锚点的选择优化,仍然选择首位作为锚点。
快排的一句话核心思想:选定一个锚点(通常选择数组第一位),比他小的往前挪,比他大的往后挪。然后递归的再去排序锚点的左数组和锚点右数组。
一个快排数组过程:
代码如下:
package LinkedList.Sort;
/**
* Created by jiangyayi on 19/7/15.
*/
public class QuickS {
//快排的一句话核心思想:选定一个锚点(通常选择数组第一位),比他小的往前挪,比他大的往后挪。
// 然后递归的再去排序锚点的左数组和锚点右数组。
public static void quickSort(int array[],int left,int right){
//注意所有递归都有结束条件,然后quickSort由于锚点位置已经确定,不参与递归过程
if(left<right){
//也不定义mid,而是用index作为递归核心,递归index的左右边
int index=getIndex(array,left,right);
quickSort(array,left,index-1);
quickSort(array,index+1,right);
}
}
//递归找锚点的合适数组位置,其最后确定的位置,就是快排的递归标志位
private static int getIndex(int array[],int left,int right){
//在循环外存放本子数组的锚点
int tmp=array[left];
//双层循环,外层循环负责对调次数
while (left<right){
//内层循环包含对比和左右各一次赋值过程,由于存了左边,先对比右边
while (left<right&&array[right]>=tmp){
right--;
}//如果小于了,赋值一次
array[left]=array[right];
while (left<right&&array[left]<=tmp){
left++;
}array[right]=array[left];
}//left不小于right了,即等于时,循环结束了
// 外层循环tmp赋值给left位置,同时返回left位置为锚点切分位
array[left]=tmp;
return left;
}
public static void main(String[] args){
int[] array={43,4,16,88,34,23,76,55,12,99,8,72,22,3} ;
quickSort(array, 0, array.length - 1);
for (int i=0;i<array.length;i++){
System.out.println(array[i]);
}
}
}
堆排序
堆排序的逻辑在于堆的性质和构建,最后使用findMin(Max)方法返回数值即可。
其排序方式其实是一种优先队列的应用方法,经常用来求解大数据量级下的TopK问题。
具体实现移步:优先队列(堆)的原理和应用