C++算法之旅、05 基础篇 | 第二章 数据结构

打印 上一主题 下一主题

主题 906|帖子 906|积分 2718

常用代码模板2——数据结构 - AcWing
笔试用数组模拟而不是结构体

使用结构体指针,new Node() 非常慢,创建10万个节点就超时了,做笔试题不会用这种方式(优化是提前初始化好数组,但这样跟数组模拟没区别了,而且代码量很长)

单链表(数组)

使用两个数组,e存储val,ne存储next。空节点next用-1表示
826 ⭐

826. 单链表 - AcWing题库
第1个插入的点下标为0,第5个插入点下标为4,第k个插入点下标为k-1;
  1. #include <algorithm>
  2. #include <cstdio>
  3. #include <iostream>
  4. using namespace std;
  5. const int N = 1e5 + 10;
  6. // head指向头结点,e[i]表示节点值,ne[i]表示节点next
  7. // idx指向可用空间(当前可以用的最新点下标)
  8. // 算法题不用考虑浪费的空间
  9. int head, e[N], ne[N], idx;
  10. void init() {
  11.     head = -1;
  12.     idx = 0;
  13. }
  14. // x 插到头结点后面
  15. void add_to_head(int x) {
  16.     e[idx] = x;
  17.     ne[idx] = head;
  18.     head = idx++;
  19. }
  20. // x 插到下标k的点后面
  21. void add(int k, int x) {
  22.     e[idx] = x;
  23.     ne[idx] = ne[k];
  24.     ne[k] = idx++;
  25. }
  26. // 将下标 k 后面点删掉
  27. void remove(int k) {
  28.     // if (ne[k] == -1) return; 题目保证合法不考虑边界情况
  29.     ne[k] = ne[ne[k]];
  30. }
  31. int main() {
  32.     int m;
  33.     cin >> m;
  34.     init();
  35.     while (m--) {
  36.         char c;
  37.         int k, x;
  38.         cin >> c;
  39.         if (c == 'H') {
  40.             cin >> x;
  41.             add_to_head(x);
  42.         } else if (c == 'D') {
  43.             cin >> k;
  44.             if (!k)
  45.                 head = ne[head];  // 特判删除头结点(链表第一个有效元素)
  46.             else
  47.                 remove(k - 1);
  48.         } else if (c == 'I') {
  49.             cin >> k >> x;
  50.             add(k - 1, x);
  51.         }
  52.     }
  53.     for (int i = head; i != -1; i = ne[i]) {
  54.         cout << e[i] << " ";
  55.     }
  56.     return 0;
  57. }
复制代码
837 ⭐⭐

837. 连通块中点的数量 - AcWing题库
保证根节点的nums有意义
  1. #include <algorithm>
  2. #include <cstdio>
  3. #include <iostream>
  4. using namespace std;
  5. const int N = 1e5 + 10;
  6. int e[N], l[N], r[N], idx;
  7. void init() {
  8.     r[0] = 1;
  9.     l[1] = 0;
  10.     idx = 2;
  11. }
  12. // 在下标k点的右边插入x(可以转化成左边)
  13. void add(int k, int x) {
  14.     e[idx] = x;
  15.     r[idx] = r[k];
  16.     l[idx] = k;
  17.     r[k] = idx;
  18.     l[r[idx]] = idx;
  19.     idx++;
  20. }
  21. // 删除下标k的点
  22. void remove(int k) {
  23.     l[r[k]] = l[k];
  24.     r[l[k]] = r[k];
  25. }
  26. int main() {
  27.     int m;
  28.     cin >> m;
  29.     init();
  30.     while (m--) {
  31.         string s;
  32.         int k, x;
  33.         cin >> s;
  34.         if (s == "L") {
  35.             cin >> x;
  36.             add(0, x);
  37.         } else if (s == "R") {
  38.             cin >> x;
  39.             add(l[1], x);
  40.         } else if (s == "D") {
  41.             cin >> k;
  42.             remove(k - 1 + 2);
  43.         } else if (s == "IL") {
  44.             cin >> k >> x;
  45.             add(l[k - 1 + 2], x);
  46.         } else if (s == "IR") {
  47.             cin >> k >> x;
  48.             add(k - 1 + 2, x);
  49.         }
  50.     }
  51.     for (int i = r[0]; i != 1; i = r[i]) {
  52.         cout << e[i] << " ";
  53.     }
  54.     return 0;
  55. }
