插入排序、归并排序以及小和问题

本文详细介绍了两种常见的排序算法:插入排序和归并排序。插入排序通过将一个无序数组中的元素逐步插入到已排序的部分来实现排序,其时间复杂度为O(N^2)。归并排序则采用分治法思想,将数组分成两个有序子序列,再合并成一个有序序列,时间复杂度为O(N*logN)。此外,还介绍了一个归并排序的应用实例——小和问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.插入排序

插入排序的思想了类似于向一个有序的数组里面插入一个数。最初我们将一个无序的数组0~N-1的0~0部分视作有序,然后取1位置的数与0位置上的数进行比较,比0位置上数小就进行交换,比0位置上数大就不变,从而使0~1部分排成有序,以此类推。也就是说,取有序部分后边的数插入到那个有序的部分,将这个数和它前面的数依次进行比较,比前面的数小就进行交换,比前面的数大就已经找到了这个数的位置,直到最后位置的数也找到了正确的位置排序就结束。

这里写图片描述
代码实现:

	/*
	 * 插入排序
	 * 时间复杂度:
	 * 是和数据的状况有关的算法流程,一律按照最差状况计算
	 * 最优:有序序列:O(N)
	 * 最差:逆序序列:O(N^2)
	 * 所以时间复杂度为:O(N^2)
	 */
	public static void sort(int[] arr) {
		if(arr == null || arr.length < 2)
			return;
		
		//i位置上的数就是这次排序要排的数
		for(int i = 1;i < arr.length;i++) {
			for(int j = i-1;j >= 0 && arr[j] > arr[j+1];j--) {
				swap(arr, j, j+1);
			}
		}
	}
	
	//进行交换的方法
	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

二.归并排序

该算法是采用分治法的一个非常典型的应用,将已有序的两个子序列合并为一个有序的序列。 我们使用一个辅助数组,大小是两个子序列大小的和,分别定义两个记录两个子序列当前小标的变量,和一个记录辅助数组的下标的变量,取两个子序列中的数进行比较,较小的数拷贝进辅助数组中,并且将下标后移一位,较大的数所在序列记录下标的变量不变,辅助数组下标也后移一位。。。以此类推,如果有一个数组已经全部放入辅助数组中,就将另一个数组中还没有放入辅助数组的数一次全部放入。

这里写图片描述
代码实现:

	/*
	 * 归并排序:
	 * 时间复杂度:O(N*logN),额外空间复杂度O(N)
	 */
	//将数组排成两个有序的数组,使用插排递归实现
	public static void sortProcess(int[] arr, int L, int R) {
		if(L == R)
			return;
		//使用右移的位运算取mid
		int mid = L + ((L - R) >> 1);
		sortProcess(arr, L, mid);
		sortProcess(arr, mid+1, R);
		merge(arr, L, mid, R);
	}
	
	//归并排序
	public static void merge(int[] arr, int L, int mid, int R) {
		//辅助数组
		int[] help = new int[R-L+1];
		int i = 0;
		int p1 = L;
		int p2 = mid + 1;
		
		//两个有序部分的数组还有数没有排的时候
		while(p1 <= mid && p2 <= R) {
			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
		
		//当其中一个数组的数已经全部放进去,将另外一个数组的没有放进去的部分放进去
		while(p1 <= mid) {
			help[i++] = arr[p1++];
		}
		while(p2 <= R) {
			help[i++] = arr[p2++];
		}
		
		//将辅助数组中的数全部放进原数组中
		for(i = 0;i < arr.length;i++) {
			arr[L + i] = help[i];
		}
	}

三.归并排序的应用——小和问题

题目描述:   在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数的小和。求一个数组的小和。

例子:
  数组:[1, 3, 4, 2, 5]
  1左边比1小的数:没有;
  3左边比3小的数:1;
  4左边比4小的数:1, 3;
  2左边比2小的数:1;
  5左边比5小的数:1, 3, 4, 2;
所以小和为:1 + 1 + 3 + 1 + 1 + 3 + 4 + 2 = 16

分析:
我们可以把小和问题转化成:求每个数右边比自己大的个数 * 自己这个数的大小,比如题中:1的右边比1大的数一共有四个,一共计算了四次1;3的右边有两个比3大的数,一共计算了两次3;4的右边有一个比4大的数,一共计算了一次4;2的右边有一个比2大的数,一共计算了一次2;5的右边没有比它大的数。
那么我们该如何将这个问题转化成归并呢?
这里写图片描述
首先使用分治的思想将数组分为上图所示,先定义一个小和值sum = 0,然后在归并的时候,是从最下层进行归并的,只需判断左边集合中的数是否小于右边集合中的数:
所以最先归并的是1和3,1<3,sum += 1 * 1,第一个1是指较小的数为1,第二个1是指,有1个数比1大;
第二次归并的是[1, 3]和4,1<4,sum += 1 * 1;3 < 4,sum += 3 * 1;
第三次归并的是2和5,2 < 5,sum += 2 * 1;
第四次归并的是[1, 3, 4]和[2, 5],1 < 2,sum += 1 * 2;3 < 5,sum += 3 * 1;4 < 5,sum += 4 * 1
所以,小和问题其实就是一个归并排序的应用
部分代码如下:

	//只是在原来归并的基础上添加了一个变量res存放小和的值,并在这个while语句中计算小和值
	int res = 0;
	while(p1 <= mid && p2 <= r) {
			res += arr[p1] < arr[p2] ? arr[p1] * (r - p2 + 1) : 0;
			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}

------------------------------------------ 手动优雅的分割线~~ ---------------------------------------------
更新于20190326:
非常感谢博主追星花狸在评论中指出我的错误,在最后的小和问题上,在阐述过程的部分有误,希望没有误导之前看过这篇博客的盆友…现已更正并加入更多解释,欢迎讨论交流!!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值