排序大总结(冒泡,选择,希尔,快排(及其优化),堆排序,插入排序,归并排序)

#排序模板
public abstract class Sort<T extends Comparable<T>> {
    public abstract void sort(T[] nums);

    protected boolean less(T v,T w){
        return v.compareTo(w)<0;
    }
    protected void swap(T[] a,int i,int j){
        T t=a[i];
        a[i]=a[j];
        a[j]=t;
    }
}
##选择排序
/**
 * 选择排序:选出最小的一个放到第一个位置,再在第二个位置选出最小的
 * 放到第二个位置
 * 时间复杂度:N*N/2+N次交换,空间复杂度1.
 */

import Sort.Sort;
class Selection <T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {
        int len=nums.length;
        for(int i=0;i<len;i++){
            int min=i;
            for(int j=1;j<len;j++){
                if(less(nums[j],nums[min])){
                    min=j;
                }
            }
            swap(nums,i,min);
        }
    }
}
##冒泡排序
/**
 *  冒泡排序:从后往前遍历,从左到右不断和相邻的比较交换,不断的让最大的或者最小的冒泡到最后
 *  优化:加个flag,初始化为false,如果遍历一遍之后没有元素交换说明已经有序直接退出。
 */
import Sort.Sort;
class BubbleSort <T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {
        boolean flag=false;
        int len=nums.length;
        for(int i=len-1;i>0&&!flag;i--){
            for(int j=0;j<i;j++){
                flag=true;
                if(less(nums[j+1],nums[j])){
                    flag=false;
                    swap(nums,j,j+1);
                }
            }
        }
    }
}
##插入排序
 /**
 * 插入排序:从头开始插,然后形成有序部分
 * 说白了就是交换逆序对
 *
 */

import Sort.Sort;
class InsertSort <T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {
        int len=nums.length;
            for (int i=0;i<len;i++){
                for(int j=i;j<len&&less(nums[j],nums[j-1]);j++){
                    swap(nums,j,j-1);
                }
            }
        }
    }
##希尔排序
/**
 * 希尔排序:是插入排序的优化,插入排序是一个一个交换,每次交换一个
 * 希尔排序是每次交换大于一个。给定一个h,对间隔h的进行比较交换
 * h逐渐减小,最后h=1.数组有序。
 */

import Sort.Sort;
class InsertSort <T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {
        int len=nums.length;
        int h=1;
        while(h<len/3){
            h=3*h+1;
        }
        while(h>=1){
            for (int i=h;i<len;i++){
                for(int j=i;j>=h&&less(nums[j],nums[j-h]);j-=h){
                    swap(nums,j,j-h);
                }
            }
            h=h/3; 
        } 
    }
}
##归并排序
/**
 * 归并排序:将数组分成两部分,分别进行排序,然后归并起来
 * 初始化一个复制数组
 */

import Sort.Sort;
class InsertSort <T extends Comparable<T>> extends Sort<T> {
    T[] aux;
    @Override
    public void sort(T[] nums) {
        //自顶向下归并排序
        aux=(T[]) new Comparable[nums.length];
        sort(nums,0,nums.length-1);
    }

    private void sort(T[] nums, int l, int h) {
        if(l>=h){
            return;
        }
        int mid=(l+h)<<1;
        sort(nums,l,mid);
        sort(nums,mid+1,h);
        merge(nums,l,mid,h);
    }

    public void merge(T[] nums,int l,int m,int h){
        int i=l;
        int j=m+1;
        for(int k=l;k<=h;k++){
            aux[k]=nums[k];
        }
        for(int k=l;k<=h;k++){
            if(i>l){
                nums[k]=aux[j++];
            }
            else if(j>h){
                nums[k]=aux[i++];
            }
            else if(aux[i].compareTo(aux[j])<=0){
                nums[k]=aux[i++];
            }else{
                nums[k]=aux[j++];
            }
        }
    }
}
##快速排序
/**
 * 快速排序:首先先把数据随机打乱,然后选取一个值,然后让比这个数大的在前,小的在后。
 * 然后在分别对这两个数组排序
 */

import Sort.Sort;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

class QuickSort <T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {
        shuffle(nums);
        sort(nums,0,nums.length-1);
    }

    private void shuffle(T[] nums) {
        List<Comparable> list=Arrays.asList(nums);
        Collections.shuffle(list);
        list.toArray(nums);
    }

    private void sort(T[] nums, int l, int h) {
        if(h<=l){
            return;
        }
        int j=patition(nums,l,h);
        sort(nums,l,j-1);
        sort(nums,j+1,h);
    }

    private int patition(T[] nums, int l, int h) {
        int i=l;
        int j=h+1;
        T v=nums[l];
        while(true){
            while(less(nums[++i],v)&&i!=h);
            while(less(v,nums[--j])&&j!=l);
            if(i>=j){
                break;
            }
            swap(nums,i,j);
        }
        swap(nums,l,j);
        return j;
    }
}
###快排性能分析
快排是原地排序,很考验选取的数的值,和数据的随机度,最好的情况下每次都能将数组对半分这样递归调用次数最少,复杂度是O(n*log(n))
最坏的情况是数组有序,找最小或者最大的元素,这样就是冒泡排序类似,复杂度O(n*n/2)
因此我们可以优化快排:
#当小数组的情况时,可以使用插入排序,插入排序快
#选取标准值的时候可以用三数取中,最好的情况时每次都能取到中位数,但是代价太高,可以取三个数,比大小找到中间那个当中位数。
#三向切分,对于有大量重复元素的时候可以使用三向切分,小于,等于,大于。
private void sort(T[] nums, int l, int h) {
        if(h<=l){
            return;
        }
       int lt=l,i=l+1,gt=h+1;
        T v=nums[l];
        while(i<=gt){
            int cmp=nums[i].compareTo(v);
            if(cmp<0){
                swap(nums,++i,lt++);
            }else if(cmp>0){
                swap(nums,--gt,i);
            }else{
                i++;
            }
        }
        sort(nums,l,lt-1);
        sort(nums,gt+1,h);
    }
