经典排序算法

选择排序:就是从第一个数开始遍历,和后面的所有元素相比,找到最小值,和第一个元素调换位置,依次向后排列

public class SelectSort{   //Comparable 相当于 泛型

    public void sort(Comparable[] array){
        int L = arr.length   //数组长度
        for(int i = 0; i < L; i++){
            int min = i;
            for(int j = i + 1, j < L;j++){
                while(less(arr[j],arr[min])) min = j;   
            }
            swap(arr,i,max);
        }
    }

    private static boolean less(Comparable v , Comparable w){
        return v.compareTo(w) < 0 ;  // 这个方法用于两个数之间的比较,< 0 , v 小于 w
    }    

    private void swap(int[] arr,int a,int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
    public static boolean isSorted(Comparable[] a){
        for(int i = 1; i< a.length; i++){
            if(less(a[i],a[i-1])) return false;
        }
        return true;
    }  // 查看数组是否有序
}

插入排序:

public class Insertion{

    public void sort(Comparable[] arr){
        int N = arr.length;
        for(int i = 1 ; i < arr.length ; i++){
            for(int j = i ; j > 0 && less(arr[j] , arr[j-1] ; j--){
                swap(arr , j , j-1);
            } 
        }
    }
    //less() , swap()等方法请见选择排序算法
    
}

希尔排序

解决的问题:对于大规模乱序数组插入排序很慢,因为它只会交换相邻的元素,希尔排序为了加快速度,交换不相邻的元素以对数组的局部进行排序

public class shell{
    public static void sort(Comparable[] a){
        int N = a.length;
        int h = 1;
        while(h < N/3) h=3*h+1;
        while(h >= 1){
            for(int i = h ; i < N ; i++){
                for(int j = i ; j >= h && less(a[j] , a[j-h]);j -= h){
                    swap(a,j,j-h);
                }
            }
            h = h/3;
        }

    }
}
//当h=1时,也就是选择排序
//最关键的语句在于for(int j = i ; j >= h && less(a[j] , a[j-h]);j -= h)与h=h/3
//后者是一步步缩小交换间隔,前者是确保不同区域(h为间隔出不同区域)的元素总比前者大

归并排序

public class Merge{
    private static Comparable[] temp; //辅助数组
    public static void sort(Comparable[] a){
        temp = new Comparable[a.length];
        sort(a,0,a.length-1);
    }
    
    private static void sort(Comparable[] a , int lo , int hi){
        if(hi <= lo) return;
        int mid = lo + (hi - lo)/2;    
        sort(a,lo,mid);
        sort(a,mid+1,hi);
        merge(a,lo,mid,hi);     //使用递归的方式,和树一样,一直找到叶结点,从下往上运行,直到根结点
    }
    public static void merge(Comparable[] a , int lo ,int mid ,int hi){
        int i = lo , j = mid + 1 ;
        for(int k = lo ; k<=hi;k++){
            temp[k] = a[k];
        }
        for(int k = lo;k <= hi;k++){
            if (i > mid)                    a[k] = temp[j++];  //意思是当mid左边的元素都排序完了,把mid右边的元素都排进原数组中
            else if (j>hi)                  a[k] = temp[i++];  //意思是当mid右边的元素都排序完了,把mid左边的元素都排进原数组中
            else if (less(temp[j],temp[i])) a[k] = temp[j++];  //意思是把mid右边的元素和左边的元素相比,如果右边的元素小于左边的元素,则执行,然后j+1, 看下一个j和i的相比大小      
            else                            a[k] = temp[i++];  //意思是如果mid右边的元素 大于左边的元素,则执行,然后i+1,看下一个i和j相比大小
        }
        
    }


//以上是将数组从小到大归并,先将左边的数组归并,再将右边的数组归并,而下面的归并也是从小到大归并,不同的是下面是将微型数组先统一归并,然后向上到更大的数组再统一归并,每次归并的数组规模是一样的
//===========
    public static void sort(Comparable[] a){
        int N = a.length;
        temp = new Comparable[N];
        for(int sz = 1 ; sz < N ; sz = sz + sz){
            for(int lo = 0 ; lo < N - sz ; lo += sz + sz){
                merge(a, lo , lo+sz-1 , Math.min(lo+sz+sz-1 , N-1));
            }
        } 
    
    }
    
