归并排序和快速排序-代码详解(Java)

 自学b站左神的算法课,记录一下。

排序原理及流程图就不介绍了,看代码 和 看介绍 是两个概念。

想学习的话可以复制到编译器自己玩玩。

(快排讲解的比较详细,欢迎勘误毕竟自己的思考可能有缺陷!)

归并排序

 

/**
 * 归并排序
 * @author hozier
 *
 */
public class MergeSort {
	
	public static void process(int arr[] , int L , int R ) {
		//排序
		if( L == R ) 
		{
			return;
		}
		
		int  mid = L + ((R - L) >> 1 ); //求中点(避免溢出)
		process(arr , L , mid);
		process(arr , mid + 1 , R);
		merge(arr , L , mid , R); //排序 归并。
		
	}
	
	public static void merge(int[] arr , int L , int M , int R ) {
		int help[] = new int[R - L + 1];  // 为什么是 R-L+1?  假设 L= 2 ,R = 7 则数组角标为:[0,1,2,3,4,5] 长度6。
		int i = 0;
		int p1 = L;
		int p2 = M + 1;
		while( p1 <= M && p2 <= R ) {
			help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; 
			// 后++ 先赋值后加。 所以并不影响help[]内元素的值,而因为在循环中++了,后续也能将整个数组遍历一边。
		}
		
		//关于这两个while循环: 当上面的循环不走了,一定是因为不满足&& , p1 大于 M or p2大于R 导致循环结束,
		//此时一定会走下面while循环中的某一个。
		while(p1 <= M) {
			help[i++] = arr[p1++] ;
		}
		while(p2 <= R) {
			help[i++] = arr[p2++];
		}
		
//关于 i 、p1 、 p2 :i只决定 help数组的角标,因为help[]是我们定义的辅助数组,用来排序。  而排序的值由arr[]提供,具体取那一个由p1、p2 作为角标提供。
		
		for(i = 0 ; i < help.length ; i++) {
			arr[L + i] = help[i];
		}
		
	} 
	

}

归并排序的衍生:小和问题

/**
 * 小和问题
 * @author hozier
 *
 */
public class SmallSum {

	public static int smallSum(int[] arr) {
		if(arr == null ||  arr.length < 2) {
			return 0;
		}
		return process (arr, 0 , arr.length - 1);
	}
	
	
	// arr[L ... R] 既要排好序 也要求小和
	public static int process(int arr[] , int L , int R ) {
		if( L == R ) {
			return 0;
		}
		int mid = L + ((R - L)>> 1); //中点		
	
		return 
				process(arr , L , mid) +
				process(arr , mid + 1, R) +
				merge(arr , L , mid , R);
	}
	
	
	public static int merge(int arr[] , int L , int M , int R) {
		int help[] = new int[R - L + 1];
		int i = 0;
		int p1 = L;
		int p2 = M + 1;
		int res = 0;
		while(p1 <= M && p2 <= R) {
			res += arr[p1] < arr[p2] ? (R - p2 + 1)*arr[p1] : 0;  // (R - p2 + 1)为右边有几个比p1大,*p1就是p1的小和数;
																									// res之所以放在前面是因为后续的p1++会导致数据进一
			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; // 归并 ; 如果p1 == p2 ,则需要将p2填进help[]中,所以使用 < 号.
		}
		
		while(p1 <= M) {
			help[i++] = arr[p1++];
		}
		while(p2 <= R) {
			help[i++] = arr[p2++];
		}
		for (i = 0; i < help.length; i++) {
			arr[L+i] = help[i];
		}
		return res;
		
	}
	
	
}

快排

前置:荷兰国旗问题

/**
 * 荷兰国旗问题
 * @author hozier
 *
 * @implNote 给定一个数组arr,和一个数sum。请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
 *	要求:时间复杂度为O(N),额外空间复杂度为O(1).
 *
 */
public class HollandFlagQuestion {
    
    
    public static int[] partition( int arr[] , int l ,int r , int num ) {
    	int less = l - 1;
    	int more = r + 1;
    	
    	while(l < more) {
    		if(arr[l] < num) {
    			swap(arr , ++less ,l++);
    		}else if(arr[l] > num) {
    			swap(arr , l , --more);
    		}else {
    			l++;
    		}
    	}
    	
    	return new int[] {less+1 , more - 1};
    	
    }
    
    
    public static void swap(int arr[] , int a , int b) {
    	int temp = arr[a];
    	arr[a] = arr[b];
    	arr[b] = temp;
    }
	
}

根据荷兰问题衍生出快排方法