##扩展,利用快排,选出第k个数。
  public T select(T[] nums,int k){
        int i=0;
        int h=nums.length-1;
        while(h>i){
            int j=patition(nums,i,h);
            if(j==k){
                return nums[k];
            }else if(j>k){
                h=j-1;
            }else{
                i=j+1;
            }
        }
        return nums[k];
    }
      private int patition(T[] nums, int l, int h) {
        int i=l;
        int j=h+1;
        T v=nums[l];
        while(true){
            while(less(nums[++i],v)&&i!=h);
            while(less(v,nums[--j])&&j!=l);
            if(i>=j){
                break;
            }
            swap(nums,i,j);
        }
        swap(nums,l,j);
        return j;
    }
##堆排序
/**
 * 堆排序:堆中的节点总是大于等于子节点,堆是完全二叉树,因此如果节点k,k的子节点分别是2k+1,2k+2,(0是下标的时候)
 */

import Sort.Sort;


class Heap <T extends Comparable<T>> extends Sort<T> {
    public  T[] heap;
    public int N=0;
    public Heap(int maxN){
        this.heap=(T[]) new Comparable[maxN+1];
    }
    public boolean isEmpty(){
        return N==0;
    }
    public int size(){
        return N;
    }
    public boolean less(int i,int j){
        return heap[i].compareTo(heap[j])<0;
    }
    private void swap(int i,int j){
        T t=heap[i];
        heap[i]=heap[j];
        heap[j]=t;
    }
    //上浮
    public void swim(int k){
      while(k>1&&less(k/2,k)){
          swap(k/2,k);
          k=k/2;
      }
    }
    //下沉
    public void sink(int k){
        while(2*k<=N){
            int j=2*k;
            if(j<N&&less(j,j+1)){
                j++;
            }
            if(!less(k,j)){
                break;
            }
            swap(k,j);
            k=j;
        }
    }
    public void insert(Comparable v){
        heap[++N]= (T) v;
        swim(N);
    }
    public T delMax(){
        T max=heap[1];
        swap(1,N--);
        heap[N+1]=null;
        sink(1);
        return max;
    }
    //堆排序,无序数组也可以从头到尾上浮,从尾到头下沉。从尾到头,速度比较快因为这样在双方子节点有序,就会遍历一半即可
    @Override
    public void sort(T[] nums) {
        N=nums.length-1;
        for(int i=N/2;i>=0;i--){
            sink(i);
        }
        while(N>1){
            swap(1,N--);
            sink(1);
        }
    }
}
###分析
一个堆的高度是log(n),所以插入删除复杂度是log(n),堆排序是对N个上浮下沉是n*log(n)、
堆排序是一种原地排序,没有利用额外的空间。
现代操作系统很少使用堆排序,因为它无法利用局部性原理进行缓存,也就是数组元素很少和相邻的元素进行比较和交换。

##########总结
稳定性:是否改变原来数组中元素的相对位置。
冒泡排序,插入排序,归并排序。


算法	稳定性	    时间复杂度	               空间复杂度	        备注
选择排序	×	      N2	                         1	
冒泡排序	√	     N2	                         1	
插入排序	√	     N ~ N2	                     1	    时间复杂度和初始顺序有关
希尔排序	× 	   N 的若干倍乘于递增序列的长度	  1   	  改进版插入排序
快速排序	×	      NlogN	                      logN	
三向切分快速排序	× 	N ~ NlogN	              logN	适用于有大量重复主键
归并排序	√	      NlogN	N	
 堆排序	  ×       NlogN	                      1       无法利用局部性原理

###快速排序是最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间近似为 ~cNlogN,这里的 c 比其它线性对数级别的排序算法都要小。

使用三向切分快速排序,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。



###java的排序算法实现
java主要的排序方法是java.util.Arrays.sort
对于原始数据类型使用三向切分快排
对于引用类型使用归并排序

 

全部评论

相关推荐

dongsheng66:如果想进大厂的话,在校经历没必要占这么大篇幅,可以把专业技能单独放一个专栏写,可以加个项目经历
点赞 评论 收藏
分享
已老实求offer😫:有点像徐坤(没有冒犯的意思哈)
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
11-24 20:55
阿里国际 Java工程师 2.7k*16.0
程序员猪皮:没有超过3k的,不太好选。春招再看看
点赞 评论 收藏
分享
评论
点赞
1
分享
牛客网
牛客企业服务