树
哈夫曼树
基本概念
给定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 SUBMIT
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
long _begin_time = clock();
#endif
while (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 SUBMIT
long _end_time = clock();
printf("\n\ntime = %ld ms", _end_time - _begin_time);
#endif
return 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,再更新dist
st[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"); // 边的数量等于顶点数 - 1
else 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,右边的数>x
while (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 100
int 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 buffer
FILE *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);
}