算法笔记
核心学习方法(课下):
- 理解模版
- 理解思想背下来
- 默写
熟练度的提高
- 删除
- 重新默写(三到五次)
- 二分查找
算法一:快速查找
核心:分治
基本流程
-
确定分界点x: q[l] q[(l+r)/2] q[r] 随机
-
调整区间:左半边小于等于x,右半边大于等于x(重要)
做法一
- 开辟两个新的数组a[],b[]
- 扫描去q[l~r] 若果q[i]小于等于x 把q[i]放在a数组,否则放入b数组
- 先把a放在q中,再把b放在q中
做法二(推荐)
- 设置两个指针分别指向左右两端(i,j)
- 若果i小于等于x,指针向右移一位,若i大于x,i指针停止运动。j指针开始与x比较,若j大于x,向左移动一位,只到遇见j小于x,停止。
- i与j指针函数进行swap交换
- 重复步骤2,直到左右指针相遇
-
递归处理左右两段落
习题练习
题目:
给定你一个长度为n的整数数列。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。输入格式:
输入共两行,第一行包含整数n
第二行包含n个整数 (所有整数均在1到10的次方范围内),表示整个数列。输出格式:
输出共一行,包含n个整数,表示排好序的数列。
数据范围:
1 < n< 10000001 < n< 1000000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5#include <iostream> using namespace std; const int N = 1e6 + 10; int n; int q[N]; // 快速排序算法 void quick_sort(int q[], int l, int r) { if (l >= r) return; // 边界条件,如果区间长度小于等于1,则无需排序 int x = q[l], i = l - 1, j = r + 1; // 设定基准值和左右指针 while (i < j) { do i++; while (q[i] < x); // 从左向右找第一个大于等于基准值的位置 do j--; while (q[j] > x); // 从右向左找第一个小于等于基准值的位置 if (i < j) swap(q[i], q[j]); // 交换两个位置上的值,使得左侧小于基准值,右侧大于基准值 } quick_sort(q, l, j); // 递归处理左半部分 quick_sort(q, j + 1, r); // 递归处理右半部分 } int main() { // 输入数据 cin >> n; for (int i = 0; i < n; i++) cin >> q[i]; // 调用快速排序算法 quick_sort(q, 0, n - 1); // 输出排序后的结果 for (int i = 0; i < n; i++) { cout << q[i] << " "; } return 0; }
算法二:归并排序
核心:分治
基本流程
时间复杂度O
-
确定分界点mid=(l+r)/2,q[mid]
-
递归排序left,right
-
归并和二为一(重点)
双指针:
- 设置一个新的数组存放排序位置
- 设置两个指针分别指向第二步递归规划好的两个序列
- 比较指针所指的元素大小,把小的放入所创建的新数列中,然后指针下移一位。
- 当其中一个指针移动至末尾的时候,直接把另一个指针以及以后元素直接插入到新建的数列即可。
习题练习
题目:
给定你一个长度为n的整数数列。
请你使用快速排序对这个数列按照从小到大进行排序。
并将排好序的数列按顺序输出。输入格式:
输入共两行,第一行包含整数n
第二行包含n个整数 (所有整数均在1到10的次方范围内),表示整个数列。输出格式:
输出共一行,包含n个整数,表示排好序的数列。
数据范围:
1 < n< 10000001 < n< 1000000
输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5#include <iostream> using namespace std; const int N = 1e6 + 10; int q[N], temp[N]; int n; // 归并排序算法 void merge_sort(int q[], int l, int r) { if (l >= r) return; // 边界条件,如果区间长度小于等于1,则无需排序 int mid = (l + r) / 2; // 计算中间位置 merge_sort(q, l, mid); // 递归处理左半部分 merge_sort(q, mid + 1, r); // 递归处理右半部分 int k = 0, i = l, j = mid + 1; // 初始化临时数组的索引和左右两部分的起始位置 // 合并两个有序部分 while (i <= mid && j <= r) { if (q[i] <= q[j]) temp[k++] = q[i++]; else temp[k++] = q[j++]; } // 将剩余部分的元素放入临时数组 while (i <= mid) temp[k++] = q[i++]; while (j <= r) temp[k++] = q[j++]; // 将排好序的临时数组复制回原数组 for (i = l, j = 0; i <= r; i++, j++) q[i] = temp[j]; } int main() { // 输入数据 cin >> n; for (int i = 0; i < n; i++) cin >> q[i]; // 调用归并排序算法 merge_sort(q, 0, n - 1); // 输出排序后的结果 for (int i = 0; i < n; i++) { cout << q[i] << " "; } return 0; }
算法三:二分排序
本质:
边界:找到一个性质将一个区间一分为二,一个区间满足,另一个区间不满足。
每次二分都是区间的选择,且每次的选择的区间都包含答案在内,当不断二分,直到最后的选择的区间为1个,那么最后的那个元素就是所求答案。
整数二分
实现
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5CAdministrator%5CDesktop%5Cc%E7%AE%97%E6%B3%95%E7%AC%94%E8%AE%B0.assets%5Ctmp6D1A.png&pos_id=img-EubtNzPF-1701222876201
(二分红颜色)
找到中间值mid=(l+r+1)/2 判断中间值是否符合性质
若满足:答案在【mid,r】 l=mid
若不满足:答案在【1,mid-1】 r=mid+1
(二分绿颜色)
找到中间节点mid=(l+r)/2
若满足:【1,mid】 r=mid
若不满足:【mid+1,r】 l=mid+1
习题练习
题目
给定一个按照升序排列的长度为n的整数数组,以及q个查询。
对于每个查询,返回一个元素k的起始位置和终止位置 (位置从0开始计数)。
如果数组中不存在该元素,则返 “-1 -1”。
输入格式
第一行包含整数n和q,表示数组长度和询问个数。
第二行包含n个整数 (均在1、10000范围内),表示完整数组。
接下来q行,每行包含一个整数k,表示一个询问元素。
输出格式
共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回“-1 -1"。
数据范围1<=n<=100000
1<=q<=10000
1<k<10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例.3 4
5 5
-1 -1
#include <iostream> using namespace std; const int N = 1e6 + 10; int q[N]; int n, m; int main() { // 输入数组长度和查询次数 cin >> n >> m; // 输入数组元素 for (int i = 0; i < n; ++i) cin >> q[i]; // 处理查询 while (m--) { int x; cin >> x; int l = 0, r = n - 1; // 二分查找左边界 while (l < r) { int mid = (l + r) >> 1; if (q[mid] >= x) r = mid; else l = mid + 1; } // 判断是否找到左边界 if (q[l] != x) { cout << "-1 -1" << endl; } else { // 输出左边界 int leftBoundary = l; // 重新初始化右指针 l = 0; r = n - 1; // 二分查找右边界 while (l < r) { int mid = (l + r + 1) >> 1; if (q[mid] <= x) l = mid; else r = mid - 1; } // 输出右边界 int rightBoundary = l; cout << leftBoundary << " " << rightBoundary << endl; } } return 0; }
浮点数二分
实现
去一个数的平方根
#include "iostream"
using namespace std;
int main(){
double x;
cin >> x;
double l=0,r=x;
while(r-l>1e-8){
double mid =(r+l)/2;
if(mid*mid>=x) r=mid;
else l=mid;
}
cout << l;
}
算法四:高精度
分类
- A+B
- A-B
- A*B
- A/B
大整数
其实在计算机的储存中大的整数是存在数组之中的,而且一边是小的存在前面q[0]=0,大的排在后面。
模拟计算过程:t为进制
实现
加
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e6 + 10;
// 定义一个函数,用于实现两个表示大整数的向量的相加
vector<int> add(vector<int> &A, vector<int> &B) {
vector<int> C; // 结果向量,存放相加后的大整数
int t = 0; // 表示进位
// 遍历两个大整数的每一位,进行相加
for (int i = 0; i < A.size() || i < B.size(); i++) {
if (i < A.size())
t += A[i]; // 加上A的当前位
if (i < B.size())
t += B[i]; // 加上B的当前位
C.push_back(t % 10); // 将相加结果的个位数加入结果向量
t /= 10; // 更新进位
}
// 如果循环结束后仍有进位,将进位加入结果向量
if (t)
C.push_back(1);
return C;
}
int main() {
string a, b;
vector<int> A, B;
// 从标准输入读入两个大整数的字符串表示
cin >> a >> b;
// 将大整数字符串转换为向量,每一位存储为一个元素,低位在前,高位在后
for (int i = a.size() - 1; i >= 0; i--)
A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i--)
B.push_back(b[i] - '0');
// 调用相加函数,得到结果向量
auto C = add(A, B);
// 输出相加结果,注意从高位到低位输出
for (int i = C.size() - 1; i >= 0; i--)
cout << C[i];
retu
减
#include <iostream>
#include "vector"
using namespace std;
const int N = 1e5 + 10;
// 比较两个整数向量 A 和 B 的大小
bool cmp(vector<int> &A, vector<int> &B) {
// 如果两个向量的大小不相等,返回较大向量的大小
if (A.size() != B.size())
return A.size() > B.size();
// 逐位比较向量的元素
for (int i = A.size() - 1; i >= 0; i--) {
if (A[i] != B[i])
// 如果找到不相等的元素,返回比较结果
return A[i] > B[i];
// 如果当前位相等,继续比较下一位
return true;
}
}
// 两个大整数向量的减法操作
vector<int> sub(vector<int> &A, vector<int> &B) {
vector<int> C;
// 逐位相减
for (int i = 0, t = 0; i < A.size(); ++i) {
t = A[i] - t;
if (i < B.size()) t -= B[i];
// 将结果存储到新的向量 C 中
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
// 去除结果向量 C 中末尾的多余零
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main() {
string a, b;
vector<int> A, B;
// 输入两个大整数字符串
cin >> a >> b;
// 将输入的字符串转换为整数向量
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0');
for (int i = b.size() - 1; i >= 0; i--) B.push_back(b[i] - '0');
// 比较向量大小并执行减法操作
if (cmp(A, B)) {
auto C = sub(A, B);
// 输出减法结果
for (int i = C.size() - 1; i >= 0; i--) cout << C[i];
} else {
auto C = sub(B, A);
// 输出带负号的减法结果
cout << "-";
for (int i = C.size() - 1; i >= 0; i--) cout << C[i];
return 0;
}
return 0;
}
乘
#include <iostream>
#include "vector"
using namespace std;
const int N = 1e5 + 10; // 定义常量 N,表示数组的最大长度
// 定义一个函数 mul,用于实现大整数与一个整数的乘法
vector<int> mul(vector<int> &A, int b) {
vector<int> C; // 存储乘法结果的数组
int t = 0; // 用于保存进位
// 遍历大整数 A 的每一位,与整数 b 相乘
for (int i = 0; i < A.size() || t; i++) {
if (i < A.size()) t += A[i] * b; // 如果 A 还有位数,将当前位与 b 相乘并加上进位
C.push_back(t % 10); // 将当前位的结果放入数组 C 中
t /= 10; // 更新进位
}
return C; // 返回乘法结果数组
}
int main() {
string a;
int b;
cin >> a >> b; // 输入大整数 a 和整数 b
vector<int> A; // 存储大整数 a 的数组
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0'); // 将大整数逆序存入数组 A
auto C = mul(A, b); // 调用乘法函数得到结果数组 C
// 输出乘法结果数组 C 中的每一位,从高位到低位
for (int i = C.size() - 1; i >= 0; i--) cout << C[i] << endl;
return 0; // 程序结束
}
ize()) t += A[i] * b; // 如果 A 还有位数,将当前位与 b 相乘并加上进位
C.push_back(t % 10); // 将当前位的结果放入数组 C 中
t /= 10; // 更新进位
}
return C; // 返回乘法结果数组
}
int main() {
string a;
int b;
cin >> a >> b; // 输入大整数 a 和整数 b
vector<int> A; // 存储大整数 a 的数组
for (int i = a.size() - 1; i >= 0; i--) A.push_back(a[i] - '0'); // 将大整数逆序存入数组 A
auto C = mul(A, b); // 调用乘法函数得到结果数组 C
// 输出乘法结果数组 C 中的每一位,从高位到低位
for (int i = C.size() - 1; i >= 0; i--) cout << C[i] << endl;
return 0; // 程序结束
}