牛客网修炼之路1(插入、归并、快排的荷兰国旗问题)

1、插入排序

插入排序,一开始对于一个数组,默认第一个数是已经排好顺序了,

这里就会从i=1开始进行插入操作,令j = i-1=1-1=0,比较11和12的大小,11<12,也就是

j+1(1)下标对应的值比j(0)下标的值要小,那么就交换。此时j--,那么j就等于-1了,比0还要小,不满足循环条件,跳出,则第一轮

外层的循环就结束了。此时的数组为:

那么继续第二次循环,那么j = i-1=2-1=1,那么比较j+1和j对应值的大小,也就是arrs[j+1] 和arrs[j]进行比较

5比12要小,进行交换,然后继续j--等于0,继续比较,因为此时5和12交换了,此时j+1(1)和j(0)的比较就变成了5和11

的比较,5比11要小,继续交换,j--,小于0退出循环。第二轮外层循环结束。此时数组为:

第三轮循环,j=i-1=3-1=2,就是8和12比较,8比较小,交换,j--1,同样的,此时是8跟11比较,比较小,交换,此时j--,j=0了,

那么就是5和8比较,发现8是比较大的,那么直接推出循环。后面的过程类似。

可以看出,跳出循环有两种情况,一种是j<0的时候,一种是arrs[j+1]比arrs[j]大的时候。

总的代码给出:
 

    @Test
	public void insertSort() {
		int[] arrs = {1,5,3,2,6,7,2,78};
		//i=1,一开始,默认第一个数已经排好序了。
		for(int i = 1; i < arrs.length; i++) {
			for(int j = i-1; j >= 0; j--) {
				//第一次比较是i和i-1进行比较,如果i比i-1
				//位置大,那么要进行交换。交换后,那么原来要插入
				//的数来到i-1的位置,j--,也就是说会比较i-1和
				//i-1-1的位置,如果不满足,那么就跳出该循环,插入的数
				//已经来到合适的位置。这里等于的时候,没有进行交换,则可以
				//知道,该排序还是满足稳定的性质的。
				if(arrs[j] > arrs[j+1]) {
					swap(j,j+1,arrs);
				}else {
					break;
				}
			}
			
		}
		print(arrs);
	}

    private void print(int[] arrs) {
		for(int i = 0; i < arrs.length; i++) {
			System.out.println(arrs[i]);
		}
	}

2、归并排序

1)归并排序的时间复杂度为n * logn,在进行排序之前,先介绍下如何将两个排好顺序的数组合并层一个排好的数组。如下图,有两组数组,分别排好,同时标上标记。

首先是定义一个两个数组长度之和一样大小的数组,同时用i 和 j标记遍历的数组。比较arrs[j] 和 arrs[i],此时j对应的值比较小,所以将2放在下面的大数组中。同时j++;然后比较i=0 和 j=1对应的值的大小,此时相等,哈哈,这是我把i=0的数放到大数组中,说明相等的数,前面那个还是在前面,那么就是稳定的(归并排序可以做到稳定)。此时i++,然后比较i=1和j=1的值,此时j=1的值比较小,所以放入到大叔组中,j++。再比较,然后放5,i++;然后放6;j++;然后放7,i++,此时发现i 已经等于数组的长度了,越界了,则退出循环,然后将生于数组中的9拷入大数组中。.

代码如下:

@Test
	public void merge() {
		int[] arrs1 = {4,5,7};
		int[] arrs2 = {2,4,6,9};
		
		int lent1 = arrs1.length;
		int lent2 = arrs2.length;
		int lent  = lent1 +lent2;
		//先声明初始化一个大数组,长度为lent
		int[] arrs = new int[lent];
		int i = 0;//初始位置的标记
		int j = 0;//初始位置的标记
		int t = 0;//大数组的index
		while(i < lent1 && j < lent2) {
			//小于等于时,拷入,左边的数组,那么排序就是稳定的。
			if(arrs1[i] <= arrs2[j]) {
				arrs[t++] = arrs1[i++];
			}else {
				arrs[t++] = arrs2[j++];
			}
		}
		
		while(i < lent1) {
			arrs[t++] = arrs1[i++];
		}
		
		while(j < lent2) {
			arrs[t++] = arrs2[j++];
		}
		
		print(arrs);//这个方法以后就不写了,前面都有,这个只是来看排好序是否是正确的。
	}

