一、链表的理论部分
https://zhuanlan.zhihu.com/p/29627391
单链表
单链表删除结点
单链表添加结点
双向链表
双向链表删除结点
双向链表添加结点
循环链表
https://zhuanlan.zhihu.com/p/360567636
一个结点
循环链表头插法
循环链表在指定位置插入结点
循环链表删除结点
二、链表的技术部分
单链表 邻接表
https://www.acwing.com/problem/content/828/
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int m;
int hh, e[N], ne[N], idx;
void add_to_head(int x){
e[idx] = x, ne[idx] = hh, hh = idx++;
}
void add_to_node(int k, int x){
e[idx] = x, ne[idx] = ne[k], ne[k] = idx++;
}
void remove(int k){
ne[k] = ne[ne[k]];
}
int main(){
hh = -1;
cin >> m;
while (m--){
string op;
cin >> op;
if (op[0] == 'H'){
int x;
cin >> x;
add_to_head(x);
}
else if (op[0] == 'D'){
int k;
cin >> k;
if (k == 0) hh = ne[hh]; //如何删除的是头结点
else remove(k - 1);
}
else{
int k, x;
cin >> k >> x;
add_to_node(k - 1, x);
}
}
for (int i = hh; i != -1; i = ne[i]) printf("%d ", e[i]);
puts("");
return 0;
}
头插法演示
单链表 结构体 TLE
// new Node(); 非常慢,链表大小1e4 1e5就会超时
// 这部分代码,希望大家能看懂C语言的指针,不推荐这种写法
#include <bits/stdc++.h>
#include <stddef.h>
// How to fix C error ‘NULL undeclared’
// https://techoverflow.net/2019/06/20/how-to-fix-c-error-null-undeclared/
using namespace std;
const int N = 1e5 + 10;
struct Node{
int data, id;
Node *next;
};
int m, idx;
Node *head;
void add_to_head(int x){
Node *t = new Node();
t->data = x;
t->next = head;
t->id = idx++;
head = t;
}
void add_to_node(int k, int x){
Node *t = head;
while (t->next != NULL){
if (t->id == k) break;
t = t->next;
}
Node *nw = new Node();
nw->data = x;
nw->id = idx++;
nw->next = t->next;
t->next = nw;
}
void remove(int k){
Node *t = head;
while (t->next != NULL){
if (t->id == k) break;
t = t->next;
}
Node *p = t->next;
t->next = p->next;
delete p;
}
int main(){
head = new Node;
head->next = NULL;
cin >> m;
while (m--){
string op;
cin >> op;
if (op[0] == 'H'){
int x;
cin >> x;
add_to_head(x);
}
else if (op[0] == 'D'){
int k;
cin >> k;
if (k == 0) head = head->next; //如何删除的是头结点
else remove(k - 1);
}
else{
int k, x;
cin >> k >> x;
add_to_node(k - 1, x);
}
}
while (head->next != NULL){
printf("%d ", head->data);
head = head->next;
}
puts("");
return 0;
}
双链表 邻接表
https://www.acwing.com/problem/content/829/
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int m;
int e[N], L[N], R[N], idx;
// k -> idx -> R[k]
void insert(int k, int x){
e[idx] = x;
R[idx] = R[k]; // 先把idx挂好
L[idx] = k;
L[R[k]] = idx; // 然后弄原来的结点
R[k] = idx++;
}
void remove(int k){
L[R[k]] = L[k];
R[L[k]] = R[k];
}
int main(){
// r[0] 指向第一个点,0 1两个点作为两端,r[0] = 1, l[1] = 0 模拟成环
// 用0, 1表示左端点、右端点,第一个数从idx = 2开始
R[0] = 1, L[1] = 0, idx = 2;
cin >> m;
while (m--){
string op;
cin >> op;
if (op == "L"){
int x;
cin >> x;
insert(0, x);
}
if (op == "R"){
int x;
cin >> x;
insert(L[1], x);
}
if (op == "D"){
int k;
cin >> k;
remove(k+1);
}
if (op == "IL"){
int k, x;
cin >> k >> x;
insert(L[k+1], x); // 2-index
}
if (op == "IR"){
int k, x;
cin >> k >> x;
insert(k+1, x);
}
}
for (int i = R[0]; i != 1; i = R[i]) printf("%d ", e[i]);
puts("");
return 0;
}
循环链表 结构体
// 约瑟夫环问题
// 1~n个人,从第1个人开始报数,数到m的人出列
// 如此循环,直到最后一个人出列为止
// 输出出列的顺序
#include <bits/stdc++.h>
using namespace std;
struct node{
int id;
node *next;
};
node *head, *r;
int n, m;
int main(){
cin >> n >> m;
head = new node();
head->id = 1;
head->next = NULL;
r = head;
for (int i = 2; i <= n; i++){
node *p = new node();
p->id = i;
p->next = NULL;
r->next = p;
r = p;
}
r->next = head;
r = head;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m - 2; j++) r = r->next; //r走到了m-1的位置上
cout << r->next->id << ' '; //m-1的下一个是m
r->next = r->next->next; //删除结点
r = r->next; //挪到下一个从1开始数
}
puts("");
return 0;
}
三、栈
学习顺序:
- 学会使用STL的基本用法
- 学会使用STL解决问题
- 学会用一维数组模拟栈,解决问题
stack stk
stack<int> s;
s.push(3);
s.push(2);
s.push(5);
cout << s.top(); // 5
s.pop();
cout << s.top(); // 2
一维数组模拟栈
https://www.acwing.com/problem/content/830/
// tt指针初始化为0,第一个数存在1上,当tt==0的时候,代表空栈
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int stk[N];
int tt = 0;
int main()
{
int M;
cin >> M;
string op;
int x;
while (M--)
{
cin >> op;
if (op == "push")
{
cin >> x;
stk[++tt] = x;
}
if (op == "pop")
{
tt--;
}
if (op == "empty")
{
if (tt == 0) cout << "YES" << endl;
else cout << "NO" << endl;
}
if (op == "query")
{
cout << stk[tt] << endl;
}
}
return 0;
}
例题,【例1-2】后缀表达式的值
//用一个栈维护数字,用一个栈维护运算符
//这道题目没有括号,另外注意运算数的数值范围比较大
//不开LL见祖宗
例题,表达式括号匹配(stack)
//用一个栈维护左括号和右括号的关系
//右括号,没有左括号配对就是非法
//左括号,最后没有右括号配对就是非法
例题,车厢调度(train)
//第一种方法,把一系列车厢,当成一个数组,1, 2, 3, 4, ..., n
//x读进来了,那么,我们就要认为:x之后的数字,都不应该出现在栈中;x之前的数字没入栈的都应该入栈
//标记在不在栈里的方法,1代表在栈里面,2代表已经出栈
//第二种方法,用一个stack硬来模拟这个进出栈的过程(冷不丁让我重写,我想我是模拟不出来的)
//如果stack空的,把前面没入栈的入栈
//如果stack非空,栈顶元素如果比我大,那是非法的;
//整个过程中,用一个变量tp,初始值设为1,来维护栈顶元素的值
例题,中缀表达式值(expr)
//在这道题目上,我一共花费了4个小时
//所以,我建议你自己做,研究至少花费2个小时。
//可以很好的提升代码能力,加深对栈的理解,对表达式的理解
//第一种方法,直接利用中缀表达式,进行求解,这个方法和前面的题目类似
//第二种方法,中缀读进来,然后转成后缀
//后缀的建立,用一个queue<node>进行维护,node可以是运算数,也可以是运算符
//node需要使用struct定义一下
//关于非法情况的判断
//首先这道题目的数据,没有那么严格,不需要判断除数不能为0的情况
//其他非法,左右括号不匹配问题;只有一位,这一位还是运算符; 上来第一位是+*/运算符,负号是可以的
//中缀表达式,两个运算符连在一块了;右括号没有左括号匹配,左括号没有右括号匹配
//网上能搜到题解,对左括号个数和右括号个数相同作为配对的原则,这个其实不严谨
//运算符优先级,可以写一个函数实现,也可以写一个map预设好
例题,P1981 [NOIP2013 普及组] 表达式求值
#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include <unordered_map>
using namespace std;
const int MOD = 10000;
stack<int> num;
stack<char> op;
void eval()
{
int b = num.top(); num.pop();
int a = num.top(); num.pop();
char c = op.top(); op.pop();
if (c == '+') num.push((a + b) % MOD);
else num.push(a * b % MOD);
}
int main()
{
string s;
cin >> s;
unordered_map<char, int> pr{{'+', 1}, {'*', 2}};
for (int i = 0; i < s.size(); i ++ )
{
if (isdigit(s[i]))
{
int j = i, x = 0;
while (j < s.size() && isdigit(s[j])) x = x * 10 + s[j ++ ] - '0';
num.push(x % MOD);
i = j - 1;
}
else
{
while (op.size() && pr[op.top()] >= pr[s[i]]) eval();
op.push(s[i]);
}
}
while (op.size()) eval();
cout << num.top() << endl;
return 0;
}
// 以后的习题中,会涉及一个表示式树的题目
四、队列
学习顺序:
- 学会使用STL的基本用法
- 学会使用STL解决问题
- 学会用一维数组模拟队列,解决问题
queue q
queue<int> q;
q.push(3);
q.push(2);
q.push(5);
cout << q.front(); // 3
q.pop();
cout << q.front(); // 2
例题,2037:【例5.4】约瑟夫问题
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
queue<int> q;
for (int i = 1; i <= n; i++) q.push(i);
int k = 0;
while (!q.empty()){
k++;
int t = q.front(); q.pop();
if (k == m){
k = 0;
printf("%d ", t);
continue;
}
q.push(t);
}
puts("");
return 0;
}
一维数组模拟队列
https://www.acwing.com/problem/content/831/
// 初始化,int hh = 0, tt = -1; //Think about it: 莫队不也是这样吗?
#include <iostream>
using namespace std;
const int N = 100010;
int q[N];
int hh = 0, tt = -1;
int main()
{
int M;
cin >> M;
string op;
int x;
while (M--)
{
cin >> op;
if (op == "push")
{
cin >> x;
q[++tt] = x;
}
if (op == "pop")
{
hh++;
}
if (op == "query")
{
cout << q[hh] << endl;
}
if (op == "empty")
{
if (hh <= tt) cout << "NO" << endl;
else cout << "YES" << endl;
}
}
return 0;
}
例题,【例2-3】围圈报数
//用queue模拟出圈的过程
例题,猴子选大王
//一般的猴子选大王,约瑟夫环问题,都是玩一个游戏,数一个固定的数m,数到就出圈
//这道题目是,从i个猴子开始数,就使用第i个规则数
//所以,这点在模拟的时候会不同。其他和普通的约瑟夫一样
例题,【例2-1】周末舞会
//模拟两个队伍循环的过程,用两个指针模拟
例题,产生数(Produce)
//这道题目的规则是一位数字,转换成一位数字
//不是多位的转换,没那么复杂,可以直接暴力的干
//遇到坑,还是要多变换姿势求解
//最开始,我自己分析题意是,转换规则,可能是 ab -> cdd,这种变态的模式
//然后我就写了1个小时而已,写不出来了。主要是控制不好字符串替换中间一段的操作,存在开头和去尾的特殊位置
//感觉,这样出测试用例,肯定是一道好模拟题
大纲要求
•【3】链表:单链表、双向链表、循环链表
•【3】栈
•【3】队列