归并排序
归并排序核心思想:
- 确定分界点:mid = l + r >> 1,分界点将数组分为left和right两边;
- 递归处理left和right,left的范围是:[l, mid], right的范围是:[mid + 1, r]
- 归并--将left和right合二为一
归并的含义是将两个或两个以上的有序表组成一个新的有序表。
void merge_sort(int q[], int l, int r)
{
if(l >= r) return;
int mid = l + r >> 1;
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while(i <= mid && j <= r)
{
if(q[i] <= q[j]) tmp[k++] = q[i++];//temp是临时用来存放
else tmp[k++] = q[j++];
}
while(i <= mid) tmp[k++] = q[i++];
while(j <= r) tmp[k++] = q[j++];
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
归并排序的时间效率:每趟归并排序的时间复杂度是,共需进行趟排序,所以时间复杂度为:
逆序对的数量
给定一个长度为 n的整数数列,请你计算数列中的逆序对的数量。
逆序对的定义如下:对于数列的第 ii 个和第 jj 个元素,如果满足 i<j且 a[i]>a[j],则其为一个逆序对;否则不是。
本题可以使用归并排序的思想解决,将数组从中间mid分为左右两个数组left和right,可以将情况分为三种情况:1.逆序对产生在左边数组 2.逆序对产生在右边数组 3.逆序对中第一个元素在左边,第二个元素在右边。mid = l + r >> 1,left的范围为[l, mid],right的范围为[mid + 1, r]。其实说是分为三种情况,其实统计逆序对的数量一直使用的第三种情况计算个数。
long long merge_sort(int q[], int l, int r)
{
if(l >= r) return 0;
int mid = (l + r) >> 1;
long long num = merge_sort(q, l, mid) + merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while(i <= mid && j <= r)//#1
{
if(q[i] <= q[j]) tmp[k++] = q[i++];
else
{
num += mid - i + 1;//#2
tmp[k++] = q[j++];
}
}
while(i <= mid) tmp[k++] = q[i++];
while(j <= r) tmp[k++] = q[j++];
for(i = l, j = 0; i <= r; i ++, j ++) q[i] = tmp[j];
return num;
}
代码解释:
#1对于这整个while循环,一边是在进行归并操作,一边是在进行逆序对个数统计操作,因为关于归并的部分已经在归并中解释过,此时只解释统计部分,当q[i]和q[j]经行比较时,q[i] <= q[j],此时不满足逆序对的规则,所以i++,当发现q[i] > q[j]时,满足逆序对的规则,此时统计逆序对个数,然后执行j++,假设为j1,j++后i并没有发生变化,如果此时也满足条件,也可以用#2求出个数,不会有重复情况,因为此时逆序对的第二个元素变为j1,逆序对与第二个元素为j时不同
#2是指当发现q[i] > q[j]时,此时i < j(因为i在左半边数组,j在右半边数组),满足逆序对的条件,而且因为同时在执行归并排序,所以左半边数组,和右半边数组分别有序,那么q[j] < q[i] < q[i + 1] < q[i + 1]...<q[mid],那么左半边序列与q[j]组成的逆序对个数就是mid - i + 1.