算法学习 | 归并排序(分治模式的完美体现)+算法题目:求数组逆序对的个数(Java描述)

归并排序

归并排序概述

  • 归并排序(Merge Sort)算法完全依照了分治模式
    分解:将n个元素分成各含n/2个元素的子序列
    解决:对两个子序列递归地排序
    合并:合并两个已排序的子序列以得到排序结果
    归并排序图解
  • 与快排的区别
    ① 归并的分解较为随意,重点是合并
    ② 快排的重点是分解(分区),无需合并
  • 关于分治模式与快速排序可点此参考学习

实现代码

/**
 * 归并排序
 * 
 * @author LRRacac
 *
 */
public class MergerSort {
	// 定义一个辅助数组
	public static int[] helper;

	/**
	 * 归并排序方法
	 */
	public static void sort(int[] arr) {
		helper = new int[arr.length]; // 为辅助数组设置长度
		mergeSort(arr, 0, arr.length - 1); // 进行归并排序
	}

	/**
	 * 排序方法
	 * 
	 * @param arr---待排序数组
	 * @param l---左边界
	 * @param r---右边界
	 */
	public static void mergeSort(int[] arr, int l, int r) {
		if (l >= r)
			return;
		int mid = (l + r) >> 1; // 获取中间位置
		mergeSort(arr, l, mid); // 对mid左侧(含mid)进行归并排序
		mergeSort(arr, mid + 1, r);// 对mid右侧进行归并排序
		merge(arr, l, mid, r); // 最后将分别排序完毕的左右侧进行合并,从而完成对整个数组的排序
	}

	/**
	 * 合并方法---将已经分别有序的左右两部分数组进行整体合并
	 * 
	 * @param arr---待排序的数组
	 * @param l---左边界
	 * @param mid---分界线(mid左右侧分别有序)
	 * @param r---右边界
	 */
	public static void merge(int[] arr, int l, int mid, int r) {
		System.arraycopy(arr, l, helper, l, r - l + 1); // 复制原数组元素到辅助数组
		int pleft = l; // 左指针
		int pright = mid + 1; // 右指针
		int current = l; // 用于记录原数组中待填入元素的索引
		while (pleft <= mid && pright <= r) { // 当左右指针有一个已经扫完了,就退出循环
			if (helper[pleft] <= helper[pright]) { // 当左指针所指元素小于等于右指针所指元素时
				arr[current] = helper[pleft];
				current++;
				pleft++;
			} else { // 当左指针所指元素大于右指针所指元素时
				arr[current] = helper[pright];
				current++;
				pright++;
			}
		}
		// 至此,左右两指针其一已经扫描完毕,如果是左侧先扫描完则无需处理
		// 如果是右侧先扫描完,则需将左侧为扫描完的元素都放入数组末尾
		while (pleft <= mid) {
			arr[current] = helper[pleft];
			current++;
			pleft++;
		}
	}
}

测试代码及结果展示

测试代码:

import java.util.Arrays;
/**
 * 归并排序测试代码
 * @author LRRacac
 *
 */
public class MergeSortTest {
	public static void main(String[] args) {
		int[] arr = {12, 2, 20, 2, 11, 7, 10, 5, 6, 13};
		System.out.println("归并排序前:"+Arrays.toString(arr));
		MergerSort.sort(arr); //调用归并排序方法
		System.out.println("归并排序后:"+Arrays.toString(arr));
	}
}

结果展示:
测试结果图片

题目:求数组中逆序对的个数

  • 逆序对: 一个数列,如果左边的数大,右边的数小,则称这两个数为一个逆序对。
  • 题目描述:求出一个数列中有多少个逆序对。

思路分析

  • 利用归并排序的思想,我们知道归并排序是对已经分别有序的左序列以及右序列进行合并,是使用两个指针即左右指针分别指向左右子序列的开头,并且逐个扫描比较两者的大小,较小者先写入数组实现排序
  • 空间位置上左序列其实是在右序列左边,也就是在左右序列合并的过程中,如果右侧指针所指元素小于左侧指针所指元素,那么右侧该元素要先排入数组,原来它的位值是在所有的左序列的右边,则很容易得到对于该右侧元素,含有逆序对的个数就是当前左侧还剩下的未排入数组的元素个数(文字描述太难了)
  • 图解:
    逆序对图解

代码及测试结果展示

/**
 * 求数组中逆序对个数
 * @author LRRacac
 *
 */
import java.util.Arrays;
public class InversePair {
	// 定义一个辅助数组
	public static int[] helper;
	// 定义一个count变量用于记录逆序对个数
	public static int count = 0;

