编程珠玑第11章--应用之排序

本文详细介绍了几种常见的排序算法,包括插入排序、快速排序等,并通过代码示例展示了不同方法的实现细节及效率对比。
1.插入排序

思想:假定x[0…0]是有序的,然后加入x[1],x[2],….,x[n];只要保证每次将待加入x[i]放入正确的位置即可。
方法一:从x[i]开始往前,两两比较,若是前者比后者大,则交换,否则终止
方法二:将x[i]暂存到temp里,两两比较,若是前者比后者大,则用前者覆盖后者,否则跳出循环,然后将temp赋给当前位置
插入排序总结,时间复杂度为O(n^2),如果数组本身就是接近有序的,会快很多

package chapter11;

import java.util.Arrays;


public class InsertionSort {
    public static int[] swap(int a,int b){
        int temp=a;
        a=b;
        b=temp;
        return new int[]{a,b};//由于Java里不支持指针,所以此处才写的如此麻烦。。。
    }
    public static void  insertionSort0(int arr[]){
        for(int i=1;i<arr.length;i++){//比较若前面的比当前的大,两个交换
            for(int j=i;j>0&&arr[j]<arr[j-1];j--){
                int re[]=swap(arr[j], arr[j-1]);
                arr[j]=re[0];
                arr[j-1]=re[1];
            }
        }
    }
    public static void insertionSort1(int arr[]){
        for(int i=1;i<arr.length;i++){
            for(int j=i;j>0&&arr[j]<arr[j-1];j--){
                int temp=arr[j];
                arr[j]=arr[j-1];
                arr[j-1]=temp;
            }
        }
    }
    public static void insertionSort2(int arr[]){
        //比较,若是前面的比当前的大,则用它覆盖后一个数;全部后移后,然后将值直接覆盖指定位置
        for(int i=1;i<arr.length;i++){
            int temp=arr[i];
            int j=i;
            for( ;j>0&&temp<arr[j-1];j--){
                arr[j]=arr[j-1];
            }
            arr[j]=temp;
        }
    }
    public static void main(String args[]){
        long current=System.currentTimeMillis();
        boolean firstIn=true;
//      for(int i=0;i<1000000000;i++){
//          int arr[]=new int[]{8,7,6,5,4,3,2,1};
//          insertionSort0(arr);
//          if(firstIn){
//              System.out.println(Arrays.toString(arr));
//              firstIn=false;
//          }
//      }
//      System.out.println((System.currentTimeMillis()-current)/1000);
//      current=System.currentTimeMillis();
//      firstIn=true;
        for(int i=0;i<1000000000;i++){
            int arr[]=new int[]{8,7,6,5,4,3,2,1};
            insertionSort1(arr);
            if(firstIn){
                System.out.println(Arrays.toString(arr));
                firstIn=false;
            }
        }
        System.out.println((System.currentTimeMillis()-current)/1000);
        current=System.currentTimeMillis();
        firstIn=true;
        for(int i=0;i<1000000000;i++){
            int arr[]=new int[]{8,7,6,5,4,3,2,1};
            insertionSort2(arr);
            if(firstIn){
                System.out.println(Arrays.toString(arr));
                firstIn=false;
            }
        }
        System.out.println((System.currentTimeMillis()-current)/1000);
    }
}

在我的机器上运行的结果

[1, 2, 3, 4, 5, 6, 7, 8]


84
[1, 2, 3, 4, 5, 6, 7, 8]
66

可见还是有区别的,思路二更有效率,思路一中交换需要三步,而思路而只需要一步,因而更快。

2.快速排序

思路:将大的数组划分成两个小的部分,然后对他们进行递归排序。主题都是使用分治法,主要是划分方法的不同,围绕第一个元素进行划分,将所有小于等于它的数移到左边,所有大于它的数移到右边。
方法一:单向划分,从左向右。
分界值boundaryValue选为最左边的元素,分解下标splitIndex初始化为left,然后进行一个循环,数组下标arrIndex从left+1到right,如果出现数组下标值小于分界值boundaryValue,将分界下标splitIndex加一,然后交换splitIndex和arrIndex对应的值;循环结束后交换splitIndex和left的值。
单向排序
方法二:单向划分,从右向左。
思想与第一个差不多,将划分元素arr【left】也包括在内了
单向排序2
方法三:双向划分
目的:为了避免所有元素相同情况下,上述的单向划分退化成为O(n^2)
思路:主循环中有两个内循环,内循环一 lIndex从数组的左边寻找大于等于arr[left]的,找到则停止加一操作;内循环二rIndex从数组的右端寻找小于等于arr【left】的,找到则停止减一操作;然后在主循环中判断lIndex与rIndex是否交叉并决定退出主循环还是交换值。最终交换arr[left]与arr[rIndex]
双向排序
方法四:双向划分加插入排序
因为当循环进行到一定程度时,待排序的部分很小,就没有必要花费很长时间进行双向排序了,直接返回就好。最后的时候对整个数组进行一下插入排序就解决了。

