一、链表的理论部分

https://zhuanlan.zhihu.com/p/29627391

单链表

image.png

单链表删除结点

image.png

单链表添加结点

image.png

双向链表

image.png

双向链表删除结点

image.png

双向链表添加结点

image.png

循环链表

https://zhuanlan.zhihu.com/p/360567636
一个结点
image.png

循环链表头插法

image.png

循环链表在指定位置插入结点

image.png

循环链表删除结点

image.png

二、链表的技术部分

单链表 邻接表

https://www.acwing.com/problem/content/828/
image.png
image.png

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int m;
  5. int hh, e[N], ne[N], idx;
  6. void add_to_head(int x){
  7. e[idx] = x, ne[idx] = hh, hh = idx++;
  8. }
  9. void add_to_node(int k, int x){
  10. e[idx] = x, ne[idx] = ne[k], ne[k] = idx++;
  11. }
  12. void remove(int k){
  13. ne[k] = ne[ne[k]];
  14. }
  15. int main(){
  16. hh = -1;
  17. cin >> m;
  18. while (m--){
  19. string op;
  20. cin >> op;
  21. if (op[0] == 'H'){
  22. int x;
  23. cin >> x;
  24. add_to_head(x);
  25. }
  26. else if (op[0] == 'D'){
  27. int k;
  28. cin >> k;
  29. if (k == 0) hh = ne[hh]; //如何删除的是头结点
  30. else remove(k - 1);
  31. }
  32. else{
  33. int k, x;
  34. cin >> k >> x;
  35. add_to_node(k - 1, x);
  36. }
  37. }
  38. for (int i = hh; i != -1; i = ne[i]) printf("%d ", e[i]);
  39. puts("");
  40. return 0;
  41. }

image.png

头插法演示
8202b6c10561485792a9dd9a9f975e2a.gif

单链表 结构体 TLE

  1. // new Node(); 非常慢,链表大小1e4 1e5就会超时
  2. // 这部分代码,希望大家能看懂C语言的指针,不推荐这种写法
  3. #include <bits/stdc++.h>
  4. #include <stddef.h>
  5. // How to fix C error ‘NULL undeclared’
  6. // https://techoverflow.net/2019/06/20/how-to-fix-c-error-null-undeclared/
  7. using namespace std;
  8. const int N = 1e5 + 10;
  9. struct Node{
  10. int data, id;
  11. Node *next;
  12. };
  13. int m, idx;
  14. Node *head;
  15. void add_to_head(int x){
  16. Node *t = new Node();
  17. t->data = x;
  18. t->next = head;
  19. t->id = idx++;
  20. head = t;
  21. }
  22. void add_to_node(int k, int x){
  23. Node *t = head;
  24. while (t->next != NULL){
  25. if (t->id == k) break;
  26. t = t->next;
  27. }
  28. Node *nw = new Node();
  29. nw->data = x;
  30. nw->id = idx++;
  31. nw->next = t->next;
  32. t->next = nw;
  33. }
  34. void remove(int k){
  35. Node *t = head;
  36. while (t->next != NULL){
  37. if (t->id == k) break;
  38. t = t->next;
  39. }
  40. Node *p = t->next;
  41. t->next = p->next;
  42. delete p;
  43. }
  44. int main(){
  45. head = new Node;
  46. head->next = NULL;
  47. cin >> m;
  48. while (m--){
  49. string op;
  50. cin >> op;
  51. if (op[0] == 'H'){
  52. int x;
  53. cin >> x;
  54. add_to_head(x);
  55. }
  56. else if (op[0] == 'D'){
  57. int k;
  58. cin >> k;
  59. if (k == 0) head = head->next; //如何删除的是头结点
  60. else remove(k - 1);
  61. }
  62. else{
  63. int k, x;
  64. cin >> k >> x;
  65. add_to_node(k - 1, x);
  66. }
  67. }
  68. while (head->next != NULL){
  69. printf("%d ", head->data);
  70. head = head->next;
  71. }
  72. puts("");
  73. return 0;
  74. }

双链表 邻接表

https://www.acwing.com/problem/content/829/
image.png
image.png

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int m;
  5. int e[N], L[N], R[N], idx;
  6. // k -> idx -> R[k]
  7. void insert(int k, int x){
  8. e[idx] = x;
  9. R[idx] = R[k]; // 先把idx挂好
  10. L[idx] = k;
  11. L[R[k]] = idx; // 然后弄原来的结点
  12. R[k] = idx++;
  13. }
  14. void remove(int k){
  15. L[R[k]] = L[k];
  16. R[L[k]] = R[k];
  17. }
  18. int main(){
  19. // r[0] 指向第一个点,0 1两个点作为两端,r[0] = 1, l[1] = 0 模拟成环
  20. // 用0, 1表示左端点、右端点,第一个数从idx = 2开始
  21. R[0] = 1, L[1] = 0, idx = 2;
  22. cin >> m;
  23. while (m--){
  24. string op;
  25. cin >> op;
  26. if (op == "L"){
  27. int x;
  28. cin >> x;
  29. insert(0, x);
  30. }
  31. if (op == "R"){
  32. int x;
  33. cin >> x;
  34. insert(L[1], x);
  35. }
  36. if (op == "D"){
  37. int k;
  38. cin >> k;
  39. remove(k+1);
  40. }
  41. if (op == "IL"){
  42. int k, x;
  43. cin >> k >> x;
  44. insert(L[k+1], x); // 2-index
  45. }
  46. if (op == "IR"){
  47. int k, x;
  48. cin >> k >> x;
  49. insert(k+1, x);
  50. }
  51. }
  52. for (int i = R[0]; i != 1; i = R[i]) printf("%d ", e[i]);
  53. puts("");
  54. return 0;
  55. }