	public static void main(String[] args) {
		int[] arr = {2, 8, 5, 4, 6};
		System.out.println("数组:"+Arrays.toString(arr)); //打印原数组
		System.out.println("逆序对的个数为:"+getCount(arr)+"个"); //调用getCount方法获取逆序对个数
	}
	
	/**
	 * 获取逆序对个数
	 */
	public static int getCount(int[] arr) {
		helper = new int[arr.length]; // 为辅助数组设置长度
		mergeSort(arr, 0, arr.length - 1); // 进行归并排序
		return count; //返回逆序对个数
	}

	/**
	 * 排序方法
	 * 
	 * @param arr---待排序数组
	 * @param l---左边界
	 * @param r---右边界
	 */
	public static void mergeSort(int[] arr, int l, int r) {
		if (l >= r)
			return;
		int mid = (l + r) >> 1; // 获取中间位置
		mergeSort(arr, l, mid); // 对mid左侧(含mid)进行归并排序
		mergeSort(arr, mid + 1, r);// 对mid右侧进行归并排序
		merge(arr, l, mid, r); // 最后将分别排序完毕的左右侧进行合并,从而完成对整个数组的排序
	}

	/**
	 * 合并方法---将已经分别有序的左右两部分数组进行整体合并
	 * 利用归并排序的合并思路来统计逆序对个数
	 * 
	 * @param arr---待排序的数组
	 * @param l---左边界
	 * @param mid---分界线(mid左右侧分别有序)
	 * @param r---右边界
	 */
	public static void merge(int[] arr, int l, int mid, int r) {
		System.arraycopy(arr, l, helper, l, r - l + 1); // 复制原数组元素到辅助数组
		int pleft = l; // 左指针
		int pright = mid + 1; // 右指针
		int current = l; // 用于记录原数组中待填入元素的索引
		while (pleft <= mid && pright <= r) { // 当左右指针有一个已经扫完了,就退出循环
			if (helper[pleft] <= helper[pright]) { // 当左指针所指元素小于等于右指针所指元素时
				arr[current] = helper[pleft];
				current++;
				pleft++;
			} else { // 当左指针所指元素大于右指针所指元素时
				arr[current] = helper[pright];
				current++;
				pright++;
				count += mid - pleft + 1; // 当右侧元素大于左侧时,则为逆序,此时与该元素组成逆序对的个数为左侧剩余元素的个数
			}
		}
		// 至此,左右两指针其一已经扫描完毕,如果是左侧先扫描完则无需处理
		// 如果是右侧先扫描完,则需将左侧为扫描完的元素都放入数组末尾
		while (pleft <= mid) {
			arr[current] = helper[pleft];
			current++;
			pleft++;
		}
	}
}

测试结果:
测试结果

骚话时刻:
感觉每个人多多少少都有点完美主义吧,总觉得一件事如果有了污点就不愿意再做下去,期待着下一个节点,为自己蹲好理由再重新出发,可在一件事情完成之前难免都会有点磕磕绊绊,这可咋办?
拿我自己学习算法做个比方吧,比如我前一段时间学排序学到一半,突然被各种各样的事情打断,以至于有几天没有接触它,自己的计划表上算法学习的任务也越欠越多,不知道你们怎么想,反正我是真的会因此抵触去学习算法。于是就越拖越久…我身边也有很多这样的例子,比如很多人打卡背单词,坚持了一阵子,结果有一天突然断了,可能下一次开始就是数n天之后hhh(偷笑,自己也中招过)。
我认为这是完美主义(处女座?)的一种体现吧(不排除有些人就是懒),总觉得一旦没有达到预期的样子,整个事情就不再完美,也就不想做下去。可能会有人说那就乐观一些,不要管他,学就是了。叫人乐观恐怕是最敷衍的方式了吧,毕竟谁都想乐观一点,问题是怎么乐观呢?我的心情就是这样,怎么调整呢?
好吧,其实我也不知道怎么处理(如果有人坚持看我废话到这里,恐怕想要锤si我)
表情包
哈哈哈,但是,就我经历来说,我会推荐你,别想!不完美就不完美!不完美我也要学!
毕竟人都是爱幻想的,完美的事情几乎是不存在的,那就接受不完美嘛,这样才有成长的空间呀,人生不就是让自己越来越成熟,没经历过啥风风雨雨,以后老了回忆起来都没啥意思对吧

归并排序告诉我,不能一次性完整地完成某项任务,那就一刀切,切成一小块一小块去完成,最后整合起来就非常强大了。所以不能完美地完成任务,那就不完美地做吧,点点滴滴积累起来你也会变得强大

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值