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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37616173/article/details/78041359

最近重新学习了算法中的排序算法,相较于几个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);
}


这两种算法的代码如下:

扫描二维码关注公众号,回复: 3158141 查看本文章
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代表right
		
		if(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) == v
		while( 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;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_37616173/article/details/78041359
今日推荐