接下来,进行归并的过程。归并采用递归的方式。举个例子,假设数组就只有两个数(为什么不是一个数,嘿嘿,其实一个数或者没有数的话,根本就不用排,直接返回就好了),那么递归的范围就是0到1,mergeSort(arrs,left,right),mergeSort(arrs,0,1),那么left =0,right = arrs.length-1.所以就会出现mid= (left +right)/2,那么mid=0,然后递归调用左边部分mergeSort(arrs,0,0),和右边部分mergeSort(arrs,1,1),然后merge(arrs,left,mid,right); merge(arrs,0,0,1)。后面都是一样的。注意的一点是,以前都是两个给定的数组,而这一次是在一个数组,用下标的方式进行merge而已,其实本质是一样的,但是会比较复杂。

   @Test
	public void testMerge() {
		int[] arrs = {1,4,2,5,3,6,4};
		sortMerge(arrs);
	}
	
	public void sortMerge(int[] arrs) {
		int left = 0;
		int right = arrs.length-1;
		sortMerge(arrs,left,right);
		print(arrs);
	}
	
	public void sortMerge(int[] arrs,int left,int right) {
		if(left == right) {
			return;
		}
		
		int mid = (left+right)>>1;//向右移动一位,其实是除于2,计算机中,为运算效率比较好。
		sortMerge(arrs, left, mid);
		sortMerge(arrs, mid+1, right);
		merging(arrs,left,mid,right);
	}
	
    //这里其实就是上述的合并两个排好序的数组的方式,只不过要扣下
	private void merging(int[] arrs, int left, int mid, int right) {
        //要临时用一个数组来拷贝需要归并范围数据
        //比如现在归并的范围是0,1,那么temp的长度
        //等于2,就是1-0+1。
		int[] temp = new int[right -left +1];
		
        //这里要用lleft来记录传入的left,left不能拿来直接使用,
        //因为要拷贝回arrs时,要使用的left
		int lleft = left;
		int rleft = mid +1;
		int index = 0;//temp数组的标记。
		while(lleft <= mid && rleft <= right) {
			if(arrs[lleft] <= arrs[rleft]) {
				temp[index++] = arrs[lleft++];
			}else {
				temp[index++] = arrs[rleft++];
			}
		}
		
		while(rleft <= right) {
			temp[index++] = arrs[rleft++];
		}
		
		while(lleft <= mid) {
			temp[index++] = arrs[lleft++];
		}
		
        //这里是将merge范围里面的数,用已经排好temp来覆盖。
        //假设是0,1范围,那么left=0开始,则arrs在这一次循环中
        //0,1范围就会和temp一样,排好序。假设是从4,6范围,那么
        //left=4开始,同样也能是的arrs的4,6范围跟此次循环后排好序
        //的temp一样。所以arrs[left++]是从left开始,不是从0开始。
		for(int t = 0;t < temp.length;t++) {
			arrs[left++] = temp[t];
		}
		
	}

现在讲一下,归并排序的一些运用,比如求最小和。

在一个数组中,每一个数左边比当前数小的数加起来。

比如1,2,3,那么1比2小,1,2比3小,所以小和为4.

1)这个可以通过双层循环,每一个数都试用一次。比如遍历1,去跟2,3比;遍历2,去跟后面比,小于3.那么就是1+1+2=4.,

这个时间复杂读是n*n。

2)通过归并的方式,因为每一次分治后,都是最终都是要merge的,在merge的时候,可以比较前面一部分和后面一部分的值的大小。过程就是归并排序而已,只不过多了一些求和的操作。

@Test
	public void getMinSum() {
		int[] arrs = {1,3,4,2,5};
		minSum(arrs);
 	}
	
	private void minSum(int[] arrs) {
        int left = 0;
        int right = arrs.length -1 ;
        int sumMerge = sumMerge(arrs,left,right);
        System.out.println("the min sum is :"+sumMerge);
        print(arrs);//为了看排序是否正确
	}

	private int sumMerge(int[] arrs, int left, int right) {
         if(left < right) {
        	 int mid = (left+right)>>1;
        	 return sumMerge(arrs, left, mid)+sumMerge(arrs, mid+1, right)+mergingSum(arrs,left,mid,right);
         }		
         return 0;
	}

	private int mergingSum(int[] arrs, int left, int mid, int right) {
		 int temp[] = new int[right -left+1];
		 int lleft = left;
		 int rleft = mid+1;
		 int result = 0;
		 int index =0;
		 while(lleft <= mid && rleft <= right) {
			 if(arrs[lleft] <= arrs[rleft]) {
				 result += arrs[lleft] * (right - rleft+1);
				 temp[index++] = arrs[lleft++];
			 }else {
				 temp[index++] = arrs[rleft++];
			 }
		 }
		 
      
		 while(lleft <= mid) {
			 temp[index++] = arrs[lleft++];
		 }
		 
		 while(rleft <= right) {
			 temp[index++] = arrs[rleft++]; 
		 }
		 
		 for(int i = 0; i < temp.length; i++) {
			 arrs[left++] = temp[i]; 
		 }
		 
		 return result;
	}

 

 

3、快速排序

1) 在进行快速排序之前,了解下荷兰国旗问题。就是将数组根据某个数划分成三部分,前面部分比这个数小,中间部分跟数相等,后面部分比这个数大。如下图,假设现在按照8进行划分。

左边标记为A,less = left -1  右边标记为B,more = right,同时指定当前位置的编辑,cur = left

现在是要将这所有的数进行上述的划分,所以,left = 0,right = arrs.length -1 +1= 5

比较cur这个下标对应的和8,比8要小,那么++less位置和cur进行交换(这里刚巧是0和0交换),同时less=0,cur也要加1,

此时为:

此时比较11和8,比8要大,跟--more后的数交换,放在右边,那么11和18交换,此时more等于4

此时cur还是等于1,因为不知道18的情况,所以还是要进行比较的。18和8比,比较大,同样的,放到右边去,--more等于3

此时:

此时cur还是要进行比较,发现跟8相等,此时什么也不做,直接cur++,等于2

继续比较,cur=2,对应12,跟8比,比8大,那么cur和--more的值进行交换,此时more=2,跟cur相等,退出,完成。

   @Test
	public void divideTreePart() {
		int[] arrs = {1,5,3,2,6,7,2,78};
		int num = 3;
		int left = 0;
		int right = arrs.length-1;
		int less = left -1;
		int more = right+1;
		int cur = left;
		
		while(cur < more) {
			if(arrs[cur] > num) {
                //交换cur和右边部分的最近的一个数。
				swap(cur,--more,arrs);
			}else if(arrs[cur] < num){
                //交换最左部分的最近一个数,如果没有中间数
                //那么cur会和自己交换后加1。如果中间有数,那么,最中间部分
                //最左边的数会和cur交换,然后cur+1,所以可以知道,使用这种方式进行
                //进行排序的算法是不稳定的,下面的归并排序会用到这个方式,所以可以知道,
                //快速排序是不稳定的。
				swap(cur++,++less,arrs);
			}else {
				cur++;
			}
		}
		
		print(arrs);
		
	}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值