C语言经典算法之层优先遍历

本文详细介绍了二叉树层序遍历的C语言代码实现,包括时间复杂度O(n)和空间复杂度O(n),讨论了其在实际应用中的优点和缺点,涉及图形用户界面渲染、数据库索引、社交网络分析等多个领域。
摘要由CSDN通过智能技术生成

目录

前言

A.建议

B.简介

一 代码实现

二 时空复杂度

A.时间复杂度

B.空间复杂度

C.总结

三 优缺点

A.优点:

B.缺点:

四 现实中的应用


前言

A.建议

1.学习算法最重要的是理解算法的每一步,而不是记住算法。

2.建议读者学习算法的时候,自己手动一步一步地运行算法。

B.简介

二叉树的层序遍历(Breadth-First Search, BFS)利用队列实现,按照从左到右、从上到下的顺序遍历二叉树的所有节点。

一 代码实现

#include <stdio.h>
#include <stdlib.h>

// 定义二叉树节点结构体
typedef struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

// 创建一个队列结构体,这里简化为只存储指针
typedef struct QueueNode {
    TreeNode *node;
    struct QueueNode *next;
} QueueNode;

// 初始化和销毁队列函数省略,可以使用链表或循环数组来实现队列

// 层序遍历二叉树函数
void levelOrderTraversal(TreeNode* root) {
    if (root == NULL) return; // 如果根节点为空,则直接返回

    QueueNode *queue = malloc(sizeof(QueueNode)); // 初始化队列
    queue->node = root;
    queue->next = NULL;

    while (queue != NULL) { // 当队列非空时继续遍历
        TreeNode *curr = queue->node; // 出队当前节点
        printf("%d ", curr->val); // 访问并打印节点值

        QueueNode *temp = queue;
        queue = queue->next; // 移动队列头指针

        // 将当前节点的左右子节点入队,如果存在的话
        if (curr->left) {
            QueueNode *new_node = malloc(sizeof(QueueNode));
            new_node->node = curr->left;
            new_node->next = queue;
            queue = new_node;
        }
        if (curr->right) {
            QueueNode *new_node = malloc(sizeof(QueueNode));
            new_node->node = curr->right;
            new_node->next = queue;
            queue = new_node;
        }

        free(temp); // 释放已访问节点对应的队列元素
    }
}

// 示例:创建一个二叉树节点
TreeNode* newNode(int val) {
    TreeNode *node = (TreeNode*)malloc(sizeof(TreeNode));
    node->val = val;
    node->left = NULL;
    node->right = NULL;
    return node;
}

int main() {
    // 假设我们有以下二叉树:
    //      1
    //     / \
    //    2   3
    //   / \   \
    //  4   5   6
    TreeNode *root = newNode(1);
    root->left = newNode(2);
    root->right = newNode(3);
    root->left->left = newNode(4);
    root->left->right = newNode(5);
    root->right->right = newNode(6);

    levelOrderTraversal(root);

    // 不要忘记在主程序结束后清理整个二叉树及其相关队列元素

    return 0;
}

上述代码中,首先定义了一个TreeNode结构体表示二叉树节点,以及一个QueueNode结构体作为队列节点,用于存储二叉树节点的指针。层序遍历函数通过将根节点入队开始,然后进入一个循环,在循环中每次出队一个节点并访问它,同时将其左右子节点按顺序入队。这样就可以保证每一层的节点都会按照从左至右的顺序被访问。

注意:实际应用中需要实现队列的初始化、入队、出队等基本操作,并在遍历完成后释放队列及所有节点以避免内存泄漏。

二 时空复杂度

A.时间复杂度

在层序遍历中,我们对每个节点执行的操作是将其入队和出队各一次。对于一棵有 n个节点的完全二叉树或任何其他形状的二叉树来说,每个节点都会经历这一过程。因此,时间复杂度主要由节点的入队和出队操作决定,即为 O(n)

B.空间复杂度

在最坏的情况下,当队列中需要存储整棵树的所有节点时(例如,在遍历一个完全二叉树时,每一层的最大节点数就是下一层的节点数),队列中元素的数量最多达到二叉树的高度 h乘以每层最多的节点数,即在满二叉树情况下等于2^(h-1)(此处的h-1为幂)。由于二叉树包含 n 个节点,它的高度h至多为 log_2(n+1))(取上界)。因此,在最坏情况下,所需的空间为 O(n)

