当前位置: 首页 > article >正文

算法基础——树

一、树的概念

1. 树的定义

       树型结构是⼀类重要的⾮线性数据结构。

2. 树的基本术语 

3. 有序树和⽆序树

4. 有根树和⽆根树

注意

        如果是⽆根树,⽗⼦关系不明确,此时我们需要把所有的情况都存下来。⽐如a和b之间有⼀条 边,我们不仅要存a有⼀个孩⼦b,也要存b有⼀个孩⼦a。

        甚⾄有的时候,在有根树的题⽬⾥,也要这样存储。

二、树的存储

1. 孩⼦表⽰法

2. 实现⽅式⼀:⽤vector数组实现

#include <iostream>
#include <vector>

using namespace std;

const int N = 1e5 + 10;

int n;
vector<int> edges[N]; // 存储树

int main()
{
    cin >> n;
    for(int i = 1; i < n; i++)
    {
        int a, b; cin >> a >> b;
        // a 和 b 之间有一条边
        edges[a].push_back(b);
        edges[b].push_back(a);
    }


    return 0;
}

3. 实现⽅式⼆:链式前向星

#include <iostream>

using namespace std;

const int N = 1e5 + 10;

// 链式前向星
int h[N], e[N * 2], ne[N * 2], id;
int n;

// 其实就是把 b 头插到 a 所在的链表后面
void add(int a, int b)
{
    id++;
    e[id] = b;
    ne[id] = h[a];
    h[a] = id;
}

int main()
{
    cin >> n;
    for(int i = 1; i < n; i++)
    {
        int a, b; cin >> a >> b;
        // a 和 b 之间有一条边
        add(a, b); add(b, a);
    }


    return 0;
}

关于vector数组以及链式前向星:

  • 前者由于⽤到了容器vector,实际运⾏起来相⽐较于后者更耗时,因为vector是动态实现的。
  • 但是在如今的算法竞赛中,⼀般不会⽆聊到卡这种常数时间。也就是vector虽然慢,但不会因此⽽超时。

三、树的遍历 

1. 深度优先遍历-DFS

间复杂度:

简单估计⼀下,在 dfs   整个过程,会把树所有的边扫描量两遍。边的数量为n 1 ,因此间复度为O(N)

空间复杂度:

最差况下,结点个数为  的树,⾼度也是 n 也就是变成⼀个链表。此递归的深度也是  n 此时的空间度为O(N)

 

2. 宽度优先遍历-BFS

间复杂度:

所有结点只会次,然后出队次,因此度为 O(N)

空间复杂度:

最坏情况下,所有的根结点都在同⼀层,此队列⾥⾯多有 n 个元素,空间杂度为 O(N)

 3. 练习

(1)题目描述 

题⽬描述:

定⼀棵树,该树⼀共 个结点,编号分别是 1 ∼ n

描述:

⼀⾏⼀个整数 n 表⽰ n 结点。

来 n ⾏,每⾏两个整数 u, v 表⽰ u 之间有条边。

测试例:

 11
 1 3
 7 3
 3 10
 1 5
 4 5
 2 1
 11 2
 6 11
 11 8
 11 9

(2)实现代码

深度优先遍历——DFS

// // 用 vector 数组存储树

// #include <iostream>
// #include <vector>

// using namespace std;

// const int N = 1e5 + 10;

// int n;
// vector<int> edges[N]; // 存储图
// bool st[N]; // 标记哪些点已经访问过了

void dfs(int u)
{
    cout << u << " ";
    st[u] = true; // 当前这个点已经访问过了

    // 访问所有的孩子
    for(auto v : edges[u])
    {
        if(!st[v])
        {
            dfs(v);
        }
    }
}

// int main()
// {
//     // 建树
//     cin >> n;
//     for(int i = 1; i < n; i++)
//     {
//         int a, b; cin >> a >> b;
//         edges[a].push_back(b);
//         edges[b].push_back(a);
//     }

//     // 深度优先遍历
//     dfs(1);

//     return 0;
// }

// 用链式前向星存储
#include <iostream>

