两种O(nlogn)级别的排序,归并排序和快速排序

来源:互联网 发布:字幕下载软件 编辑:程序博客网 时间:2024/06/07 05:12

最近重新学习了算法中的排序算法,相较于几个O(n^2)级别的排序算法(冒泡排序,选择排序,插入排序,希尔排序等) O(nlogn)级别的排序算法有普遍更快的速度,相对于O(n^2)级别的排序算法来说也更难理解。下面我把我最近学习这两个排序的想法说一下。

归并排序:归并排序就是将数组不断的等分,等分到一组只有一个数,然后向上合并,例如第一次合并只有自己 就直接合并,然后就会变成两两一组 这时就有大小关系了,按照我们的规则交换之,再向上合并,最后就可以得到一个排好序的数组,大概是

private static void __mergeSort_(int[] arr, int l, int r) {//l代表left r代表right

            if(l >= r){
               return ;
              }

      int mid = (l + r) / 2; //这里是求得中间值 例如 0~8 就是4这个位置 4~8 就是6这个位置
            //递归
           __mergeSort_(arr, l, mid);   
           __mergeSort_(arr, mid + 1, r);

            //调用核心
           __merge(arr, l, mid, r);
         }

不断的等分,然后调用比较的方法。当分成一组只有一个值的时候 就return;向上返回


快速排序:快速排序的思想就是选择一个数作为基数  去遍历数组小于的放在基数左边 大于的放在基数右边,然后根据基数的位置 递归即可求解。大概是

private static void __quickSort(int[] arr, int l, int r) {

if(l >= r){
return;
}

//取到这次基数的位置
int p = __partition(arr, l, r);
__quickSort(arr, l, p - 1);
__quickSort(arr, p + 1, r);
}


这两种算法的代码如下:

