第七章 二叉树
1.二叉树的概念





2.二叉树的存储

顺序存储



链式存储


cpp
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n;
int l[N], r[N];
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
// 存下 i 号结点的左右孩子
cin >> l[i] >> r[i];
}
return 0;
}

3.二叉树的遍历
1.深度优先遍历


先序:先输出后遍历
中序:先遍历后输出




cpp
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n;
int l[N], r[N];
// 先序遍历:根 → 左 → 右
void dfs1(int p)
{
if(p == 0) return;
cout << p << " "; //根
dfs1(l[p]); //左
dfs1(r[p]); //右
}
// 中序遍历:左 → 根 → 右
void dfs2(int p)
{
if(p == 0) return;
dfs2(l[p]);//左
cout << p << " ";//根
dfs2(r[p]); //右
}
// 后序遍历:左 → 右 → 根
void dfs3(int p)
{
if(p == 0) return;
dfs3(l[p]);
dfs3(r[p]);
cout << p << " ";
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> l[i] >> r[i];
}
dfs1(1); // 先序遍历
cout << endl;
dfs2(1); // 中序遍历
cout << endl;
dfs3(1); // 后序遍历
cout << endl;
return 0;
}
2.宽度优先遍历
cpp
#include <iostream>
#include <queue>
using namespace std;
const int N = 300;
int n;
int l[N], r[N];
void bfs()
{
queue<int> q;
q.push(1);
while(q.size())
{
auto p = q.front();
q.pop();
cout << p << " ";
// 左右孩子入队
if(l[p]) q.push(l[p]);
if(r[p]) q.push(r[p]);
}
cout << endl;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> l[i] >> r[i];
}
bfs();
return 0;
}
1.新二叉树


cpp
#include <iostream>
using namespace std;
const int N = 300;
int n;
char root;
char l[N], r[N]; // 存二叉树
void dfs(char root)
{
if(root == '*') return; // * 代表空结点
cout << root;
dfs(l[root]);
dfs(r[root]);
}
int main()
{
cin >> n;
// 处理根节点
cin >> root;
cin >> l[root] >> r[root];
for(int i = 2; i <= n; i++)
{
char t; cin >> t;
cin >> l[t] >> r[t];
}
// 前序遍历
dfs(root);
return 0;
}
2.二叉树的遍历



cpp
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n;
int l[N], r[N]; // 存二叉树
// 前序遍历:根 -> 左 -> 右
void dfs1(int root)
{
if(!root) return;
cout << root << " ";
dfs1(l[root]);
dfs1(r[root]);
}
// 中序遍历:左 -> 根 -> 右
void dfs2(int root)
{
if(!root) return;
dfs2(l[root]);
cout << root << " ";
dfs2(r[root]);
}
// 后序遍历:左 -> 右 -> 根
void dfs3(int root)
{
if(!root) return;
dfs3(l[root]);
dfs3(r[root]);
cout << root << " ";
}
int main()
{
cin >> n;
// 建树
for(int i = 1; i <= n; i++)
cin >> l[i] >> r[i];
// 前序遍历
dfs1(1);
cout << endl;
// 中序遍历
dfs2(1);
cout << endl;
// 后序遍历
dfs3(1);
return 0;
}
3.二叉树深度




cpp
#include <iostream>
#include <queue>
using namespace std;
const int N = 1e6 + 10;
int n;
int l[N], r[N];
int bfs()
{
queue<int> q;
q.push(1);
int depth = 0;
while(q.size())
{
int sz = q.size();
depth++;
for(int i = 0; i < sz; i++)
{
int u = q.front(); q.pop();
if(l[u]) q.push(l[u]);
if(r[u]) q.push(r[u]);
}
}
return depth;
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) cin >> l[i] >> r[i];
cout << bfs() << endl;
return 0;
}
4.求先序排列



cpp
#include <iostream>
using namespace std;
string a, b;
// a:中序遍历序列
// b:后序遍历序列
// [l1,r1] 中序区间
// [l2,r2] 后序区间
void dfs(int l1, int r1, int l2, int r2)
{
// 递归出口:区间为空
if(l1 > r1) return;
// 后序最后一个元素是当前子树根节点
char root = b[r2];
cout << root;
// 在中序序列找到根的位置p
int p = l1;
while(a[p] != root) p++;
// 左子树节点数量:p - l1
// 左子树:中序[l1,p-1] 后序[l2 , l2 + p - l1 - 1]
dfs(l1, p - 1, l2, l2 + p - l1 - 1);
// 右子树:中序[p+1,r1] 后序[l2 + p - l1 , r2 - 1]
dfs(p + 1, r1, l2 + p - l1, r2 - 1);
}
int main()
{
cin >> a >> b;
dfs(0, a.size() - 1, 0, b.size() - 1);
return 0;
}
5.American Heritage


