Skip to content

Commit dd79840

Browse files
committed
Update format
1 parent 6ca761b commit dd79840

3 files changed

Lines changed: 696 additions & 0 deletions

File tree

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
- [归并排序](#归并排序)
2+
- [算法详解](#算法详解)
3+
- [例题:归并排序](#例题归并排序)
4+
- [算法模板](#算法模板)
5+
- [练习:逆序对](#练习逆序对)
6+
- [练习:剑指 Offer 51. 数组中的逆序对](#练习剑指-offer-51-数组中的逆序对)
7+
8+
9+
## 归并排序
10+
11+
### 算法详解
12+
13+
稳定,思想:分治
14+
15+
时间复杂度:nlogn
16+
17+
![](https://raw.githubusercontent.com/timerring/picgo/master/picbed/image-20220908204727373.png)
18+
19+
以数组的中间部分来分,分为左边和右边。
20+
21+
+ 确定分界点。mid = (1+r)/2;
22+
23+
+ 递归排序left和right。
24+
25+
+ 合二为一(重点)。合成一个有序的数组。
26+
27+
合并的方法:双指针法。
28+
29+
![](https://raw.githubusercontent.com/timerring/scratchpad2023/main/2023/image-20220908203821274.png)
30+
31+
![](https://raw.githubusercontent.com/timerring/scratchpad2023/main/2023/image-20220908203945084.png)
32+
33+
![](https://raw.githubusercontent.com/timerring/picgo/master/picbed/image-20220908204101457.png)
34+
35+
由于归并排序是稳定的,因此在两数相同的时候可以把第一个数字移动到尾部。
36+
37+
![](https://raw.githubusercontent.com/timerring/picgo/master/picbed/image-20220908204332955.png)
38+
39+
40+
41+
### 例题:归并排序
42+
43+
给定你一个长度为 n 的整数数列。
44+
45+
请你使用归并排序对这个数列按照从小到大进行排序。
46+
47+
并将排好序的数列按顺序输出。
48+
49+
**输入格式**
50+
51+
输入共两行,第一行包含整数 $n$。
52+
53+
第二行包含 $n$ 个整数(所有整数均在 1∼$10^9$ 范围内),表示整个数列。
54+
55+
**输出格式**
56+
57+
输出共一行,包含 $n$ 个整数,表示排好序的数列。
58+
59+
**数据范围**
60+
61+
1≤ n ≤100000
62+
63+
**输入样例:**
64+
65+
```
66+
5
67+
3 1 2 4 5
68+
```
69+
70+
**输出样例:**
71+
72+
```
73+
1 2 3 4 5
74+
```
75+
76+
### 算法模板
77+
78+
```cpp
79+
#include <iostream>
80+
81+
using namespace std;
82+
83+
const int N = 1e5 + 10;
84+
85+
int a[N], tmp[N];
86+
87+
void merge_sort(int q[], int l, int r)
88+
{
89+
if (l >= r) return;
90+
91+
int mid = l + r >> 1;
92+
// 取中间的位置
93+
94+
// 分别归并排序左右两侧,进行排序
95+
merge_sort(q, l, mid), merge_sort(q, mid + 1, r);
96+
97+
// =========归并的过程===========
98+
// k表示已经归并的数,i为指向左半边序列的起点,j为指向右半边序列的起点。
99+
int k = 0, i = l, j = mid + 1;
100+
while (i <= mid && j <= r)
101+
// 进行判断,每次把小的那部分放在当前位置上。
102+
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
103+
else tmp[k ++ ] = q[j ++ ];
104+
// 如果左右两边没有循环完的话,贴在数组最后
105+
while (i <= mid) tmp[k ++ ] = q[i ++ ];
106+
while (j <= r) tmp[k ++ ] = q[j ++ ];
107+
108+
// 存回q数组中
109+
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
110+
}
111+
112+
int main()
113+
{
114+
int n;
115+
scanf("%d", &n);
116+
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
117+
118+
merge_sort(a, 0, n - 1);
119+
120+
for (int i = 0; i < n; i ++ ) printf("%d ", a[i]);
121+
122+
return 0;
123+
}
124+
```
125+
126+
127+
128+
### 练习:逆序对
129+
130+
给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。
131+
132+
逆序对的定义如下:对于数列的第 i 个和第 j个元素,如果满足 i<j 且 a[i]>a[j],则其为一个逆序对;否则不是。
133+
134+
输入格式
135+
136+
第一行包含整数 n,表示数列的长度。
137+
138+
第二行包含 n个整数,表示整个数列。
139+
140+
输出格式
141+
142+
输出一个整数,表示逆序对的个数。
143+
144+
数据范围
145+
146+
1≤n≤100000,
147+
数列中的元素的取值范围 $[1,10^9]$。
148+
149+
输入样例:
150+
151+
```
152+
6
153+
2 3 4 5 6 1
154+
```
155+
156+
输出样例:
157+
158+
```
159+
5
160+
```
161+
162+
code:
163+
164+
思路还是标准的归并,但是需要注意的是res的范围。首先不妨考虑最复杂的情况,即这组数是从大到小排序好的,也就是一共可以组成$(n - 1) + (n - 2) + ... + 1 = \frac{n (n - 1)}{2}$,这里n最大是100000,因此逆序对最大为$5*10^9$,超过了int范围,可以选择开long long存储。
165+
166+
```cpp
167+
#include <iostream>
168+
169+
using namespace std;
170+
171+
typedef long long LL;
172+
173+
const int N = 1e5 + 10;
174+
175+
int a[N], tmp[N];
176+
177+
LL merge_sort(int q[], int l, int r)
178+
{
179+
if (l >= r) return 0;
180+
181+
int mid = l + r >> 1;
182+
// 收集每次排序中的逆序对
183+
LL res = merge_sort(q, l, mid) + merge_sort(q, mid + 1, r);
184+
185+
int k = 0, i = l, j = mid + 1;
186+
while (i <= mid && j <= r)
187+
// i指向的是左半段,j指向的是右半段
188+
// 正常情况是左半段小于右半段
189+
if (q[i] <= q[j]) tmp[k ++] = q[i ++];
190+
// 否则说明是逆序,j和(i到mid)之间的所有数都组成逆序对,因为i之后的部分都大于i,同样也就大于j。
191+
else
192+
{
193+
res += mid - i + 1;
194+
tmp[k ++ ] = q[j ++ ];
195+
}
196+
while (i <= mid) tmp[k ++ ] = q[i ++ ];
197+
while (j <= r) tmp[k ++ ] = q[j ++ ];
198+
199+
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
200+
201+
return res;
202+
}
203+
204+
int main()
205+
{
206+
int n;
207+
scanf("%d", &n);
208+
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
209+
210+
cout << merge_sort(a, 0, n - 1) << endl;
211+
212+
return 0;
213+
}
214+
```
215+
216+
217+
218+
### 练习:剑指 Offer 51. 数组中的逆序对
219+
220+
[剑指 Offer 51. 数组中的逆序对](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/)
221+
222+
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
223+
224+
示例 1:
225+
226+
```
227+
输入: [7,5,6,4]
228+
输出: 5
229+
```
230+
231+
232+
限制:
233+
234+
```
235+
0 <= 数组长度 <= 50000
236+
```
237+
238+
code
239+
240+
```cpp
241+
class Solution {
242+
public:
243+
int reversePairs(vector<int>& nums) {
244+
vector<int> tmp(nums.size());
245+
return merge_sort(nums, tmp, 0, nums.size() - 1);
246+
}
247+
248+
long long merge_sort(vector<int>& nums, vector<int>& tmp, int l, int r)
249+
{
250+
if( l >= r) return 0;
251+
252+
int mid = l + r >> 1;
253+
254+
long long res = merge_sort(nums, tmp, l, mid) + merge_sort(nums, tmp, mid + 1, r);
255+
256+
int k = 0, i = l, j = mid + 1;
257+
258+
while( i <= mid && j <= r){
259+
if(nums[i] <= nums[j]) tmp[k ++ ] = nums[i ++ ];
260+
else{
261+
res += mid - i + 1;
262+
tmp[k ++ ] = nums[j ++ ];
263+
}
264+
}
265+
266+
while(i <= mid) tmp[k ++ ] = nums[i ++ ];
267+
while(j <= r) tmp[k ++ ] = nums[j ++ ];
268+
269+
for(i = l, j = 0; i <= r; i ++, j ++ ) nums[i] = tmp[j];
270+
271+
return res;
272+
}
273+
};
274+
```
275+
276+
277+

0 commit comments

Comments
 (0)