常用模板
two pointers:利用问题本身与序列的特性,使用两个下标 i,j 对序列进行扫描(可以同向扫描,也可以反向扫描),以较低的复杂度(一般是)解决问题。
定和问题
给定一个递增的正整数序列和一个正整数M,求序列中的两个不同位置的数a和b,使得它们的和恰好为M,输出所有满足条件的方案。
模板如下:
while (i < j)
{
if (a[i] + a[j] == m)
{
cout << i << ' ' << j;
++i;
--j;
}
else if (a[i] + a[j] < m)
++i;
else
--j;
}
由于 i 的初值为0,j 的初值为n - 1(i,j 分别指向序列的第一个和最后一个元素),而 i 只递增,j 只递减,循环当 i >= j 时停止,因此 i 和 j 的操作最多为n次。即时间复杂度为。
序列合并问题
假设有两个递增序列A与B,要求将它们合并为一个递增序列C。设置两个下标 i 和 j,初值均为0,分别指向序列A和序列B的第一个元素。
int Merge(int A[], int B[], int C[], int n, int m)
{
int i = 0, j = 0, index = 0; // i指向A[0],j指向B[0]
while (i < n && j < m)
{
if (A[i] <= B[j]) // 如果A[i] <= B[j],将A[i]加入序列C
C[index++] = A[i++];
else // 如果A[i] > B[j],将B[i]加入序列C
C[index++] = B[J++];
}
while (i < n)
C[index++] = A[i++]; // 将序列A的剩余元素加入序列C
while (j < m)
C[index++] = B[j++]; // 将序列B的剩余元素加入序列C
return index;
}
2-路归并排序
递归实现
反复将当前需要排序的区间[left, right]分为两半,对两个子区间[left, mid]和[mid + 1, right]分别递归进行归并排序,然后将两个已经有序的子区间合并为有序序列即可。
模板如下:
const int maxn = 100;
// 将数组A的[L1, R1]与[L2, R2]区间合并为有序区间(此处L2即为R1 + 1)
void Merge(int A[], int L1, int R1, int L2, int R2)
{
int i = L1, j = L2; // i指向A[L1],j指向A[L2]
int temp[maxn], index = 0; // temp临时存放合并后的数组,index为其下标
while (i <= R1 && j <= R2)
{
if (A[i] <= A[j])
temp[index++] = A[i++];
else
temp[index++] = A[j++];
}
while (i <= R1)
temp[index++] = A[i++]; // 将[L1, R1]的剩余元素加入序列temp
while (j <= R2)
temp[index++] = A[j++]; // 将[L2, R2]的剩余元素加入序列temp
for (int i = 0; i < index; ++i)
A[L1 + i] = temp[i]; // 将合并后的序列赋值回数组A
}
// 将array数组当前区间[left, right]进行归并排序
void MergeSort(int A[], int left, int right)
{
if (left < right) // 只要left小于right
{
int mid = (left + right) / 2; // 取[left, right]的中点
MergeSort(A, left, mid); // 递归,将左子区间[left, mid]归并排序
MergeSort(A, mid + 1, right); // 递归,将右子区间[mid + 1, right]归并排序
Merge(A, left, mid, mid + 1, right); // 将左子区间和右子区间合并
}
}
Merge函数在该模板中的序列合并问题中是有区别。在序列合并中是while (i < n && j < m),而在该模板中是while (i <= R1 && j <= R2)。问题在于,早序列合并中,n和m是两个序列的长度,而数组下标是从0开始的,数组下标取不到n或m,因此是一个左闭右开的区间。而在该模板中,R1和R2都是可以取到的,因此是一个左闭右闭的区间,循环中要加等号。
非递归实现
2-路归并排序的非递归实现是递归实现的反过程,递归从整体到局部,而非递归是局部到整体。令步长step的初值为2,然后将数组(数组下标从1开始)中每step个元素作为一组,将其内部进行排序。即把左step / 2个元素与右step / 2个元素合并,而若元素个数不超过step / 2则不操作(这一般发生在序列末尾)。再令step乘以2,重复以上操作,直到step / 2超过元素个数。因为如果step / 2不超过元素个数n,那么还是可以进行一次分组排序的。
// step为组内元素个数,step / 2为左子区间元素个数,等号可以把不取
void MergeSort(int A[])
{
for (int step = 2; step / 2 <= n; step *= 2)
{ // 每step个元素一组,组内前step / 2和后step / 2个元素进行合并
for (int i = 1; i <= n; i += step) // 对每一组
{
int mid = i + step / 2 - 1; // 左子区间元素个数为step / 2
if (mid + 1 <= n) // 右子区间存在元素则合并
// 左子区间为[i, mid],右子区间为[mid + 1, mid(i + step - 1, n)]
Merge(A, i, mid, mid + 1, min(i + step - 1, n)); // 调用递归模板中的Merge函数
}
}
}
当然,如果题目中只要求给出归并排序每一趟结束时的序列,那么完全可以使用sort函数来代替Merge函数(只要时间限制允许),模板如下:
// step为组内元素个数,step / 2为左子区间元素个数,等号可以把不取
void MergeSort(int A[])
{
for (int step = 2; step / 2 <= n; step *= 2)
{ // 每step个元素一组,组内[i, min(i + step, n + 1)]进行排序
for (int i = 1; i <= n; i += step) // 对每一组
sort(A + i, A + min(i + step, n + 1));
}
// 此处可以输出归并排序的某一趟结束的序列
}
快速排序
这一块的知识点内容建议阅读算法笔记4.6.3小节——快速排序,这里就不做简单的记录或总结了。
简单快排
模板如下:
// 对区间[left, right]进行划分
int Partition(int A[], int left, int right)
{
int temp = A[left]; // 将A[left]存放至临时变量temp
while (left < right) // 只要left与right不相遇
{
while (left < right && A[right] > temp) // 反复左移right
--right;
A[left] = A[right]; // 将A[right]挪到A[left]
while (left < right && A[left] <= temp) // 反复右移left
++left;
A[right] = A[left]; // 将A[left]挪到A[right]
}
A[left] = temp; // 把temp放到left与right相遇的地方
return left; // 返回相遇的下标
}
// 快速排序,left和right初值为序列首尾下标(例如1与n)
void QuickSort(int A[], int left, int right)
{
if (left < right) // 当前区间的长度超过1
{
int pos = Partition(A, left, right); // 将[left, right]按A[left]一分为二
QuickSort(A, left, pos - 1); // 对左子区间递归进行快速排序
QuickSort(A, pos + 1, right); // 对右子区间递归进行快速排序
}
}
生成随机数的模板:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main()
{
srand((unsigned)time(NULL));
for (int i = 0; i < 10; ++i)
cout << rand() << endl;
return 0;
}
生成[a, b]范围内的随机数:
int c = rand() % (b - a + 1) + a;
rand()函数只能生成[0, RAND_MAX]范围内的整数,RAND_MAX是stdlib头文件中的一个常数,随机器不同而不同。在一般的机器中它是32767,用十六进制表示就是0x7FFF,即b只要超过了32767就生成不了。那么怎么生成更大范围(a, b)内的随机数呢?主要有以下几种:
- 多次生成rand随机数,然后用位运算拼接起来;
- 多次生成rand随机数,然后将它们相乘;
- 随机生成每一个数位的值(0~9),然后拼接成一个大整数;
- 先用rand()生成一个[0, RAND_MAX]范围内的随机数,然后用这个随机数除以RAND_MAX,这样就会得到一个[0, 1]范围内的浮点数。再将浮点数乘以(b - a)然后加上a即可。
其中最后一个方法是比较常用的,且得到的数据更为随机。其原理在于得到的浮点数相当于[a, b]范围内的比例位置,后面的乘以(b-a)再加上a的作用等同于前面生成指定范围内随机数,不要想太多了。
(int)(round(1.0 * rand() / RAND_MAX * (b - a) + a));
随机快排
在A[left···right]中随机选取一个主元p,然后以A[p]作为主元来进行划分。
模板如下:
// 选取随机主元,对区间[left, right]进行划分
int RandPartition(int A[], int left, int right)
{
// 生成[left, right]内的随机数p
int p = (round(1.0 * rand() / RAND_MAX * (right - left) + left));
swap(A[p], A[left]); // 交换A[p]和A[left]
// 以下为原先Partition函数的划分过程,没有任何改变
int temp = A[left]; // 将A[left]存放至临时变量temp
while (left < right) // 只要left与right不相遇
{
while (left < right && A[right] > temp) // 反复左移right
--right;
A[left] = A[right]; // 将A[right]挪到A[left]
while (left < right && A[left] <= temp) // 反复右移left
++left;
A[right] = A[left]; // 将A[left]挪到A[right]
}
A[left] = temp; // 把temp放到left与right相遇的地方
return left; // 返回相遇的下标
}
问题A:二路归并排序(mergesort)递归法 [2*+]
后台测试数据出错,不管怎么做都是错的,所以别做(反正我也是真讨厌这个OJ平台…)。
问题 B: 基础排序III:归并排序
题目描述
归并排序是一个时间复杂度为O(nlogn)的算法,对于大量数据远远优于冒泡排序与插入排序。
这是一道排序练习题,数据量较大,请使用归并排序完成。
输入
第一行一个数字n,代表输入的组数
其后每组第一行输入一个数字m,代表待排序数字的个数
其后m行每行一个数据,大小在1~100000之间,互不相等,最多有10万个数据。
输出
升序输出排好序的数据,每行一个数字
样例输入
1
10
10
9
8
7
6
5
4
3
2
1
样例输出
1
2
3
4
5
6
7
8
9
10
Note
归并排序简单应用,注意传入参数的时候是左闭右闭的,所以要传入[0, m - 1]。
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 100010;
void Merge(int A[], int L1, int R1, int L2, int R2)
{
int i = L1, j = L2;
int temp[maxn], index = 0;
while (i <= R1 && j <= R2)
{
if (A[i] <= A[j])
temp[index++] = A[i++];
else
temp[index++] = A[j++];
}
while (i <= R1)
temp[index++] = A[i++];
while (j <= R2)
temp[index++] = A[j++];
for (int i = 0; i < index; ++i)
A[L1 + i] = temp[i];
}
void MergeSort(int A[], int left, int right)
{
if (left < right)
{
int mid = (left + right) / 2;
MergeSort(A, left, mid);
MergeSort(A, mid + 1, right);
Merge(A, left, mid, mid + 1, right);
}
}
int main()
{
int n, m;
while (cin >> n)
{
while (n--)
{
cin >> m;
int a[m];
for (int i = 0; i < m; ++i)
cin >> a[i];
MergeSort(a, 0, m - 1);
for (int i = 0; i < m; ++i)
cout << a[i] << endl;
}
}
}
问题 C: 快速排序 qsort [2*]
题目描述
输入n个整数,用快速排序的方法进行排序
Input
第一行数字n 代表接下来有n个整数
接下来n行,每行一个整数
Output
升序输出排序结果
每行一个数据
Sample Input
5
12
18
14
13
16
Sample Output
12
13
14
16
18
提示
n<=5000
Note
无。
#include <iostream>
#include <algorithm>
using namespace std;
int Partition(int A[], int left, int right)
{
int temp = A[left];
while (left < right)
{
while (left < right && A[right] > temp)
--right;
A[left] = A[right];
while (left < right && A[left] <= temp)
++left;
A[right] = A[left];
}
A[left] = temp;
return left;
}
void QuickSort(int A[], int left, int right)
{
if (left < right)
{
int pos = Partition(A, left, right);
QuickSort(A, left, pos - 1);
QuickSort(A, pos + 1, right);
}
}
int main()
{
int n, m;
while (cin >> n)
{
int a[n];
for (int i = 0; i < n; ++i)
cin >> a[i];
QuickSort(a, 0, n - 1);
for (int i = 0; i < n; ++i)
cout << a[i] << endl;
}
}
问题 D: 二分递归快排(Qsort) [2*]
快速排序就是基于二分递归的,所以将问题C的代码复制提交即可。
一定要自己写一遍哦~~~
来源:CSDN
作者:SakuraJI
链接:https://blog.csdn.net/qq_37701948/article/details/103711607