cpp
#include <iostream>
using namespace std;
string a, b;
// a:中序遍历
// b:先序遍历
// 输出:后序遍历
void dfs(int l1, int r1, int l2, int r2)
{
// 递归出口:区间没有节点
if(l1 > r1) return;
// 先序第一个元素 b[l2] 是根节点
int p = l1;
while(a[p] != b[l2]) p++;
// 左子树递归
dfs(l1, p - 1, l2 + 1, l2 + p - l1);
// 右子树递归
dfs(p + 1, r1, l2 + p - l1 + 1, r2);
// 左右全部处理完,输出根(后序:左→右→根)
cout << b[l2];
}
int main()
{
cin >> a >> b;
dfs(0, a.size() - 1, 0, b.size() - 1);
return 0;
}

6.二叉树问题





这道题并没有告诉左右节点的位置 所以需要用vector或者链式前向星做
cpp
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int N = 110;
int n;
vector<int> edges[N]; // 存树
int fa[N]; // fa[i] 表示 i 结点的父亲
int dist[N]; // dist[i] 表示 i 到 x 的最短距离
int dfs(int u)
{
int ret = 0;
for(auto v : edges[u])
{
ret = max(ret, dfs(v));
}
return ret + 1;
}
int bfs()
{
queue<int> q;
q.push(1);
int ret = 0;
while(q.size())
{
int sz = q.size();
ret = max(ret, sz);
while(sz--)
{
int u = q.front(); q.pop();
for(auto v : edges[u])
{
q.push(v);
}
}
}
return ret;
}
int main()
{
cin >> n;
for(int i = 1; i < n; i++)
{
int u, v; cin >> u >> v;
// u -> v
edges[u].push_back(v);
fa[v] = u;
}
// 求深度
cout << dfs(1) << endl;
// 求宽度
cout << bfs() << endl;
// 求距离
int x, y; cin >> x >> y;
while(x != 1)
{
dist[fa[x]] = dist[x] + 1;
x = fa[x];
}
int len = 0;
while(y != 1 && dist[y] == 0)
{
len++;
y = fa[y];
}
cout << dist[y] * 2 + len << endl;
return 0;
}


第八章 堆和 priority_queue
1.堆的定义


2.堆的存储


3.核心操作
向上调整算法

cpp
int n; // 标记堆的大小
int heap[N]; // 存堆 - 默认是一大根堆
// 向上调整算法
void up(int child)
{
int parent = child / 2;
// 如果父结点存在,并且权值比父结点大
while(parent >= 1 && heap[child] > heap[parent])
{
swap(heap[child], heap[parent]);
// 交换之后,修改下次调整的父子关系,注意顺序不能颠倒
child = parent;
parent = child / 2;
}
}
如果是创建小根堆的话 那么需要比较看看是不是小 如果小的话 那么就是进行与父辈进行颠倒
时间复杂度: 最差情况需要走⼀个树高,因此时间复杂度为 log N。

向下调整算法


cpp
int n; // 标记堆的大小
int heap[N]; // 存堆 - 默认是一大根堆
// 向下调整算法
void down(int parent)
{
int child = parent * 2;
while(child <= n) // 如果还有孩子
{
// 找出两个孩子谁是最大的
if(child + 1 <= n && heap[child + 1] > heap[child]) child++;
// 最大的孩子都比我小,说明是一个合法的堆
if(heap[child] <= heap[parent]) return;
swap(heap[child], heap[parent]);
// 交换之后,修改下次调整的父子关系,注意顺序不能颠倒
parent = child;
child = parent * 2;
}
}
时间复杂度:最差情况需要走⼀个树高,因此时间复杂度为 log N。
1.堆的模拟实现
1 创建

2 插入

时间复杂度:时间开销在向上调整算法上,时间复杂度为 O (log N) 。
3 删除栈顶元素


时间复杂度:时间开销在向上调整算法上,时间复杂度为 O (log N) 。
4 堆顶元素

时间复杂度:显然是 O(1) 。
5 堆的大小

时间复杂度:显然是 O(1) 。
2.priority_queue
1.优先级队列

2.创建 priority_queue - 初阶



cpp
#include <iostream>
#include <vector>
#include <queue> // 优先级队列的头文件在 queue 里面
using namespace std;
// 优先级队列的使用
void test()
{
int a[10] = {1, 41, 23, 10, 11, 2, -1, 99, 14, 0};
priority_queue<int> heap; // 默认写法下,是一个大根堆
for(auto x : a)
{
heap.push(x); // 插入元素
}
while(heap.size())
{
cout << heap.top() << " "; // 获取堆顶元素的值
heap.pop(); // 删除元素
}
}
int main()
{
test();
return 0;
}

