最简练写法实现归并排序【C++代码】

目录

归并排序

C++代码实现

稳定性分析

求逆序对


归并排序

归并排序的思想是分治。其过程也就是“分”和“治”的两个步骤。

对于一个序列,我们先将其一分为二,分别排好序,然后再合并这两个有序数列。

对于被平分成的两部分怎么排序呢?再一分为二,递归下去,直至分成单个元素时,即自然有序了。

C++代码实现

网上很多代码会把归并排序的分和治两个环节分开写,这样可能好理解,但是函数太多了,冗余。

我更习惯将其合并起来写,看起来更简洁。

int t[50005]; //用于暂时存放合并的元素的空数组
void merge_sort(int* a,int l,int r){
//分
	if (l==r) return;  //递归出口
	int mid=(l+r)/2;
	merge_sort(a,l,mid);
	merge_sort(a,mid+1,r);
//治
	int i=l;
	int j=mid+1;
	for (int k=l; k<=r; k++){ //合并
		if ( (j>r) || (i<=mid && a[i]<=a[j]) ) {
			t[k]=a[i];
			i++;
		} else {
			t[k]=a[j];
			j++; 
		}
	}
	for (int k=l; k<=r; k++) a[k]=t[k]; //复制
}

函数调用 void merge_sort(int* a,int l,int r),传入参数分别为 待排序数组,以及左右边界l,r。 

要注意当数组用vector时,需要传入的是引用,也就是说,必须修改原数组。

代码分成两大部分

第一部分是“分”:

    先计算出区间[l,r]的中点mid,然后一直递归下去。
    递归出口是 (l==r),也就是一个数的时候,自然是有序的,也就可以执行第二部分的“治”的步骤。

第二部分是“治”:

    对于排好序的两个数组,进行合并。
    合并的元素需要暂时放到一个空的数组,最后再复制回来。t就是那个暂时的空数组。
    具体合并的方法是用两个指针 i ,j 分别指向待合并的两部分。其中判断a[i]<=a[j]的条件需要特别注意,要考虑周全。
    if ( (j>r) || (i<=mid && a[i]<=a[j]) ) 的意思是: 如果右区间越界(也就是合并完了),或者左区间没越界并且满足小于条件,才选择左区间的数。

稳定性分析

归并排序是稳定的吗?答:上述版本是稳定的。

我们分析一下,在合并两个有序部分时,当 a[i]==a[j] ,我们总是先取 a[i] ,而指针 i 是永远小于 j 的,所以是稳定的。

而如果判断条件    if ( (j>r) || (i<=mid && a[i]<a[j]) )  也就是缺少这个等号的话,就不是稳定的。

求逆序对

利用归并排序求逆序对只需要加一个统计变量即可。

在合并两个有序部分的过程中,当选择了左区间的指针 i 时,我们查看下这时的 j 的大小,计算出已经选择了几个数, 也就是 j-(mid+1)。

 因为 i 是小于 j 的,但是这几个数已经被选择就说明 a[i]>a[j] ,所以有几个数,就是有几个逆序对。

int ans=0;  //用于统计逆序对
int t[50005]; //用于暂时存放合并的元素的空数组
void merge_sort(vector<int>& a,int l,int r){
//分
	if (l==r) return;  //递归出口
	int mid=(l+r)/2;
	merge_sort(a,l,mid);
	merge_sort(a,mid+1,r);
//治
	int i=l;
	int j=mid+1;
	for (int k=l; k<=r; k++){ //合并
		if ( (j>r) || (i<=mid && a[i]<=a[j]) ) {
			t[k]=a[i];
			i++;
            ans+=j-(mid+1); //统计逆序对
		} else {
			t[k]=a[j];
			j++;
            // ans += mid - i + 1; //逆序对也可以在此统计
		}
	}
	for (int k=l; k<=r; k++) a[k]=t[k]; //复制
}

逆序对放在else分支好像更好理解, 当前要选择 j,但是在顺序上,当前的 i ~ mid 都排在 j 前面,也就是说 这几个数都是逆序对。