以下是四种方法的源程序
package chapter11;

import java.util.Arrays;
import java.util.Random;

public class QuickSort {
    //单向划分,从左向右
    public static void quickSort1(int arr[],int left,int right){
        if(left>=right)
            return;
        int boundaryValue=arr[left] ;//分界值
        int splitIndex=left;//记录分界下标的变化,《=splitIndex的都是小于分界值boundaryValue ,>splitsplitIndex的元素都是大于等于分界值boundaryValue
        int temp=0;
        for(int i=left+1;i<=right;i++){
            //循环不变式 arr[left+1...splitIndex]<boundaryValue arr[splitIndex+1....right]>=boundaryValue
            if(arr[i]<boundaryValue ){
                splitIndex++;
                temp=arr[splitIndex];
                arr[splitIndex]=arr[i];
                arr[i]=temp;
            }
        }
        arr[left]=arr[splitIndex];//将分界元素放到对象的分界下标上,交换即可
        arr[splitIndex]=boundaryValue ;
        quickSort1(arr, left, splitIndex-1);
        quickSort1(arr, splitIndex+1, right);
    }
    //单向划分,从右向左,其实就省了最后的交换划分元素过程
    public static void quickSort2(int arr[],int left,int right){
        if(left>=right)
            return;
        int splitIndex=right+1;
        for(int arrIndex=right;arrIndex>=left;arrIndex--){
            if(arr[arrIndex]>=arr[left]){
                splitIndex--;
                int temp=arr[splitIndex];
                arr[splitIndex]=arr[arrIndex];
                arr[arrIndex]=temp;
            }
        }
        quickSort2(arr, left, splitIndex-1);
        quickSort2(arr, splitIndex+1, right);
    }
    //双向划分,lIndex++,直到从左边找到大于等于分界值得元素或者rIndex大于rIndex;
    //rIndex--,直到从右边找到的元素小于等于分界值;
    //然后判断lIndex,rIndex的大小,若是
    public static void quickSort3(int arr[],int left,int right){
        //      System.out.println("boundaryValue="+arr[left]+ " left="+left+" right="+right);
        if(left>=right){
            return;
        }
        int temp=0;
        int boundaryValue =arr[left];
        int lIndex=left;
        int rIndex=right+1;
        while(true){
            do lIndex++ ; while(lIndex<=rIndex&&arr[lIndex]<boundaryValue);
            do rIndex-- ; while(arr[rIndex]>boundaryValue);
            if(lIndex>rIndex){
                break;
            }
            temp=arr[lIndex];
            arr[lIndex]=arr[rIndex];
            arr[rIndex]=temp;
        }
        arr[left] =arr[rIndex];
        arr[rIndex]=boundaryValue ;
        //      System.out.println("=>"+Arrays .toString(arr));
        quickSort3(arr, left, rIndex-1);
        quickSort3(arr, rIndex+1, right);
    }

