【信竞入门】树型结构(树的重心,树的直径,二叉堆)

本文详细介绍了树的几种常见存储方式(静态数组、邻接矩阵和邻接表),以及先序、中序、后序遍历、深度计算、重心求解、直径查找、二叉堆和优先队列的实现。通过实例展示了这些概念在IT技术中的应用。
摘要由CSDN通过智能技术生成

树的常见存储

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的第一个元素
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值