算法体系结构第四课

一、归并排序
1.递归法
递归函数:(basecase:当L = R时,return)
取得数组的中间元素下标mid,
对它左边也就是[L…mid]调用递归进行排序,
对它右边也就是[mid…R]调用递归进行排序,
对这两边进行merge;

merge函数:传入参数有 L、R、mid、arr
准备一个帮助数组help[ ],与它对应的下标值 i,两个指针p1和p2,
当两个指针都未越界的时候,

help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];

比较p1和p2索引值对应数组的值,谁大就把谁放入帮助数组当中,并且i++,放了谁就把对应的指针++,
只会有一个指针越界,越界了就停下,
把没有越界的指针对应的那半部分值挨着写到帮助数组中

while (p1 <= M) {
    
    
    help[i++] = arr[p1++];
}
特别注意:此处i++的目的是,有可能一个指针越界了,另一个指针对应的那一半的元素还有很多,
这个时候需要挨个把那一半的元素全都写到帮助数组中去,i是必须要++

最后把帮助数组拷贝给arr就可以了

2.迭代法
步长为每次merge的半组长度,每次乘2
步长大于N,退出
每次L从0开始,对对应步长的组进行merge,
L小于N时循环,mid = L + 步长 - 1,
如果mid大于等于N,则退出,
右边界 R = min(mid + 步长 , N - 1)

上面两句话的意思是,如果没有右组,就不merge
如果右组不够,也要merge,只不过取的是剩下的元素

防溢出,当步长大于N / 2时,退出,

因为当N非常大,步长接近N时,步长乘2后可能会溢出整数的最大值,变成负数,
而当步长小于 N/2 时则可以很放心的让它乘2

最后让步长乘2即可;

二、小和问题
描述:判断每个数的左边有几个数比它小,把那些小的数加起来
判断每个数的左边有几个比它小的数,可以转换为,判断每个数的右边有几个数比它大,有几个,就自身乘以几,每个数都这么做,也可以产生正确的小和;
选取某个普通的数作为对象,当它作为右组的时候,对它操作将不产生小和,当它作为左组时,如果它的值比它所比较的右组的元素的值小,那就会产生小和,而如果右组是排好序的,还可以轻易地判断产生了几个小和

故对原有的归并排序进行一定的改写即可解决此问题
递归函数:判空,返回左边排好序时产生的小和 + 右边排好序时产生的小和 + 对左右两边进行merge时产生的小和

return 
		process(arr, l, mid)
		+
		process(arr, mid + 1, r)
		+
		merge(arr, l, mid, r);

merge函数:在原有的递归版归并排序的基础上,在两个数组都没有越界的while中,添加一句:
当左组严格小于右组的时候,把左组元素的值乘以 (右组右边界R - 该右组元素的位置p2 + 1)收集起来
当左右组碰上相同的元素的时候,先拷贝右组的元素,也就是当左组元素严格小于右组元素时才拷贝,对于收集小和而言实质是指针的移动

说明:碰上相同元素时,可以先拷贝左组元素,因为右组是排好序的,小和数也是明确的,
但这样不太好实现,所以可以先拷贝右组元素,拷贝时不产生小和,p2跳到下一个右组元素
这样做似乎可以保证一个统一的逻辑,从而用一句代码有力地表达出来!

最后返回收集的结果即可;

我懂了,如果相等的元素在右组中有重复的,就不能确定有多少数比左组的相等的数大,
所以要先拷贝右组的元素,当相等的元素完全跳过之后,就可以判断有多少个比它大的数了,
所以只能拷贝右组的相等的数;

就是说我拿着左组的每个元素判断,在右组中有多少个比它大,如果相等时先拷贝左组元素,相当于没碰它;

三、逆序对
描述:任何一个左边的数大于右边的数时,构成一个逆序对
把小和问题改为从每组的末尾开始比较即可;

四、指针不回退技巧
设windowR为右组第一个数,对左组进行遍历,当右组的数大于windowR所指的数时,windowR++,
不大于时,则可以推算出左组中比右组当前的数小的个数,而因为右组是有序的,所以操作下一个左组元素的时候,不必将指针回退;

猜你喜欢

转载自blog.csdn.net/dgytjhe/article/details/119086363