3.创建 priority_queue - 内置类型
cpp
内置类型就是 C++ 提供的数据类型,⽐如 int、double、long long 等。以 int 类型为例,分
别创建⼤根堆和⼩根堆。

vector指的是动态顺序表
cpp
#include <iostream>
#include <vector>
#include <queue> // 优先级队列的头文件在 queue 里面
using namespace std;
// 内置类型
void test()
{
int a[10] = {1, 41, 23, 10, 11, 2, -1, 99, 14, 0};
// 大根堆
priority_queue<int> heap1; // 默认就是大根堆
// priority_queue<数据类型, 存数据的结构, 数据之间的比较方式>
priority_queue<int, vector<int>, less<int>> heap2; // 也是大根堆
// 小根堆
priority_queue<int, vector<int>, greater<int>> heap3; // 小根堆
// 测试
for(auto x : a)
{
heap1.push(x);
heap2.push(x);
heap3.push(x);
}
while(heap1.size())
{
cout << heap1.top() << " "; // 获取堆顶元素的值
heap1.pop(); // 删除元素
}
cout << endl;
while(heap2.size())
{
cout << heap2.top() << " "; // 获取堆顶元素的值
heap2.pop(); // 删除元素
}
cout << endl;
while(heap3.size())
{
cout << heap3.top() << " "; // 获取堆顶元素的值
heap3.pop(); // 删除元素
}
cout << endl;
}
int main()
{
test();
return 0;
}

4.创建 priority_queue - 结构体类型

cpp
#include <iostream>
#include <vector>
#include <queue> // 优先级队列的头文件在 queue 里面
using namespace std;
struct node
{
int a, b, c;
// // 以 b 为基准,定义大根堆
// bool operator < (const node& x) const
// {
// return b < x.b;
// }
// 以 b 为基准,定义小根堆
bool operator < (const node& x) const
{
return b > x.b;
}
};
void test()
{
priority_queue<node> heap;
for(int i = 1; i <= 10; i++)
{
heap.push({i, i + 1, i + 2});
}
while(heap.size())
{
auto [a, b, c] = heap.top();
heap.pop();
cout << a << " " << b << " " << c << endl;
}
}
int main()
{
test();
return 0;
}



1 堆


cpp
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n; // 标记堆的大小
int heap[N]; // 小根堆
// 向上调整算法
void up(int child)
{
int parent = child / 2;
while(parent >= 1 && heap[child] < heap[parent])
{
swap(heap[child], heap[parent]);
child = parent;
parent = child / 2;
}
}
// 向下调整算法
void down(int parent)
{
int child = parent * 2; // 左孩子
while(child <= n)
{
// 找出两个孩子中的最小值
if(child + 1 <= n && heap[child + 1] < heap[child])
child++;
if(heap[child] >= heap[parent])
return;
swap(heap[child], heap[parent]);
parent = child;
child = parent * 2;
}
}
// 进堆
void push(int x)
{
n++;
heap[n] = x;
up(n);
}
// 删除堆顶元素
void pop()
{
swap(heap[1], heap[n]);
n--;
down(1);
}
int main()
{
int T; cin >> T;
while(T--)
{
int op; cin >> op;
if(op == 1) // 进堆
{
int x; cin >> x;
push(x);
}
else if(op == 2) // 输出堆顶
{
cout << heap[1] << endl;
}
else // 删除堆顶元素 op == 3
{
pop();
}
}
return 0;
}
2 第k小



cpp
#include <iostream>
#include <queue>
using namespace std;
int n, m, k;
priority_queue<int> heap; // C++默认priority_queue是【大根堆】
int main()
{
cin >> n >> m >> k;
// 先读入初始n个数字
for(int i = 1; i <= n; i++)
{
int x; cin >> x;
heap.push(x);
// 一旦堆大小超过k,删掉最大的元素
if(heap.size() > k)
heap.pop();
}
// m次操作
while(m--)
{
int op; cin >> op;
if(op == 1) // 添加新数字
{
int x; cin >> x;
heap.push(x);
if(heap.size() > k)
heap.pop();
}
else if(op == 2) // 查询第k小的数
{
if(heap.size() == k)
cout << heap.top() << endl;
else
cout << -1 << endl;
}
}
return 0;
}

3 除2!

