算法笔记
结构体的sort(stable_sort)排序
1. sort使用方法
sort(first, second, cmp)
:其中first是数组起始地址,second是结束地址,cmp是排序方式。在[first, second)
区间内进行排序。
2.结构体内重载比较运算符
- 由于
sort()
函数只能对数组进行排序,因此定义结构体数组时,需要先重载比较运算符才能使用sort(). -
- 直接使用
sort(q, q + n)
,默认从小到大排序。
- 直接使用
-
- 若需要从大到小排,第三个参数为
greater<Person>()
即大根堆。
- 若需要从大到小排,第三个参数为
- 单关键字排序:
struct Person
{
string name;
int score;
bool operator< (const Person& t) const
{
return score < t.score;
}
bool operator> (const Person& t) const
{
return score > t.score;
}
}q[N];
- 双关键字排序
struct Person
{
string name;
int id;
int score;
bool operator< (const Person& t) const
{
if (score != t.score) return score < t.score;//分数不同时按分数从小到大排序
else return id < t.id;//分数相同时按ID从小到大排序,我们可以把id设为数据输入时后的i值,代表谁先输入
}q[N];
sort(q, q + n);//从小到大排序
stable_sort(q, q + n, greater<Person>());//从大到小排序
3.自定cmp
函数
我们可以在main
函数外定义一个bool
类型的cmp
比较函数,进而起到跟重载小于号相同的作用。
//还是上面的题目
bool cmp (person t1, person t2) {//person是结构体排序对象,实现从小到大排序
if (t1.score < t2.score) return true;//返回的bool是对小于号来说
else if (t1.score > t2.score) return false;
else (t1.score == t2.score) return t1.id < t2.id;//t1在t2之前读入的话就返回t1
}
高精度加法
当输入数据超出变量存储范围时,用数组以大端方式存储数据,Little-Endian。
大对应高地址。而端指的是存储数据的字节顺序的尾端。
//当输入数字爆LL时,将其存到vector<int>内
int add(vector<int> &A, vector<int> &B) {
int res = 0;
for (int i = 0, d = 0; i < A.size() || i < B.size(); i ++) {//当A和B至少有一个非空时,继续进行
if (i < A.size()) d += A[i];//d是i对应位数字相加的和
if (i < B.size()) d += B[i];
d /= 10;//是否有向上进位
res += d;//记录下进位的次数
}
return res;
}
int main() {
string a, b;
while (cin >> a >> b) {
vector<int> A, B;
if (a == "0" && b == "0") break;
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');
int ans = add(A, B);////vector传引用就是传名字
if (ans == 0) printf("No carry operation.\n");
else if (ans == 1) printf("1 carry operation.\n");
else printf("%d carry operations.\n", ans);
}
return 0;
}
日期问题
- 首先写出日期问题经典的三个函数:
const int month[13] = {//平年12个月的天数
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
int is_leapyear(int year){//判断该年是否为闰年
if ((year % 4 == 0 && year % 100 !=0) || year % 400 == 0)
return 1;//闰年二月比平年多加一天
else return 0;
}
int get_days (int y, int m){
if (m == 2)
return month[m] + is_leapyear(y);
else return month[m];
}
接着根据题目要求具体问题具体分析。
若出现TLE,我们需要对计算次数优化到 10^8
以下
- 优化方案: 先对year操作,再对天数操作.
int main(){
int T = 0;//共T组数据,a表示要累加的天数
cin >> T;
int y, m, d, a;
while (T--){
cin >> y >> m >> d >> a;
if (m == 2 && d == 29) {//特判2.29号,因为第二年一定没有2.29
a --;
m = 3;
d = 1;
}
while (a > get_year_days(y, m)){//直接减去一整年的天数,year加1
a -= get_year_days(y, m);
y ++;
}
while (a --){//常规操作
d ++;
if (d > get_days(y, m)){
m ++;
d = 1;
if (m > 12){
m = 1;
y ++;
}
}
}
printf ("%04d-%02d-%02d\n", y, m, d);
}
return 0;
}
int get_year_days (int y, int m){
if (m <=2){//1.1至2.28加一年要根据本年是否为闰年
return 365 + is_leapyear(y);
}
else if (m > 2){//3.1至12.31加一年要根据第二年是否是闰年
return 365 + is_leapyear(y + 1);
}
}
表达式求值和中缀转后缀
表达式求值
我们需要维护两个栈,分别是运算符栈和数栈
代码背过
#include <iostream>
#include <string>
#include <algorithm>
#include <unordered_map>
#include <stack>
using namespace std;
stack<char> op;
stack<int> num;
void eval()
{
auto b = num.top(); num.pop();//实现a op b,需要先弹出b,在弹出a.
auto a = num.top(); num.pop();
auto c = op.top(); op.pop();
int x;
if (c == '+') x = a + b;
else if (c == '-') x = a - b;
else if (c == '*') x = a * b;
else x = a / b;
num.push(x);//运算结果压栈
}
int main()
{
string s;
cin >> s;
unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};//定义运算符优先级
for (int i = 0; i < s.size(); i ++ )
{
if (isdigit(s[i]))//判断是否为0~9的整数
{
int j = i, x = 0;
while (j < s.size() && isdigit(s[j]))//算出该数字是多少
x = x * 10 + s[j ++ ] - '0';
num.push(x);
i = j - 1;//j多加了1,需要减去
}
else if (s[i] == '(') op.push(s[i]);//左括号直接压op栈
else if (s[i] == ')')//读入右括号,op栈pop至左括号
{
while (op.top() != '(') eval();
op.pop();
}
else//读入四则运算操作符
{
while (!op.empty() && op.top() != '(' && pr[op.top()] >= pr[s[i]])//结束条件:op栈是否读完;是否读至左括号(括号内自成一);op栈顶优先级是否高于低于读入操作符
eval();
op.push(s[i]);
}
}
while (!op.empty()) eval();//op栈未pop完,继续操作。
cout << num.top() << endl;
return 0;
}
哈弗曼树与堆
1.构建大根堆
#include <queue>
priority_queue<int> Big_heap;//大根堆
priority_queue<int, vector<int>, greater<int>> Small_heap;//小根堆
2.每次pop出两个堆顶元素,操作后在push,直到堆内仅剩一个元素
此时堆内唯一元素的值就是hafuman树的根节点数值。
while (heap.size() > 1){
int a = heap.top();
heap.pop();
int b = heap.top();
heap.pop();
int x = a + b;
heap.push(x);
}
3.K叉hafuman树
要点:
- 堆内元素双关键字排序,先按数值大小排序,再按树高
height
排序。(pair
自动实现双关键字排序) (n - 1) % (k - 1) == 0
若不满足则向堆内push{0, 0}
,直至满足条件(此时才能正确构建出hafuman树)- 每次连续读出K个结点,
sum
为结点数值和,height
为max
(K个结点树高)。每轮读出K个结点后,再push(sum, height + 1)
。
int main(){
priority_queue<PLI, vector<PLI>, greater<PLI>> heap;//构建PLI的小根堆
for (int i = 0; i < n; i ++){//输入pair类型元素{LL, int}
LL x = 0;
cin >> x;
heap.push({x, 0});
}
while ((n - 1) % (k - 1) != 0){//补0
heap.push({0, 0});
n ++;
}
LL res = 0;//
while (heap.size() > 1){
LL sum = 0;
int height = 0;
for (int i = 0; i < k; i ++){
auto x = heap.top();
sum += x.first;
height = max(height, x.second);//每次更新树高,共更新k次
heap.pop();
}
res += sum;
heap.push({sum, height + 1});//将新K叉树push进堆
auto x = heap.top();//堆内唯一元素的值即是我们所求
cout << res << endl << x.second;
}
}
pair与双关键字排序
1.pair的创建与初始化
pair<string, string> anon;
// 创建一个空对象anon,两个元素类型都是string
pair<string, int> word_count;
// 创建一个空对象 word_count, 两个元素类型分别是string和int类型
pair<string, vector<int> > line;
// 创建一个空对象line,两个元素类型分别是string和vector类型
2.pair的排序用法
//可以按如下代码自行尝试理解
#include<cstdio>
#include<map>
#include <algorithm>
using namespace std;
typedef pair<int,int> P;
P a[10];
int main(){
for(int i=0;i<5;i++){
scanf("%d%d",&a[i].first,&a[i].second);
}
sort(a,a+5);
for(int i=0;i<5;i++){
printf("%d %d\n",a[i].first,a[i].second);
}
}
还可以自定义排序方法,类似于struct,不再赘叙。
dfs递归回溯算法
1.全排列问题
分层枚举n个位置
int main(){
cin >> n;//对1~n数字全排列
dfs(0);
return 0;
}
void dfs(int u){
if (u == n){//最后一层已枚举完,输出该分支对应全排列即可。
for (int i = 0; i < n; i ++){
cout << path[i] << " ";
}
}
else if (u < n){//u从0开始计数,此时枚举第(u + 1)层,
for (int i = 1; i <= n; i ++){//for循环找出未使用过的数字,st[i]==1代表已用过。
if (!st[i]) {
st[i] = 1;
path[u] = i;
dfs(u + 1);//递归到下一层
st[i] = 0;//递归返回时恢复现场
}
}
}
}
- 要点:
1.dfs
递归函数后紧跟恢复现场
2.分清楚枚举第i
层(第i
个位置)与第i
个数字
3.不理解就画出树形递归图(u为0时代表第一个位置上的数字)
2.N皇后问题
1.第一种解法:按g[x][y]
枚举各个位置的情况,分为放皇后或者不放皇后
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int col[N], row[N], d[2*N], ud[2*N];
int n;
char g[N][N];
void dfs(int x, int y, int s) {
if (y == n) {
y = 0;
x ++;
}
if (x == n) {
if (s == n) {
for (int i = 0; i < n; i ++)
puts(g[i]);
puts("");
}
return;
}
if (!row[x] && !col[y] && !d[x - y + n] && !ud[y + x]) {
row[x] = col[y] = d[x - y + n] = ud[y + x] = 1;
g[x][y] = 'Q';
dfs (x, y + 1, s + 1);
g[x][y] = '.';
row[x] = col[y] = d[x - y + n] = ud[y + x] = 0;
}
dfs (x, y + 1, s);
}
int main() {
cin >> n;
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++)
g[i][j] = '.';
dfs (0, 0, 0);
return 0;
}
- 要点:
- 1.dfs()返回条件是什么?--数组行数越界。注意:此时无论放置皇后个数是否为
n
,只要数组行数越界就需要返回。 - 何时输出正确分支答案? --放置皇后个数等于题目要求的n数
- 2.递归到下一位置的情况有几种?--画出递归图分析,放皇后或不放皇后。
2.第二种解法,按行枚举,该行皇后所在的位置
- 与第一种解法相比,不用显式的判断x行的每一列,而用一个for循环代替,遍历该行所有位置。
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int row[N], dg[N * 2], udg[N * 2];
char g[N][N];
int n;
void dfs(int u, int s) {//u是列号
if (u == n) {
if (s == n) {
for (int i = 0; i < n; i ++)
puts(g[i]);
puts("");
}
return;
}
for (int i = 0; i < n; i ++) {//遍历u列下的所有行i
if (!row[i] && !dg[i + u] && !udg[i - u + n]) {
if (g[i][u] != 'Q') {
row[i] = dg[i + u] = udg[i - u + n] = 1;
g[i][u] = 'Q';
dfs(u + 1, s + 1);
g[i][u] = '.';
row[i] = dg[i + u] = udg[i - u + n] = 0;
}
}
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++)
g[i][j] = '.';
dfs(0, 0);
return 0;
}
3.计算冲突皇后个数
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100010;
ll col[N], row[N], udg[N * 2], dg[N * 2];
int main() {
int n;
cin >> n;
while (n --) {
int x, y;
cin >> x >> y;
col[y] ++;
row[x] ++;
udg[y - x + N] ++;
dg[x + y] ++;
}
ll cnt = 0;
for (int i = 0; i <= N; i ++) {
if (col[i] >= 2) cnt += col[i] * (col[i] - 1) / 2;//组合数计算该行or列的冲突个数
if (row[i] >= 2) cnt += row[i] * (row[i] - 1) / 2;
}
for (int i = 1; i <= N * 2; i ++) {
if (dg[i] >= 2) cnt += dg[i] * (dg[i] - 1) / 2;
if (udg[i] >= 2) cnt += udg[i] * (udg[i] - 1) / 2;
}
cout << cnt;
return 0;
}
BFS算法
1.BFS求最短路(二维)
- 由于bfs宽搜的特性,第一次访问到目的结点时的访问次数就是最短路径。
- 二维bfs需要操作图
g[x][y]
- 故需要用二维数组
path[][]
存储dist
信息,queue<pair<int int>> q
维护bfs
队列。
int g[N][N], path[N][N];
int bfs(){
queue<PII> q;
q.push({0, 0});//初始化访问队列
while (q.size()) {//队列非空时继续操作
auto t = q.front();//保存队头元素
q.pop();
for (int i = 0; i < 4; i ++) {//顺时针判断前后左右四个方向是否入队
int x = t.first + dx[i], y = t.second + dy[i];
if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && path[x][y] == -1) {
q.push({x, y});//g[][]==0,表示可以通过;path[][]==-1表示该点没有访问过
path[x][y] = path[t.first][t.second] + 1;//更新path在,大小为上一层path加一
}
}
}
return path[目标位置];//此时返回到达目标位置需要的步数
}
2.BFS求最短路(一维)
-
一维bfs需要两种数据结构,假设操作字符串数组,那么我们需要
-
unordered_map<string, int> dist
用来记录是否访问过这个字符串;同时可以作为记录当前节点的bfs
层次 -
queue<string> q
作为我们的bfs
队列 。
int bfs(string s){
unordered_map <string, int> dist;//记录当前string字符串是bfs的第几层,并同时作为判重st
queue <string> q;
q.push(s);
dist[s] = 0;//初始化strat字符串是第0层
while (q.size()) {//队列不空时一直操作
auto t = q.front();
q.pop();
if (t.find("2012") < n) return dist[t];
//t.find(S)函数,寻找当前字符串t中是否含S字符子串,若有返回子串位置,没有找到则返回无穷大数
for (int i = 1; i < n; i ++) {//操作字符串t的n - 1种移位
string r = t;
swap(r[i - 1], r[i]);
if (!dist.count(r)) {//是否访问过字符串r,没有则入队,且更新到r的dist
q.push(r);
dist[r] = dist[t] + 1;//更新dist
}
}
}
return -1;//没找到“2012”,返回-1
}
模拟题
- 在进入每轮循环时先判断退出条件
- 可以利用数学关系简化模拟流程
因式分解
1.分解质因数:
- 给定数字
N
,将其分解为质因数,并输出每个质因数的底数
和指数
- 此处有两个数学结论,
N
最多有一个大于sqrt(N)
的质因数,因此我们只需要找到其小于等于sqrt(N)
的质因数,最后再判断N
是否分解完(N = 1 ?)
,若N != 1
,则最后剩下的 N 也是一个质因数。- 从
i = 2
开始枚举,每次除净i
,则从2 - N / i
满足条件(N % i == 0
)的i
都是N
的质因数.
solution:
#1.判定n是不是质数
int is_prime(int x){
if (x < 2) return 0;
for (int i = 1; i <= x / i; i ++) //从1到sqrt(n),因式分解的性质
if (x % i == 0) return 0;//若x能被i整除,则x不是质数
return 1;
}
#2.将数字n分解质因数
void divide(int n) {
for (int i = 2; i <= n / i; i ++) {//从i = 2开始枚举所有质因数,注意i的取值范围,当n为平方数时,i必须取到他的开方大小,也就是 i^2 = n;因此必须是<=号
if(n % i == 0) {//(易证)满足此条件的i只能是质数
int cnt = 0;
while(n % i == 0) {//除干净所有的质数i
cnt ++;
n /= i;
}
printf("底数为%d 指数为%d", i, cnt);
}
if(n > 1) printf("底数为%d 指数为%d", n, 1);//若枚举到最后n不等于1,则最后这个数也是n的质因数
}
2.阶乘因式分解质因数
- 要求:给定数字
N
,返回N!
的末尾0的个数 - 算法1:求出N!并求出末尾0的个数。时间复杂度O(N),无法通过所有数据
- 算法2:阶乘因式分解
已知 2 * 5 == 10,故我们只要找出1~n中含质因数2的个数s2和含质因数5的个数s5,取min,就能得到含因数10的个数
观察数列1~n:1,2,3,4,5,6,7,8,9,10...
。
假设我们求 n!
中 5
因子的个数,即:5,10,15,20,25...
提取公因数5
,有5 *(1,2,3,4,5)
,我们发现提出因子5
后仍然满足我们的要求,故记录一个因子的个数cnt = n / 5
。
int main() {
int N = 0;
cin >> N;
int cnt = 0;
while (N) {
N /= 5;
cnt += N;
}
cout << cnt;
}
3.求a和b的最大公约数(greatest common divisor)
- 可以直接使用STL中的
__gcd(int a, int b)
函数
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
cout << "gcd(6, 20) = " << __gcd(6, 20) << endl;
}
#OUTPUT:
gcd(6, 20) = 2
位运算
1.将数字x中的每一位都取出来
- 假设有数字
x
不超过32
位,即unsigned int
现将其二进制表示下的01串表示为字符串:
string str1;
for(int i = 31; i >= 0; i --) { //从最高位开始,每次取出x二进制表示下的一位数字
str1 += to_string(x >> i & 1); //x >> i 表示 x 右移i位
}
- 存在的问题:当数字不足32位时,即我们转化得到的字符串
str1
前面会有一连串的“前导零”
string str1;
while (x){
str1 += to_string(x & 1);//从个位开始取出数字,直到x为零
x >>= 1;
}
reverse(str1.begin(), str1.end());//将得到的逆序字符串翻转
vector.begin() .end();
与 .front() .back()
的区别:
前一对是传递指针,后一组是传递引用
操作矩阵
1.矩阵乘法
void mul (int res[][N], int a[][N], int b[][N]) {...}
二维数组传递参数,第一维可以省略,第二维不可省略(告诉编译器数组行列是如何划分的)
链接:关于二维数组传参做形参
void mul (int res[][N], int a[][N], int b[][N]) {
int tem[N][N] = {0};//中间数组,用来存储一次矩阵乘法的结果
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++)
for (int k = 0; k < n; k ++)
tem[i][j] += a[i][k] * b[k][j];
memcpy(res, tem, sizeof tem);//将结果复制到res数组
}
int main() {
a[][];//将初始数组输入
for (int i = 0; i < n; i ++) res[i][i] = 1;//单位矩阵,实现第一次矩阵乘法
while (k --){//计算a矩阵的k次幂
mul (res, res, a);//矩阵乘法
}
2.旋转矩阵
- 输出矩阵
a
顺时针旋转90
度的新矩阵 - 初始化
vector
:vector<vector<int>> a(N, vector<int> (M));
定义一个N
行M
列的二维数组 - 用
vector
存储矩阵,并传递参数给核心函数vector<vector<int>> rotate
# 将整个矩阵旋转
vector<vector <int>> rotate (vector<vector <int>> a) {//传递二维vector参数
auto res = a;
for (int i = 0; i < n; i ++)
for (int j = 0, k = n - 1; j < n; j ++, k --)//画图理解,res矩阵的第i行是a矩阵第i列倒序输出
res[i][j] = a[k][i];
return res;
}
int main(){
vector<vector<int>> a(N, vector<int> (N));//用vector存储,并初始化
for (int i = 0; i < 4; i ++) {//实现四次旋转,每次顺时针旋转90度,可以等效为逆时针旋转
a = rotate(a);
}
}
# 旋转 坐标(x, y) 为左上角且 边长为n 的矩阵a
# 按旋转整个矩阵的模板写,在最后赋值的时候加上偏移量x, y即可
void rotate(int x, int y, int b) {//常规写法,普通定义二维数组
int tem[N][N] = {0};//中间数组
for (int i = 0; i < n; i ++)
for (int j = 0, k = n - 1; j < n; j ++, k --)
tem[i + x][j + y] = a[k + x][i + y];//在常规旋转的模板上加上坐标偏移量
memcpy(a, tem, sizeof tem);//更新a数组,以实现多次旋转操作
}
3.分块矩阵
- 从给定的
n * m
矩阵中找出最小面积子矩阵,使得子矩阵内元素之和不大于K
#include <bits/stdc++.h>
using namespace std;
int n, m, k;
const int N = 110;
int a[N][N], s[N][N];
int compute(int i, int j, int x, int y) {//前缀和计算左上角为(i, j) 右下角为(x, y)的矩阵元素之和。
int ans = 0;
ans = s[x][y] - s[i - 1][y] - s[x][j - 1] + s[i - 1][j - 1];
return ans;
}
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
cin >> a[i][j];
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];//构造前缀和数组,代表以(i, j)为右下角的矩阵元素和。
}
}
int ans = 0x3f3f3f3f;
for (int i = 1; i <= n; i ++) {//先枚举左上角位置,再枚举右下角位置
for (int j = 1; j <= m; j ++) {
for (int x = i; x <= n; x ++) {
for (int y = j; y <= m; y ++) {
if (compute(i, j, x, y) >= k) {//当矩阵元素和大于等于K时计算矩阵面积
ans = min(ans, (y - j + 1) * (x - i + 1));
}
}
}
}
}
if (ans >= 0x3f3f3f3f) cout << -1;
else cout << ans;
return 0;
}
前缀和
s[i]
代表数组a[]
在1 ~ i
位置上的数据之和
a[0] = 0;
for (int i = 1; i <= n; i ++) {//i从1开始,a[0]初始化为0,防止越界
s[i] = s[i - 1] + a[i];
}
- 通常通过数学关系推出
s[i] - s[j]
的关系表达式,再通过前缀和求出结果。常用来求解一段的距离
#给出环形数组a[], 求i到j的最短距离,a[i] 表示i - 1到 i 的距离。
int a[N], s[N];
int main() {
int n ;
cin >> n;
for (int i = 1; i <= n; i ++)
cin >> a[i];//输入初始数组
for (int i = 1; i <= n; i ++)
s[i] = s[i - 1] + a[i];
int i, j;
cin >> i >> j;
if (j < i) swap(i, j);//给出的i,j大小关系不确定
printf("%d\n", min(s[j - 1] - s[i - 1], s[n] - s[j - 1] + s[i - 1]));
}
最短路问题
1.spfa
-
建议直接使用
spfa
模板spfa() + add()
加边模板分为三个部分:
- 建立队列(根据情况选择 模拟循环队列 还是 使用queue),
- 队列非空时循环,
- 单次循环内执行入队出队,记得同步更新
st[]
状态数组
int h[N], e[M], w[M], ne[M], idx;
void add(int a, int b, int c) {//头结点为a, 边表结点为b, 边权为c
e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx ++;//尾插法逆序建立邻接表
}
void spfa(int start, int dist[]){//朴素版,传入start起始位置和dist[]数组
queue<int> q;
q.push(start);
st[start] = 1;//将start入队,同步更改st数组
memset(dist, 0x3f, sizeof dist1);//清空距离数组,不能写dist不然返回的是指针长度
dist[start] = 0;
while(q.size()) {//当队列不空时,继续循环
int t = q.front();
q.pop();//出队
for (int i = h[t]; i != -1; i ++) {//遍历该头结点对应的邻接表,直到遍历到i = -1为止
if (...) continue;//判断条件,不满足条件时跳过该次更新
int j = e[i];
if (dist[j] > dist[t] + w[i]) {//更新距离
dist[j] = dist[t] + w[i];
if (! st[j]) {//当j未入队时,将其入队
q.push(j);
st[j] = 1;
}
}
}
}
}
哈希表
1.按字典序输出结果时,可以考虑使用<map>
存储键值。
//给出一个 01 字符串,求其每一个子串出现的次数,并将结果按字典序输出,有多组输入数据
int main (){
map <string, int> mp;
while (cin >> s) {
mp.clear();//清空上一次输入的mp
for (int i = 0; i < s.size(); i ++) {//遍历s的起点
string str;//取出以s为起点的所有01串
for (int j = i; j < s.size(); j ++) {
str += s[i];
mp[str] ++;//对应hash值+1
}
}
for (auto t : mp) {//按字典序输出mp中所有元素
cout << t.first << ":" << t.second << endl;
}
}
return 0;
}
- 操作字符串时有两个小函数:
isalpha()
和isupper()
isalpha(val)
:用来判断val是否是字母isupper(val)
:用来判断val是否是大写字母
//统计一行英文句子中出现的各个单词出现次数。单词由空格、句号和逗号隔开,并将结果由字典序输出。
int main() {
string s;
getline(cin, s);
//记住这个函数形式!
for (int i = 0; i < s.size(); i ++) {
if (!isalpha(s[i])) continue;//isalpha(val):判断val是否是一个字母
int j = i;
string str;
while (isalpha(s[j]) && j < s.size()) {
if (isupper(s[j])) s[j] += 'a' - 'A';//isupper(val):判断val是否是一个大写字母
str += s[j ++];
}//退出while循环时,j的值多加了一次1
mp[str] ++;//对应位置hash值+1
i = j;//将i指针跳到j的位置上
}
//
for (auto t : mp) {
cout << t.first << ":" << t.second << endl;
}
return 0;
}
分形
- 将每一次的递归结果用
vector <string>
来存储字符串图形 - 对第
k
层来说,首先我们需要递归找到第k - 1
层对应图形,并将其存储下来,和原始图形一起更新我们第k
层的图形
#include <bits/stdc++.h>
using namespace std;
int n, k;
vector <string> p;//存储原始形态get(1)
vector <string> get (int k) {//输出p的k次形态
if (k == 1) return p;//递归退出
vector<string> s = get(k - 1);//递归
int m = s.size();//上一层递归结果的边长,记做m
vector<string> ans(n * m);
for (int i = 0; i < n * m; i ++)//将ans初始化
ans[i] = string(n * m, ' ');
for (int i = 0; i < n; i ++) //get(n - 1)中是空格的位置,get(n)中相应位置的m*m格子一定也是空格。
for (int j = 0; j < n; j ++)
if (p[i][j] != ' ')
for (int x = 0; x < m; x ++)//填入非空格子
for (int y = 0; y < m; y ++)
ans[i * m + x][j * m + y] = s[x][y];//根据上一层的边长m和原始边长n即可求出相应坐标
return ans;
}
int main() {
while (cin >> n) {
p.clear();
getchar();//
if (n == 0) break;
for (int i = 0; i < n; i ++) {//
string line;
getline(cin, line);
p.push_back(line);
}
cin >> k;
auto ans = get(k);
for (auto s : ans)//
cout << s << endl;
}
}