<think>好的,我现在需要帮用户理解归并排序C++代码。首先,我得回忆一下归并排序的基本原理,确保自己理解正确。归并排序是分治法的典型应用,分为分解和合并两个阶段。分解阶段将数组不断二分,直到子数组长度为1,然后合并阶段将有序的子数组合并成更大的有序数组。 接下来,用户可能需要代码的结构。我得考虑如何用C++实现递归分解和合并。主函数可能包括mergeSort和merge两个函数。mergeSort负责递归拆分数组,merge负责合并两个有序数组。需要注意的是,递归的终止条件是当数组长度小于等于1时返回。 然后,我需要处理具体的实现细节。例如,在merge函数中,需要创建临时数组来存放左右两部分的数据。合并时,使用双指针遍历左右数组,比较元素大小,依次将较小的放入原数组。处理剩余元素时,可能需要循环将剩余部分复制回去。 还要注意代码中的边界条件,比如数组索引是否正确,临时数组的大小是否合适。例如,在计算左右子数组的长度时,mid的位置是否正确,特别是当数组长度为奇数时如何处理。此外,在合并过程中,左右数组的索引是否从0开始,循环条件是否正确,避免越界。 用户可能对代码的某些部分有疑问,比如临时数组的使用、递归的深度,或者时间复杂度的分析。归并排序的时间复杂度是O(n log n),空间复杂度是O(n),这部分可能需要解释清楚。同时,代码中的inplace合并是否真的原地,其实这里用了临时数组,所以空间复杂度是O(n)。 另外,示例代码的正确性很重要。需要确保示例能够运行,并且正确排序。例如,测试用例中的数组是否正确分割和合并。例如,给出的示例数组是{12, 11, 13, 5, 6, 7},排序后应该是{5,6,7,11,12,13},需要确认代码输出是否正确。 还有,用户可能需要了解代码的优化点,比如当子数组较小时可以使用插入排序来减少递归开销,或者如何避免频繁分配临时数组。但在基础代码中可能不需要涉及这些,保持简洁明了更重要。 后,总结整个思路,分步骤解释代码的执行流程,确保用户能理解每个部分的作用。可能需要用自然语言描述代码的执行过程,比如先分解数组,再合并,合并时的比较和复制步骤。同时,指出关键点,如递归终止条件、双指针合并策略等。</think>以下是归并排序C++代码分步解析: --- ### 一、算法原理 归并排序采用**分治法**思想: 1. **分解**:将数组递归地二分为子数组,直到每个子数组只剩1个元素 2. **合并**:将两个有序子数组合并为一个新的有序数组 时间复杂度:$O(n \log n)$ 空间复杂度:$O(n)$ --- ### 二、完整代码实现 ```cpp #include <vector> using namespace std; void merge(int arr[], int left, int mid, int right) { int n1 = mid - left + 1; int n2 = right - mid; // 创建临时数组 vector<int> L(n1), R(n2); // 拷贝数据到临时数组 for (int i = 0; i < n1; i++) L[i] = arr[left + i]; for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j]; // 合并临时数组 int i = 0, j = 0, k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } // 处理剩余元素 while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } void mergeSort(int arr[], int left, int right) { if (left >= right) return; int mid = left + (right - left) / 2; mergeSort(arr, left, mid); // 排序左半部 mergeSort(arr, mid + 1, right); // 排序右半部 merge(arr, left, mid, right); // 合并两个有序数组 } ``` --- ### 三、关键代码解析 #### 1. merge函数 ```cpp void merge(int arr[], int left, int mid, int right) { // 计算左右子数组长度 int n1 = mid - left + 1; // 左子数组长度 int n2 = right - mid; // 右子数组长度 // 创建临时数组存储子数组 vector<int> L(n1), R(n2); // 拷贝数据(注意索引偏移) for (int i = 0; i < n1; i++) L[i] = arr[left + i]; // 左子数组从left开始 for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j]; // 右子数组从mid+1开始 // 双指针合并算法 int i = 0, j = 0, k = left; while (i < n1 && j < n2) { // 比较两个子数组当前元素 if (L[i] <= R[j]) { arr[k++] = L[i++]; } else { arr[k++] = R[j++]; } } // 处理剩余元素 while (i < n1) arr[k++] = L[i++]; while (j < n2) arr[k++] = R[j++]; } ``` #### 2. mergeSort函数 ```cpp void mergeSort(int arr[], int left, int right) { if (left >= right) return; // 递归终止条件 int mid = left + (right - left)/2; // 防溢出写法 mergeSort(arr, left, mid); // 递归处理左半部 mergeSort(arr, mid + 1, right); // 递归处理右半部 merge(arr, left, mid, right); // 合并已排序的子数组 } ``` --- ### 四、使用示例 ```cpp int main() { int arr[] = {12, 11, 13, 5, 6, 7}; int n = sizeof(arr)/sizeof(arr[0]); mergeSort(arr, 0, n-1); // 输出排序结果:5 6 7 11 12 13 for (int i = 0; i < n; i++) cout << arr[i] << " "; return 0; } ``` --- ### 五、算法特点 1. **稳定排序**:相等元素的相对位置不变 2. **适合外排序**:可处理存储在外部存储的大数据 3. **非原地排序**:需要额外$O(n)$空间 4. **递归实现**:通过函数调用栈实现分治逻辑 实际应用中,当子数组长度较小时(约15-20个元素),可改用插入排序优化性能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值