C.总结

综上所述:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

无论二叉树的具体形态如何,只要树中节点总数为 n,层序遍历的时间和空间复杂度都是线性的,与节点总数成正比。

三 优缺点

A.优点:

  1. 直观性:层序遍历的结果直观地反映了二叉树每一层节点的分布情况,对于可视化和理解树的结构非常有帮助。

  2. 层次信息保留:适用于那些需要按照层级关系处理问题的情况,例如在图形用户界面中渲染树形菜单或者在游戏中构建游戏地图时,需按照层级从上至下、从左至右进行绘制。

  3. 邻接节点遍历:如果一个应用需求是要访问每个节点的相邻同层节点,层序遍历可以很好地满足这个需求,因为每一轮队列操作后得到的就是同一层的所有节点。

  4. 并行处理:由于层序遍历天然地将节点按层次分组,所以它适合于并行或分布式计算环境,可以在一定程度上实现并行遍历和处理。

  5. 问题解决:特定的问题如“找出二叉树的最大宽度”、“检查是否为平衡二叉树”等可以通过层序遍历来高效解决。

B.缺点:

  1. 空间消耗:在最坏情况下,层序遍历需要使用队列来存储整棵树的某一层节点,这意味着所需的空间复杂度可能达到 O(n),其中 n 是树中的节点总数。对于高度不平衡的二叉树,即使节点数较少,也可能导致较大的内存占用。

  2. 不适合深度优先搜索适用场景:对于一些依赖于子树内部结构的查找和更新操作(比如求解表达式树的值、查找二叉搜索树中的特定元素),层序遍历不如深度优先遍历(DFS)直接有效。

  3. 无法充分利用堆栈的优势:相比于深度优先遍历通常使用的堆栈,层序遍历所用的队列在某些场合下可能导致额外的数据移动和交换开销。

  4. 顺序依赖:若要实现某种特定顺序的遍历,比如前序遍历或后序遍历,层序遍历不是直接对应这些遍历方式,需要结合其他逻辑才能实现。

