前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >算法基础-非线性结构

算法基础-非线性结构

作者头像
DearXuan
发布于 2022-02-24 05:19:42
发布于 2022-02-24 05:19:42
1.1K00
代码可运行
举报
运行总次数:0
代码可运行

非线性结构的概念

线性结构是指逻辑上各个结点一一对应的关系,例如链表,即使它在储存上可能并不是顺序储存

非线性结构是指逻辑上存在一对多关系的结点的结构,例如树,图等。它们的任何结点都可能对应着其它多个不同的结点

有根树

二叉树

二叉树在逻辑上是一种树状结构,最顶上的结点被称为根结点,每个结点都有 key, lChild和rChild值,分别记录该结点的值,左子树指针和右子树指针,当key为空(NULL)时,该结点为根结点。二叉树的左右子树可能为空,也可能根本就没有左右子树,但是除了左右子树以外,不能出现第三棵子树

多叉树

若将二叉树的左右子树推广到无限制子树的结构,便成为多叉树。多叉树的子树数量是不确定的,因此需要用链表储存

下面给出实现多叉树结构的代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Node;
struct Tree;
 
struct Node{
    Tree* tree;
    Node* next;
};
 
struct Tree{
    int value;
    Node* child;
};

树的遍历

现有一棵如下图所示的二叉树

深度优先遍历

深度优先遍历的思想是先访问完当前结点对应的整个子树,然后再访问另一个结点

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void DFS(Tree* tree){
    if(tree != NULL){
        DFS(tree->lChild);
        printf("%d ",tree->value);
        DFS(tree->rChild);
    }
}

在上面的代码中,先访问了当前结点的左子树,然后才输出自己结点,最后访问右子树,该遍历方法属于中序遍历,即输出自己结点的代码位于输出左右子树结点的代码的中间,输出顺序为:左子树→自己→右子树

同理先序遍历的顺序为:自己→左子树→右子树

先序遍历的顺序为:左子树→右子树→自己

上图所示的二叉树的中序遍历顺序为:7 3 8 1 9 4 0 5 2 6

广度优先遍历

广度优先遍历需要借助队列实现,它按照结点的深度顺序输出结点。首先把根结点加入队列,从根节点开始,每当输出一个结点,就把它的子节点全部加入队列,直到队列为空

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void BFS(Tree* tree){
    if(tree == NULL) return;
    queue<Tree*>* q = new queue<Tree*>();
    q->push(tree);
    while (!q->empty()){
        Tree* front = q->front();
        q->pop();
        printf("%d ",front->value);
        if(front->lChild != NULL) q->push(front->lChild);
        if(front->rChild != NULL) q->push(front->rChild);
        free(front);
    }
}

上图所示二叉树的广度优先遍历顺序为:0 1 2 3 4 5 6 7 8 9

对于图的概念,可以参考下列文章

人工智能基础-图论初步 - DearXuan的主页

矩阵法

使用矩阵 M 来表示图 G,将 G 中的每个结点量化为一个数字,M(i,j)=0表示 G 中 i 和 j 所代表的结点不相邻,M(i,j)=1表示 G 中 i 和 j 代表的结点相邻

显然同一个图 G 中结点 V 的个数是固定的,设为 n,因此矩阵 M 是一个 n 阶的方阵

例如以下无向图

它用矩阵表示为

0

1

2

3

4

0

1

1

1

0

1

1

1

1

0

1

1

2

1

0

1

1

0

3

0

1

1

1

0

4

1

1

0

0

1

实际上 M(i,j) 除了表示相邻状态以外,还可以用来表示权值,例如路径长度,只需要把 1 改成具体权重即可,这时 0 可能会引起误解,因此我们可以定义一个特殊的或者正常情况下不可能取到的数来表示不相邻,例如 -1 或 999

邻接链表法

当边数远小于顶点数时,采用矩阵表示会严重浪费空间

邻接链表法将图 G 的所有顶点具体化为一个结点,并保存在长度固定的数组中,每个结点都储存了当前结点的值和图 G 中对应顶点的所有边

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Vertex{
    int value;
    Edge* edge;
};

struct Edge{
    Vertex* vertex;
    Edge* next;
};

图的遍历

与二叉树类似,图的遍历也可以分为深度优先遍历和广度优先遍历,但不同之处在于,二叉树中不存在回路,而图中存在回路,所以会出现重复遍历的情况,因此我们需要给每个结点额外增加一个变量,以储存该结点是否已经被访问过

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Vertex{
    int value;
    bool isVisited;
    Edge* edge;
};
 
struct Edge{
    Vertex* vertex;
    Edge* next;
};
深度优先遍历

