10类排序算法及改进

基本内容:冒泡、选择、插入、希尔、快排、堆排、归并排、桶排、计数排、基数排、改进冒泡排、三路快排、非递归归并排。

测试平台http://www.lintcode.com/zh-cn/problem/sort-integers/

1.冒泡排序: 稳定,O(n*n)。

    public void bubbleSort(int[] arr) {
        for(int i=0;i<arr.length-1;i++){//n个元素,只需要n-1趟,每趟固定一个元素到后面
            for(int j=0;j<arr.length-i-1;j++){
                if(arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                }
            }
        }
    }

改进的冒泡排:极端当序列本身有序时,只需要遍历一遍。

    public void bubbleSortImprove(int[] arr) {
        boolean exchanged=true;
        int exchangedLast=arr.length-1;
        while(exchanged){
            exchanged=false;
            int position=-1;//记录最近发生交换的位置
            for(int i=0;i<exchangedLast;i++){
                if(arr[i]>arr[i+1]){
                    exchanged=true;
                    swap(arr,i,i+1);
                    position=i;
                }
            }
            exchangedLast=position;
        }
    }

2.选择排序:不稳定,O(n*n); 相较于冒泡排序,比较的次数是一样的,但元素交换的次数少了,一共只需要交换n-1次

    public void selectionSort(int[] arr) {
        for(int i=0;i<arr.length-1;i++){//对于n个元素,只需要n-1次交换:每次从[0,n-i-1]中找最大值与n-i-1位置交换
            int maxIndex=0;//记录[0,j]中最大值所在的索引,j∈[0,arr.length-i-1]
            for(int j=1;j<=arr.length-i-1;j++){
                if(arr[j]>arr[maxIndex]){
                    maxIndex=j;
                }
            }
            swap(arr,maxIndex,arr.length-i-1);
        }
    }

3.插入排序:稳定,O(n)(序列基本顺序)~O(n*n)(序列完全逆序)。

    public void insertSort(int[] arr) {
        for(int i=1;i<arr.length;i++){
            int val=arr[i];
            int j=i-1;//待考虑位置,可能需要后移
            while(j>=0 && arr[j]>val){//把[0,i-1]中大于val的元素均后移一个单位
                arr[j+1]=arr[j];
                j--;
            }
            arr[j+1]=val;
        }
    }

4.希尔排序:不稳定。变步长的插入排序。

    public void shellSort(int[] arr) {
        int step=arr.length/3;
        while(step>1){
            insertSort(arr,step);
            step/=3;
        }
        insertSort(arr,1);
    }
    /**
     * 步长为k的插入排序,...i-2k,i-k,i
     */
    public void insertSortK(int[] arr,int k) {
        for(int i=k;i<arr.length;i++){
            int val=arr[i];
            int j=i-k;//待考虑位置,看是否需要后移k个单位
            while(j>=0 && arr[j]>val){//把[0,i-1]中大于val的元素均后移一个单位
                arr[j+k]=arr[j];
                j-=k;
            }
            arr[j+k]=val;
        }
    }

5.快排:不稳定,O(nlogn)~O(n*n),与主元选择的好坏有关。

    public void quickSort(int[] arr,int start,int end){
        if(start>=end){
            return;
        }
        int index=partion(arr,start,end);
        quickSort(arr,start,index-1);
        quickSort(arr,index+1,end);
    }
    public int partion(int[] arr,int start,int end){
        int low=start,high=end;
        int key=arr[end];//固定主元在最后
        while(low<high){
            while(low<high && arr[low]<key){//找>=主元的数
                low++;
            }
            while(high>low && arr[high]>=key){//找<主元的数
                high--;
            }
            swap(arr,low,high);
        }
        swap(arr,low,end);
        return low;
    }

三路快排:当重复元素很多时,可以大量减少子问题的数据量。

    public void quickSort(int[] arr) {
           sort(arr,0,arr.length-1);
    }
    //三路快排,思路就是2个指针分出3个区间
    //[start,i]维护小于key的区间,[i+1,k]维护等于k的区间,[k+1,j)维护大于k的区间
    public void sort(int[] arr,int start,int end){
        if(start>=end){
            return;
        }
        int key=arr[(int)(Math.random()*(end-start))+start];//随机选主元值
        int i=start-1,k=start-1;
        int j=start;
        while(j<=end){
            if(arr[j]>key){
                j++;
                continue;
            }else if(arr[j]==key){
                swap(arr,++k,j);
            }else{
                swap(arr,++k,j);
                swap(arr,++i,k);
            }
            j++;
        }
        sort(arr,start,i);
        sort(arr,k+1,end);
    }

6.堆排序:不稳定,O(nlogn)。