    //这里 sz 为子数组的大小,sz < N 的意思是 子数组的大小不能 大于等于N,如果>=N就不是子数组了,=N的情况已经说明排好了,不需要merge了
    //sz = sz + sz 这里说明每次是2*sz的数组进行排序的
    // lo < N - sz 而不是 N - 1 的原因,我认为是 如果是N -1 的话, 会导致当sz = ?时 原数组末尾的几个已经排过的元素会被打乱,如一共18个元素的数组,在sz = 2 时,本来是到lo = 12 时就结束了,如果sz - 1 ,那么就会到lo=16 , 会出现merge(16,17,17)的情况, 打乱原本已经排序好的末尾元素的顺序,从而排序失败
    //lo + sz - 1 , lo + sz + sz - 1 中的 - 1 时因为lo也是一个占位(在分数组里)所以减去重复的位置
    // N - 1 的原因是在最后排序的时候,如果元素不够sz个大小,则使用剩余元素再次排序



}

快速排序

归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以整个数组排序;

快速排序将数组排序的方式则是当两个子数组都有序时整个数组也就自然有序了 ;

归并排序递归调用发生在处理整个数组之前,快速排序递归调用发生在处理整个数组之后。

快速排序最重要的是在于切分的位置,打乱数组的目的就是避免,切分元素总是在最小或最大的元素之间。

public class Quick{
    public static void sort(Comparable[] a){
        StdRandom.shuffle(a);  //打乱输入数据的排序-> 随机排序
        sort(a,0,a.length-1);
    }
    private static void sort(Comparable[] a , int lo ,int hi){
        if(lo >= hi) return;
        int j = partition(a,lo,hi);
        sort(a,lo,j-1);
        sort(a,j+1,hi);
    }
    private static int partition(Comparable[] a, int lo,int hi){
        int i = lo;    
        Comparable v = a[lo];
        int j = hi + 1;
        while(true){
            while(less(a[++i],v)) if(i = hi) break;
            while(less(v,a[--j])) if(j = lo) break;
            if(i >= j) break;
            swap(a,i,j);
        }
        swap(a,lo,j);
        return j;
    }

}

三向切分

因为有的时候数组中可能有很多重复的元素,假如都是重复的元素,会影响排序性能,假设都是重复的元素,那么上述算法还是要将进行比较。三向的意思是分别对应小于、等于和大于切分元素的数组元素。

public class Quick3way{
    private static void sort(Comparable[] a , int lo ,int hi){
        if(hi <= lo) return;
        int lt = lo , i = lo + 1 , gt = hi;
        Comparable v = a[lo];
        while(i <= gt){
            int cmp = a[i].compareTo(v);
            if    (cmp < 0)   swap(a,lt++,i++);  //如果a[i] 比 v小的情况
            else if(cmp > 0) swap(a,i,gt--);    
            else             i++;
        } //现在a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]成立
        sort(a,lo,lt-1);
        sort(a,gt + 1,hi);
    }
    



}

堆排序

public class HeapSort(){
	public static void sort(Comparable[] a){
		int N = a.length;
		for(int i = N / 2 ; i >= 1;i--){
			sink(a,i , N);
		}
		while(N > 1){
			swap(a,1,N--);
			sink(a,1,N);
		}
	}
	private void sink(Comparable[] a , int k , int N){
		while( 2*k <= N){
			int j = 2 * k;
			if(j < N && less(a,j,j+1)) j++;
			if(!less(k,j)) break;
			swap(a , k , j);
			k = j;
		}	
	}
	private static boolean less(Comparable[] a , int i , int j){
		return a[i].compareTo(a[j]) <  0;
	}
	private static void swap(Comparable[] a , int i , int j){
		Comparable temp = a[i];
		a[i] = a[j];
		a[j] = a[i];
	}
}
// sort中 i = N /2 , 是找到树中除叶结点以外的所有结点,然后使用下沉 算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值