每当访问一个结点时,如果该结点存在多个相邻结点,则按顺序把其中一个相邻结点的全部相邻结点都访问完,再访问另一个相邻结点

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void DFS(Vertex* vertex){
    if(vertex != NULL){
        printf("%d ",vertex->value);
        vertex->isVisited = true;
    }
    Edge* edge = vertex->edge;
    while (edge != NULL){
        if(!edge->vertex->isVisited){
            DFS(edge->vertex);
        }
        edge = edge->next;
    }
}

一个结点开始进行深度优先搜索的时间点称为发现时间,搜索完该结点所有边的时间点称为结束时间

广度优先遍历

每当访问一个结点时,先把该结点的相邻结点全部访问完,再访问相邻结点的相邻结点,即一层层访问下去

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void BFS(Vertex* vertex){
    queue<Vertex*> q;
    q.push(vertex);
    while (!q.empty()){
        Vertex* front = q.front();
        printf("%d ",front->value);
        front->isVisited = true;
        q.pop();
        Edge* edge = front->edge;
        while (edge != NULL){
            if(!edge->vertex->isVisited){
                q.push(edge->vertex);
                edge->vertex->isVisited = true;
            }
            edge = edge->next;
        }
    }
}

拓扑序列

偏序关系

将有向图 G 中的所有顶点看作一个集合,图 G 中的每个有向边都是关于集合中两个顶点的关系,若有向图 G 中不存在回路,则有向图 G 中的每条边共同构成了一个偏序关系。如果存在从顶点 A 到顶点 B 的通路,则称 A 在 B 的前面

线性次序

将有向图 G 中的所有顶点线性排列,使得任意标注一条有向边后,都是从左指向右边

拓扑排序

上面的经典例题展示了每天起床穿衣服的先后次序,边上的数字为深度优先搜索的发现时间和结束时间

将其用数字表示

我们可以从图中得到一个拓扑序列:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
8,6,3,4,0,1,7,2,5

这样无论我们标出哪一条有向边,它在拓扑序列上总是从左边指向右边

实际上这个序列恰好是深度优先搜索的结束时间的降序,下面我们用代码来求出这个图的各个顶点的发现时间和结束时间,我们将发现时间和结束时间作为顶点的参数添加到结构体内

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct Vertex{
    int index;
    int startTime;
    int endTime;
    bool isVisited;
    Edge* edge;
};
 
struct Edge{
    Vertex* vertex;
    Edge* next;
};

将原本的图用一个类来表示

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Graph{
public:
    int size;
    Graph(int size);
    void addEdge(int edge1, int edge2);
    Vertex* get(int index);
    void GetDFSTime();
 
private:
    Vertex** G;
    void DFS(int index, int* order);
};

下面是具体实现代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Graph::Graph(int size) {
    this->size = size;
    G = (Vertex**) malloc(size * sizeof(Vertex));
    for(int i=0;i<size;i++){
        G[i] = (Vertex*)malloc(sizeof(Vertex));
        G[i]->index = i;
        G[i]->isVisited = false;
        G[i]->edge = NULL;
    }
}
 
void Graph::addEdge(int edge1, int edge2) {
    Edge* edge = (Edge*)malloc(sizeof(Edge));
    edge->vertex = G[edge2];
    edge->next = NULL;
    if(G[edge1]->edge == NULL){
        G[edge1]->edge = edge;
    }else{
        Edge* e = G[edge1]->edge;
        while (e->next != NULL){e = e->next;}
        e->next = edge;
    }
}
 
Vertex* Graph::get(int index) {
    if(index < 0 || index >= size){
        return NULL;
    }else{
        return G[index];
    }
}
 
void Graph::GetDFSTime(){
    int order = 0;
    for(int i=0;i<size;i++){
        DFS(i,&order);
    }
    for(int i=0;i<size;i++){
        printf("index =%2d: %2d, %2d\n",G[i]->index, G[i]->startTime, G[i]->endTime);
    }
}
 
void Graph::DFS(int index, int* order){
    Vertex* vertex = get(index);
    if(vertex == NULL || vertex->isVisited) return;
    G[index]->startTime = ++(*order);
    vertex->isVisited = true;
    Edge* edge = vertex->edge;
    while (edge != NULL){
        if(!edge->vertex->isVisited){
            DFS(edge->vertex->index, order);
        }
        edge = edge->next;
    }
    G[index]->endTime = ++(*order);
}