复制代码
哈希表

情景

  • 把 1e5 个值域 -1e9~1e9 的数(值域大,个数少)映射到 1e5 的范围的哈希函数
  • \(h(x) \in (0,1e5) = x\ mod\ 1e5\)  模数一般要取质数,离2的整数幂尽可能远
  • 发生冲突:两个不同值域的数映射成了同一个数
存储结构-解决冲突的方式 ⭐
算法题99%一般只有添加、查找,若要实现删除不会真删掉,开一个bool数组标记删除

  • 开放寻址法(常用)

    • 开一个一维数组,范围是题目数据范围的2~3倍,即 2e5 ~ 3e5 区间的质数,该数组存储实际的 x 值
    • 计算哈希值,如果哈希值已被占用,则移动到下一个位置,从前往后找
    • h(11) = 3 在 数组[3] 存入 11,h(4) = 3 在数组[4] 存入 4
    • 与拉链法不同,查询函数返回 x 所在的位置,如果 x 不存在返回应该存储的位置
    • 需要约定一个标志,不在 x 的值域范围内,表示当前位置为空,如 0x3f3f3f3f

  • 拉链法

    • 开一个一维数组 \([0,大于1e5的最小质数]\) 存储所有哈希值对应的链表
    • h(11) = 3 在 数组[3] 开一条链,插入 11
    • 若 h(4) = 3 在 数组[3] 已开的链插入 4
    • 期望情况下,每条链长度 1,时间复杂度 \(O(1)\)


字符串哈希-字符串前缀哈希法(特殊)
应用:可以快速判断一个字符串某两段是否相同。KMP望而却步(KMP擅长循环节),极困难题可用这种方法

  • 先预处理所有前缀的哈希,h[0] = 0, h[1] = "A" 的hash,h[2] = "AB" 的hash...

    • 把字符串看成 P 进制的数,然后模 Q

  • **某个字符不能映射成 0,否则 AA 跟 A 两个哈希都是 0 **
  • 该哈希法假定人品足够好,不考虑冲突
  • 经验值:当 p = 131 或 13331,Q 取 \(2^{64}\) (用 unsinged long long可以不取模,溢出相当于取模)
⭐ 我们可以利用前缀哈希算出任意子段的哈希值

  • 预处理前缀 h(i) = h(i-1) * p + str


与整数离散化的区别
整数离散化需要保序,而且不会发生冲突,每一个元素都有唯一确定的位置(1 ~ n)
840 ⭐拉链法

840. 模拟散列表 - AcWing题库
h 数组维护 N 条链,空节点表示-1,e 数组与 ne 数组维护链上的每一个节点,idx为每个节点分配唯一标识符;插入操作从链表头结点插入
  1. #include <algorithm>
  2. #include <cstdio>
  3. #include <iostream>
  4. using namespace std;
  5. const int N = 1e5 + 10;
  6. int stk[N], tt;
  7. int main() {
  8.     int m;
  9.     cin >> m;
  10.     while (m--) {
  11.         string s;
  12.         int x;
  13.         cin >> s;
  14.         if (s == "push") {
  15.             cin >> x;
  16.             stk[++tt] = x;
  17.         } else if (s == "pop") {
  18.             tt--;
  19.         } else if (s == "empty") {
  20.             if (tt > 0)
  21.                 puts("NO");
  22.             else
  23.                 puts("YES");
  24.         } else if (s == "query") {
  25.             cout << stk[tt] << endl;
  26.         }
  27.     }
  28.     return 0;
  29. }