    public static int generateRandom(int left,int right){
        Random random=new Random();
        return random.nextInt(right-left+1)+left;
    }
    public static void insertionSort2(int arr[]){
        //比较,若是前面的比当前的大,则用它覆盖后一个数;全部后移后,然后将值直接覆盖指定位置
        for(int i=1;i<arr.length;i++){
            int temp=arr[i];
            int j=i;
            for( ;j>0&&temp<arr[j-1];j--){
                arr[j]=arr[j-1];
            }
            arr[j]=temp;
        }
    }
    //双向划分加插入排序
    public static void quickSort4(int arr[],int left,int right,int cutOff){
//              System.out.println("boundaryValue="+arr[left]+ " left="+left+" right="+right);
        if(right-left<cutOff){
            return;
        }
        int temp=0;

        int result=generateRandom(left, right);//交换到最左端
//      System.out.println("reslut="+result);
        temp=arr[left];
        arr[left]=arr[result];
        arr[result]=temp;

        int boundaryValue =arr[left];
        int lIndex=left;
        int rIndex=right+1;
        while(true){
            do lIndex++ ; while(lIndex<=rIndex&&arr[lIndex]<boundaryValue);
            do rIndex-- ; while(arr[rIndex]>boundaryValue);
            if(lIndex>rIndex){
                break;
            }
            temp=arr[lIndex];
            arr[lIndex]=arr[rIndex];
            arr[rIndex]=temp;
        }
        arr[left] =arr[rIndex];
        arr[rIndex]=boundaryValue ;
//      System.out.println("=>"+Arrays .toString(arr));
        quickSort4(arr, left, rIndex-1,cutOff);
        quickSort4(arr, rIndex+1,right,cutOff);
    }
    public static void quickSort4( int arr[], int cutOff){
        quickSort4(arr, 0,arr.length-1, cutOff);
        insertionSort2(arr);
    }
    public static void main(String args[]){
        int arr[]=new int[]{10,13,1,11, 12,8,14,3,4, 15,4,5,12};

        //      int arr[]={2,2,2,2,2, 2,2,2,2,2};
//              quickSort1(arr, 0 ,arr.length-1);
//      quickSort3(arr, 0 ,arr.length-1);
        quickSort4(arr, 3);
//      quickSort2(arr, 0 ,arr.length-1);
        System.out.println(Arrays.toString(arr));

    }
}

课后习题
2.在上面的方法二已经写了
3.取不同的cutOff值对一个较大的数组进行排序,选择运行时间少的
4.看了后面答案的,但是感觉它的想x[i].bit[depth]会产生数组下标越界,所以在原来的基础上稍微改了一下。思路就是先看字符串长度是否满足本次深度的要求,若是不满足则跳过不予考虑:若是满足了,将当前深度为0的放到前面,为1的放到后面。

package chapter11;

import java.util.Arrays;

public class BitStringSort {
    public static void bitStringSort(String arr[],int left,int right,int depth){
        if(left>=right){
            return;
        }
        int splitIndex1=left;
        for(int arrIndex=left;arrIndex<=right;arrIndex++){
            if(arr[arrIndex].length()<depth){//根据bitString长度划分
                String temp=arr[arrIndex];
                arr[arrIndex]=arr[splitIndex1];
                arr[splitIndex1]=temp;
                splitIndex1++;
            }
        }
        int splitIndex2=splitIndex1;
        for(int arrIndex=splitIndex2;arrIndex<=right&&splitIndex2<=right;arrIndex++){
            try {
                if(arr[arrIndex].charAt(depth)=='0'){
                    String temp=arr[arrIndex];
                    arr[arrIndex]=arr[splitIndex2];
                    arr[splitIndex2]=temp;
                    splitIndex2++;
                }

            } catch (Exception e) {
                String temp=arr[arrIndex];
                arr[arrIndex]=arr[splitIndex2];
                arr[splitIndex2]=temp;
                splitIndex2++;
            }
        }
        bitStringSort(arr, splitIndex1, splitIndex2-1, depth+1);
        bitStringSort(arr, splitIndex2, right, depth+1);
    }
    public static void main(String args[]){
        String arr[]=new String[10];
        String temp="";
        for(int i=arr.length-1;i>=1;i-=2 ){
            arr[i]=temp+"0";
            arr[i-1]=temp+"1";
            temp+="1";
        }
        System.out.println(Arrays.toString(arr));
        bitStringSort(arr, 0, arr.length-1, 0);
        System.out.println(Arrays.toString(arr));
    }
}

运行结果

[11111, 11110, 1111, 1110, 111, 110, 11, 10, 1, 0]
[0, 1, 10, 11, 110, 111, 1110, 1111, 11110, 11111]

6.选择排序
选择排序将数组中所有最小的放在想x[0],然后从1…n中剩下的选择最小的放在x[1],以此类推

public static void chooseSort(int arr[]){
        int minIndex;
        for(int outerIndex=0;outerIndex<arr.length-1;outerIndex++){
            minIndex=outerIndex;
            for(int innerIndex=outerIndex+1;innerIndex<arr.length;innerIndex++){
                if(arr[innerIndex]<arr[minIndex]){
                    minIndex=innerIndex;
                }
            }
            int temp=arr[outerIndex];
            arr[outerIndex]=arr[minIndex];
            arr[minIndex]=temp;
        }
    }

希尔排序(递减增量排序)请见
http://blog.csdn.net/morewindows/article/details/6668714
9.如何在O(n)时间内找到数组中第k个最小元素的问题。
思路:利用双向划分+插入排序的快速排序,也就是上面的方法四。在此基础上进行改写,只划分我们需要的部分,即arr[k]所在的部分;里面的generateRandom函数与上面的相同

