#include<iostream>
using namespace std;
void Merge(int arr[],int r[],int start,int mid,int end)
{
int i = start, j = mid+1,k=start;
while(i<=mid && j<= end)
{
if(arr[i]>arr[j])
{
r[k++] = arr[j++];
}
else{
r[k++] = arr[i++];
}
}
while(i<=mid)
{
r[k++]=arr[i++];
}
while(j<=end)
{
r[k++] = arr[j++];
}
}
void MergeSort(int arr[],int start,int end)
{
int r[10000];
if(start==end) return ;
int mid = (start+end)/2;
MergeSort(arr,start,mid);
MergeSort(arr,mid+1,end);
Merge(arr,r,start,mid,end);
for(int i=start;i<=end;i++)
{
arr[i] = r[i];
}
}
int main()
{
int arr[]={9,7,3,6,5,8,1,4,2,10};
MergeSort(arr,0,9);
for(int i=0;i<10;i++)
{
cout<<arr[i]<<" ";
}
return 0;
}
很多小白不知道为什么函数中这么多参数。我来一一讲解:
void MergeSort(int arr[],int start,int end)
首先看这个MergeSort函数。主函数中首先调用它。它是归并排序的主入口。参数arr[]是要排序的数组。start是开始位置,end是结束位置。
void MergeSort(int arr[],int start,int end)
{
int r[10000]; //这个是一个辅助数组,现在不知道要用它,看到后面在回来看这个。
if(start==end) return ; //想一下,一直“二分”,总要有个结束的位置,位置就是只要一个元素(即这二者相等)
int mid = (start+end)/2;//这是来计算中间位置的
MergeSort(arr,start,mid);//调用自身,注意这里真正有用的是调用到最下面一层的“start、mid”的数值
MergeSort(arr,mid+1,end);//当执行完这个时就会出现下面一个图(1)
Merge(arr,r,start,mid,end);//进行合并,这里用到了辅助数组,因为要把合并的数据放在辅助数组中,所以要另开一个辅助数组。
for(int i=start;i<=end;i++)//我们想要的是原数组排好序,所以讲辅助数组赋值给原数组。
{
arr[i] = r[i];
}
}
仔细看已知两个有序数组,如何合并成一个大的数组呢?
void Merge(int arr[], int r[], int start, int mid, int end)
{
// 合并两个已排序的子数组到数组 r 中。
int i = start, j = mid + 1, k = start;
// 初始化三个指针:i 指向第一个子数组的起始位置,j 指向第二个子数组的起始位置(mid+1),k 指向结果数组的起始位置。
while (i <= mid && j <= end)
{
// 循环条件:两个子数组都没有遍历完。
if (arr[i] > arr[j])
{
// 如果第一个子数组的当前元素大于第二个子数组的当前元素,
r[k++] = arr[j++];
// 将第二个子数组的元素放入结果数组中,并移动 j 和 k 指针。
}
else
{
// 否则,将第一个子数组的当前元素放入结果数组中。
r[k++] = arr[i++];
// 并移动 i 和 k 指针。
}
}
while (i <= mid)
{
// 将第一个子数组剩余的元素复制到结果数组中。
r[k++] = arr[i++];
// 移动 i 和 k 指针。
}
while (j <= end)
{
// 将第二个子数组剩余的元素复制到结果数组中。
r[k++] = arr[j++];
// 移动 j 和 k 指针。
}
}
-
void Merge(int arr[], int r[], int start, int mid, int end)
:- 声明了一个名为
Merge
的函数,接受五个参数:待合并的数组arr
,结果数组r
,起始索引start
,中间索引mid
和结束索引end
。
- 声明了一个名为
-
int i = start, j = mid + 1, k = start;
:- 初始化三个指针:
i
指向第一个子数组的起始位置start
,j
指向第二个子数组的起始位置mid + 1
,k
指向结果数组的起始位置start
。
- 初始化三个指针:
-
while (i <= mid && j <= end)
:- 当
i
指针未超过第一个子数组的结束位置mid
且j
指针未超过第二个子数组的结束位置end
时,执行循环。
- 当
-
if (arr[i] > arr[j])
:- 如果第一个子数组的当前元素
arr[i]
大于第二个子数组的当前元素arr[j]
。
- 如果第一个子数组的当前元素
-
r[k++] = arr[j++];
:- 将第二个子数组的当前元素
arr[j]
赋值给结果数组r
的当前位置k
,然后将j
和k
向后移动一位。
- 将第二个子数组的当前元素
-
else { r[k++] = arr[i++]; }
:- 否则,将第一个子数组的当前元素
arr[i]
赋值给结果数组r
的当前位置k
,然后将i
和k
向后移动一位。
- 否则,将第一个子数组的当前元素
-
while (i <= mid)
:- 将第一个子数组中剩余的元素复制到结果数组
r
中。
- 将第一个子数组中剩余的元素复制到结果数组
-
r[k++] = arr[i++];
:- 将
arr[i]
赋值给r[k]
,然后将i
和k
向后移动一位。
- 将
-
while (j <= end)
:- 将第二个子数组中剩余的元素复制到结果数组
r
中。
- 将第二个子数组中剩余的元素复制到结果数组
-
r[k++] = arr[j++];
:- 将
arr[j]
赋值给r[k]
,然后将j
和k
向后移动一位。
- 将
接下来看一个由归并排序推导出来的题目:
对于给定的数组A,计算其逆序对的总数。即:
【输入形式】
输入包含1组测试用例。
一个测试用例占一行,第一个整数表示数组的长度,后面紧跟者数组中的各个整数元素,中间都用一个空格分开。
数组的长度
范围
每个数字A[i]的范围为
【输出形式】
输出一个整数,表示逆序对的个数。
【样例输入】
5
4 5 1 3 2
【样例输出】
7
其实只要加一行代码就OK了:
要统计数组中的逆序对数量,可以在合并两个子数组的过程中,当发现左子数组的当前元素大于右子数组的当前元素时,增加逆序对的计数。具体来说,如果
arr[i] > arr[j]
,则arr[i]
与arr[j]
及其之后的所有元素形成逆序对,因为子数组是有序的。
#include<iostream>
using namespace std;
int res=0;
void Merge(int arr[],int r[],int start,int mid,int end)
{
int i = start, j = mid+1,k=start;
while(i<=mid && j<= end)
{
if(arr[i]>arr[j])
{
r[k++] = arr[j++];
res += (mid - i + 1); // 增加逆序对的数量
}
else{
r[k++] = arr[i++];
}
}
while(i<=mid)
{
r[k++]=arr[i++];
}
while(j<=end)
{
r[k++] = arr[j++];
}
}
void MergeSort(int arr[],int start,int end)
{
int r[10000];
if(start==end) return ;
int mid = (start+end)/2;
MergeSort(arr,start,mid);
MergeSort(arr,mid+1,end);
Merge(arr,r,start,mid,end);
for(int i=start;i<=end;i++)
{
arr[i] = r[i];
}
}
int main()
{
int arr[10000];
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>arr[i];
MergeSort(arr,1,n);
for(int i=1;i<=n;i++)
{
cout<<arr[i]<<" ";
}
cout<<endl;
cout<<"逆序对数量:"<<res<<endl;
return 0;
}