进行测试

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
    Graph g(9);
    g.addEdge(0,1);
    g.addEdge(0,7);
    g.addEdge(1,2);
    g.addEdge(1,7);
    g.addEdge(2,5);
    g.addEdge(3,2);
    g.addEdge(3,4);
    g.addEdge(4,5);
    g.addEdge(6,7);
    g.GetDFSTime();
}

得到运行结果

将各个顶点按结束时间(第二个)降序排列,得到的序列即是拓扑序列

DFS与拓扑序列的关系

在上面的代码中,我们直接用DFS的结束时间来作为拓扑排序的依据,下面给出依据

我们只需要证明:如果存在从 A 到 B 的有向边,则 A 的结束时间大于 B 的结束时间

我们知道,在深度优先遍历中,如果存在从 A 到 B 的有向边,那么DFS会先访问 A,然后访问 B,等访问完 B 的全部子结点后,才会回溯到 A,因此 B 总是在 A 之前结束,即 A 的结束时间大于 B 的结束时间

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年2月23日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
2021年 最新 多阶段构建dockerfile实现java源码编译打jar包并做成镜像
多阶段构建指在Dockerfile中使用多个FROM语句,每个FROM指令都可以使用不同的基础镜像,并且是一个独立的子构建阶段。使用多阶段构建打包Java应用具有构建安全、构建速度快、镜像文件体积小等优点.
猫头虎
2024/04/07
5240
2021年 最新 多阶段构建dockerfile实现java源码编译打jar包并做成镜像
单元测试(Spring)
YGingko
2017/12/28
4.8K0
第三十五章:SpringBoot与单元测试的小秘密
单元测试对于开发人员来说是非常熟悉的,我们每天的工作也都是围绕着开发与测试进行的,在最早的时候测试都是采用工具Debug模式进行调试程序,后来Junit的诞生也让程序测试发生了很大的变化。我们今天来讲解下基于SpringBoot结合Junit怎么来完成单元测试。 本章目的 基于SpringBoot平台整合Junit分别完成客户端、服务端的单元测试。 SpringBoot 企业级核心技术学习专题 专题 专题名称 专题描述 001 Spring Boot 核心技术 讲解SpringBoot一些企业级层面的核心组
恒宇少年
2018/06/27
1.4K0
quartz
Quartz是功能强大的开源作业调度库,几乎可以集成到任何Java应用程序中-从最小的独立应用程序到最大的电子商务系统。Quartz可用于创建简单或复杂的计划,以执行数以万计,数以万计的工作。任务定义为标准Java组件的作业,它们实际上可以执行您可以对其执行的任何编程操作。Quartz Scheduler包含许多企业级功能,例如对JTA事务和集群的支持。
阿超
2022/08/16
1.4K0
quartz
SpringBoot单元测试(实例)
这里我们分别使用@WebMvcTest和@SpringBootTest两种方式测试一个控制器方法是否满足测试用例。
别团等shy哥发育
2023/02/25
1.3K0
SpringBoot单元测试(实例)
基于SpringBoot聊单元测试的分层
之前分享了关于质量内建的话题关于单元测试引起了大家的讨论,对于单元测试这件事情本身是比较熟悉的,但大家的反馈是比较难执行,矛盾在于很多测试做不了单元测试,或者让测试做性价比不是很高,这件事情推给开发之后又容易不了了之,其中一个很重要的点是,测试和开发没有同频对话的能力,各种细节难以敲定,落地的实际价值不容易度量,所以这篇文章我就基于常见的springboot框架,聊一聊单元测试分层的几种实践方式,从测试的视角给同学们一些知识面的拓展,也让大家熟悉下单元测试的常见玩法。
周辰晨
2022/09/20
8410
基于SpringBoot聊单元测试的分层
SpringBoot学习笔记(二)——SpringBoot测试JUnit5、 SpringBoot 配置、Spring IoC与自动装配
Spring Test与JUnit等其他测试框架结合起来,提供了便捷高效的测试手段。而Spring Boot Test 是在Spring Test之上的再次封装,增加了切片测试,增强了mock能力。
张果
2022/05/09
4.3K0
SpringBoot学习笔记(二)——SpringBoot测试JUnit5、 SpringBoot 配置、Spring IoC与自动装配
Spring Boot 2.x基础教程:事务管理入门
我们在开发企业应用时,通常业务人员的一个操作实际上是对数据库读写的多步操作的结合。由于数据操作在顺序执行的过程中,任何一步操作都有可能发生异常,异常会导致后续操作无法完成,此时由于业务逻辑并未正确的完成,之前成功操作的数据并不可靠,如果要让这个业务正确的执行下去,通常有实现方式:
程序猿DD
2020/07/14
6870
SpringBoot2.x 单元测试
我曾经在 单元测试指南 一文中写到过单元测试的必要性和 Java 单元测试相关的工具及方法。单元测试能帮助我们在早期就规避、发现和修复很多不易察觉的 bug 和漏洞,而且更能保障后期的需求变动和代码重构时所带来的隐患,减少测试成本和维护成本。在 SpringBoot2.x 集成和写单元测试更加容易了。
Abalone
2022/07/14
1.8K0
SpringBoot对单元测试支持、常用单元测试功能使用实例
Spring Boot 提供了许多注解和工具帮助开发人员测试应用,在其官方文档中也用了大量篇幅介绍单元测试的使用。在谷歌每周的 TGIF (ThanksGod, it's Friday)员工大会中有一项就是 宣布-周单元测试竞赛获胜的工程师。谷歌之所以这么重视单元测试,就是为了保证程序质量,鼓励大家多写测试代码。国内大多数开发人员对单元测试有所忽视,这也是我写本章内容的原因所在。
愿天堂没有BUG
2022/10/28
1.8K0
SpringBoot对单元测试支持、常用单元测试功能使用实例
springboot之单元测试
来源:http://www.51testing.com springboot在写完之后,肯定都需要进行单元测试,如下给出一些样例   工程层次结构如图   代码如下:   controller: pa
顾翔
2019/12/11
2940
springboot之单元测试
SpringBoot2版本Caused by: java.sql.SQLSyntaxErrorException: Table 'dinner.hibernate_sequenc
1、SpringBoot2版本Caused by: java.sql.SQLSyntaxErrorException: Table 'dinner.hibernate_sequenc报错。
别先生
2019/07/10
1.5K0
junit+mock+spring-test构建后台单元测试
    在对一些现有代码进行修改时,或者修改现有BUG的时候。都有可能对已有的代码产生影响,产生新的问题。那么怎么能避免新问题的产生呢?那就是执行回归测试,但如果是人工进行费时费力,测试的还不全面。况且一般在进度的压力下,相信很少有人会因为修改一个问题而去回归测试以前的功能。
用户2193479
2019/02/22
3.5K0
单元测试指南
在我们公司中要做单元测试,确实比较难,因为公司缺少这种氛围,有也只是局部的,大多数工程师没有这方面的习惯和素养,很多人都是有一定的抵触的心理,经过我私下的了解大概有以下几种原因吧。
Abalone
2022/07/14
6.3K0
单元测试指南
【SpringBoot】Http请求统一异常(返回数据)处理与单元测试
这个ResultUtil中的方法,其实写在BaseController中也挺不错的
谙忆
2021/01/21
8270
【SpringBoot】Http请求统一异常(返回数据)处理与单元测试
springboot使用@SpringBootTest注解进行单元测试「建议收藏」
@SpringBootTest注解是SpringBoot自1.4.0版本开始引入的一个用于测试的注解。基本用法如下:
全栈程序员站长
2022/07/29
3.1K0
一文让你了解微服务契约测试
谈到微服务,大家都想到契约测试,到底什么是契约测试呢,为什么要使用契约测试呢,关于这样的文章很多,本文将结合Spring Boot让你了解微服务契约测试。
顾翔
2024/09/10
1340
一文让你了解微服务契约测试
【从零开始】springboot单元测试(一)
工作十来年,代码也写了不少,接受过“祖传屎山”,也经历过非常优雅规范的流程,一直心里有些遗憾的,是后来绝大部分公司(不分大小)都忽略了最低成本质量保证的方法:单元测试。虽然很多公司在提,但是很少有公司愿意给程序猿分配写单元测试相应的工作量,因为这玩意表面看起来投入收益不成正比,似乎都是在做无用功,但是在产品的整个生命周期,单元测试却是产品质量的最低保证。
小尘哥
2022/12/07
4150
【从零开始】springboot单元测试(一)
一起玩转微服务(11)——一切从简单开始
使用Spring Bboot是快乐并且简单的,不需要繁琐的配置就能够完成一套非常强大的应用。
cloudskyme
2020/06/28
6640
一起玩转微服务(11)——一切从简单开始
Springboot 使用单元测试
单元测试其实是一种廉价的技术,是由开发者创建运行测试代码,用于对程序模块(软件设计的最小单位)进行正确性检验的一种做法。 而所谓的最小单元,就是指应用的最小可测试部件。 在面向对象领域,最小单元对应于类的某个成员方法。
Java3y
2019/12/05
1.2K0
Springboot 使用单元测试
推荐阅读
相关推荐
2021年 最新 多阶段构建dockerfile实现java源码编译打jar包并做成镜像
更多 >
LV.1
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档