public static void quickSort5(int arr[],int left,int right,int cutOff,int k){
        //      System.out.println("boundaryValue="+arr[left]+ " left="+left+" right="+right);
        if(right-left<cutOff){
            return;
        }
        int temp=0;

        int result=generateRandom(left, right);//交换到最左端
        //System.out.println("reslut="+result);
        temp=arr[left];
        arr[left]=arr[result];
        arr[result]=temp;

        int boundaryValue =arr[left];
        int lIndex=left;
        int rIndex=right+1;
        while(true){
            do lIndex++ ; while(lIndex<=rIndex&&arr[lIndex]<boundaryValue);
            do rIndex-- ; while(arr[rIndex]>boundaryValue);
            if(lIndex>rIndex){
                break;
            }
            temp=arr[lIndex];
            arr[lIndex]=arr[rIndex];
            arr[rIndex]=temp;
        }
        arr[left] =arr[rIndex];
        arr[rIndex]=boundaryValue ;
        //System.out.println("=>"+Arrays .toString(arr));
        if(k>rIndex) //只继续划分k所在的部分

            quickSort5(arr, rIndex+1,right,cutOff,k);
        if(k<rIndex){
            quickSort5(arr, left, rIndex-1,cutOff,k);
        }
    }

    public static void quickSort5( int arr[], int cutOff,int k){
        quickSort5(arr, 0,arr.length-1, cutOff,k);
        insertionSort2(arr);
    }
    public static void main(String args[]){

        int arr[]=new int[1000];
        for(int i=0;i<arr.length;i++ ){
            arr[i]=1000-i;
        }
        quickSort5(arr, 40, 51);

        System.out.println(Arrays.toString(arr)+"\n=>arr[51]="+arr[51]);
    }

11.

在单向划分的基础上更改,令t=arr[left],用splitLeft记录<t的最后一个下标,用splitRight记录==t的最后一个下标。
情况二:当未划分部分arr[arrIndex]<t时

小于t时
情况二:当未划分部分arr[arrIndex]==t时
等于t时
循环结束后最后交换一下arr[left]与arr[splitLeft]的值
final
下面是程序

package chapter11;

import java.util.Arrays;

public class t11 {
    //单向划分的改写
    public static void quickSort_equalInCenter(int arr[],int left,int right){
        if(left>=right){
            return;
        }
        int splitLeft=left;
        int splitRight=left;
        for(int arrIndex=left+1;arrIndex<=right;arrIndex++){
            if(arr[arrIndex]<arr[left]){
                int temp=arr[splitRight+1];
                for(int i=splitLeft+1;i<=splitRight;i++){
                    arr[i+1]=arr[i];
                }
                splitLeft++;
                splitRight++;
                arr[splitLeft]=arr[arrIndex];
                arr[arrIndex]=temp;
            }

            if(arr[arrIndex]==arr[left]){
                int temp=arr[splitRight+1];
                splitRight++;
                arr[splitRight]=arr[arrIndex];
                arr[arrIndex]=temp;
            }
        }
        if(splitLeft>left){
            int temp=arr[splitLeft];
            arr[splitLeft]=arr[left];
            arr[left]=temp;

        }
        System.out.println(Arrays.toString(arr));
        System.out.println("splitLeft="+splitLeft+"  splitRight="+splitRight);
        quickSort_equalInCenter(arr, left, splitLeft-1);
        quickSort_equalInCenter(arr, splitRight+1, right);

    }
    public static void main(String args[]){
        int arr[]={2,1,4,2,5 ,2,4,3,5,3, 2,1};
        quickSort_equalInCenter(arr, 0, arr.length-1);
        System.out.println(Arrays.toString(arr));
    }
}

运行结果

[1, 1, 2, 2, 2, 2, 4, 3, 5, 3, 5, 4]
splitLeft=2  splitRight=5
[1, 1, 2, 2, 2, 2, 4, 3, 5, 3, 5, 4]
splitLeft=0  splitRight=1
[1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5]
splitLeft=8  splitRight=9
[1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5]
splitLeft=6  splitRight=7
[1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5]
splitLeft=10  splitRight=11
[1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5]

总结
1.对于算法的描述,画图有时比描述更易懂。
2.理解算法之后,要尽量用几个关键字将算法的主要思路说出来
3.排序算法真的很重要,所以还是多看几遍吧(微笑)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值