一、链表的理论部分
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的下一个是mr->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(); // 5s.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(); // 3q.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】队列
