树
哈夫曼树
基本概念
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
带权路径长度:
哈夫曼编码
注意考虑树只有一个点的情况 ```cpp
include
include
include
include
include
include
using namespace std;
const int N = 1010;
char s[N];
unordered_map
int main() {
#ifdef SUBMITfreopen("in.txt", "r", stdin);freopen("out.txt", "w", stdout);long _begin_time = clock();#endifwhile (scanf("%s", s), strcmp(s, "END") != 0) {int len = strlen(s);rec.clear(); // 记得清空for (int i = 0; i < len; i++) {rec[s[i]]++;}priority_queue<int, vector<int>, greater<int>> heap; // 优先队列没有clear方法for (auto it = rec.begin(); it != rec.end(); it++) {heap.push(it->second);// cout << (double)it->second / len << ' ';}// cout << endl;int avg = 0;if (heap.size() == 1) { // 注意,只有一个点时,带权路径长度就是那个点的权值avg = heap.top();}while (heap.size() > 1) { // 至少有两个点才能合并double a1 = heap.top(); heap.pop();double a2 = heap.top(); heap.pop();avg += a1 + a2;heap.push(a1 + a2);}// cout << avg << endl;int ori = len * 8;printf("%d %d %.1lf\n", ori, avg, (double)ori / avg);}#ifdef SUBMITlong _end_time = clock();printf("\n\ntime = %ld ms", _end_time - _begin_time);#endifreturn 0;
}
<a name="IVH85"></a>## [Trie字符串统计](https://www.acwing.com/problem/content/description/837/)```cpp#include <iostream>using namespace std;const int N = 2e4 + 10;int son[N][26], cnt[N], idx; // idx=0既是根结点,又代表没有孩子char s[N];void insert(char s[]) {int p = 0;for (int i = 0; s[i]; i++) {int u = s[i] - 'a';if (!son[p][u]) son[p][u] = ++idx;p = son[p][u];}cnt[p]++;}int query(char s[]) {int p = 0;for (int i = 0; s[i]; i++) {int u = s[i] - 'a';if (!son[p][u]) return 0;p = son[p][u];}return cnt[p];}int main() {int n;cin >> n;while (n--) {char op[2];scanf("%s%s", op, s);if (op[0] == 'I') {insert(s);}else {printf("%d\n", query(s));}}return 0;}
图
Prim算法求最小生成树
时间复杂度是 O(n^2+m) n表示点数,m表示边数
#include <iostream>#include <cstring>using namespace std;const int N = 510, INF = 0x3f3f3f3f;int g[N][N];int dist[N];bool st[N];int n, m;int prim() {memset(dist, 0x3f, sizeof dist);int ans = 0;for (int i = 0; i < n; i++) {int t = -1;for (int j = 1; j <= n; j++) {if (!st[j] && (t == -1 || dist[j] < dist[t]))t = j;}if (i && dist[t] == INF) return INF;if (i) ans += dist[t]; // 防止自环,先更新ans,再更新distst[t] = true;for (int j = 1; j <= n; j++) dist[j] = min(dist[j], g[t][j]);}return ans;}int main() {cin >> n >> m;memset(g, 0x3f, sizeof g);while (m--) {int u, v, w;cin >> u >> v >> w;g[u][v] = g[v][u] = min(g[u][v], w);}int ans = prim();if (ans == INF) puts("impossible");else cout << ans;return 0;}
Kruskal算法求最小生成树
时间复杂度是 O(mlogm), n表示点数,m表示边数
#include <iostream>#include <algorithm>using namespace std;const int N = 2e5 + 10;int p[N];struct Edge {int a, b, w;bool operator< (const Edge &W) const {return w < W.w;}}edges[N];int find(int x) {if (x != p[x]) p[x] = find(p[x]);return p[x];}int main() {int n, m;cin >> n >> m;for (int i = 0; i < m; i++) {scanf("%d%d%d", &edges[i].a, &edges[i].b, &edges[i].w);}sort(edges, edges + m); // 将所有边按权值从小到大排序for (int i = 1; i <= n; i++) p[i] = i;int cnt = 0, ans = 0;for (int i = 0; i < m; i++) {int a = edges[i].a, b = edges[i].b, w = edges[i].w;int fa = find(a), fb = find(b);if (fa != fb) { // 两个顶点不在一个连通块当中p[fa] = fb;cnt++;ans += w;}}if (cnt < n - 1) puts("impossible"); // 边的数量等于顶点数 - 1else cout << ans;return 0;}
Dijkstra求最短路-朴素
时间复杂度是 O(n^2+m), n表示点数,m表示边数
#include <iostream>#include <cstring>using namespace std;const int N = 510;int g[N][N], dist[N];bool st[N];int n, m;int dijkstra(int p) {memset(dist, 0x3f, sizeof dist);dist[p] = 0;for (int i = 0; i < n; i++) {int t = -1;for (int j = 1; j <= n; j++) {if (!st[j] && (t == -1 || dist[j] < dist[t]))t = j;}st[t] = true;for (int j = 1; j <= n; j++)dist[j] = min(dist[j], dist[t] + g[t][j]);}if (dist[n] == 0x3f3f3f3f) return -1;return dist[n];}int main() {cin >> n >> m;memset(g, 0x3f, sizeof g);while (m--) {int x, y, z;scanf("%d%d%d", &x, &y, &z);g[x][y] = min(g[x][y], z);}cout << dijkstra(1);return 0;}
Dijkstra求最短路-堆优化
时间复杂度O(mlogn),n是点数,m是边数
#include <iostream>#include <queue>#include <cstring>using namespace std;const int N = 1.5e5 + 10, INF = 0x3f3f3f3f;typedef pair<int, int> PII;int n, m;int h[N], e[N], ne[N], w[N], idx;int dist[N];bool st[N];priority_queue<PII, vector<PII>, greater<PII>> heap;void add(int a, int b, int c) {w[idx] = c;e[idx] = b;ne[idx] = h[a];h[a] = idx++;}int dijkstra() {memset(dist, 0x3f, sizeof dist);dist[1] = 0;heap.push({0, 1});while (!heap.empty()) {auto p = heap.top(); heap.pop();int ver = p.second, d = p.first;if (st[ver]) continue;st[ver] = true;for (int i = h[ver]; i != -1; i = ne[i]) {int j = e[i];if (dist[ver] + w[i] < dist[j]) {dist[j] = dist[ver] + w[i];heap.push({dist[j], j});}}}if (dist[n] > INF / 2) return -1;return dist[n];}int main() {cin >> n >> m;memset(h, -1, sizeof h);while (m--) {int x, y, z;scanf("%d%d%d", &x, &y, &z);add(x, y, z);}cout << dijkstra();return 0;}
Floyd求最短路
时间复杂度O(n^3)
#include <iostream>#include <cstring>using namespace std;const int N = 210, INF = 0x3f3f3f3f;int g[N][N];int main() {int n, m, k;cin >> n >> m >> k;for (int i = 1; i <= n; i++) {for (int j = 1; j <= n; j++) {if (i == j) g[i][j] = 0;else g[i][j] = INF;}}for (int i = 0; i < m; i++) {int x, y, z;scanf("%d%d%d", &x, &y, &z);g[x][y] = min(g[x][y], z);}for (int k = 1; k <= n; k++) {for (int i = 1; i <= n; i++) {for (int j = 1; j <= n; j++) {g[i][j] = min(g[i][j], g[i][k] + g[k][j]);}}}while (k--) {int i, j;scanf("%d%d", &i, &j);if (g[i][j] >= INF / 2) printf("impossible\n"); // 可能有负边else printf("%d\n", g[i][j]);}}
有向图的拓扑序列
#include <iostream>#include <cstring>using namespace std;const int N = 1e5 + 10;int h[N], e[N], ne[N], idx;int d[N];int q[N], hh, tt = -1;int n, m;void add(int a, int b) {d[b]++;e[idx] = b;ne[idx] = h[a];h[a] = idx++;}bool toposort() {for (int i = 1; i <= n; i++) {if (!d[i]) { // 入度为0的点q[++tt] = i;}}while (hh <= tt) {int p = q[hh++];for (int i = h[p]; i != -1; i = ne[i]) {int j = e[i];if (--d[j] == 0) {q[++tt] = j;}}}return hh == n; // 所有节点都入队}int main() {cin >> n >> m;memset(h, -1, sizeof h);while (m--) {int x, y;scanf("%d%d", &x, &y);add(x, y);}if (toposort()) {for (int i = 0; i < n; i++)printf("%d ", q[i]);}else puts("-1");return 0;}
哈希
字符串哈希
#include <iostream>using namespace std;const int N = 1e5 + 10, P = 131;typedef unsigned long long ULL;char str[N];ULL h[N], p[N];ULL get(int l, int r) {return h[r] - h[l - 1] * p[r - l + 1];}int main() {int n, m;cin >> n >> m;scanf("%s", str + 1);p[0] = 1;for (int i = 1; i <= n; i++) {h[i] = h[i - 1] * P + str[i];p[i] = p[i - 1] * P;}while (m--) {int l1, r1, l2, r2;scanf("%d%d%d%d", &l1, &r1, &l2, &r2);if (get(l1, r1) == get(l2, r2)) {puts("Yes");}else puts("No");}}
模拟散列表
#include <iostream>#include <cstring>using namespace std;const int N = 100003, null = 0x3f3f3f3f;int h[N];int find(int a) {int k = ((a % N) + N) % N;while (h[k] != null && h[k] != a) {k++;if (k == N) k = 0;}return k;}int main() {memset(h, 0x3f, sizeof h);int n;cin >> n;while (n--) {char op[2];int x;scanf("%s%d", op, &x);if (op[0] == 'I') {h[find(x)] = x;}else {if (h[find(x)] != x) puts("No");else puts("Yes");}}return 0;}
堆
堆排序
只实现插入+查询最小
#include <iostream>using namespace std;const int N = 1e5 + 10;int h[N], hcnt;void down(int t) {int p = t;if (t * 2 <= hcnt && h[t * 2] < h[p]) p = t * 2;if (t * 2 + 1 <= hcnt && h[t * 2 + 1] < h[p]) p = t * 2 + 1;if (p != t) {swap(h[p], h[t]);down(p);}}int main() {int n, m;cin >> n >> m;hcnt = n;for (int i = 1; i <= n; i++) {scanf("%d", &h[i]);}for (int i = n / 2; i; i--) down(i); // 建堆O(n)while (m--) {printf("%d ", h[1]);h[1] = h[hcnt--];down(1);}return 0;}
模拟堆
#include <iostream>#include <cstring>using namespace std;const int N = 1e5 + 10;int h[N], hp[N], ph[N], hcnt, pcnt;void hswap(int a, int b) { // 三种交换swap(ph[hp[a]], ph[hp[b]]);swap(hp[a], hp[b]);swap(h[a], h[b]);}void up(int p) {while (p / 2 && h[p] < h[p / 2]) {hswap(p, p / 2);p >>= 1;}}void down(int p) {int t = p;if (p * 2 <= hcnt && h[p * 2] < h[t]) t = p * 2;if (p * 2 + 1 <= hcnt && h[p * 2 + 1] < h[t]) t = p * 2 + 1;if (t != p) {hswap(t, p);down(t);}}int main() {int n;cin >> n;while (n--) {char op[5];scanf("%s", op);if (op[0] == 'I') {int x;scanf("%d", &x);hcnt++, pcnt++;h[hcnt] = x;ph[pcnt] = hcnt, hp[hcnt] = pcnt;up(hcnt);}else if (op[0] == 'P') {printf("%d\n", h[1]);}else if (strcmp(op, "DM") == 0) {hswap(1, hcnt);--hcnt;down(1);}else if (op[0] == 'D') {int k;scanf("%d", &k);k = ph[k];hswap(k, hcnt);hcnt--;up(k), down(k);}else if (op[0] == 'C') {int k, x;scanf("%d%d", &k, &x);k = ph[k];h[k] = x;up(k), down(k);}}return 0;}
链表
反转链表
非常值得借鉴的做法,遍历链表,将其存储到数组中进行各种处理
#include <iostream>#include <vector>#include <algorithm>using namespace std;const int N = 1e5 + 10;int h, e[N], ne[N];vector<int> q;int main() {int n, k;cin >> h >> n >> k;while (n--) {int addr, d, next;scanf("%d%d%d", &addr, &d, &next);e[addr] = d, ne[addr] = next;}for (int i = h; i != -1; i = ne[i]) {q.push_back(i);}for (int i = 0; i + k - 1 < q.size(); i += k) {reverse(q.begin() + i, q.begin() + i + k);}for (int i = 0; i < q.size(); i++) {printf("%05d %d ", q[i], e[q[i]]);if (i + 1 == q.size()) printf("-1\n");else printf("%05d\n", q[i + 1]);}return 0;}
反转链表
传统的链表做法
非递归
时间复杂度O(n),空间复杂度O(1)
/*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(NULL) {}* };*/class Solution {public:ListNode* reverseList(ListNode* head) {ListNode* h = new ListNode(0);ListNode *p = head;while (p) {ListNode *q = p->next;p->next = h->next;h->next = p;p = q;}return h->next;}};
递归
时间复杂度O(n),空间复杂度O(n)
/*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(NULL) {}* };*/class Solution {public:ListNode* reverseList(ListNode* head) {if (!head || !head->next) return head;ListNode *p = reverseList(head->next);head->next->next = head;head->next = nullptr;return p;}};
第k大数
知识点
基于快排的快速选择算法,做法就是在基准值的位置放好之后,如果大于基准值的数的数量cnt>=k,就在左半段寻找第k大数,否则在右半段寻找第k-cnt大数。每次选择了左右之一继续,因此时间复杂度为
模板
// 快排模板void quick_sort(int q[], int l, int r) {if (l >= r) return;// 随机选取基准int idx = l + rand() % (r - l + 1);int x = q[idx];int i = l - 1, j = r + 1;while (i < j) { //x左边的数<x,右边的数>xwhile (q[++i] < x);while (q[--j] > x);if (i < j) swap(q[i], q[j]);}quick_sort(q, l, j);quick_sort(q, j + 1, r);}// 快速选择void quick_select(int q[], int l, int r, int k) {if (l >= r) return q[l];int idx = l + rnad() % (r - l + 1);int x = q[idx];int i = l - 1, j = r + 1;while (i < j) {while (q[++i] < x);while (q[--j] > x);if (i < j) swap(q[i], q[j]);}int tmp = j - l + 1;if (tmp < k) return quick_select(q, j + 1, r, k - tmp);else return quick_select(q, l, j, k);}
字符串
strcpy和ctrncpy的实现
int my_strcmp(const char *s1, const char *s2){while(*s1 && *s2 && *s1 == *s2){s1++;s2++;}return *s1 - *s2;}int my_strncmp(const char *s1, const char *s2, size_t n) {while (n--) {if (*s1 && *s2 && *s1 == *s2) {s1++;s2++;}else return *s1 - *s2;}return 0;}
strcat和strncat的实现
注意,dest和src不能相同,可以考虑加上assert判断,但库函数不会有这个问题
char *my_strcat(char *dest, const char *src){char *ret = dest;while (*dest)dest++;while (*dest++ = *src++);return ret;}char *my_strncat(char *dest, const char *src,int n){char *ret = dest;while (*dest)dest++;while (n--) //通过此次while循环,将第二个字符串前n的字符连接到第一个字符串上*dest++ = *src++;*dest = '\0';return ret;}
动态规划
数字三角形
#include <iostream>using namespace std;const int N = 510;int a[N][N];int main() {int n;cin >> n;for (int i = 1; i <= n; i++) {for (int j = 1; j <= i; j++) {scanf("%d", &a[i][j]);}}for (int i = n - 1; i >= 1; i--) {for (int j = 1; j <= i; j++) {a[i][j] += max(a[i + 1][j], a[i + 1][j + 1]);}}cout << a[1][1];return 0;}
文件操作
打开关闭
#include <stdio.h>#include <stdlib.h>#define N 100int main() {FILE *fp;char str[N + 1];//判断文件是否打开失败!if ( (fp = fopen("d:\\demo.txt", "r")) == NULL ) {puts("Fail to open file!");exit(0);}//循环读取文件的每一行数据while( fgets(str, N, fp) != NULL ) {printf("%s", str);}//操作结束后关闭文件!fclose(fp);return 0;}
| 控制读写权限的字符串(必须指明) | |
|---|---|
| 打开方式 | 说明 | 
| “r” | 以“只读”方式打开文件。只允许读取,不允许写入。文件必须存在,否则打开失败。 | 
| “w” | 以“写入”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。 | 
| “a” | 以“追加”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。 | 
| “r+” | 以“读写”方式打开文件。既可以读取也可以写入,也就是随意更新文件。文件必须存在,否则打开失败。 | 
| “w+” | 以“写入/更新”方式打开文件,相当于w和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。 | 
| “a+” | 以“追加/更新”方式打开文件,相当于a和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。 | 
| 控制读写方式的字符串(可以不写) | |
| 打开方式 | 说明 | 
| “t” | 文本文件。如果不写,默认为"t"。 | 
| “b” | 二进制文件。 | 
读写
- int fputc( int c, FILE *fp );
 - int fputs( const char s, FILE fp );
 - int fprintf(FILE fp,const char format, …)
 - int fgetc( FILE * fp );
 - char fgets( char buf, int n, FILE *fp );
 int fscanf(FILE fp, const char format, …) ```cpp
include
int main() { FILE *fp = NULL;
fp = fopen(“/tmp/test.txt”, “w+”); fprintf(fp, “This is testing for fprintf…\n”); // FILE放开头 fputs(“This is testing for fputs…\n”, fp); // FILE放最后 fclose(fp); }
include 
int main() { FILE *fp = NULL; char buff[255];
fp = fopen(“/tmp/test.txt”, “r”); fscanf(fp, “%s”, buff); printf(“1: %s\n”, buff );
fgets(buff, 255, (FILE*)fp); printf(“2: %s\n”, buff );
fgets(buff, 255, (FILE*)fp); printf(“3: %s\n”, buff ); fclose(fp);
}
// 二进制IO(以数据块的形式读写文件) size_t fread(void ptr, size_t size_of_elements, size_t number_of_elements, FILE a_file); size_t fwrite(const void ptr, size_t size_of_elements, size_t number_of_elements, FILE a_file);
<a name="331tq"></a>## 大文件首先把整个文件读进来```cpp// define bufferFILE *fin=fopen("input.bin", "rb");fseek(fin, 0, SEEK_END);long f_length=ftell(fin);fseek(fin, 0, SEEK_SET);fread(buffer, 1, f_length, fin);
然后考虑分行,可以采用strtok函数
#include <string.h>#include <stdio.h>int main () {char str[80] = "This is\nwww.runoob.com\nwebsite";const char s[2] = "\n";char *token;/* 获取第一个子字符串 */token = strtok(str, s);/* 继续获取其他的子字符串 */while( token != NULL ) {printf("%s\n", token);token = strtok(NULL, s);}return(0);}
