写在前面:这篇博客来源于对一串归并排序代码的修改,本文将指出其错误并且将修改后代码的性能与另一种写法的性能进行对比。
首先来看下面一串代码 从代码语义上来理解并没有什么问题 但在程序运行过程中实际上会出现一定错误
#include <iostream>
using namespace std;
#define N 7
typedef int Rcd;
int Temp[N];
void Merge (Rcd R[], int low, int mid, int high, Rcd T[]){
int P_left=low, P_right=mid+1, P_result=low;
while(P_left<=mid && P_right<=high ){
if(R[P_left]<R[P_right]){
T[P_result]=R[P_left]; P_result++;P_left++;
}
else{
T[P_result]=R[P_right];P_result++; P_right++;
}
}
while(P_left<=mid){
T[P_result]=R[P_left]; P_result++; P_left++;
}
while(P_right<=high ) {
T[P_result]=R[P_right]; P_result++; P_right++;
}
}
void MSort( Rcd R[], int low, int high, Rcd T[]){
//对R[low..high]进行归并排序,结果存入T[low..high]
if( low == high ) T[low]=R[low]; //递归边界
else{
int mid=(low+high)/2;
MSort(R, low , mid,Temp); //左侧递归
MSort(R, mid+1, high,Temp);//右侧递归
Merge(Temp, low, mid, high, T); //归并
}
}
int main(){
int R[7]={1,3,5,2,7,4,9};
MSort(R,0,6,R);
for(int i=0;i<7;i++)
cout<< R[i]<<" ";
return 0;
}
最终输出结果为 1 2 2 2 4 4 9
那么存在的问题是什么呢? 实际上是数组在更新时自身发生了冲突
通过分析我们发现:在程序运行的大部分时间里都是Temp
数组在更新自己 这也就导致了Temp
自身会发生冲突
这里给出一个例子:当对数组中的5
和2
进行归并时,P_left=2,P_right=3,P_result=2
放在代码中考虑,即R[2]=5, R[3]=2
∵5 > 2
故使得T[2]=2
即原来的5
被2
覆盖掉了 影响了后面本应当使得T[3]=5
的操作
回到刚才所说的 R
和T
实质上都是Temp
最终导致了错误
因此为了避免同一数组发生冲突应当开一个辅助数组来拷贝当前数组的值
只需修改Merge
函数部分 如下:
void Merge (Rcd R[], int low, int mid, int high, Rcd T[]){
int P_left=low, P_right=mid+1, P_result=low;
int r[N];
memcpy(r, R, sizeof r);
while(P_left<=mid && P_right<=high ){
if(r[P_left]<r[P_right]){
T[P_result]=r[P_left]; P_result++;P_left++;
}
else{
T[P_result]=r[P_right];P_result++; P_right++;
}
}
while(P_left<=mid){
T[P_result]=r[P_left]; P_result++; P_left++;
}
while(P_right<=high ){
T[P_result]=r[P_right]; P_result++; P_right++;
}
}
问题解决完了 下面我们来分析这种写法与另一种写法的性能
先贴一个另一种写法的代码 记为写法2 同时上述代码记为写法1
#include <iostream>
using namespace std;
#define N 100010
typedef int Rcd;
int T[N];
void Merge (Rcd R[], int low, int mid, int high){
int P_left=low, P_right=mid+1, P_result=low;
while(P_left<=mid && P_right<=high ){
if(R[P_left]<=R[P_right]){
T[P_result]=R[P_left]; P_result++;P_left++;
}
else{
T[P_result]=R[P_right];P_result++; P_right++;
}
}
while(P_left<=mid){
T[P_result]=R[P_left]; P_result++; P_left++;
}
while(P_right<=high ) {
T[P_result]=R[P_right]; P_result++; P_right++;
}
for(int i=low;i<=high;i++) R[i]=T[i]; //从T写回R,注意对比教材
}
void MSort( Rcd R[], int low, int high){
//对R[low..high]进行归并排序
if( low == high ) return; //递归边界
else{
int mid=(low+high)/2;
MSort(R, low , mid); //左侧递归
MSort(R, mid+1, high);//右侧递归
Merge(R, low, mid, high); //归并
}
}
int main(){
int R[N];
int n;
cin >> n;
for(int i = 0; i < n; i ++ )
cin >> R[i];
MSort(R,0,n - 1);
for(int i=0;i<n;i++)
cout<< R[i]<<" ";
return 0;
}
两者的时间复杂度整体上来看都是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的 但是:
写法1的除了函数传参要多辅助数组①以外 还要针对辅助数组再建一个辅助数组②(也就是上面代码中的r数组)这就使得建立的辅助数组②每次都要拷贝一下辅助数组① 即每次进行Merge
操作都有
O
(
n
)
O(n)
O(n)的时间复杂度
而写法2每次执行for(int i=low;i<=high;i++) R[i]=T[i];
次数一定是小于等于n
的(high-low+1
次) 显然相较于写法1更快一些
在空间复杂度上:就像上面提到的 写法1需要针对Temp
数组额外开一个辅助数组r
而写法2就不需要 故写法1的空间复杂度上要大一些
在洛谷的快速排序模板题中提交验证也是这样的 题目链接
下面的提交记录为写法1 上面为写法2
综上所述:我认为写法2不管在时间复杂度还是空间复杂度上都要优于写法1 同时写法2也更好理解
尾语:
感谢您观看我的文章 如有错误欢迎在评论区指正~