package cyd;public class NLognSort {public static void main(String[] args) {testSort(10000,0,10000);}//测试性能    public static void testSort(int size, int rangeL, int rangeR){        int[] arr = randomArr(size, rangeL, rangeR);        long startTime = System.currentTimeMillis();        quickSort3Ways(arr);        //boolean flag = isSortArr(arr);        //判断 是否排序成功        /*if(!flag){            System.err.println("没有排序成功");            return;        }*/        long endTime = System.currentTimeMillis();        System.err.println(((double)(endTime - startTime))/1000.0 + "s");        printArr(arr);    }//归并排序算法public static void mergeSort(int[] arr){__mergeSort_(arr, 0, arr.length - 1); // 借用Python的规范,表示私有方法,不希望用户调用}//归并算法递归实现private static void __mergeSort_(int[] arr, int l, int r) {//l代表left r代表rightif(l >= r){return ;}//优化2  在 范围极小时,数组趋近于有序,这时可以采用插入排序 可以加快效率/*if( r - l < 15){insertSortArray(arr, l, r);return;}*/int mid = (l + r) / 2; //这里是求得中间值 例如 0~8 就是4这个位置 4~8 就是6这个位置//递归__mergeSort_(arr, l, mid);   __mergeSort_(arr, mid + 1, r);//调用核心//优化1 由于归并算法的保证,左边和右边一定是有序的,如果左边大于了右边//这时我们才去对它排序,这样对于较为有序的数组排序就能够提前结束if(arr[mid] > arr[mid + 1])__merge(arr, l, mid, r);}//归并排序算法核心private static void __merge(int[] arr, int l, int mid, int r) {int[] aux = new int[r - l + 1];  //这是临时数组,大小为传入的 大小 也就是当前要归并的数组大小for(int i = l ; i <= r; i ++){aux[i - l] = arr[i]; //临时表复制}int i = l, j = mid + 1; for(int k = l; k <= r; k++){if(i > mid){  //如果 i大于 mid 说明左边的元素已经被排好但是 有右边的没有被排好,这时就要将右边的值写入arr并且游标右移arr[k] = aux[j - l];j++;}else if(j > r){ //如果 j大于 mid 说明右边边的元素已经被排好但是 有左边的没有被排好,这时就要将左边的值写入arr并且游标右移arr[k] =aux[i - l];i++;}else if( aux[i - l] < aux[j - l]){ //如果当前组左边小于右边 就将左边的值给arr 左边游标右移arr[k] = aux[i - l];i++;}else{  //剩下这种情况只能是 右边大于左边了arr[k] = aux[j - l];j++;}}}//快速排序public static void quickSort(int[] arr){__quickSort(arr , 0, arr.length - 1);}//递归实现快速排序  //每次找到当前的中间值,然后等分左右,再继续从左右依次这样做 就实现了快速排序private static void __quickSort(int[] arr, int l, int r) {if(l >= r){return;}//优化1  数据小的时候  几乎有序,使用插入排序/*if( r - l < 15){insertSortArray(arr, l, r);return;}*/int p = __partition(arr, l, r);__quickSort(arr, l, p - 1);__quickSort(arr, p + 1, r);}//快速排序核心private static int __partition(int[] arr, int l, int r) {//这是标准位置的值,最后排序完成左边的比这小右边的比这大//初始版本  取第一个元素 ,但是有一个弊端 如果 数组本身近乎有序,那么大于当前值的就会非常多,导致右侧元素多,就会导致快速排序退化成O(n^2)级别的排序//int p = arr[l];//改进  从 l 到   r 中随机取 一个数  与 起始位置交换作为起始位置,这时 就不会出现这种情况了swap(arr, l , (int) (Math.round((Math.random()*(r - l) + l))));int p = arr[l];int j = l;for(int i = l + 1; i <= r; i++){if(arr[i] < p){swap(arr, i , j + 1);j++;}}swap(arr, j , l);return j;}// 改进 快速 排序  如果 大量重复数的情况下,就会将等于标定点的元素都放在右边 这就又导致了 快排变成了  O(n^2) 级别的排序  //改进public static void quickSort2(int[] arr){__quickSort2(arr, 0, arr.length - 1);}private static void __quickSort2(int[] arr, int l, int r) {if(l >= r){return;}int p = __partition2(arr, l, r);__quickSort2(arr, l, p - 1);__quickSort2(arr, p + 1, r);}private static int __partition2(int[] arr, int l, int r) {swap(arr, l , (int) (Math.round((Math.random()*(r - l) + l))));int p = arr[l];//直接将arr分成两半,左边向右检索,右边向左检索 找到两边 左边大于 arr[l]的 和 右边小于 arr[l] 的 交换  // 最后 将标定 值  和 j最后位置的值 交换int i = l + 1;int j = r;while( true ){while(i <= r &&arr[i] < p) i++;while(j >= l + 1 && arr[j] > p) j--;if(i > j) break;swap(arr, i ,j);i++;j--;}swap(arr, j, l);return j;}//3路快排   将 数组分成 小于 标准值 等于标准值 和 大于标准值 三部分 ,然后再对 小于部分 和大于部分3路快排 等于部分就不需要再排了public static void quickSort3Ways(int[] arr){__quickSort3Way(arr, 0, arr.length - 1);}private static void __quickSort3Way(int[] arr, int l, int r) {if( l >= r){return;}//// v为pivot,初始存储在arr[l]的位置 swap(arr, l , (int) (Math.round((Math.random()*(r - l) + l))));int p = arr[l];int lt = l; // 循环过程中保持 arr[l+1...lt] < v int gt = r + 1; // 循环过程中保持 arr[gt...r] > v int i = l + 1; // 循环过程中保持 arr[lt+1...i) == vwhile( i < gt){if(arr[i] < p){ // 这里相当于 如果小于的话 直接 将 i 和 lt 都向后移一位/*swap(arr, i ,lt + 1);*/  //这步其实没有必要lt++;i++;}else if(arr[i] > p){swap(arr, i ,gt - 1);gt--;}else{i++;}}swap(arr, l, lt);__quickSort3Way(arr, l, lt - 1);__quickSort3Way(arr, gt, r);}//插入排序   从左往右 第一个数 假设已经排好,第二个数是否小于第一个数 是的话交换,第三个数是否小于    //第二个数 小于交换 并判断 第二个数(也就是之前的第三个数) 是否小于第一个数 小于交换  。。。。。    public static void insertSortArray(int[] arr) {        for (int i = 1; i < arr.length; i++) {            //倒着往前遍历,如果存在 小于前一个数的 交换 否则 不交换并且break ,比选择排序 快很多 不用全部循环两次            for (int j = i; j > 0 && arr[j] < arr[j - 1]; j--) {                swap(arr, j, j - 1);            }        }    }        //插入排序  排序任意位置的数    public static void insertSortArray(int[] arr,int l, int r) {        for (int i = r; i < arr.length; i++) {            //倒着往前遍历,如果存在 小于前一个数的 交换 否则 不交换并且break ,比选择排序 快很多 不用全部循环两次            for (int j = i; j > l && arr[j] < arr[j - 1]; j--) {                swap(arr, j, j - 1);            }        }    }//输出    public static void printArr(int[] arr){        for(int i = 0; i < arr.length; i++){            if(i != arr.length - 1){                System.err.print(arr[i]+",");            }else{                System.err.println(arr[i]);            }        }    }    /**     * 生成随机数     * @param size     * @param rangeL     * @param rangeR     * @return     */    public static int[] randomArr(int size, int rangeL, int rangeR){        if(rangeL >= rangeR){            return null;        }        int arr[] = new int[size];        for(int i = 0; i < size; i++){            arr[i] = (int) (Math.round((Math.random()*(rangeR - rangeL ) + rangeL)));            for(int j = 0; j <=i; j++ ){                if(i != j){                    if(arr[i] == arr[j] ){                        i--;                        break;                    }                }            }        }        return arr;    }    public static boolean isSortArr(int[] arr){        for(int i =0 ; i < arr.length - 1; i++){            if(arr[i] > arr[i + 1]){                return false;            }        }        return true;    }    //交换    public static void swap(int arr[], int leftNum, int rightNum){        int temp = arr[leftNum];        arr[leftNum] = arr[rightNum];        arr[rightNum] = temp;    }}