using namespace std;

const int N = 1e5 + 10;

// 存图
int h[N], e[N * 2], ne[N * 2], id;
int n;

bool st[N]; // 标记哪些点已经访问过了

void add(int a, int b)
{
    id++;
    e[id] = b;

    ne[id] = h[a];
    h[a] = id;
}

void dfs(int u)
{
    cout << u << " ";
    st[u] = true;

    for(int i = h[u]; i; i = ne[i])
    {
        int v = e[i]; // 孩子
        if(!st[v])
        {
            dfs(v);
        }
    }
}

int main()
{
    // 建树
    cin >> n;
    for(int i = 1; i < n; i++)
    {
        int a, b; cin >> a >> b;
        add(a, b); add(b, a);
    }

    // 深度优先遍历
    dfs(1);

    return 0;
}


// 斐波那契数列
int fib(int n)
{
    if(n == 0 || n == 1) return n;

    return fib(n - 1) + fib(n - 2);
}

 宽度优先遍历——BFS

// 用 vector 数组存树
// #include <iostream>
// #include <queue>
// #include <vector>

// using namespace std;

// const int N = 1e5 + 10;

// int n;
// vector<int> edges[N]; // 存树
// bool st[N]; // 标记哪些节点已经访问过了

// void bfs()
// {
//     queue<int> q;
//     q.push(1);
//     st[1] = true;

//     while(q.size())
//     {
//         int u = q.front(); q.pop();
//         cout << u << " ";

//         for(auto v : edges[u])
//         {
//             if(!st[v])
//             {
//                 q.push(v);
//                 st[v] = true;
//             }
//         }
//     }
// }

// int main()
// {
//     // 建树
//     cin >> n;
//     for(int i = 1; i < n; i++)
//     {
//         int a, b; cin >> a >> b;
//         edges[a].push_back(b);
//         edges[b].push_back(a);
//     }

//     bfs();

//     return 0;
// }


// 用链式前向星存储树
#include <iostream>
#include <queue>

using namespace std;

const int N = 1e5 + 10;

int n;
int h[N], e[N * 2], ne[N * 2], id;
bool st[N];

void add(int a, int b)
{
    id++;
    e[id] = b;
    ne[id] = h[a];
    h[a] = id;
}

void bfs()
{
    queue<int> q;
    q.push(1);
    st[1] = true;

    while(q.size())
    {
        int u = q.front(); q.pop();
        cout << u << " ";

        for(int i = h[u]; i; i = ne[i])
        {
            int v = e[i];
            if(!st[v])
            {
                q.push(v);
                st[v] = true;
            }
        }
    }
}

int main()
{
    cin >> n;
    for(int i = 1; i < n; i++)
    {
        int a, b; cin >> a >> b;
        add(a, b); add(b, a);
    }

    bfs();

    return 0;
}


http://www.kler.cn/a/614330.html

相关文章:

  • 表单对象与当前行对象的 区别
  • [编程题]16、偶数求和
  • 4月手机新品前瞻,影像,性能与设计卷得起飞
  • 图解AUTOSAR_SWS_SPIHandlerDriver
  • Git项目要改变仓库地址
  • 生成树和VRRP实验
  • 第十三章:面向对象思想(OOP)与面向过程思想的对比与应用
  • 如何缩短研发周期,降低研发成本?全星APQP软件为您提供解决方案
  • 29_项目
  • QML中使用Image显示图片和使用QQuickItem显示图片
  • 【C#】关键字 volatile
  • JVM - 垃圾回收器常见问题
  • 下一代数据工程:实时智能数据网格(Real-Time Data Mesh)
  • 【有外界干扰的BFS】经典题P2895Meteor Shower S
  • AI大模型、机器学习以及AI Agent开源社区和博客
  • [代码随想录] KMP 算法 28. 找出字符串中第一个匹配项的下标 459. 重复的子字符串
  • mac安装mongoDB的正确姿势
  • 智网安全:守护未来数字文明的基石
  • Vue3 配合 fullPage.js 打造高效全屏滚动网页
  • spring security认证流程分析