循环链表 结构体

  1. // 约瑟夫环问题
  2. // 1~n个人,从第1个人开始报数,数到m的人出列
  3. // 如此循环,直到最后一个人出列为止
  4. // 输出出列的顺序
  5. #include <bits/stdc++.h>
  6. using namespace std;
  7. struct node{
  8. int id;
  9. node *next;
  10. };
  11. node *head, *r;
  12. int n, m;
  13. int main(){
  14. cin >> n >> m;
  15. head = new node();
  16. head->id = 1;
  17. head->next = NULL;
  18. r = head;
  19. for (int i = 2; i <= n; i++){
  20. node *p = new node();
  21. p->id = i;
  22. p->next = NULL;
  23. r->next = p;
  24. r = p;
  25. }
  26. r->next = head;
  27. r = head;
  28. for (int i = 1; i <= n; i++){
  29. for (int j = 1; j <= m - 2; j++) r = r->next; //r走到了m-1的位置上
  30. cout << r->next->id << ' '; //m-1的下一个是m
  31. r->next = r->next->next; //删除结点
  32. r = r->next; //挪到下一个从1开始数
  33. }
  34. puts("");
  35. return 0;
  36. }

三、栈

学习顺序:

  1. 学会使用STL的基本用法
  2. 学会使用STL解决问题
  3. 学会用一维数组模拟栈,解决问题

栈是只能在某一端插入和删除的特殊线性表

stack stk

  1. stack<int> s;
  2. s.push(3);
  3. s.push(2);
  4. s.push(5);
  5. cout << s.top(); // 5
  6. s.pop();
  7. cout << s.top(); // 2

一维数组模拟栈

https://www.acwing.com/problem/content/830/
image.png
image.png

  1. // tt指针初始化为0,第一个数存在1上,当tt==0的时候,代表空栈
  2. #include <iostream>
  3. using namespace std;
  4. const int N = 1e5 + 10;
  5. int stk[N];
  6. int tt = 0;
  7. int main()
  8. {
  9. int M;
  10. cin >> M;
  11. string op;
  12. int x;
  13. while (M--)
  14. {
  15. cin >> op;
  16. if (op == "push")
  17. {
  18. cin >> x;
  19. stk[++tt] = x;
  20. }
  21. if (op == "pop")
  22. {
  23. tt--;
  24. }
  25. if (op == "empty")
  26. {
  27. if (tt == 0) cout << "YES" << endl;
  28. else cout << "NO" << endl;
  29. }
  30. if (op == "query")
  31. {
  32. cout << stk[tt] << endl;
  33. }
  34. }
  35. return 0;
  36. }

例题,【例1-2】后缀表达式的值

  1. //用一个栈维护数字,用一个栈维护运算符
  2. //这道题目没有括号,另外注意运算数的数值范围比较大
  3. //不开LL见祖宗

例题,表达式括号匹配(stack)

  1. //用一个栈维护左括号和右括号的关系
  2. //右括号,没有左括号配对就是非法
  3. //左括号,最后没有右括号配对就是非法

例题,车厢调度(train)

  1. //第一种方法,把一系列车厢,当成一个数组,1, 2, 3, 4, ..., n
  2. //x读进来了,那么,我们就要认为:x之后的数字,都不应该出现在栈中;x之前的数字没入栈的都应该入栈
  3. //标记在不在栈里的方法,1代表在栈里面,2代表已经出栈
  4. //第二种方法,用一个stack硬来模拟这个进出栈的过程(冷不丁让我重写,我想我是模拟不出来的)
  5. //如果stack空的,把前面没入栈的入栈
  6. //如果stack非空,栈顶元素如果比我大,那是非法的;
  7. //整个过程中,用一个变量tp,初始值设为1,来维护栈顶元素的值

例题,中缀表达式值(expr)

  1. //在这道题目上,我一共花费了4个小时
  2. //所以,我建议你自己做,研究至少花费2个小时。
  3. //可以很好的提升代码能力,加深对栈的理解,对表达式的理解
  4. //第一种方法,直接利用中缀表达式,进行求解,这个方法和前面的题目类似
  5. //第二种方法,中缀读进来,然后转成后缀
  6. //后缀的建立,用一个queue<node>进行维护,node可以是运算数,也可以是运算符
  7. //node需要使用struct定义一下
  8. //关于非法情况的判断
  9. //首先这道题目的数据,没有那么严格,不需要判断除数不能为0的情况
  10. //其他非法,左右括号不匹配问题;只有一位,这一位还是运算符; 上来第一位是+*/运算符,负号是可以的
  11. //中缀表达式,两个运算符连在一块了;右括号没有左括号匹配,左括号没有右括号匹配
  12. //网上能搜到题解,对左括号个数和右括号个数相同作为配对的原则,这个其实不严谨
  13. //运算符优先级,可以写一个函数实现,也可以写一个map预设好