四 现实中的应用

  1. 图形用户界面(GUI)渲染

    • 层序遍历常用于构建和显示具有层级结构的元素,如文件系统目录树、菜单树或组织架构图。按照层次顺序从根节点开始逐层渲染,可以确保同一层次的节点在同一水平线上显示。
  2. 网络游戏地图生成

    • 在某些游戏中,地图可能采用多级嵌套的空间设计,类似于二叉空间分割树。层序遍历可以用来生成游戏世界中的区块加载序列,按层次从近到远加载地图区域,提高用户体验。
  3. 数据库索引结构

    • B树(B-Tree)是一种自平衡的树数据结构,广泛应用于数据库和文件系统的索引中。其内部节点的子节点数通常大于2,但仍采用类似层序遍历的方式进行搜索和插入操作,以保证搜索性能。
  4. 社交网络关系分析

    • 社交网络中,用户的关注者与被关注者关系可以抽象为一种二叉树或更复杂的图结构。层序遍历可用于实现“好友的好友”推荐算法,即先获取第一层的好友列表,再依次获取他们的好友列表,以此类推。
  5. 解决特定问题

    • 在算法竞赛或实际开发中,许多问题可以通过层序遍历来高效解决,例如求解一棵二叉树的最大宽度(每一层节点最多时的节点数)、检查二叉树是否是完全二叉树、判断两棵二叉树是否形状相同等。
  6. 并行计算与分布式处理

    • 对于大规模数据集,如果它们可以映射到二叉树结构上,层序遍历便于将工作分解成独立的层,在多核处理器或分布式系统中并行处理每层的数据。
  7. 图像处理与计算机视觉

    • 在某些图像分割或者特征提取算法中,图像可以视为二维像素点构成的树状结构,层序遍历可以帮助分块处理图像,或是递归地对图像区域进行分割和分析。
  • 40
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是C语言实现图的深度优先遍历和广度优先遍历算法的代码: ```c #include <stdio.h> #include <stdlib.h> #define MAX_VERTEX_NUM 100 // 边表节点 typedef struct ArcNode { int adjvex; // 邻接点的下标 struct ArcNode *next; // 指向下一个邻接点的指针 } ArcNode; // 顶点节点 typedef struct VNode { int data; // 顶点的数据 ArcNode *first; // 指向第一个邻接点的指针 } VNode; // 邻接表图 typedef struct { VNode vexs[MAX_VERTEX_NUM]; // 顶点数组 int vexnum, arcnum; // 顶点数和边数 } ALGraph; // 初始化邻接表图 void InitGraph(ALGraph *G) { int i; G->vexnum = G->arcnum = 0; for (i = 0; i < MAX_VERTEX_NUM; i++) { G->vexs[i].first = NULL; } } // 添加边 void AddArc(ALGraph *G, int v1, int v2) { ArcNode *p = (ArcNode *)malloc(sizeof(ArcNode)); p->adjvex = v2; p->next = G->vexs[v1].first; G->vexs[v1].first = p; G->arcnum++; } // 深度优先遍历 void DFS(ALGraph *G, int v, int *visited) { ArcNode *p; printf("%d ", G->vexs[v].data); visited[v] = 1; p = G->vexs[v].first; while (p) { if (!visited[p->adjvex]) { DFS(G, p->adjvex, visited); } p = p->next; } } // 广度优先遍历 void BFS(ALGraph *G, int v, int *visited) { int queue[MAX_VERTEX_NUM], front = 0, rear = 0; ArcNode *p; printf("%d ", G->vexs[v].data); visited[v] = 1; queue[rear++] = v; while (front != rear) { v = queue[front++]; p = G->vexs[v].first; while (p) { if (!visited[p->adjvex]) { printf("%d ", G->vexs[p->adjvex].data); visited[p->adjvex] = 1; queue[rear++] = p->adjvex; } p = p->next; } } } int main() { ALGraph G; int visited[MAX_VERTEX_NUM] = {0}; int i; InitGraph(&G); G.vexs[0].data = 0; G.vexs[1].data = 1; G.vexs[2].data = 2; G.vexs[3].data = 3; G.vexs[4].data = 4; G.vexs[5].data = 5; AddArc(&G, 0, 1); AddArc(&G, 0, 2); AddArc(&G, 0, 5); AddArc(&G, 1, 0); AddArc(&G, 1, 2); AddArc(&G, 1, 3); AddArc(&G, 2, 0); AddArc(&G, 2, 1); AddArc(&G, 2, 3); AddArc(&G, 2, 4); AddArc(&G, 3, 1); AddArc(&G, 3, 2); AddArc(&G, 3, 4); AddArc(&G, 4, 2); AddArc(&G, 4, 3); AddArc(&G, 4, 5); AddArc(&G, 5, 0); AddArc(&G, 5, 4); printf("DFS: "); DFS(&G, 0, visited); printf("\n"); for (i = 0; i < MAX_VERTEX_NUM; i++) { visited[i] = 0; } printf("BFS: "); BFS(&G, 0, visited); printf("\n"); return 0; } ``` 这里我们通过邻接表来表示图,其中`VNode`是顶点节点,包括了顶点的数据和指向第一个邻接点的指针,`ArcNode`是边表节点,包括了邻接点的下标和指向下一个邻接点的指针。`ALGraph`是邻接表图,包括了顶点数组和顶点数、边数。 `InitGraph`函数用于初始化邻接表图,将顶点数和边数都赋值为0,同时将每个顶点的第一个邻接点指向空。 `AddArc`函数用于添加边,首先分配一个边表节点,将邻接点的下标赋值给它,然后将该节点插入到顶点的邻接点链表的最前面,最后将边数加1。 `DFS`函数是深度优先遍历算法,从第v个顶点开始遍历,首先输出该顶点的数据,然后将该顶点标记为已访问,接着遍历该顶点的每一个邻接点,如果该邻接点没有被访问过,则递归调用DFS函数。 `BFS`函数是广度优先遍历算法,从第v个顶点开始遍历,首先输出该顶点的数据,然后将该顶点标记为已访问,并将其加入到队列中,接着从队列的最前面取出一个顶点,遍历该顶点的每一个邻接点,如果该邻接点没有被访问过,则输出它的数据,标记为已访问,并将其加入到队列的最后面。 最后在`main`函数中,我们创建了一个邻接表图,然后分别调用了DFS函数和BFS函数进行遍历

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JJJ69

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值