找到最大的偶数减少一半 所以需要用大根堆
cpp
#include <iostream>
#include <queue>
using namespace std;
typedef long long LL;
int n, k;
priority_queue<int> heap; // 默认大根堆
int main()
{
cin >> n >> k;
LL sum = 0; // 所有数字总和
for(int i = 1; i <= n; i++)
{
int x; cin >> x;
sum += x;
if(x % 2 == 0) // 只把偶数放进堆
heap.push(x);
}
// 最多进行k次操作
while(heap.size() && k--)
{
int t = heap.top() / 2; // 取出最大偶数,减半
heap.pop();
sum -= t; // 总和减少t
if(t % 2 == 0) // 减半之后依然是偶数,重新放回堆,可以继续切割
heap.push(t);
}
cout << sum << endl;
return 0;
}
4 最小函数值

cpp
#include <iostream>
#include <queue>
using namespace std;
typedef long long LL;
const int N = 1e4 + 10;
int n, m;
LL a[N], b[N], c[N];
struct node
{
LL f; // 当前函数计算结果
LL num; // 第几个函数的编号
LL x; // 当前代入的x值
bool operator <(const node& x) const
{
// priority_queue默认大根堆,return f > x.f 实现【小根堆】
return f > x.f;
}
};
priority_queue<node> heap;
// 二次函数:a[i] * x² + b[i] * x + c[i]
LL calc(LL i, LL x)
{
return a[i] * x * x + b[i] * x + c[i];
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
cin >> a[i] >> b[i] >> c[i];
}
// 初始:所有函数先代入 x=1 放进小根堆
for(int i = 1; i <= n; i++)
{
heap.push({calc(i, 1), i, 1});
}
// 取出前m个最小的函数值
while(m--)
{
auto t = heap.top();
heap.pop();
LL f = t.f, num = t.num, x = t.x;
cout << f << " ";
// 当前x取出后,把同一个函数 x+1 的结果入堆
heap.push({calc(num, x + 1), num, x + 1});
}
return 0;
}

5 序列合并



cpp
#include <iostream>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N], b[N];
struct node
{
int sum; // a[i] + b[j] 的和
int i, j; // 数组a下标i,数组b下标j
bool operator < (const node& x) const
{
// priority_queue默认大根堆,sum>x.sum 构建小根堆
return sum > x.sum;
}
};
priority_queue<node> heap; // 小根堆
int main()
{
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) cin >> b[i];
// 初始化:把所有 a[i]+b[1] 入堆
for(int i = 1; i <= n; i++)
{
heap.push({a[i] + b[1], i, 1});
}
// 取出前n小的和
for(int k = 1; k <= n; k++)
{
node t = heap.top(); heap.pop();
int sum = t.sum, i = t.i, j = t.j;
cout << sum << " ";
// 当前j的下一个位置j+1,如果不越界,入堆 a[i]+b[j+1]
if(j + 1 <= n)
heap.push({a[i] + b[j + 1], i, j + 1});
}
return 0;
}

6.舞蹈课



cpp
#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
const int N = 2e5 + 10;
int n;
int s[N]; // 标记性别 0/1
//双向链表
int e[N];
int pre[N], ne[N];
struct node
{
int d; // 技术差值
int l, r; //相邻两个人编号
//小根堆定义
bool operator < (const node& x) const
{
if(d != x.d) return d > x.d;
else if(l != x.l) return l > x.l;
else return r > x.r;
}
};
priority_queue<node> heap;
bool st[N]; //标记已经组队出局的人
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
char ch; cin >> ch;
if(ch == 'B') s[i] = 1;
else s[i] = 0;
}
for(int i = 1; i <= n; i++)
{
cin >> e[i];
//初始化双向链表
pre[i] = i - 1;
ne[i] = i + 1;
}
pre[1] = 0; //第一个和最后一个分别没有前面和后面的
ne[n] = 0; //0代表不存在
//把所有相邻异性放入堆
for(int i = 2; i <= n; i++)
{
if(s[i] != s[i - 1])
{
heap.push({abs(e[i] - e[i - 1]), i - 1, i});
}
}
vector<node> ret;
while(heap.size())
{
node t = heap.top(); heap.pop();
int d = t.d, l = t.l, r = t.r;
// 如果两人其中一人已经组队,直接跳过这条无效配对
if(st[l] || st[r]) continue;
ret.push_back(t);
st[l] = st[r] = true; //标记两人出局
//双向链表删除 l,r
int L = pre[l];
int R = ne[r];
ne[L] = R;
pre[R] = L;
//新邻居如果是异性,加入堆
if(L != 0 && R != 0 && s[L] != s[R])
{
heap.push({abs(e[L] - e[R]), L, R});
}
}
cout << ret.size() << endl;
for(auto& x : ret)
{
cout << x.l << " " << x.r << endl;
}
return 0;
}