class heapSort{
    	private int[] arr;
    	private int size;
    	public heapSort(int[] arr){
    		this.arr=arr;
    		this.size=arr.length;
    	}
    	private void sort(){
    		//1.使得整体成为一个大堆
    		for(int i=arr.length/2;i>=0;i--){//易证:
    			maxHeapify(i);
    		}
    		//2.每次把最大的元素(堆顶)放到最后,再进行堆维护
    		while(size>1){
    			swap(arr,0,size-1);
    			size--;
    			maxHeapify(0);//维护堆性质
    		}
    	}
    	//设left(i),right(i)均是大堆,使以i为根的成为大堆--非递归版
    	private void maxHeapify(int i){
    		while(i<this.size){
	    		int max=i;//当前结点,左孩子,右孩子哪个大
	    		if(2*i+1<=this.size-1 && arr[2*i+1]>arr[max]){
	    			max=2*i+1;
	    		}
	    		if(2*i+2<=this.size-1 && arr[2*i+2]>arr[max]){
	    			max=2*i+2;
	    		}
	    		if(max==i){//整体已经是大堆了
	    			break;
	    		}else{
	    			swap(arr,i,max);//交换值
	    			i=max;//相当于递归
	    		}
    		}
    	}
    	private void swap(int[] arr,int i,int j){
    	    int temp=arr[i];
    	    arr[i]=arr[j];
    	    arr[j]=temp;
        }
    }

7.自上而下的归并排序:稳定,O(nlogn)

    public void mergeSort(int[] arr,int start,int end){
        if(start>=end){
            return;
        }
        int mid=(start+end)/2;//分别对两个子段进行归并排,再进行合并
        mergeSort(arr,start,mid);
        mergeSort(arr,mid+1,end);
        merge(arr,start,mid,end);
    }
    //合并两个有序段arr[start,mid],arr[mid+1,end]
    private void merge(int[] arr,int start,int mid,int end){
        int[] help=new int[end-start+1];
        int index1=start,index2=mid+1;//指向当前左右两段最小值
        int index=0;
        while(index1<=mid && index2<=end){
            if(arr[index1]<=arr[index2]){
                help[index++]=arr[index1++];
            }else{
                help[index++]=arr[index2++];
            }
        }
        //两个while只可能有一个满足
        while(index1<=mid){
            help[index++]=arr[index1++];
        }
        while(index2<=end){
            help[index++]=arr[index2++];
        }
        //进行复制
        index=0;
        while(index<help.length){
            arr[start++]=help[index++];
        }
    }

自下而上的非递归的归并排序:回想自上而下的归并排序的过程,算法实际上是逐层切分成最小单元(只有一个元素)后再向上合并,因此可以手动非递归的模拟整个过程。

    public void mergeSort(int[] arr){
        for(int step=1;step<arr.length;step*=2){//本次进行归并的数据块的大小,一开始只有一个数,大小为1
            for(int i=0;i<arr.length;i+=2*step){//依次合并两个数据块。j-1)-i+1=2*step-->j=i+2*step
                merge(arr,i,step);
            }
        }
    }
    /**
     * 合并两个连续的数据块。start:左边数据块起点;step数据块大小(长度)
    **/
    public void merge(int[] arr,int start,int step){
        int index1=start,end1=start+step-1;
        int index2=start+step,end2=start+2*step-1;
        if(index2>=arr.length){//实际上只有一段
            return;
        }
        //是两段
        end2=Math.min(end2,arr.length-1);//notice;
        int[] help=new int[(end2-index1+1)];
        int index=0;
        while(index1<=end1 && index2<=end2){
            if(arr[index1]<=arr[index2]){
                help[index++]=arr[index1++];
            }else{
                help[index++]=arr[index2++];
            }
        }
        while(index1<=end1){
             help[index++]=arr[index1++];
        }
        while(index2<=end2){
             help[index++]=arr[index2++];
        }
        //复制
        index=0;
        while(index<help.length){
            arr[start++]=help[index++];
        }
    }

8.计数排序:非基于比较的算法,当数值固定在某个区间时,用空间换时间。

例子:按照年龄对peoples进行排序,已知peopls.age∈[0,200]。未在oj验证。

    //时间复杂度为O(n),空间复杂度为O(n+k),k是数值区间大小(max-min+1)
    public void countSort(people[] peoples){
        int[] count=new int[201];
        //1.遍历一遍,统计每个年龄的人数
        for(int i=0;i<peoples.length;i++){
            count[peoples[i].age]++;
        }
        //2.使count[i]变为age<=i的总人数,
        for(int i=1;i<count.length;i++){
            count[i]=count[i]+count[i+1];
        }
        //3.进行定位:设count[people.age]==k,即当前年龄<=people.age的有k个人,排序后应该放在区间[0,k-1],因此这个人可以放在k-1位置
        peoplesHelp=new int[peoples.length];
        for(int i=0;i<peoples.length;i++){
            peoplesHelp[count[peoples[i].age]-1]=peoples[i];
            count[peoples[i].age]--;//notice
        }
        //4.复制
        for(int i=0;i<peoplesHelp.length;i++){
            peoples[i]=peoplesHelp[i];
        }
    }

9.基数排序:先按照权值低的属性排,再按照权值高的属性排。但是应该选择一种稳定的排序算法(冒泡排,直接插入排,归并排),因为若高位相同时,若算法是不稳定的,则会导致原有的低位失效。

例:把82,89升序排;先按个位排得到:82,89,再按十位排(若算法不稳定,可能得到):89,82


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值