什么是逆序对?
数组中任意两个元素,凡是满足左边数字大于右边的,就构成一个逆序对。例如【5,9,3,6,8,10,1,4】 (10,1)就构成一个逆序对。
本文将介绍使用归并算法来求解逆序对的相关问题。
如何求逆序对呢?
方法一:暴力法
最先想到的就是暴力法,双重for循环判断即可,这种方式就不在介绍了。
方法二:归并算法
求之前,我们先理解一下归并排序算法。一定要先理解归并排序算法,能理解归并排序算法了,那么求逆序对的问题就很简单,就是在归并排序算法上加上几行代码。
归并排序算法
归并排序算法,主要就是采用分治法的思想,将数组分解,分解的子序列长度为1时,这个子序列就是有序的,然后将分解之后的子序列两两进行合并即可。
详细的归并排序算法可以查看其他资料。
上代码:
//使用递归的方式,将待排序的数组进行分解
public void mergeSort(int[] a,int low,int high){
if (low < high) {
//递归分解,右移一位,相当除2
int mid = (low + high) >> 1;
mergeSort(a, low, mid);
mergeSort(a, mid + 1, high);
//合并
merge(a, low, mid, high);
}
}
//将两个子序列归并成一个子序列
public void merge(int[] a,int low,int mid,int high){
int[] temp = new int[a.length];
int pa = low;
int pb = mid+1;
//新的数组,用来临时存放两个子序列的归并结果
int pt = 0;
while(pa <= mid && pb <= high){
//左侧子序列 取出来一个值 插入到新数组
if (a[pa] < a[pb]){
temp[pt] = a[pa];
pa++;
pt++;
}else{
//左侧子序列 取出来一个值 插入到新数组
temp[pt] = a[pb];
pb++;
pt++;
}
}
//处理数据有剩余
//左侧子序列有剩余
while(pa <= mid){
temp[pt] = a[pa];
pa++;
pt++;
}
//右侧子序列有剩余
while(pb <= high){
temp[pt] = a[pb];
pb++;
pt++;
}
//将临时存放结果 保存到原数组中
for (int i = 0 ; i < pt ; i++){
a[low+i] = temp[i];
}
}
//主函数测试 注:Print是自定义的一个打印数组元素的方法
public static void main(String[] args) {
int[] b = {5,9,3,6,8,10,1,4};
TestSort testSort = new TestSort();
System.out.println("排序前:");
Print(a);
testSort.mergeSort(a,0,a.length-1);
System.out.println("排序后:");
Print(a);
}
分析:归并算法其实就是分解然后在合并,这其中有一点,就是准备合并的两个子序列是已经有序的,合并的时候,将两个子序列中的最小值放进新的临时数组中,这样就能完成两个子序列合并之后的序列是有序的。于是我们可以利用这一点来求解逆序对的问题。
归并算法求解逆序对
问题分析
回归正题,我们相求逆序对,那就是左边的数值大于右边的数值就行,那我们看一下当归并排序算法在合并两个子序列的时候。如下图:
我们发现,当合并两个子序列的时候,当右侧子序列的值能插入临时数组时,那么左侧子序列中的值都要比右侧子序列准备插入的值要大,这样左侧子序列的所有值与右侧当前这个值一组合,那不就构成了逆序对嘛(左大右小)。
比如上图:右侧子序列此时能插入1到临时数组中,那么此时,左侧序列的所有值3、4、6、9都比1大,这样一组合,那逆序对不就是(3,1)(4,1)(6,1)(9,1) 。请结合上图仔细思考,只要能理解这一点,那么逆序对的问题就好理解了。
问题解决
通过上面的分析,在合并两个子序列时,我们可以在右侧子序列插入临时数组直接加上一些操作,比如,使用for循环将左侧子序列的值打印出来。
以下就是我们新增加的代码。完整代码见代码实例。
//遍历左侧子序列的所有值
for (int i = pa; i <= mid; i++) {
System.out.println("("+a[i]+","+a[pb]+")");
}
代码实例
求数组中所有的逆序对代码:
//递归(分解序列)
public void mergeSort(int[] a,int low,int high){
if (low < high) {
//递归分解,右移一位,相当除2,小了一倍
int mid = (low + high) >> 1;
mergeSort(a, low, mid);
mergeSort(a, mid + 1, high);
//合并
merge(a, low, mid, high);
}
}
//归并(合并序列)
public void merge(int[] a,int low,int mid,int high){
int[] temp = new int[a.length];
//左侧子序列的头部
int pa = low;
//左侧子序列的头部
int pb = mid+1;
//临时数组的头部
int pt = 0;
while(pa <= mid && pb <= high){
if (a[pa] < a[pb]){
temp[pt] = a[pa];
pa++;
pt++;
}else{
//可以求出所有的逆序对。
for (int i = pa; i <= mid; i++) {
System.out.println("("+a[i]+","+a[pb]+")");
}
temp[pt] = a[pb];
pb++;
pt++;
}
}
//处理数据有剩余
//pa有剩余
while(pa <= mid){
temp[pt] = a[pa];
pa++;
pt++;
}
//pb有剩余
while(pb <= high){
temp[pt] = a[pb];
pb++;
pt++;
}
for (int i = 0 ; i < pt ; i++){
a[low+i] = temp[i];
}
}
扩展内容
文章到了这里,就求出了数组中的所有逆序对了。
那么为什么写这篇文章呐,我遇到一个面试题,是求给定的一个数组中的最大逆序对的。因此写这篇文章记录一下。
请写一段函数代码,输入一个数组,比如【5,9,3,6,8,10,1,4】找到数组中“加和”最大的“逆序对”。
其实思想是一样的,只不过是不需要将逆序对全输出出来了,求个加和最大就行了。
上代码:
while(pa <= mid && pb <= high){
if (a[pa] < a[pb]){
temp[pt] = a[pa];
pa++;
pt++;
}else{
// 遍历所有的逆序对。
// for (int i = pa; i <= mid; i++){
// System.out.println("("+a[i]+","+a[pb]+")");
}
//优化,直接加上左序列的末尾元素(末尾元素最大)
if (a[mid]+ a[pb] > (Integer) map.get("max")){
map.put("max",a[mid]+ a[pb]);
map.put("value","("+a[mid]+","+a[pb]+")");
}
temp[pt] = a[pb];
pb++;
pt++;
}
}
总结
以上就是我所遇到的问题,希望能对大家有所帮助,其实重点就是向弄清楚归并排序算法,剩下的就是该算法的演进和扩展。
仓促成文,如有纰漏,还请各位读者指出,如有其他问题,可以在下方留言,我看到了会和大家讨论解决的。