例题,P1981 [NOIP2013 普及组] 表达式求值

  1. #include <iostream>
  2. #include <cstring>
  3. #include <algorithm>
  4. #include <stack>
  5. #include <unordered_map>
  6. using namespace std;
  7. const int MOD = 10000;
  8. stack<int> num;
  9. stack<char> op;
  10. void eval()
  11. {
  12. int b = num.top(); num.pop();
  13. int a = num.top(); num.pop();
  14. char c = op.top(); op.pop();
  15. if (c == '+') num.push((a + b) % MOD);
  16. else num.push(a * b % MOD);
  17. }
  18. int main()
  19. {
  20. string s;
  21. cin >> s;
  22. unordered_map<char, int> pr{{'+', 1}, {'*', 2}};
  23. for (int i = 0; i < s.size(); i ++ )
  24. {
  25. if (isdigit(s[i]))
  26. {
  27. int j = i, x = 0;
  28. while (j < s.size() && isdigit(s[j])) x = x * 10 + s[j ++ ] - '0';
  29. num.push(x % MOD);
  30. i = j - 1;
  31. }
  32. else
  33. {
  34. while (op.size() && pr[op.top()] >= pr[s[i]]) eval();
  35. op.push(s[i]);
  36. }
  37. }
  38. while (op.size()) eval();
  39. cout << num.top() << endl;
  40. return 0;
  41. }
  42. // 以后的习题中,会涉及一个表示式树的题目

四、队列

学习顺序:

  1. 学会使用STL的基本用法
  2. 学会使用STL解决问题
  3. 学会用一维数组模拟队列,解决问题

队列是限定在一端进行插入,另一端进行删除的特殊线性表

queue q

  1. queue<int> q;
  2. q.push(3);
  3. q.push(2);
  4. q.push(5);
  5. cout << q.front(); // 3
  6. q.pop();
  7. cout << q.front(); // 2

例题,2037:【例5.4】约瑟夫问题

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. int main()
  4. {
  5. int n, m;
  6. cin >> n >> m;
  7. queue<int> q;
  8. for (int i = 1; i <= n; i++) q.push(i);
  9. int k = 0;
  10. while (!q.empty()){
  11. k++;
  12. int t = q.front(); q.pop();
  13. if (k == m){
  14. k = 0;
  15. printf("%d ", t);
  16. continue;
  17. }
  18. q.push(t);
  19. }
  20. puts("");
  21. return 0;
  22. }

一维数组模拟队列

https://www.acwing.com/problem/content/831/
image.png
image.png

  1. // 初始化,int hh = 0, tt = -1; //Think about it: 莫队不也是这样吗?
  2. #include <iostream>
  3. using namespace std;
  4. const int N = 100010;
  5. int q[N];
  6. int hh = 0, tt = -1;
  7. int main()
  8. {
  9. int M;
  10. cin >> M;
  11. string op;
  12. int x;
  13. while (M--)
  14. {
  15. cin >> op;
  16. if (op == "push")
  17. {
  18. cin >> x;
  19. q[++tt] = x;
  20. }
  21. if (op == "pop")
  22. {
  23. hh++;
  24. }
  25. if (op == "query")
  26. {
  27. cout << q[hh] << endl;
  28. }
  29. if (op == "empty")
  30. {
  31. if (hh <= tt) cout << "NO" << endl;
  32. else cout << "YES" << endl;
  33. }
  34. }
  35. return 0;
  36. }

例题,【例2-3】围圈报数

  1. //用queue模拟出圈的过程

例题,猴子选大王

  1. //一般的猴子选大王,约瑟夫环问题,都是玩一个游戏,数一个固定的数m,数到就出圈
  2. //这道题目是,从i个猴子开始数,就使用第i个规则数
  3. //所以,这点在模拟的时候会不同。其他和普通的约瑟夫一样

例题,【例2-1】周末舞会

  1. //模拟两个队伍循环的过程,用两个指针模拟

例题,产生数(Produce)

  1. //这道题目的规则是一位数字,转换成一位数字
  2. //不是多位的转换,没那么复杂,可以直接暴力的干
  3. //遇到坑,还是要多变换姿势求解
  4. //最开始,我自己分析题意是,转换规则,可能是 ab -> cdd,这种变态的模式
  5. //然后我就写了1个小时而已,写不出来了。主要是控制不好字符串替换中间一段的操作,存在开头和去尾的特殊位置
  6. //感觉,这样出测试用例,肯定是一道好模拟题

大纲要求

•【3】链表:单链表、双向链表、循环链表

•【3】栈

•【3】队列