复制代码
840 ⭐开放寻址法
  1. #include <algorithm>
  2. #include <cstdio>
  3. #include <iostream>
  4. #include <unordered_map>
  5. using namespace std;
  6. const int N = 1e5 + 10;
  7. int op[N], num[N], opt = -1, numt = -1;
  8. unordered_map<char, int> priority;
  9. // 计算后缀表达式
  10. void eval() {
  11.     auto b = num[numt--];
  12.     auto a = num[numt--];
  13.     auto c = op[opt--];
  14.     int res;
  15.     if (c == '-')
  16.         res = a - b;
  17.     else if (c == '+')
  18.         res = a + b;
  19.     else if (c == '*')
  20.         res = a * b;
  21.     else if (c == '/')
  22.         res = a / b;
  23.     num[++numt] = res;
  24. }
  25. int main() {
  26.     priority = {{'-', 1}, {'+', 1}, {'*', 2}, {'/', 2}};
  27.     string str;
  28.     cin >> str;
  29.     for (int i = 0; i < str.size(); i++) {
  30.         if (isdigit(str[i])) {
  31.             int j = i, res = 0;
  32.             while (isdigit(str[j])) res = res * 10 + str[j++] - '0';
  33.             num[++numt] = res;
  34.             i = j - 1;
  35.         } else if (str[i] == '(')
  36.             op[++opt] = str[i];
  37.         else if (str[i] == ')') {
  38.             while (op[opt] != '(') eval();
  39.             opt--;
  40.         } else {
  41.             while (opt >= 0 && priority[str[i]] <= priority[op[opt]]) eval();
  42.             op[++opt] = str[i];
  43.         }
  44.     }
  45.     while (opt >= 0) eval();
  46.     cout << num[numt];
  47.     return 0;
  48. }
复制代码
841 ⭐⭐ 字符串哈希

841. 字符串哈希 - AcWing题库
如下的朴素算法会超时
  1. #include <algorithm>
  2. #include <cstdio>
  3. #include <iostream>
  4. #include <stack>
  5. #include <unordered_map>
  6. using namespace std;
  7. stack<int> num;
  8. stack<char> op;
  9. void eval() {
  10.     auto b = num.top();
  11.     num.pop();
  12.     auto a = num.top();
  13.     num.pop();
  14.     auto c = op.top();
  15.     op.pop();
  16.     int x;
  17.     if (c == '+')
  18.         x = a + b;
  19.     else if (c == '-')
  20.         x = a - b;
  21.     else if (c == '*')
  22.         x = a * b;
  23.     else
  24.         x = a / b;
  25.     num.push(x);
  26. }
  27. int main() {
  28.     unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};
  29.     string str;
  30.     cin >> str;
  31.     for (int i = 0; i < str.size(); i++) {
  32.         auto c = str[i];
  33.         if (isdigit(c)) {
  34.             // 第一类双指针
  35.             int x = 0, j = i;
  36.             while (j < str.size() && isdigit(str[j]))
  37.                 x = x * 10 + str[j++] - '0';
  38.             i = j - 1;  // 考虑i++
  39.             num.push(x);
  40.         } else if (c == '(')
  41.             op.push(c);
  42.         else if (c == ')') {
  43.             while (op.top() != '(') eval();
  44.             op.pop();
  45.         } else {
  46.             while (op.size() && pr[op.top()] >= pr[c]) eval();
  47.             op.push(c);
  48.         }
  49.     }
  50.     while (op.size()) eval();
  51.     cout << num.top() << endl;
  52.     return 0;
  53. }
复制代码
p 数组提前存储预处理的 p 次方的值,减少计算量
  1. #include <algorithm>
  2. #include <cstdio>
  3. #include <iostream>
  4. using namespace std;
  5. const int N = 1e5 + 10;
  6. int que[N], st = -1, ed = -1;
  7. int main() {
  8.     int m;
  9.     cin >> m;
  10.     while (m--) {
  11.         string str;
  12.         int x;
  13.         cin >> str;
  14.         if (str == "push") {
  15.             cin >> x;
  16.             que[++ed] = x;
  17.         } else if (str == "pop") {
  18.             st++;
  19.         } else if (str == "empty") {
  20.             if (st == ed)
  21.                 puts("YES");
  22.             else
  23.                 puts("NO");
  24.         } else if (str == "query") {
  25.             cout << que[st + 1] << endl;
  26.         }
  27.     }
  28.     return 0;
  29. }
复制代码
STL

size()、empty() 所有容器都有,时间复杂度 \(O(1)\)
clear() 并不是所有容器都有,队列、优先队列、栈;范围遍历可以遍历所有容器
系统为某一个程序分配空间时,所需的时间与空间大小无关,与申请次数有关

vector

变长数组,倍增,支持比较运算(字典序(4个3小于3个4))。有erase但用得不多
插入操作\(O(1)\):插入1e6次,申请空间的次数 \(log_21e6\) ,拷贝的次数均摊是 1 (数组大小从1到1e6,总共拷贝次数是1e6(1+2+4+8+...))
[code]#include #include using namespace std;int main() {    // ^ 定义一个长度为10的vector,每个数都是3    vector a(10, 3);    vector b[10];    a.push_back(1);    cout

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

羊蹓狼

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表