/**
 * 快排   --> 基于荷兰国旗问题的进化
 * @author hozier
 *
 */
public class QuickSort {
	
	
	public static void quickSort(int arr[]) {
		if(arr == null || arr.length < 2) {
			return;
		}
		quickSort(arr , 0 , arr.length - 1);
	}
	
	public static void quickSort(int[] arr , int L , int R ) {
		if(L < R) {
			swap(arr ,  L + (int)Math.random()*(R - L + 1) , R );
			
			int[] p = partition(arr , L , R ); // 返回一个长度为二的p[]:其值为 arr[]的左边界、右边界
			
			quickSort(arr ,L , p[0] - 1);
			quickSort(arr , p[1]+1 , R);
		}
	}
	
	
	/* partition: 分治,方法 */
	public static int[] partition(int[] arr , int L , int R ) {
		
		int less = L - 1;   // “<区”  右边界
		int more = R;      //  “>区”  左边界
		while(L < more ) {   // L 为当前数的位置, L<more用于判断边界是否交合,没交和就走判断,交合了说明整合区间已经判断完毕 方法停止。
			
			if(arr[L] < arr[R]) {// 当前数 < 划分值
				//++less;		// 为什么要交换?在自己思考的时候会突然陷入一种误区:既然当前数<划分值 时区间才会推进,为什么不直接++呢?因为
				//L++;			// ++的效率要比swap高吧,但是换成如此的代码后排序却会出错,为什么?这是为了确保 ''相等情况下" 代码的合理性。
									// 当划分值为5 ,区间有5 的时候,如果直接++,那岂不是将5也包含进<区间了? 故当数组中没有相同元素时这样写是可以的哦。
				swap(arr , ++less , L++); 
				
			}else if(arr[L] > arr[R]) {  // 当前数 > 划分值
				swap(arr , -- more , L );
				
			}else {      //  当前数 == 划分值
				L++;
			}
		}
		swap(arr , more ,R);
		return new int[] {less +1 , more};  
// 着重注意此区间, 此为递归的核心。我们知道,在荷兰国旗中返回的区间为{less+1,more-1},为什么呢?
//  因为less代表的是下次运算时数组的右边界,more为~的左边界。
//  而仔细看递归方法中的形参:(L , p[0]-1) ; (p[1]+1 , R), 之所以+1就是+1-1后结果为less,
//  而more为什么不用-1呢,原因在于 我们运算初期将划分值移到了右边,在循环完毕后将其换了回来,
//  这个过程其实就是将等于划分值的数放在了右边界上,相当于右边界-1
//  当然 less+1 和 p[0] -1  的加减是可以省略的,加上可能是因为和more相互参照吧(但是不加感觉更好理解)
		
	}
	
	
	/* 交换方法 */
	public static void swap(int arr[] , int a , int b) {
		int temp = arr[a];
		arr[a] = arr[b];
		arr[b] = temp;
	}
	
}

解析中各值不相等的情况(即使用 ++less和 L++的情况)

测试:

/**
 * 测试各算法
 * @author hozier
 *
 */
public class TestAll {

	
	static int arr[] = new int[]{1,3,6,-10,9,2,-5,67,32,1, 99 , -20 , 1}; //10个数
	
	static int arrE[] = new int[] {1,2,2,2,2,2,2,4};//{1,3,4,2,5};
	
	
	/**
	 * 测试mergeSort 归并排序
	 */
	@Test
	public void test01() {
		MergeSort mergeSort = new MergeSort();
		
		mergeSort.process(arr, 7, 9);
		
		for (int i : arr) {
			System.err.print(" " + i);
		}
	}
	
	
	/**
	 * 测试 小和问题
	 */
	@Test
	public void test02() {
		SmallSum smallSum = new SmallSum();
		
		System.out.println(smallSum.smallSum(arrE));                                                                                                                                                                                                                                                                                                                                                                           
		
		
	}
	
	
	/**
	 * 测试 荷兰国旗问题
	 */
	@Test
	public void test03() {
		HollandFlagQuestion hf= new HollandFlagQuestion();
		
	    int[] arr = {1,5,3,6,2,8,9,10,3,5};
	    int num = 5;

	    hf.partition(arr , 0 , 9 ,5);
	    
	    for (int i : arr) {
			System.out.println(i);
		}
		   
	}
	
	
	/**
	 * 测试 快排
	 */
	@Test
	public void test04() {
		QuickSort qs  = new QuickSort();
		int[] arr =  {1,3,2,4,6,-2,-7,5,9};
		qs.quickSort(arr);
		for (int i : arr) {
			System.out.print(" "+i);
		}
		
	}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值