'commit'
This commit is contained in:
18
TangDou/CSP-J/J1_2022/1_3_0.cpp
Normal file
18
TangDou/CSP-J/J1_2022/1_3_0.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
int main() {
|
||||
int x = 101;
|
||||
int y = 201;
|
||||
int *p = &x;
|
||||
int *q = &y;
|
||||
p = q;
|
||||
printf("%d %d\n", x, y);
|
||||
//输出:101 201
|
||||
|
||||
printf("%d %d", *p, *q);
|
||||
//输出:201 201
|
||||
|
||||
//所以答案:D
|
||||
return 0;
|
||||
}
|
||||
26
TangDou/CSP-J/J1_2022/1_3_1.cpp
Normal file
26
TangDou/CSP-J/J1_2022/1_3_1.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
int var = 20; // 实际变量的声明
|
||||
int *ip; // 指针变量的声明
|
||||
|
||||
ip = &var; // 在指针变量中存储 var 的地址
|
||||
|
||||
cout << "Value of var variable: ";
|
||||
cout << var << endl;
|
||||
|
||||
// 输出在指针变量中存储的地址
|
||||
cout << "Address stored in ip variable: ";
|
||||
cout << ip << endl;
|
||||
|
||||
cout << "Address stored in var variable: ";
|
||||
cout << &var << endl;
|
||||
|
||||
// 访问指针中地址的值
|
||||
cout << "Value of *ip variable: ";
|
||||
cout << *ip << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
239
TangDou/CSP-J/J1_2022/22张图带你深入剖析前缀、中缀、后缀表达式以及表达式求值.md
Normal file
239
TangDou/CSP-J/J1_2022/22张图带你深入剖析前缀、中缀、后缀表达式以及表达式求值.md
Normal file
@@ -0,0 +1,239 @@
|
||||
## 22张图带你深入剖析前缀、中缀、后缀表达式以及表达式求值
|
||||
|
||||
### 一、基本概念
|
||||
在本篇文章当中主要跟大家介绍以下几点
|
||||
|
||||
* 前缀、中缀和后缀表达式。
|
||||
* 如何将中缀表达式转化成后缀表达式。
|
||||
* 如何使用后缀表达式进行求值。
|
||||
|
||||
表达式求值这是一个比较经典的计算机系统基础问题,但是整个过程比较抽象,本文主要通过图解的方法帮助大家理解这个问题。
|
||||
|
||||
#### 表达式介绍
|
||||
后缀表达式也称作**逆波兰表达式**,前缀表达式也称作**波兰表达式**,这个是因为这是由波兰数学家**杨-武卡谢维奇**提出来的,用于简化命题逻辑的一种方法。
|
||||
|
||||
#### 中缀表达式
|
||||
我们常见的数学表达式就是中缀表达式,比如说:$1+2$,像这种我们从小到大经常见到的表达式就叫做中缀表达式,这个表达式的特点就是将运算符(加减乘除)放在两个操作数(数字)中间。
|
||||
|
||||
#### 后缀表达式
|
||||
后缀表达式和中缀表达式的最大区别就是,它不是将运算符放在操作数中间,而是**将运算符放在操作数后面**,比如上面的中缀表达式$1+2$转化成后缀表达式就为$12+$。
|
||||
|
||||
#### 前缀表达式
|
||||
前缀表达式就是将运算符放在操作数的前面,比如上面的中缀表达式$1+2$转化成前缀表达式之后为$+12$。
|
||||
|
||||
### 二、人脑模拟中缀表达式转后缀表达式
|
||||
|
||||
上面的表达式还是比较简单,可能不足以帮助大家好好理解表达式之间的转化过程。
|
||||
|
||||
中缀表达式:$\large A+B∗(C−D)−E/F$
|
||||
|
||||
现在我们来将上面的中缀表达式转化成后缀表达式,我们的第一个计算的部分如下(括号里面的优先计算):
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/53c7d9e60cad8dd4b63d8c71abe07eb2.jpeg'></center>
|
||||
|
||||
根据我们的转化原理:将**运算符**放在**操作数**后面,
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/e9d88adb98b5afb38e0d10670266ed6a.png'></center>
|
||||
|
||||
上面的得到的后缀表达式继续进行转化(现在需要计算$B$乘以后面的那个部分):
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/e5a7cce83636389ca4b340eaf1264a3e.png'></center>
|
||||
|
||||
继续进行转化(从左往后进行计算):
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/b9fabd73bac52afccc09e4e6337ee4e7.png'></center>
|
||||
|
||||
继续进行转化(除法的优先级比减法高):
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/70179cc85a30ff049c3be3184e2110c2.png'></center>
|
||||
|
||||
得到最终的结果:
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/8480c73c558584d465b54c0a4eca3681.png'></center>
|
||||
|
||||
|
||||
### 三、程序计算中缀表达式转化成后缀表达式
|
||||
|
||||
将中缀表达式转化成后缀表达式有以下几条规则(下面的栈是存储操作符的栈,而且只存储操作符):
|
||||
|
||||
* 从左到右进行遍历。
|
||||
* 遇到操作数,直接加入到后缀表达式当中。
|
||||
* 遇到界限符。遇到“(”直接入栈,遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到弹出“(” 为止,注意:“(” 不加入后缀表达式。
|
||||
* 遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到“(”或栈空则停止。之后再把当前运算符入栈。
|
||||
|
||||
现在我们根据上面的规则来将上文当中的中缀表达式转化成后缀表达式。
|
||||
|
||||
* 遍历到$A$,根据第二条规则,将A加入到后缀表达式当中,当前的后缀表达式为:$A$。
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/ea6936a34b07797083d35605acaa8d76.png'></center>
|
||||
|
||||
* 现在遍历到加号,根据前面的规则需要弹出栈里面优先级大的运算符,再将加号加入到栈中,当前的后缀表达式为$A$。
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/057681ea757e264c8f3a46574ab6aea1.png'></center>
|
||||
|
||||
* 遍历到$B$,直接加入到后缀表达式当中,目前的后缀表达式为$AB$。
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/2edf75ff93d90fcd7d19a1f34665f866.png'></center>
|
||||
|
||||
* 遍历到∗,根据规则直接将其加入到栈中,当前后缀表达式为$AB$。
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/6742625433f6e76d76c75b676ffc7dca.png'></center>
|
||||
|
||||
遍历到$($,根据规则直接将其加入到栈中,当前后缀表达式为$AB$。
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/89158870bd472b693025ba7c6498338c.png'></center>
|
||||
|
||||
* 遍历到$C$,则直接将其加入到后缀表达式当中,当前后缀表达式为$ABC$。
|
||||
* 遍历到$−$,根据规则将其加入到栈中,当前后缀表达式为$ABC$。
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/4fca6c5a9bc5fd84e245d25552fb7f2f.png'></center>
|
||||
|
||||
* 遍历到$D$,则将其直接加入到后缀表达式当中,当前的后缀表达式为$ABCD$。
|
||||
|
||||
* 遍历到$)$,则需要将栈中的符号弹出,直到遇到$($,当前的后缀表达式为$ABCD−$。
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/1cc7acadcf784df6248449f9e78670f4.png'></center>
|
||||
|
||||
* 遍历到$−$,需要将栈中优先级**大于等于**$−$的运算符弹出,则当前的后缀表达式为$ABCD−∗+$,再将$−$压入栈中。
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/dd083b7b90c4cb66eb9d30e4f5b26dd2.png'></center>
|
||||
|
||||
* 遍历到$E$,直接将数字加入到后缀表达式当中,则当前的后缀表达式为$ABCD−∗+E$。
|
||||
* 遍历到$/$,将栈中优先级**大于等于**$/$的运算符,再将$/$压入到栈中,当前的后缀表达式为$ABCD−∗+E$。
|
||||
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/7732f1fe1339a2aebdff799225b9f19e.jpeg'></center>
|
||||
|
||||
* 遍历到F,直接将其加入到后缀表达式当中,则当前的后缀表达式为$ABCD−∗+EF$。
|
||||
|
||||
* 最终将栈中所有的运算符都弹出,得到的后缀表达式为$ABCD−∗+EF/−$
|
||||
|
||||
经过上面的过程就可以将一个中缀表达式转成后缀表达式了,大家如果想要代码实现,只需要在遍历数据的时候根据上面四个规则一个个进行判断即可,程序并不困难,就是逻辑稍微有点多,需要考虑更多的问题,在写代码的时候需要细致一点。
|
||||
|
||||
### 三、程序计算后缀表达式求值
|
||||
在前文当中我们已经得到了表达式$A+B∗(C−D)−E/F$的后缀表达式为$ABCD−∗+EF/−$。现在我们需要将这个后缀表达式进行求值。根据后缀表达式求值主要有以下两条规则:
|
||||
|
||||
* 如果遇到数字直接将其加入到数字栈。
|
||||
* 如果遇到操作符直接从操作数栈弹出两个数据进行对应的运算,再将运算结果加入到栈中。
|
||||
|
||||
现在我们进行后缀表达式的求值过程;
|
||||
|
||||
* 首先前面四个$ABCD$都是数字,根据上面提到的第一条规则,我们都需要将数字压入到栈当中,因此遍历四个数字之后,情况如下:
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/e8f3212879dc4e4b87eb607e7ae371ee.jpeg'></center>
|
||||
|
||||
现在遍历到$−$,我们需要将$D$和$C$弹出,然后进行$−$操作的运算,再将结果压入栈中。
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/f681fa9b5a303ad0c85eb423fc37cc7b.jpeg'></center>
|
||||
|
||||
* 现在遍历到$∗$,我们需要将$C−D=M$和$B$弹出,进行乘法运算,然后将结果压入栈中。
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/c848243995314917822e32f85f2e39ae.jpeg'></center>
|
||||
|
||||
* 现在我们遍历到$+$,需要将栈中剩余的两个数弹出,进行加法运算,在将结果压栈。
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/cfd184bd0a450fc03aa441949705ad02.jpeg'></center>
|
||||
|
||||
* 遍历$EF$都需要将这两个数压入到栈当中。
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/92a9b87d2053664f746e3bb5c7b84746.jpeg'></center>
|
||||
|
||||
|
||||
* 现在遍历到$/$,需要进行除法运算,在将得到的结果压入到栈中。
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/57cb80568dde50150e4c543cd58f616f.jpeg'></center>
|
||||
|
||||
* 最后遍历到$−$,将栈中的两个数弹出栈,进行减法运算得到最后的结果。
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/img_convert/c54004ae13fa4693b4d433752cc0d543.jpeg'></center>
|
||||
|
||||
相信经过上面过程你对后缀表达式求值的过程已经非常清楚了。
|
||||
|
||||
|
||||
### 五、计算表达式的值
|
||||
|
||||
```c++
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
/*
|
||||
测试用例I:
|
||||
(2+2)*(1+1)
|
||||
|
||||
答案:8
|
||||
|
||||
测试用例II:注意:不能在输入中带空格,会算错!!!
|
||||
2+(3*4)-((5*9-5)/8-4)
|
||||
|
||||
答案:13
|
||||
*/
|
||||
|
||||
stack<int> num; //数字栈
|
||||
stack<char> op; //操作符栈
|
||||
|
||||
//优先级表
|
||||
unordered_map<char, int> h{{'+', 1},
|
||||
{'-', 1},
|
||||
{'*', 2},
|
||||
{'/', 2}};
|
||||
|
||||
/**
|
||||
* 功能:计算两个数的和差积商
|
||||
*/
|
||||
void eval() {
|
||||
int a = num.top(); //第二个操作数
|
||||
num.pop();
|
||||
|
||||
int b = num.top(); //第一个操作数
|
||||
num.pop();
|
||||
|
||||
char p = op.top(); //运算符
|
||||
op.pop();
|
||||
|
||||
int r = 0; //结果
|
||||
//计算结果
|
||||
if (p == '+')
|
||||
r = b + a;
|
||||
else if (p == '-')
|
||||
r = b - a;
|
||||
else if (p == '*')
|
||||
r = b * a;
|
||||
else if (p == '/')
|
||||
r = b / a;
|
||||
//结果入栈
|
||||
num.push(r);
|
||||
}
|
||||
|
||||
//读取连续的数字
|
||||
void readNum(string s, int &pos, int &x) {
|
||||
while (pos < s.size() && isdigit(s[pos])) {
|
||||
x = x * 10 + s[pos] - '0';
|
||||
pos++;
|
||||
}
|
||||
//在循环里加冒了,需要返回一步,方便下一个人开始
|
||||
pos--;
|
||||
}
|
||||
|
||||
int main() {
|
||||
//读入表达式
|
||||
string s;
|
||||
cin >> s;
|
||||
//遍历字符串的每一位
|
||||
for (int i = 0; i < s.size(); i++) {
|
||||
// 1、数字则入栈
|
||||
if (isdigit(s[i])) {
|
||||
//读出完整的数字
|
||||
int x = 0;
|
||||
readNum(s, i, x);
|
||||
num.push(x); //数字入栈
|
||||
}
|
||||
// 2、左括号无优先级,直接入栈
|
||||
else if (s[i] == '(')
|
||||
op.push(s[i]);
|
||||
// 3、右括号计算最近一对括号里面的
|
||||
else if (s[i] == ')') {
|
||||
//一直找到到左括号
|
||||
while (op.top() != '(') eval(); //将左右括号之间的计算完,维护回栈里
|
||||
//左括号出栈
|
||||
op.pop();
|
||||
} else { // 4、运算符
|
||||
//如果待入栈运算符优先级低,则先计算
|
||||
while (op.size() && h[op.top()] >= h[s[i]]) eval();
|
||||
op.push(s[i]); //操作符入栈
|
||||
}
|
||||
}
|
||||
// 5、//剩余的进行计算
|
||||
while (op.size()) eval();
|
||||
// 6、输出结果
|
||||
cout << num.top() << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
BIN
TangDou/CSP-J/J1_2022/22张图带你深入剖析前缀、中缀、后缀表达式以及表达式求值.pdf
Normal file
BIN
TangDou/CSP-J/J1_2022/22张图带你深入剖析前缀、中缀、后缀表达式以及表达式求值.pdf
Normal file
Binary file not shown.
1
TangDou/CSP-J/J1_2022/3.drawio
Normal file
1
TangDou/CSP-J/J1_2022/3.drawio
Normal file
@@ -0,0 +1 @@
|
||||
<mxfile host="Electron" modified="2022-10-30T03:07:44.875Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="p3V4fADDvfqN527BojSy" version="20.3.0" type="device"><diagram id="PJBWzxTVwBHiu0aUklnw" name="第 1 页">5Vlbb5swGP01vFRqBTYk9LFNL5u0SZ3ysPXRBQe8GUyN08B+/exgwsW5tU0atCpS5O/YGPuc72InFpwkxT1HWfydhZhawA4LC95YAIx9IL8VUFaA515WQMRJWEFOA0zJX6xBW6NzEuK8M1AwRgXJumDA0hQHooMhztmiO2zGaPetGYqwAUwDRE30JwlFXKE+GDf4F0yiuH6zM9L7S1A9WO8kj1HIFi0I3lpwwhkTVSspJpgq7mpequfuNvSuFsZxKvZ54MHPx7kbT+9n4Mfj17vACeyrcz3LC6JzvWG9WFHWDHA2T0OsJrEteL2IicDTDAWqdyEll1gsEiotRzb1dJgLXGxcp7PavfQazBIseCmH1C6j+SprR9D2oqHf01DcYn6kMaQFj1YTN5zIhqblFRQBg6LifRzNWCq0oyseDsGZAwZGGhyeXzlDc6zR8DgCQ/OjscFRObjgG1zG8g3S7AJA9UGBwZ7ct+hSlAvO/uAJo4xLJGUpVrwRSnsQoiRKpRlInrDErxWLRJbOK92RkDBUr1mrSVe1Y+REx+vKMjZlGa2RBR5LlsvNsuDPI4sRLaeWpT5ptsjHoTwDapNxEbOIpYjeNmiPpmbMN8YyrddvLESpyUNzwbpq4oKIX+rxC09bj62em0LPvDTKrWqoxW7XQu6NzXmAd/umQDzCYtc4U1uOKRLkpbuOwyu1x2kVp+GVOvarAKAoz0nQ5X27f0veeNmSRZkrXZTRCLO0ypZMppr2VjUPqJ4D9pTPXy/fjkJVY3urrN/wwIjcSZOQvW7krzJBPUW1T/1U+ybTm8iFvYn6lbTiwZho6XSrbb/DD80rwVn2eTJ4v7CCk2dw87Zx9vx59OhX1NPr4Q41T++fc99aCT8mlUL7f0ml5iX4TB2gCguMqArXJy5bkWpl57KFEhVSS/uodxqKZ2Jwgd6rn9A149y5/NBAN6/nMvFCGW6Ges+b1DvG1WeQ6vXT9ADkM38oOHSe1ufi+l6zPBVfgPqQvP5g3CT316T2EOXxch2Nw9Q/2Ls9b7EAnPkBDgLDtWTPk++53qnO5+OTFhW/Vwv8QxWVvge/uahIs/nDpBre/OsEb/8B</diagram></mxfile>
|
||||
517
TangDou/CSP-J/J1_2022/CSP-J2022.md
Normal file
517
TangDou/CSP-J/J1_2022/CSP-J2022.md
Normal file
@@ -0,0 +1,517 @@
|
||||
## 2022 CCF 非专业级别软件能力认证第一轮 试题解析
|
||||
|
||||
[零一原创:2022CSP-J初赛真题答案及全面讲解](https://mp.weixin.qq.com/s/Lr9PQ5vpikYGkw1sMdbRww)
|
||||
|
||||
[数学编程罗老师 CSP-j2022](https://www.bilibili.com/video/BV1dW4y1v7HH)
|
||||
[CSP-J 2022 入门级 第一轮 阅读程序(2) 第22-27题](https://blog.csdn.net/lq1990717/article/details/126992019)
|
||||
|
||||
### 一、单项选择题
|
||||
|
||||
#### 1、$C++$语言的面向对象特性:
|
||||
$A$、$C++$中调用$printf$函数
|
||||
$printf$函数在$C$语言中就存在,用来进行输出。在$C++$中,输入和输出增加了$cin$,$cout$。而$C$语言不是面向对象的,所以,$printf$与面向对象无关。**此题答案选择A**。
|
||||
|
||||
所谓面向对象,是指针对某些事物进行抽象,归纳为同一类物品,具有共同的属性,比如:鸟类包括麻雀,丹顶鹤,鹦鹉等,共同属性是有翅膀,孵蛋等。$B,C,D$都提到了**类**字样或"**Class**,**Struct**"等,这均是明显的面向对象概念。
|
||||
|
||||
---
|
||||
|
||||
#### 2、非法出栈顺序
|
||||
办法:出栈的顺序并不唯一,只能是一个个选项进行判断:
|
||||
入栈顺序 $6 5 4 3 2 1$
|
||||
|
||||
$A、5 4 3 6 1 2$
|
||||
$6$入,$5$入,此时$5$出,则栈中只有$6$
|
||||
$4$入,$4$出,则栈中只有$6$
|
||||
$3$入,$3$出,则栈中只有$6$
|
||||
$2$ 入栈,栈中 $6 2$
|
||||
$2$出栈,剩$6$
|
||||
$1$入栈,剩$6 1$
|
||||
$1$出栈,剩$6$
|
||||
$6$出栈,栈空 $OK$,$A$没有问题。
|
||||
|
||||
|
||||
$B. 4 5 3 1 2 6$
|
||||
$6 5 4$ 入栈,$4$出栈,栈中剩 $6 5$
|
||||
$5$出栈,栈中剩$6$
|
||||
$3$入栈,$3$出栈,栈中剩$6$
|
||||
$2$入栈,$1$入栈,此时$1$出栈,则栈中剩$6 2$
|
||||
$2$出栈,剩$6$
|
||||
$6$出栈,栈空,$OK$,$B$没有问题。
|
||||
|
||||
$C、3 4 6 5 2 1$
|
||||
$6 5 4 3$入栈,$3$出栈,栈中剩 $6 5 4$
|
||||
$4$出栈,栈中剩$6 5$,此时要求$6$出栈,作不到,$C$错误!
|
||||
|
||||
$D、2 3 4 1 5 6$
|
||||
为了$2$能先出栈,必须先入栈,则 $6 5 4 3 2$入栈
|
||||
$2 3 4$ 出栈 剩 $6 5$
|
||||
$1$入栈 剩 $6 5 1$
|
||||
$1 5 6$出栈,$OK$,没有问题,答案选$C$
|
||||
|
||||
---
|
||||
|
||||
#### 3、 如图理解,答案:$D$
|
||||

|
||||
|
||||
---
|
||||
|
||||
#### 4、链表和数组的区别
|
||||
|
||||
定义
|
||||
|
||||
数组:一组具有相同数据类型的变量的集合。
|
||||
|
||||
链表:一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
|
||||
|
||||
**区别**
|
||||
|
||||
逻辑结构:
|
||||
(1)数组在内存中连续;链表采用动态内存分配的方式,在内存中不连续。
|
||||
(2)数组在使用前事先固定长度,不能改变数组长度;链表支持动态增删元素。
|
||||
(3)数组元素减少时会造成内存浪费;链表可以使用malloc或new来申请内存,不用时使用free或delete来释放内存。
|
||||
|
||||
内存结构:
|
||||
数组从栈上分配内存,使用方便但自由度小;链表在堆上分配内存,自由度大但要注意造成内存泄漏。
|
||||
|
||||
访问效率:
|
||||
数组在内存中顺序存储,通过下标访问,访问效率高;链表需要从头遍历访问,访问效率低。
|
||||
|
||||
越界问题:
|
||||
数组大小固定,存在访问越界的风险;链表只要能申请空间就无越界风险。
|
||||
|
||||
$A$、数组不能排序,链表可以
|
||||
很明显,说反了,数组可以进行排序,反倒是链接不能排序
|
||||
|
||||
$B$、链表比数组能存储更多的信息
|
||||
这个不一定,数组也能保存很多信息,只要你开的数组够大
|
||||
|
||||
$C$、数组大小固定,链表大小可动态调整
|
||||
这个完全正确,当然,这里说的数组是指static数组,不是指vector
|
||||
|
||||
$D$、以上均正确,这个无疑可以排除掉,因为最起码A是错的。
|
||||
|
||||
---
|
||||
#### 5、此题与上面第二题其实本质上是一样的,我们来模拟分析一下:
|
||||
|
||||
为了$e2$先出队列,必须它先入栈,即入栈应该是
|
||||
$e1,e2$ 此时栈中 $e1,e2$
|
||||
$e2$出栈,入队列,出队列,栈内$e1$
|
||||
$e4$想要出队列,需要$e4$出栈,则$e3,e4$入栈,栈内$e1,e3,e4$
|
||||
$e4$出栈后,栈内$e1,e3$
|
||||
$e3$出栈,$e3$入队列,$e3$出队列,栈内$e1$
|
||||
$e6$想出队列,必须先入栈,则$e1,e5,e6$
|
||||
$e6$出队列,$e5$出队列,$e1$出队列,栈空
|
||||
|
||||
回头看下,栈内最长长度为$3$,选择$B$
|
||||
|
||||
---
|
||||
|
||||
#### 6、对表达式$a+(b-c)*d$的前缀表达式为( ),其中$+,-,*,/$是运算符。
|
||||
* $b-c$ 先来,$-bc$
|
||||
* 乘法随后 $*-bcd$
|
||||
* 最后是加法$+a*-bcd$
|
||||
答案是$B$
|
||||
|
||||
详细的解析见博文[22张图带你深入剖析前缀、中缀、后缀表达式以及表达式求值](https://www.cnblogs.com/littlehb/p/16706908.html)
|
||||
|
||||
---
|
||||
#### 7、哈夫曼编码
|
||||
|
||||
[哈夫曼编码(Huffman Coding)原理详解](https://www.cnblogs.com/littlehb/p/16707494.html)
|
||||
|
||||
[其它博文讲解](https://blog.csdn.net/weixin_42950079/article/details/121438730)
|
||||
|
||||
[哈夫曼编码(Huffman Coding)多图详细解析](https://blog.csdn.net/Demon_LMMan/article/details/115789360)
|
||||
|
||||
<!-- 让表格居中显示的风格 -->
|
||||
<style>
|
||||
.center
|
||||
{
|
||||
width: auto;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
<div class="center">
|
||||
|
||||
| $a$ | $b$ | $c$ | $d$ | $e$ |
|
||||
| ---- | ---- |---- | ---- | ---- |
|
||||
| $10$ | $15$ | $30$ | $16$ | $29$ |
|
||||
|
||||
</div>
|
||||
|
||||
#### 1、按哈夫曼的思路排序:
|
||||
|
||||
<div class="center">
|
||||
|
||||
| $a$ | $b$ | $d$ | $e$ |$c$ |
|
||||
| ---- | ---- |---- | ---- | ---- |
|
||||
| $10$ | $15$ | $16$ |$29$ |$30$ |
|
||||
|
||||
</div>
|
||||
|
||||
#### 2、构建哈夫曼树
|
||||
|
||||

|
||||
|
||||
答案:$B$
|
||||
|
||||
---
|
||||
|
||||
#### 8、完全二叉树
|
||||
|
||||
<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/2022/09/27a65e22a5238aac7a4301d6eef10a8b.png"/>
|
||||
|
||||
|
||||
[解释I](https://blog.csdn.net/weixin_50502862/article/details/121521582)
|
||||
|
||||
[解释II](https://www.zhihu.com/question/457988131)
|
||||
|
||||

|
||||
|
||||
此题选:$C$
|
||||
|
||||
#### 9、有向连通图+邻接矩阵存储
|
||||
|
||||
* 有向,邻接矩阵记录的是每条**有向边**
|
||||
* 连通图,最简单的连通图是一个链,即$N-1$条边,使用邻接矩阵记录的话,就是$N$个点
|
||||
|
||||
所以非零元素最少是$N$个,选择$B$
|
||||
|
||||
#### 10、对数据结构表述不恰当的是:
|
||||
|
||||
* 图的深度优先遍历遍历算法常使用的数据结构为栈。
|
||||
$dfs$本质是递归,使用的数据结构是栈,此说法正确。
|
||||
|
||||
* 栈的访问原则为后进先出,队列的访问原则是先进先出
|
||||
这种说法是正确的,有时也说栈是先进后出
|
||||
|
||||
* 队列常常被用于广度优先搜索算法
|
||||
正确
|
||||
* 栈与队列存在本质不同,无法用栈实现队列
|
||||
此说法不正确,用两个栈可以模拟出一个队列。
|
||||
|
||||
[用两个栈实现一个队列&用两个队列实现一个栈](https://blog.csdn.net/cherrydreamsover/article/details/80466781)
|
||||
|
||||
#### 11、哪种操作顺序上正确的:
|
||||

|
||||
|
||||

|
||||
|
||||
#### 12、以下排序算法的常见实现中,哪个选项的说法是错误的:
|
||||
A、冒泡排序算法是稳定的
|
||||
|
||||
B、简单选择排序是稳定的
|
||||
|
||||
C、简单插入排序是稳定的
|
||||
|
||||
D、归并排序算法是稳定的
|
||||
|
||||
| | 算法 |
|
||||
| ---- | ---- |
|
||||
| **稳定** | 冒泡排序、插入排序、归并排序、基数排序 |
|
||||
| **不稳定** | 堆排序、快速排序、希尔排序、选择排序 |
|
||||
|
||||
答案:$B$
|
||||
#### 冒泡排序
|
||||

|
||||
|
||||
#### 选择排序
|
||||

|
||||
|
||||
#### 插入法
|
||||

|
||||
|
||||
#### 归并排序
|
||||

|
||||
|
||||
#### $Q$: 什么是排序算法的稳定性?
|
||||
排序前后两个相等的数相对位置不变,则算法稳定
|
||||
|
||||
|
||||
#### $Q:$ 为什么选择排序不稳定呢?
|
||||
答:假设有一个待排序的序列: `2 3 2 1 4`
|
||||
我们知道第一趟排序后就会选择第$1$个元素$2$和元素$1$交换,那么原来序列中两个$2$的相对顺序就被破坏了,所以选择排序是 <font color='red' size=4><b>不稳定</b></font> 的排序算法。
|
||||
|
||||
#### 排序的稳定性
|
||||

|
||||
|
||||
**不稳定:快选堆希**
|
||||
|
||||
[各种排序算法稳定性分析](https://www.cnblogs.com/littlehb/p/16710891.html)
|
||||
|
||||
#### 13、八进制数32.1对应的十进制数是()
|
||||
$A. 24.125$
|
||||
$B. 24.250$
|
||||
$C. 26.125$
|
||||
$D. 26.250$
|
||||
|
||||
$32=2*8^0+3*8^1=2+24=26$ ,所以排除$A,B$
|
||||
小数部分
|
||||
$0.1=1*8^{-1}=1/8=0.125$ 答案选择$C$
|
||||
|
||||
#### 14、互不相同的子串
|
||||
$abcab$
|
||||
|
||||
一个:$a,b,c$ 共$3$个
|
||||
两个:$ab,bc,ca$
|
||||
三个:$abc,bca,cab$
|
||||
四个:$abca,bcab$
|
||||
五个:$abcab$
|
||||
|
||||
共$12$个,答案:$A$
|
||||
|
||||
#### 15、递归描述正确的:$B$
|
||||
|
||||
---
|
||||
|
||||
### 阅读程序类试题的方法论
|
||||
|
||||
1. 举栗子模拟,一般采用不大不小的数字,如果有多个参数,尽量别设置一样的大小,比如$n=4,m=5$
|
||||
2. 用人脑模拟电脑进行运行,以纸笔形式记录计算结果
|
||||
3. 数形结合画成图,以图形式方式方便理解
|
||||
4. 阅读程序愿意考查递归或动态规划问题,对于递归问题可能着重考查重复计算次数等,要把这一块的知识彻底搞清楚
|
||||
5. 推导计算要由边界开始,比如一行一列,二行一列,....,不要上来就想通用形式。
|
||||
6. 有送分题,可以注意边界情况
|
||||
7. 有的时候要靠心算的,对心智要求挺高
|
||||
8. 有些数不好算,时间还长的,可以考虑蒙一个答案,不要期望答100分
|
||||
9. 平时多阅读一些有关的信息学奥赛常见问题,比如:约瑟夫圆环,高空扔鸡蛋等,有原型再思考就心里更有底。
|
||||
10. 根据前面推出的一些数据,来找规律,推测后面的数据。
|
||||
|
||||
### 二、阅读程序
|
||||

|
||||
|
||||
|
||||
#### 十六进制与二进制转换
|
||||
再来研究一下$0x33,0x55$:
|
||||
这样写是$16$进制的意思,可以理解为:
|
||||
* $0x33=3*16^0+3*16^1=3+38=51$
|
||||
* $0x55=5*16^0+5*16^1=5+80=85$
|
||||
但是,如果我们真的把它转为十进制,再转成二进制进行位运算,可就真的是南辕北辙了:
|
||||
|
||||
十六进制与二进制存在天生的亲戚关系,转化起来不用经过十进制,可以飞快转:
|
||||
$\large 0x33=(0011~0011)_2$
|
||||
$\large 0x55=(0101~0101)_2$
|
||||
|
||||
<font color='red' size=4><b>
|
||||
规律:每个十六进制数位,都可以转为$4$位二进制,然后拼在一起即可。</b></font>
|
||||
|
||||
## 本题核心:用二进制+位运算+暴算
|
||||
|
||||
#### 16、删去第$7$行与第$14$行的$unsigned$,程序行为不变。($√$)
|
||||
解答:$short$与$unsigned~ short$只差一个符号位,$short$是$15$位,而$unsigned~ short$是$16$位。从上面的推导可以知道,我们最多使用了$8$位,是不是有符号不是很重要,可以去掉。
|
||||
|
||||
**答案:正确**
|
||||
|
||||
#### 17、将第$7$行与第$13$行的$short$均改为$char$,程序行为不变。($×$)
|
||||
解答:数字$0$的$ASCII$码是$48$,如果如果$x=2$,按$char$去接入,则用$48+2=50$去计算结果,原来是$2$,现在是$50$,结果肯定不一样啊,答案错误。
|
||||
|
||||
#### 18、程序总是输出一个整数 $0$。($×$)
|
||||
解答:很明显,上面已经算出了答案,并不是$0$,答案错误。
|
||||
|
||||
#### 19、当输入为$2~2$时,输出为 $10$。($×$)
|
||||
如果$x=2,y=2$,则按代码一行一行的代入计算即可
|
||||
$$
|
||||
\large \left\{\begin{matrix}
|
||||
x=(0010)_2 & \\
|
||||
y=(0010)_2 &
|
||||
\end{matrix}\right.
|
||||
$$
|
||||
|
||||
|
||||
```c++
|
||||
x=2 , y=2
|
||||
x= 0000 0010
|
||||
x<<2 0000 1000
|
||||
|
||||
x= 0000 0010
|
||||
|x<<2 0000 1000
|
||||
--------------------------
|
||||
0000 1010
|
||||
& 0x33 0011 0011
|
||||
--------------------------
|
||||
x= 0000 0010
|
||||
x<<1 0000 0100
|
||||
--------------------------------------
|
||||
0000 0110
|
||||
& 0x55 0101 0101
|
||||
-------------------------------------
|
||||
x 0000 0100
|
||||
|
||||
y= 0000 0100
|
||||
y<<1 = 0000 1000
|
||||
| x 0000 0100
|
||||
z= ----------------------
|
||||
0000 1100 =>12
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
y= 0010
|
||||
|
||||
结果:$\large 00001100$,结果应该是$12$。答案错误。
|
||||
|
||||
#### 20、当输入为$2~2$时,输出为$59$。($×$)
|
||||
解答:不解释。
|
||||
|
||||
### 单选题
|
||||
|
||||
#### 21、当输入为$13~8$时,输出为$(B)$
|
||||
$A.0 ~~~~ B.209 ~~~~ C.197 ~~~~ D.226$
|
||||
|
||||
最终$x=81=(0101 0001)_2$,$y=64=(01000000)_2$
|
||||
|
||||
答案应该:$2^7+2^6+2^4+2^0=128+64+16+1=209$
|
||||
|
||||
|
||||
```c++
|
||||
x= 13 y=8
|
||||
|
||||
x 0000 1101
|
||||
|x<<2 0011 0100
|
||||
---------------------------
|
||||
0011 1101
|
||||
&0x33 0011 0011
|
||||
-------------------------
|
||||
0011 0001
|
||||
|x<<1 0110 0010
|
||||
----------------------------
|
||||
0111 0011
|
||||
&0x55 0101 0101
|
||||
----------------------------
|
||||
0101 0001
|
||||
|
||||
|
||||
y 0000 1000
|
||||
|y<<2 0010 0000
|
||||
-------------------------
|
||||
0010 1000
|
||||
&0x33 0011 0011
|
||||
-------------------------
|
||||
0010 0000
|
||||
|y<<1 0100 0000
|
||||
--------------------------
|
||||
0110 0000
|
||||
& 0x55 0101 0101
|
||||
--------------------------
|
||||
0100 0000
|
||||
y<<1 1000 0000
|
||||
|x 0101 0001
|
||||
---------------------
|
||||
1101 0001
|
||||
|
||||
128+64+16+1=209
|
||||
```
|
||||
|
||||
|
||||
#### 22、阅读程序回答问题
|
||||

|
||||
|
||||
<font color='blue' size=4><b>
|
||||
* 先看选择项目,再看题面
|
||||
* 看明白哪个简单,就先做哪个,挑数字小的开整
|
||||
* 打表,对,打表,因为无论是递推,递归,动态规划,都可以用打表来理解
|
||||
</b></font>
|
||||
|
||||
#### (1)、知识点:
|
||||
`numeric_limits`:$C++$中取极值的方法,比如`numeric_limits(int)::max`就是取`INT_MAX`
|
||||
|
||||
#### (2)、递推与递归
|
||||
上面一个递归,后面一个是递推,看来是一个问题的两种解法。
|
||||
|
||||
#### (3)、解题路径:大力出奇迹,打表过样例
|
||||
<center><img src='https://cdn.jsdelivr.net/gh/littlehb/ShaoHuiLin/20221103140526.png'></center>
|
||||
|
||||
|
||||

|
||||
|
||||
* 现在让我们求的是$7,3$,也就是求$7*2^{7-1}=7*2^6=7*64=448$
|
||||
判断题说结果是$449$,应该是<font color='red' size=4><b>说法错误</b></font>。
|
||||
|
||||
|
||||
* 因为当$m=100$时,$n$的变化规律为$1$个$1$,$2$个$2$,$4$个$3$,$8$个$4$,$16$个$5$,$32$个$6$,最后剩下$37$个$7$,因此结果是$7$。
|
||||
|
||||
|
||||
#### 阅读程序第$3$题
|
||||

|
||||
|
||||
[黄海的二分总结](https://www.cnblogs.com/littlehb/p/16472541.html)
|
||||
|
||||
|
||||
#### (4)、深入思考:扔鸡蛋问题
|
||||
[漫画:有趣的扔鸡蛋问题](https://mp.weixin.qq.com/s/nMC55qvgsQNQfncAEOM20Q?)
|
||||
|
||||
[漫画:什么是动态规划?(整合版)](https://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653190796&idx=1&sn=2bf42e5783f3efd03bfb0ecd3cbbc380&chksm=8c990856bbee8140055c3429f59c8f46dc05be20b859f00fe8168efe1e6a954fdc5cfc7246b0&scene=21#wechat_redirect)
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
$$\large dp(k,n)=\mathop{min}\limits_{0<=i<=n}\{max\{dp(k-1,i-1),dp(k,n-i)\}+1\}$$
|
||||
|
||||
|
||||
|
||||
<font color='red' size=4><b>结论:爆算+找规律</b></font>
|
||||
|
||||
$1$
|
||||
$2~2$
|
||||
$3~3~3$
|
||||
$4~4~4~4$
|
||||
$5~5~5~5~5$
|
||||
$6~6~6~6~6~6$
|
||||
|
||||
|
||||
#### 高空扔鸡蛋的代码
|
||||
```c++
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
const int N = 110;
|
||||
int dp[N][N];
|
||||
|
||||
//在n层楼,剩余m个鸡蛋,返回最少的尝试次数
|
||||
int f(int n, int m) {
|
||||
if (m == 1) return n; //如果只有一个鸡蛋,那么需要枚举n层楼
|
||||
if (n == 0) return 0; //如果没有楼,就不用再试了
|
||||
if (dp[n][m]) return dp[n][m]; //计算过就返回
|
||||
|
||||
int ret = INT_MAX;
|
||||
for (int i = 1; i <= n; i++) //在当前场景下,尝试每一层楼进行扔鸡蛋,把所有可能计算出来,挑一个尝试次数最小的
|
||||
// f(n - i, m):鸡蛋没碎,楼层太矮,继续在i+1~n 这n-i层楼进行尝试,还有m个鸡蛋
|
||||
// f(i - 1, m - 1):鸡蛋碎了,楼层太高,继续在1~i-1层楼进行尝试
|
||||
// +1:代价是使用了一个次数
|
||||
ret = min(ret, max(f(n - i, m), f(i - 1, m - 1)) + 1); //
|
||||
return dp[n][m] = ret;
|
||||
}
|
||||
|
||||
int g(int n, int m) {
|
||||
for (int i = 1; i <= n; i++) dp[i][1] = i;
|
||||
for (int j = 1; j <= m; j++) dp[0][j] = 0;
|
||||
|
||||
for (int i = 1; i <= n; i++)
|
||||
for (int j = 2; j <= m; j++) {
|
||||
dp[i][j] = INT_MAX;
|
||||
for (int k = 1; k <= i; k++)
|
||||
dp[i][j] = min(dp[i][j], max(dp[i - k][j], dp[k - 1][j - 1]) + 1);
|
||||
}
|
||||
return dp[n][m];
|
||||
}
|
||||
int main() {
|
||||
int n, m;
|
||||
cin >> n >> m;
|
||||
|
||||
cout << f(n, m) << endl
|
||||
<< g(n, m) << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
#### 阅读程序第$3$题
|
||||

|
||||
|
||||
[黄海的二分总结](https://www.cnblogs.com/littlehb/p/16472541.html)
|
||||
1
TangDou/CSP-J/J1_2022/CSP-J2022_7.drawio
Normal file
1
TangDou/CSP-J/J1_2022/CSP-J2022_7.drawio
Normal file
@@ -0,0 +1 @@
|
||||
<mxfile host="Electron" modified="2022-09-19T23:59:08.714Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" version="20.3.0" etag="sVdTdiU_AGrtvn9FmtEd" type="device"><diagram id="pRatYBhsBdaIkLf27_gC" name="第 1 页">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</diagram></mxfile>
|
||||
1
TangDou/CSP-J/J1_2022/CSP-J2022_8.drawio
Normal file
1
TangDou/CSP-J/J1_2022/CSP-J2022_8.drawio
Normal file
@@ -0,0 +1 @@
|
||||
<mxfile host="Electron" modified="2022-09-20T00:11:40.273Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="1zbbeZfyBCVdHB9Yb5_j" version="20.3.0" type="device"><diagram id="sDIemgx6XfFFfB5THmSB" name="第 1 页">7VtRk5owEP41Pp5DEojwenq9e2hnruPD3T11EILQInFiPLW/vqChgolKOyjbqzM3c2STCH7f7pfNBntkOFs/Cn8ef+EhS3vYCtc9MuphjGyMe8WfFW52Fnfg7AxTkYRq0N4wTn4yZbSUdZmEbFEbKDlPZTKvGwOeZSyQNZsvBF/Vh0U8rd917k+ZZhgHfqpbX5JQxspKHXvf8cSSaVzeGlFv1zPzy9HqqyxiP+Sriok89MhQcC53V7P1kKUFeiUwu3mfjvT+fjLBMtlkwrfRczoii5enN46+jh38wr2HO1c9m9yU31jwZRayYo7VI/dcyJhPeeannzmf50aUG78zKTeKK38peW6K5SxVvWydyFc1vbh+K677jmqN1pWu0aZsZFJsXvcDi+ZbtW8/bdsq50U8k+pBCkDvdUwUTAu+FAE7AUTpXL6YMnlinPJnFtb8RiH+yPiM5c+XDxAs9WXyXncjX3nj9Pe4PV/5haLsD+jzNPqKBxurZsaz/N99a4yiD8koAcWo+tx3P13W71TlOE1z/SuoXcWJZOO5vwVilUtwnTh/Md+JYpSsCwdoju47E5KtT+Kheqml9E0pfCl3q71c2soUV4SytLUPoKXjdcGY+EuVI/Wg6FsD50xgbFvPTCQ5TEy0Hi24YbTYsKLFEBxABPD61DigqMGakGHYQkbsupAhq2slMwAGT8k6Wt1Jw6CgoIICEbB69e9QOgBFKdF0zsAxJJ1zXWA6Z1oYbjpXz7fOBgWClZiZggCI0HXADazMzNYUy76gYkVJmg55ysX2g0noMzcKcvtCCv6DVXpo4LJJ1I7GIe9gU0q61jgDxFfSOARc4ZymUQQrlcPOf6BwjbmBlZM5msIZyIKUk9l0AEyvKNicrD3vpk292wXl3XgAVnmOVjjvrL51rsJ5SKyx4nkFtj1QbFNNywyhCUnLBjYBpmX6Iect96qVUs6felqgYgLfzj1b4BSB4nSg6ZxhmYOkc8jCwDaZ5LbJPLqoN63VEFhKR7rbZX4cSmEJHdLrb4YEBZbSoZrQkUHXQnfL6I56V9OSDYFVTiPdpXSnKe2CGljVNKSX0wxkXerAIHIDFhgPDCauYzsnawLNJQ4fFOA6lzik7/tNb7ZBWiaIZwPDUN9TmF4Yg4ShQy1gGLo6hhg2htSFhqGnYwj9JRGbwsKw3GtXMbzksXULGHoUGoaGl8uBH4whRDAsEInBEYFX5Gl9Xba9riE0+CH0Yl9ZiAGDoSE/vGQZ4TArjyJszspDOqEObSkrJ6gPDXdDTnnF3VDoMDe0Tbi7eEJoS7gTC18P97y5/xHktq/yW1Ly8As=</diagram></mxfile>
|
||||
BIN
TangDou/CSP-J/J1_2022/CSP-J2022_参考答案.jpg
Normal file
BIN
TangDou/CSP-J/J1_2022/CSP-J2022_参考答案.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 187 KiB |
BIN
TangDou/CSP-J/J1_2022/CSP2022-J1.pdf
Normal file
BIN
TangDou/CSP-J/J1_2022/CSP2022-J1.pdf
Normal file
Binary file not shown.
43
TangDou/CSP-J/J1_2022/CSP_J22.cpp
Normal file
43
TangDou/CSP-J/J1_2022/CSP_J22.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
const int N = 110;
|
||||
int dp[N][N];
|
||||
|
||||
//在n层楼,剩余m个鸡蛋,返回最少的尝试次数
|
||||
int f(int n, int m) {
|
||||
if (m == 1) return n; //如果只有一个鸡蛋,那么需要枚举n层楼
|
||||
if (n == 0) return 0; //如果没有楼,就不用再试了
|
||||
if (dp[n][m]) return dp[n][m]; //计算过就返回
|
||||
|
||||
int ret = INT_MAX;
|
||||
for (int i = 1; i <= n; i++) //在当前场景下,尝试每一层楼进行扔鸡蛋,把所有可能计算出来,挑一个尝试次数最小的
|
||||
// f(n - i, m):鸡蛋没碎,楼层太矮,继续在i+1~n 这n-i层楼进行尝试,还有m个鸡蛋
|
||||
// f(i - 1, m - 1):鸡蛋碎了,楼层太高,继续在1~i-1层楼进行尝试
|
||||
// +1:代价是使用了一个次数
|
||||
ret = min(ret, max(f(n - i, m), f(i - 1, m - 1)) + 1); //
|
||||
return dp[n][m] = ret;
|
||||
}
|
||||
|
||||
int g(int n, int m) {
|
||||
for (int i = 1; i <= n; i++) dp[i][1] = i;
|
||||
for (int j = 1; j <= m; j++) dp[0][j] = 0;
|
||||
|
||||
for (int i = 1; i <= n; i++)
|
||||
for (int j = 2; j <= m; j++) {
|
||||
dp[i][j] = INT_MAX;
|
||||
for (int k = 1; k <= i; k++)
|
||||
dp[i][j] = min(dp[i][j], max(dp[i - k][j], dp[k - 1][j - 1]) + 1);
|
||||
}
|
||||
return dp[n][m];
|
||||
}
|
||||
int main() {
|
||||
int n, m;
|
||||
// cin >> n >> m;
|
||||
|
||||
// cout << f(n, m) << endl
|
||||
// << g(n, m) << endl;
|
||||
|
||||
cout << f(15, 3) << endl;
|
||||
return 0;
|
||||
}
|
||||
99
TangDou/CSP-J/J1_2022/Huffman.cpp
Normal file
99
TangDou/CSP-J/J1_2022/Huffman.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include <stdio.h>
|
||||
#define MAXBIT 100
|
||||
#define MAXVALUE 10000 //
|
||||
#define MAXLEAF 30 //叶节点数目
|
||||
#define MAXNODE MAXLEAF * 2 - 1 //节点数目
|
||||
//首先构造出一颗哈夫曼树的结点,包括
|
||||
//权重weight,父亲parent, 左儿子lchild,右儿子rchild。
|
||||
typedef struct
|
||||
{
|
||||
int bit[MAXBIT];
|
||||
int start;
|
||||
} HCodeType; //编码结构体
|
||||
typedef struct HNodeType {
|
||||
int weight;
|
||||
int parent;
|
||||
int lchild;
|
||||
int rchild;
|
||||
} HNodeType; //结点结构体
|
||||
//开始创建一颗哈夫曼树
|
||||
void Huffman(HNodeType HuffNode[MAXNODE], int n) {
|
||||
int i, j, m1, m2, x1, x2; // i,j循环变量,m1,m2:构造哈弗曼树不同过程中两个最小权值结点的权值
|
||||
// x1,x2 构造哈弗曼树不同过程中两个最小权值结点在数组中的序号
|
||||
for (i = 0; i < 2 * n - 1; i++) //初始化哈弗曼树数组中的结点
|
||||
{
|
||||
HuffNode[i].weight = 0;
|
||||
HuffNode[i].parent = -1;
|
||||
HuffNode[i].lchild = -1;
|
||||
HuffNode[i].rchild = -1;
|
||||
}
|
||||
//输入n个叶子结点的权值
|
||||
for (i = 0; i < n; i++) {
|
||||
printf("Plese input weight of leaf node %d:\n", i);
|
||||
scanf("%d", &HuffNode[i].weight);
|
||||
}
|
||||
//循环构造哈弗曼树
|
||||
for (i = 0; i < n - 1; i++) {
|
||||
m1 = m2 = MAXVALUE; // m1,m2中存放两个无父结点且结点权值最小的两个结点
|
||||
x1 = x2 = 0;
|
||||
//找出所有结点中权值最小、无父节点的两个结点,并合并为一颗二叉树
|
||||
for (j = 0; j < n + i; j++) {
|
||||
if (HuffNode[j].weight < m1 && HuffNode[j].parent == -1) {
|
||||
m2 = m1;
|
||||
x2 = x1;
|
||||
m1 = HuffNode[j].weight;
|
||||
x1 = j;
|
||||
} else if (HuffNode[j].weight < m2 && HuffNode[j].parent == -1) {
|
||||
m2 = HuffNode[j].weight;
|
||||
x2 = j;
|
||||
}
|
||||
}
|
||||
HuffNode[x1].parent = n + i;
|
||||
HuffNode[x2].parent = n + i;
|
||||
HuffNode[n + i].weight = HuffNode[x2].weight + HuffNode[x1].weight;
|
||||
HuffNode[n + i].lchild = x1;
|
||||
HuffNode[n + i].rchild = x2;
|
||||
}
|
||||
}
|
||||
/*
|
||||
5
|
||||
10 15 30 16 29
|
||||
*/
|
||||
int main() {
|
||||
HNodeType HuffNode[MAXNODE]; //定义一个结点结构体数组
|
||||
HCodeType HuffCode[MAXLEAF], cd; //定义一个编码结构体数组,同时定义一个临时变量来存放求解编码时的信息
|
||||
int i, j, c, n, p;
|
||||
printf("Please input n:\n");
|
||||
scanf("%d", &n);
|
||||
Huffman(HuffNode, n);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
cd.start = n - 1;
|
||||
c = i;
|
||||
p = HuffNode[c].parent;
|
||||
while (p != -1) //父节点存在
|
||||
{
|
||||
if (HuffNode[p].lchild == c) {
|
||||
cd.bit[cd.start] = 0;
|
||||
} else {
|
||||
cd.bit[cd.start] = 1;
|
||||
}
|
||||
cd.start--; //求编码的低一位
|
||||
c = p;
|
||||
p = HuffNode[c].parent; //设置下一循环条件
|
||||
}
|
||||
for (j = cd.start + 1; j < n; j++) {
|
||||
HuffCode[i].bit[j] = cd.bit[j];
|
||||
}
|
||||
HuffCode[i].start = cd.start;
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
printf("%d 's Huffman code is:", i);
|
||||
for (j = HuffCode[i].start + 1; j < n; j++) {
|
||||
printf("%d", HuffCode[i].bit[j]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
getchar();
|
||||
return 0;
|
||||
}
|
||||
19
TangDou/CSP-J/J1_2022/Test.cpp
Normal file
19
TangDou/CSP-J/J1_2022/Test.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
vector<int> fac;
|
||||
int main() {
|
||||
int n = 9;
|
||||
|
||||
for (int i = 1; i * i <= n; i++) {
|
||||
if (n % i == 0) {
|
||||
fac.push_back(i);
|
||||
if (i != n / i) fac.push_back(n / i);
|
||||
}
|
||||
}
|
||||
sort(fac.begin(), fac.end());
|
||||
for (int i = 0; i < fac.size(); i++)
|
||||
cout << fac[i] << " ";
|
||||
|
||||
return 0;
|
||||
}
|
||||
28
TangDou/CSP-J/J1_2022/UnsignedShort.cpp
Normal file
28
TangDou/CSP-J/J1_2022/UnsignedShort.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
/*
|
||||
%d 有符号32位整数
|
||||
%u 无符号32位整数
|
||||
%lld 有符号64位整数
|
||||
*/
|
||||
printf("short:%d bytes,max=%d,min=%d\n", (int)sizeof(short), SHRT_MAX, SHRT_MIN);
|
||||
printf("unsigned short:%d bytes,max=%d\n", (int)sizeof(unsigned short), USHRT_MAX, 0);
|
||||
puts("");
|
||||
|
||||
printf("int:%d bytes,max=%d,min=%d\n", (int)sizeof(int), INT_MAX, INT_MIN);
|
||||
printf("unsigned int:%d bytes,max=%u\n", (int)sizeof(unsigned int), UINT_MAX, 0);
|
||||
puts("");
|
||||
|
||||
printf("long long:%d bytes,max=%lld,min=%lld\n", (int)sizeof(long long));
|
||||
//使用 %lld 就可以打印一个unsigned long long了,glib中的guint64就是unsigned long long。这是GNU编译器支持的。
|
||||
printf("unsigned long long:%d bytes,max=%lld,min=%d\n", (int)sizeof(unsigned long long));
|
||||
|
||||
double a = 1, b = 1;
|
||||
if (a * b == 1)
|
||||
printf("True");
|
||||
else
|
||||
printf("False");
|
||||
return 0;
|
||||
}
|
||||
21
TangDou/CSP-J/J1_2022/一元二次方程求根公式的推导过程.md
Normal file
21
TangDou/CSP-J/J1_2022/一元二次方程求根公式的推导过程.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## 一元二次方程求根公式的推导过程
|
||||
|
||||
$Q$:求根公式$\LARGE x=\frac{-b \pm \sqrt{b^2-4ac}}{2a}$是怎么来的呢?
|
||||
|
||||
我们下面从头来推导一下:
|
||||
|
||||
$\because (a+b)^2=(a+b)(a+b)=a^2+2ab+b^2$
|
||||
|
||||
那么,形式为$ax^2+bx+c=0(a\neq 0)$的方程,我们也想办法进行配方:
|
||||
$$\large \therefore x^2+\frac{b}{a} x+\frac{c}{a}=0$$
|
||||
$$\large x^2+\frac{b}{a} x=-\frac{c}{a}$$
|
||||
$$\large x^2+\frac{b}{a}x+\frac{b^2}{4a^2}=\frac{b^2}{4a^2}-\frac{c}{a}$$
|
||||
|
||||
$$\large (x+\frac{b}{2a})^2=\frac{b^2-4ac}{4a^2}$$
|
||||
|
||||
左右同时开方
|
||||
|
||||
$$\large x+\frac{b}{2a}=\pm \frac{\sqrt{b^2-4ac}}{2a}$$
|
||||
|
||||
$$\therefore \large x=\frac{-b \pm \sqrt{b^2-4ac}}{2a}$$
|
||||
|
||||
120
TangDou/CSP-J/J1_2022/哈夫曼编码(Huffman Coding)原理详解.md
Normal file
120
TangDou/CSP-J/J1_2022/哈夫曼编码(Huffman Coding)原理详解.md
Normal file
@@ -0,0 +1,120 @@
|
||||
## 哈夫曼编码($Huffman$ $Coding$)原理详解
|
||||
|
||||
### 一、哈夫曼编码简介
|
||||
哈夫曼编码,又称为霍夫曼编码($Huffman$ $Coding$)
|
||||
|
||||
是一种可变长编码($VLC$, $variable$ $length$ $coding$)方式,比起定长编码的 $ASCII$ 编码来说,哈夫曼编码能节省很多的空间,因为每一个字符出现的频率不是一致的;
|
||||
|
||||
是一种用于**无损数据压缩**的熵编码算法,通常**用于压缩重复率比较高的字符数据**。
|
||||
|
||||
如果我们通过转换成$ASCII$码对应的二进制数据将字符串 $BCAADDDCCACACAC$ 通过二进制编码进行传输,那么一个字符传输的二进制位数为 $8bits$,那么总共需要 $120$ 个二进制位;而如果使用哈夫曼编码,该串字符可压缩至$28$位。
|
||||
|
||||
[$Q$:为什么一个字节是八个bit](https://blog.csdn.net/snowin1994/article/details/91393390)
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/d0211bfe087b4107890fa997cd73289b.png'></center>
|
||||
|
||||
### 二、哈夫曼编码方法
|
||||
|
||||
哈夫曼编码首先会使用字符的**频率**创建一棵树,然后**通过这个树的结构为每个字符生成一个特定的编码**,出现**频率高**的字符**使用较短的编码**,出现**频率低**的则**使用较长的编码**,这样就会使编码之后的字符串平均长度降低,从而达到数据无损压缩的目的。
|
||||
|
||||
#### 1. 计算字符串中每个字符的频率:
|
||||
<center><img src='https://img-blog.csdnimg.cn/b0d35cf91810464da8f7a7b0721ec0bc.png'></center>
|
||||
|
||||
#### 2. 按照字符出现的频率进行排序,组成一个队列 $Q$
|
||||
出现频率最低的在前面,出现频率高的在后面。
|
||||
<center><img src='https://img-blog.csdnimg.cn/54ea35b4150446e189666259f03957f5.png'></center>
|
||||
|
||||
#### 3. 把这些字符作为叶子节点开始构建一颗哈夫曼树
|
||||
|
||||
哈夫曼树又称为最优二叉树,是一种带权路径长度最短的二叉树。
|
||||
|
||||
* 首先创建一个空节点 $z$,将最小频率的字符分配给 $z$ 的左侧,并将频率排在第二位的分配给 $z$ 的右侧,然后将 $z$ 赋值为两个字符频率的和;然后从队列 $Q$ 中删除 $B$ 和 $D$,并将它们的和添加到队列中,上图中 $*$ 表示的位置。
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/873651259ed846f99f930469824b0d3a.png'></center>
|
||||
|
||||
|
||||
* 紧接着,重新创建一个空的节点 $z$,并将 $4$ 作为左侧的节点,频率为 $5$ 的 $A$ 作为右侧的节点,$4$ 与 $5$ 的和作为父节点,并把这个和按序加入到队列中,再**根据频率从小到大构建树结构(小的在左)**。
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/4e339808036a4d3397a94b979932d02c.png'></center>
|
||||
|
||||
* 继续按照之前的思路构建树,直到所有的字符都出现在树的节点中,哈夫曼树构建完成。
|
||||
|
||||
**节点的带权路径长度**:为从根节点到该节点的路径长度与节点权值的乘积。
|
||||
|
||||
该二叉树的带权路径长度 $WPL = 6 * 1 + 5 * 2 + 3 * 3 + 1 * 3 = 28$
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/37253e0c06cb4585aa80f209da1fd12f.png'></center>
|
||||
|
||||
* 4. 对字符进行编码
|
||||
|
||||
哈夫曼树构建完成,下面我们要对各个字母进行编码,
|
||||
|
||||
**编码原则**:对于每个非叶子节点,将 $0$ 分配给连接线的左侧,$1$ 分配给连接线的右侧,最终得到字符的编码就是从根节点开始,到该节点的路径上的 $01$ 序列组合。
|
||||
|
||||
<center><img src='https://img-blog.csdnimg.cn/ea0051bc825c498189736c084b4abffc.png'></center>
|
||||
|
||||
因此各个字母的编码分别为:
|
||||
|
||||
<!-- 让表格居中显示的风格 -->
|
||||
<style>
|
||||
.center
|
||||
{
|
||||
width: auto;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
<div class="center">
|
||||
|
||||
| $A$ | $B$ | $C$ | $D$ |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| $11$ | $100$ | $0$ | $101$ |
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
在没有经过哈夫曼编码之前,字符串$\large BCAADDDCCACACAC$的二进制为:
|
||||
```c++
|
||||
10000100100001101000001010000010100010001000100010001000100001101000011010000010100001101000001010000110100000101000011
|
||||
```
|
||||
也就是占了 $120$ 比特;
|
||||
|
||||
编码之后为:
|
||||
```c++
|
||||
1000111110110110100110110110
|
||||
```
|
||||
占了 $28$ 比特。
|
||||
|
||||
#### 5. 确定发送的数据
|
||||
|
||||
哈夫曼编码将发送字符串的数据长度极大压缩,考虑到接收方的编码,还需要把哈夫曼树的结构也传递过去。
|
||||
|
||||
即字符占用的 $32$ 比特和 频率占用的 $15$ 比特也需要传递过去。
|
||||
|
||||
总体上,编码后比特数为$32 + 15 + 28 = 75$,比 $120$ 比特少了 $45$ 个,效率还是非常高的。
|
||||
|
||||
<font color='red' size=4><b>注:之所以把字符和频率再发过去,对方就知道$A:11$,$B:1001$...,以此来对$ABCD$进行相应的解码工作。</b></font>
|
||||
<center><img src='https://img-blog.csdnimg.cn/004639adea4f4fab851eff4729619dd5.png'></center>
|
||||
|
||||
根据原文进行验证吧:
|
||||
$$\large BCAADDDCCACACAC$$
|
||||
|
||||
从本质上讲,哈夫曼编码是将最宝贵的资源(**最短的编码**)给出现概率最多的数据。
|
||||
|
||||
在上面的例子中,$C$ 出现的频率最高,它的编码为 $0$,就省下了不少空间。
|
||||
|
||||
|
||||
### 三、特点
|
||||
|
||||
哈夫曼编码有三个特点:
|
||||
|
||||
* 带权路径长度$WPL$最短且唯一
|
||||
* 编码互不为前缀(一个编码不是另一个编码的开头)
|
||||
* 哈夫曼树和编码都不唯一
|
||||
|
||||
**$Q$:为什么通过哈夫曼编码后得到的二进制码不会有前缀的问题呢?**
|
||||
这是因为在哈夫曼树中,每个字母对应的节点都是叶子节点,而他们对应的二进制码是由根节点到各自节点的路径所决定的,正因为是叶子节点,每个节点的路径不可能和其他节点有前缀的关系。
|
||||
|
||||
**$Q2$:为什么通过哈夫曼编码获得的二进制码短呢?**
|
||||
因为哈夫曼树是带权路径长度最短的树,权值较大的节点离根节点较近。而带权路径长度是指:树中所有的叶子节点的权值乘上其到根节点的路径长度,这与最终的哈夫曼编码总长度成正比关系的。
|
||||
BIN
TangDou/CSP-J/J1_2022/哈夫曼编码(Huffman Coding)原理详解.pdf
Normal file
BIN
TangDou/CSP-J/J1_2022/哈夫曼编码(Huffman Coding)原理详解.pdf
Normal file
Binary file not shown.
133
TangDou/CSP-J/J1_2022/常见排序算法的稳定性分析.md
Normal file
133
TangDou/CSP-J/J1_2022/常见排序算法的稳定性分析.md
Normal file
@@ -0,0 +1,133 @@
|
||||
## 各种排序算法稳定性分析
|
||||
|
||||
### 一、排序算法稳定性
|
||||
排序算法稳定性指假定在待排序的记录序列中,**存在多个具有相同的关键字的记录,经过排序这些记录的相对次序保持不变**,则称这种排序算法是稳定的;否则称为不稳定的。
|
||||
|
||||
排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。
|
||||
|
||||
举个栗子:
|
||||
<!-- 让表格居中显示的风格 -->
|
||||
<style>
|
||||
.center
|
||||
{
|
||||
width: auto;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
<div class="center">
|
||||
|
||||
|
||||
| 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|
||||
| ---- | ---- |---- | ---- | ---- | ---- | ---- |
|
||||
| $2$ | $\LARGE 3$ | $4$ | $\LARGE 3$ | $6$ | $7$ | $8$ |
|
||||
|
||||
</div>
|
||||
|
||||
**结论:**
|
||||
|
||||
<div class="center">
|
||||
|
||||
| | 算法 |
|
||||
| ---- | ---- |
|
||||
| **稳定** | 冒泡排序、插入排序、归并排序、基数排序 |
|
||||
| **不稳定** | 堆排序、快速排序、希尔排序、选择排序 |
|
||||
|
||||
</div>
|
||||
|
||||
现在分析一下常见的排序算法的稳定性,每个都给出简单的理由。
|
||||
|
||||
### 二、是否稳定的原因分析
|
||||
|
||||
#### 1、冒泡排序
|
||||
|
||||
冒泡排序就是把小的元素往前调(或者把大的元素往后调)。注意是相邻的两个元素进行比较,而且是否需要交换也发生在这两个元素之间。
|
||||
|
||||
所以,如果两个元素相等,我想你是不会再无聊地把它们俩再交换一下。
|
||||
|
||||
如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个元素相邻起来,最终也不会交换它俩的位置,所以相同元素经过排序后顺序并没有改变。
|
||||
|
||||
所以**冒泡排序是一种稳定排序算法**。
|
||||
|
||||
#### 2、选择排序
|
||||
|
||||
选择排序即是给每个位置选择待排序元素中当前最小的元素。比如给第一个位置选择最小的,在剩余元素里面给第二个位置选择次小的,
|
||||
|
||||
依次类推,直到第$n-1$个元素,第$n$个元素不用选择了,因为只剩下它一个最大的元素了。
|
||||
|
||||
那么,在一趟选择时,如果当前锁定元素比后面一个元素大,而后面较小的那个元素又出现在一个与当前锁定元素相等的元素后面,那么交换后位置顺序显然改变了。
|
||||
|
||||
**呵呵!比较拗口,举个例子:序列$5~ 8~ 5~ 2~ 9$, 我们知道第一趟选择第$1$个元素$5$会与$2$进行交换,那么原序列中两个$5$的相对先后顺序也就被破坏了。**
|
||||
|
||||
所以**选择排序不是一个稳定**的排序算法。
|
||||
|
||||
#### 3、插入排序
|
||||
|
||||
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有$1$个元素,也就是第一个元素(默认它有序)。
|
||||
|
||||
比较是从有序序列的末尾开始,也就是把待插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面。
|
||||
|
||||
否则一直往前找直到找到它该插入的位置。如果遇见一个与插入元素相等的,那么把待插入的元素放在相等元素的后面。
|
||||
|
||||
所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序仍是排好序后的顺序,所以**插入排序是稳定的**。
|
||||
|
||||
#### 4、快速排序
|
||||
|
||||
快速排序有两个方向,左边的$i$下标一直往右走(当条件$a[i] <= a[center_index]$时),其中$center_index$是中枢元素的数组下标,一般取为数组第$0$个元素。
|
||||
|
||||
而右边的$j$下标一直往左走(当$a[j] > a[center_index]$时)。
|
||||
|
||||
如果$i$和$j$都走不动了,$i <= j$, 交换$a[i]和a[j]$,重复上面的过程,直到$i>j$。交换$a[j]$和$a[center_index]$,完成一趟快速排序。
|
||||
|
||||
在中枢元素和$a[j]$交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 $5~ 3~ 3~ 4~ 3~ 8~ 9~ 10~ 11~ $
|
||||
|
||||
现在中枢元素$5$和$3$(第$5$个元素,下标从$1$开始计)交换就会把元素$3$的稳定性打乱。
|
||||
|
||||
所以快速排序是一个**不稳定的排序算法**,不稳定发生在中枢元素和$a[j]$交换的时刻。
|
||||
|
||||
#### 5、归并排序
|
||||
|
||||
归并排序是把序列递归地分成短序列,递归出口是短序列只有$1$个元素(认为直接有序)或者$2$个序列($1$次比较和交换),
|
||||
|
||||
然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。
|
||||
|
||||
可以发现,在$1$个或$2$个元素时,$1$个元素不会交换,$2$个元素如果大小相等也没有人故意交换,这不会破坏稳定性。
|
||||
|
||||
那么,在短的有序序列合并的过程中,稳定是是否受到破坏?
|
||||
|
||||
没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。
|
||||
|
||||
所以,**归并排序也是稳定的排序算法**。
|
||||
|
||||
#### 6、基数排序
|
||||
|
||||
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。
|
||||
|
||||
有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序结果就是高优先级高的在前,高优先级相同的情况下低优先级高的在前。
|
||||
|
||||
基数排序基于分别排序,分别收集,所以其是稳定的排序算法。
|
||||
|
||||
#### 7、希尔排序
|
||||
|
||||
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;
|
||||
|
||||
当元素基本有序时,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比O(N^2)好一些。
|
||||
|
||||
由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,
|
||||
|
||||
但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱。
|
||||
|
||||
所以**$shell$排序是不稳定的排序算法**。
|
||||
|
||||
#### 8、堆排序
|
||||
|
||||
我们知道堆的结构是节点$i$的孩子为$2*i$和$2*i+1$节点,大顶堆要求父节点大于等于其$2$个子节点,小顶堆要求父节点小于等于其$2$个子节点。
|
||||
|
||||
在一个长为$n$的序列,堆排序的过程是从第$n/2$开始和其子节点共$3$个值选择最大(大顶堆)或者最小(小顶堆),这$3$个元素之间的选择当然不会破坏稳定性。
|
||||
|
||||
但当为$n/2-1, n/2-2, ...1$这些个父节点选择元素时,就会破坏稳定性。
|
||||
|
||||
有可能第$n/2$个父节点交换把后面一个元素交换过去了,而第$n/2-1$个父节点把后面一个相同的元素没有交换,那么这$2$个相同的元素之间的稳定性就被破坏了。
|
||||
|
||||
所以,**堆排序不是稳定的排序算法**。
|
||||
BIN
TangDou/CSP-J/J1_2022/推导过程.xlsx
Normal file
BIN
TangDou/CSP-J/J1_2022/推导过程.xlsx
Normal file
Binary file not shown.
234
TangDou/CSP-J/J1_2022/表达式转换与表达式求值.md
Normal file
234
TangDou/CSP-J/J1_2022/表达式转换与表达式求值.md
Normal file
@@ -0,0 +1,234 @@
|
||||
[题目传送门](https://www.bilibili.com/video/BV1EG4y1h7P1)
|
||||
|
||||
第四题还没看
|
||||
|
||||
## 表达式转换与表达式求值
|
||||
|
||||
### 一、四则运算
|
||||
#### 中缀转后缀
|
||||
```c++
|
||||
/*
|
||||
比如:
|
||||
a+b*c+(d*e+f)*g
|
||||
|
||||
应输出:
|
||||
abc*+de*f+g*+
|
||||
*/
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace std;
|
||||
|
||||
unordered_map<char, int> h{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'(', 0}};
|
||||
string infix; //输入的中缀表达式
|
||||
string postfix; //后缀的结果表达式
|
||||
stack<char> s; //使用到的操作符+数字栈,这里的数字不用真的计算,所以统一按字符处理
|
||||
|
||||
int main() {
|
||||
cin >> infix;
|
||||
for (int i = 0; i < infix.size(); i++) {
|
||||
//是数字或字母的情况,直接记录到后缀表达式中
|
||||
if (isdigit(infix[i]) || (infix[i] >= 'a' && infix[i] <= 'z'))
|
||||
postfix += infix[i];
|
||||
else {
|
||||
//① 栈为空时直接入栈
|
||||
if (s.empty()) s.push(infix[i]);
|
||||
//② 左括号入栈
|
||||
else if (infix[i] == '(')
|
||||
s.push(infix[i]);
|
||||
//③ 如果是右括号,就在栈中不断弹出操作符和数字,直到栈顶是(为止
|
||||
else if (infix[i] == ')') {
|
||||
while (s.top() != '(') {
|
||||
postfix += s.top();
|
||||
s.pop();
|
||||
}
|
||||
//弹出左括号,但不输出
|
||||
s.pop();
|
||||
} else {
|
||||
//④栈顶元素的优先级大于等于当前的运算符,就将其输出
|
||||
while (s.size() && h[infix[i]] <= h[s.top()]) {
|
||||
postfix += s.top();
|
||||
s.pop();
|
||||
}
|
||||
//⑤当前运算符入栈
|
||||
s.push(infix[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
//⑥ 如果不为空,就把所有的元素全部弹出
|
||||
while (s.size()) {
|
||||
postfix += s.top();
|
||||
s.pop();
|
||||
}
|
||||
//输出后缀表达式
|
||||
cout << postfix << endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
#### 后缀表达式求值
|
||||
```c++
|
||||
#include <iostream>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
//下面的代码实现了由中缀表达式求值的过程
|
||||
using namespace std;
|
||||
|
||||
/*
|
||||
测试用例I:
|
||||
(2+2)*(1+1)
|
||||
|
||||
答案:8
|
||||
|
||||
测试用例II:
|
||||
2+(3*4)-((5*9-5)/8-4)
|
||||
|
||||
答案:13
|
||||
|
||||
*/
|
||||
|
||||
stack<int> num; //数字栈
|
||||
stack<char> op; //操作符栈
|
||||
|
||||
//优先级表
|
||||
unordered_map<char, int> h{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}, {'(', 0}};
|
||||
|
||||
/**
|
||||
* 功能:计算两个数的和差积商
|
||||
*/
|
||||
void eval() {
|
||||
int a = num.top(); //第二个操作数
|
||||
num.pop();
|
||||
|
||||
int b = num.top(); //第一个操作数
|
||||
num.pop();
|
||||
|
||||
char p = op.top(); //运算符
|
||||
op.pop();
|
||||
|
||||
int r = 0; //结果
|
||||
//计算结果
|
||||
if (p == '+')
|
||||
r = b + a;
|
||||
else if (p == '-')
|
||||
r = b - a;
|
||||
else if (p == '*')
|
||||
r = b * a;
|
||||
else if (p == '/')
|
||||
r = b / a;
|
||||
//结果入栈
|
||||
num.push(r);
|
||||
}
|
||||
|
||||
int main() {
|
||||
//读入表达式
|
||||
string s;
|
||||
cin >> s;
|
||||
//遍历字符串的每一位
|
||||
for (int i = 0; i < s.size(); i++) {
|
||||
//① 如果是数字,则入栈
|
||||
if (isdigit(s[i])) {
|
||||
//读出完整的数字
|
||||
int x = 0;
|
||||
while (i < s.size() && isdigit(s[i])) {
|
||||
x = x * 10 + s[i] - '0';
|
||||
i++;
|
||||
}
|
||||
i--; //加多了一位,需要减去
|
||||
|
||||
num.push(x); //数字入栈
|
||||
}
|
||||
//② 左括号无优先级,入栈
|
||||
else if (s[i] == '(')
|
||||
op.push(s[i]);
|
||||
//③ 右括号时,需计算最近一对括号里面的值
|
||||
else if (s[i] == ')') {
|
||||
//从栈中向前找,一直找到左括号
|
||||
while (op.top() != '(') eval(); //将左右括号之间的计算完,维护回栈里
|
||||
//左括号出栈
|
||||
op.pop();
|
||||
} else { //④ 运算符
|
||||
//如果待入栈运算符优先级低,则先计算
|
||||
while (op.size() && h[op.top()] >= h[s[i]]) eval();
|
||||
op.push(s[i]); //操作符入栈
|
||||
}
|
||||
}
|
||||
while (op.size()) eval(); //⑤ 剩余的进行计算
|
||||
|
||||
printf("%d\n", num.top()); //输出结果
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
### 二、逻辑运算
|
||||
#### 中缀转后缀
|
||||
```c++
|
||||
//下面的代码实现了中缀的逻辑表达式 转 后缀的逻辑表达式
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace std;
|
||||
/*
|
||||
测试用例:
|
||||
0&(0|1|0)
|
||||
|
||||
答案:
|
||||
001|0|&
|
||||
*/
|
||||
unordered_map<char, int> h{{'|', 1}, {'&', 2}, {'(', 0}};
|
||||
string infix; //输入的中缀表达式
|
||||
string postfix; //后缀的结果表达式
|
||||
stack<char> s; //使用到的操作符+数字栈,这里的数字不用真的计算,所以统一按字符处理
|
||||
|
||||
int main() {
|
||||
cin >> infix;
|
||||
for (int i = 0; i < infix.size(); i++) {
|
||||
//是数字或字母的情况,直接记录到后缀表达式中
|
||||
if (isdigit(infix[i]) || (infix[i] >= 'a' && infix[i] <= 'z'))
|
||||
postfix += infix[i];
|
||||
else {
|
||||
//① 栈为空时直接入栈
|
||||
if (s.empty()) s.push(infix[i]);
|
||||
//② 左括号入栈
|
||||
else if (infix[i] == '(')
|
||||
s.push(infix[i]);
|
||||
//③ 如果是右括号,就在栈中不断弹出操作符和数字,直到栈顶是(为止
|
||||
else if (infix[i] == ')') {
|
||||
while (s.top() != '(') {
|
||||
postfix += s.top();
|
||||
s.pop();
|
||||
}
|
||||
//弹出左括号,但不输出
|
||||
s.pop();
|
||||
} else {
|
||||
//④栈顶元素的优先级大于等于当前的运算符,就将其输出
|
||||
while (s.size() && h[infix[i]] <= h[s.top()]) {
|
||||
postfix += s.top();
|
||||
s.pop();
|
||||
}
|
||||
//⑤当前运算符入栈
|
||||
s.push(infix[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
//⑥ 如果不为空,就把所有的元素全部弹出
|
||||
while (s.size()) {
|
||||
postfix += s.top();
|
||||
s.pop();
|
||||
}
|
||||
//输出后缀表达式
|
||||
cout << postfix << endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
#### 后缀表达式求值
|
||||
$(1,a_1,b_1) | (?,a_2,b_2) \Rightarrow (1,a_1,b_1+1)$
|
||||
|
||||
$(0,a_1,b_1) | (?,a_2,b_2) \Rightarrow (?,a_1+a_2,b_1+b_2)$
|
||||
|
||||
$(1,a_1,b_1) \& (?,a_2,b_2) \Rightarrow (?,a_1+a_2,b_1+b_2)$
|
||||
|
||||
$(0,a_1,b_1) \& (?,a_2,b_2) \Rightarrow (0,a_1+1,b_1)$
|
||||
Reference in New Issue
Block a user