树的常见存储
int N, h[N], e[N * 2], ne[N * 2], idx;
/*1.静态数组*/
struct node{
int left,right,val;
int father;
}tree[N];
/*2.邻接矩阵(本质上就是无向图)*/
/*3.邻接表*/
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
树的遍历:
/*先序、中序和后序遍历比较简单,递归或非递归实现*/
// dfs: 返回以u为根的树的结点个数
int dfs(int u){
st[u] = ture;
f[u]=1;
for (int i = h[u]; i != -1; i = ne[i])
{int j = e[i]; // 遍历下一个节点
if (!st[j]) { // 遍历节点没有遍历过
f[u] += dfs(j);} // 累加size大小,继续向下搜
}
return f[u];
}
//bfs: 层次遍历
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}
二叉树的深度:
void dfs(int id, int deep) {
if (id == 0) return ;//到达叶子节点时返回
ans = max(ans, deep);//更新答案
dfs(tree[id].left, deep+1);//向左遍历
dfs(tree[id].right, deep+1);//向右遍历
}
树的重心:
重心是指树中删除某个结点后剩下的最大连通子树的结点数目最小,那么这个节点被称为树的重心。
性质:
1.树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个重心,他们的距离和一样。
2.一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
3.一棵树最多有两个重心,且相邻。
4.对于无权树,以重心为根,所有子树的大小都不超过整棵树大小的一半,即其所有子树大小 ≤ N / 2
//求树的重心,即比较树中的每个节点u,以u为根的子树节点数k和总数n-k取最大值
//返回以u为根的子树中节点的个数,包括u节点
int dfs(int u) {
int res = 0; //存储 删掉某个节点之后,最大的连通子图节点数
st[u] = true; //标记访问过u节点
int sum = 1; //存储 以u为根的树 的节点数, 包括u,如图中的4号节点
//访问u的每个子节点
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
//因为每个节点的编号都是不一样的,所以 用编号为下标 来标记是否被访问过
if (!st[j]) {
int s = dfs(j); // u节点的单棵子树节点数 如图中的size值
res = max(res, s); // 记录最大联通子图的节点数
sum += s; //以j为根的树 的节点数
}
}
//n-sum 如图中的n-size值,不包括根节点4;
res = max(res, n - sum); // 选择u节点为重心,最大的 连通子图节点数
ans = min(res, ans); //遍历过的假设重心中,最小的最大联通子图的 节点数
return sum;
}
树的直径:
树中距离最远的两个点之间的距离被称为树的直径。
性质:
1.若有多条直径,则所有的直径之间皆有公共点。
2.直径的两端一定是叶子。
/*1.找到一个最深的点a,以a为根再找离a最远的b,ab即为直径*/
void dfs(int now,int fa) {
d[now]=d[fa]+1; // 深度=父亲+1
if (d[now]>d[deepest]) deepest=now; // 获取最深节点
for (int i=0,v;i<edges[now].size();i++) // 遍历每一条边
if ((v=edges[now][i])!=fa) dfs(v,now); // 如果是father就可以不用dfs
}
/*2.以每个点为根,找子树最大深度和次大深度,相加即为该点为公共点的直径,求最大值*/
void dp(int u){ // 动态规划
b[u] = true; // 经过节点 u
bool flag = false; // 记录节点 u 是否有儿子
for(register int i = 0; i < e[u].size(); i++){
int k = e[u][i];
if(b[k]) // 已经经过节点 k,说明节点 k 是节点 u 的祖先
continue;
flag = true; // 节点 u 有 e[u][i] 这个儿子
dp(k);
if(f[k] + 1 > f[u]){ // e[u][i] 到节点 i 的距离比 f[i] 更长,更新 f[i] 与 g[i] 的值
g[u] = f[u];
f[u] = f[k] + 1;
}
else if(f[k] + 1 > g[u]) // e[u][i] 到节点 i 的距离没有 f[i] 长,但比 g[i] 更长,更新 g[i]
g[u] = f[k] + 1;
}
if(!flag){ // 节点 u 没有儿子,即为叶子节点
f[u] = 0;
g[u] = 0; // 边界条件
}
return;
}
二叉堆
必须保证根节点大于(或小于)两个子节点,插入元素时放在末尾,向上调整;删除元素时堆顶元素与末尾元素交换,再删除末尾元素,堆顶元素向下调整。
//以大根堆为例
void up(int x) {
while (x > 1 && h[x] > h[x / 2]) {
swap(h[x], h[x / 2]);
x /= 2;
}
}
void down(int x) {
while (x * 2 <= n) {
t = x * 2;
if (t + 1 <= n && h[t + 1] > h[t]) t++;
if (h[t] <= h[x]) break;
swap(h[x], h[t]);
x = t;
}
}
优先队列
只允许在底端加入元素,并从顶端取出元素,自动依照队列中元素大小按顺序排列。
//升序队列 小顶堆 great 小到大
priority_queue <int,vector<int>,greater<int> > pri_que;
//降序队列 大顶堆 less 大到小 默认
priority_queue <int,vector<int>,less<int> > pri_que;
q.size();//返回q里元素个数
q.empty();//返回q是否为空,空则返回1,否则返回0
q.push(k);//在q的末尾插入k
q.pop();//删掉q的第一个元素
q.top();//返回q的第一个元素