数据结构

课程设计报告

广州大学 计算机科学与网络工程学院

计算机系 17级计科专业2班

2019年6月30日

广州大学学生实验报告

开课学院及实验室:计算机科学与工程实验室                      2019年07月01日

学院

计算机科学与网络工程学院

年级/专业/

计科172

姓名

学号

实验课程名称

数据结构课程设计

成绩

实验项目名称

神秘国度的爱情故事

指导老师

  • 实验目的
  1. 自行设计数据的存储结构
  2. 自行设计数据的数据结构
  3. 能熟练应用所学知识,有一定查阅文献及运用文献资料能力
  4. 能体现创造性思维,或有独特见解

二、实验原理

1、树的深度优先的遍历策略

2、时间截

3、树的结点深度,以及结点出现顺序的记录

4、求出两个结点的lca

5、分情况讨论c点是否在a和b结点的路径上

6、LCA转RMQ算法

7、邻接表存储结构

8、ST算法的应用

三、实验内容

神秘国度的爱情故事(难度系数 1.5)

题目要求:某个太空神秘国度中有很多美丽的小村,从太空中可以想见,小村间有路相连,更精确一点说,任意两村之间有且仅有一条路径。小村 A 中有位年轻人爱上了自己村里的美丽姑娘。每天早晨,姑娘都会去小村 B 里的面包房工作,傍晚 6 点回到家。年轻人终于决定要向姑娘表白,他打算在小村 C 等着姑娘路过的时候把爱慕说出来。问题是,他不能确定小村 C 是否在小村 B 到小村 A 之间的路径上。你可以帮他解决这个问题吗?

输入要求:输入由若干组测试数据组成。每组数据的第 1 行包含一正整数 N ( l 《 N 《 50000 ) , 代表神秘国度中小村的个数,每个小村即从0到 N - l 编号。接下来有 N -1 行输入,每行包含一条双向道路的两个端点小村的编号,中间用空格分开。之后一行包含一正整数 M ( l 《 M 《 500000 ) ,代表着该组测试问题的个数。接下来 M 行,每行给出 A 、 B 、 C 三个小村的编号,中间用空格分开。当 N 为 O 时,表示全部测试结束,不要对该数据做任何处理。

输出要求:对每一组测试给定的 A 、 B 、C,在一行里输出答案,即:如果 C 在 A 和 B 之间的路径上,输出 Yes ,否则输出 No。

思路:在这个课设的题目中,非常巧妙的提出了“任意两村之间有且仅有一条路径”,我们可以想象的到,这是n个结点且刚好有n-1条边的连通图,以任意一个结点为根结点进行广度优先遍历,便会生成一棵树。所以我们处理的方法就是对一棵树的任意两个结点求他们的最近公共祖先(lca)。这里我采用了用邻接表的方式存储图,然后每插入一个与之相连的边的时候,都会用头插法的方式更新邻接表。双向边的处理则是通过两次插入来实现。通过DFS预处理出这棵树的结点深度和结点出现次序的信息。在查询结点a和结点b之间的最近公共祖先的时候,我们可以找出结点a和结点b首次出现的次序,然后在此区间内找到深度最小的结点(这里涉及RMQ算法:即区间求最值的问题),该节点就是结点a和b的最近公共祖先。那么如何解决C点是否在A和B的路径上呢?我们可以先找出A和B的最近公共祖先为D,A和C的最近公共祖先为AC,B和C的最近公共祖先为BC。如果AC==C并且BC==C,则说明C同时是A和B的最近公共祖先,这里需要分情况讨论,如果C==D的话,则说明C就是A和B的最近公共祖先,如果C!=D,则说明C不是A和B的最近公共祖先,则A到D再走到B的路径中,不会经过C结点。如果只有AC==C或者BC==C,则说明C是A或者B中一个且只有一个结点的祖先结点。如果C是A的祖先结点,不是B的祖先结点,则说明C在A和D的路径上,则C肯定是在A和B的路径上。如果C是B的祖先结点,不是A的祖先结点,则说明C在B和D的路径上,则C肯定是在A和B的路径上。如果C不是A和B中任意一个结点的祖先结点,那么从A到B的路径上不会经过C。

随机生成树的算法我是采用了比较简单的算法,算法如下:

#include<cstdio>

#include<cstdlib>

#include<ctime>

#include<iostream>

using namespace std;

typedef long long ll;

int random(int n)

{

return (ll)rand() * rand() % n;

}

int main()

{

srand((unsigned)time(0)); // 初始化随机种子

//生成n个点,n-1条边,附带1e9的权值的树

for(int i = 2; i <= n; ++i)

{   //从点i向1~i-1 之间的点随机连一条边

int fa = random(i - 1) + 1;

int val = random(1000000000) + 1;

printf("%d %d %d\n", fa, i, val);

}

return 0;

}

主程序代码:

#include <cstdio>

#include <cstring>

#include <algorithm>

#include <iostream>

#include <stdio.h>

#include <malloc.h>

#include<ctime>

#define MAXN 1010

#define MAXM 100000

#define MAXV 1000

typedef long long ll;

using namespace std;

int random(int n)//生成小于n的随机数

{

return (ll)rand() * rand() % n;

}

typedef char InfoType;

//以下定义邻接矩阵类型

typedef struct

{

int no;                        //顶点编号

InfoType info;                 //顶点其他信息

} VertexType;                  //顶点类型

//以下定义邻接表类型

typedef struct ANode

{

int adjvex;                    //该边的邻接点编号

struct ANode* nextarc;    //指向下一条边的指针

int weight;                    //该边的相关信息,如权值(用整型表示)

} ArcNode;                          //边结点类型

typedef struct Vnode

{

InfoType info;                 //顶点其他信息

int count;                     //存放顶点入度,仅仅用于拓扑排序

ArcNode* firstarc;             //指向第一条边

} VNode;                       //邻接表头结点类型

typedef struct

{

VNode adjlist[MAXV];      //邻接表头结点数组

int n, e;                      //图中顶点数n和边数e

} AdjGraph;                        //完整的图邻接表类型

//-----------------------------------------------------------

int vs[MAXN << 1];//第i次DFS访问节点的编号

int depth[MAXN << 1];//第i次DFS访问节点的深度

int id[MAXN];//id[i] 记录在vs数组里面 i节点第一次出现的下标

int dfs_clock;//时间戳

int e,n;//点数 边数 查询数

int dp[MAXN << 1][20];//dp[i][j]存储depth数组  以下标i开始的,长度为2^j的区间里 最小值所对应的下标

//----邻接表的基本运算算法------------------------------------

void Init_CreateAdj(AdjGraph*& G,int n, int e) //初始化图的邻接表

{

int i, j;

ArcNode* p;

G = (AdjGraph*)malloc(sizeof(AdjGraph));

for (i = 1; i <= n; i++)           //给邻接表中所有头结点的指针域置初值

G->adjlist[i].firstarc = NULL;

G->n = n; G->e = e;

}

void AddNode(AdjGraph*& G, int u, int v) //由边集创建图

{

ArcNode* p;

p = (ArcNode*)malloc(sizeof(ArcNode)); //创建一个结点p

p->adjvex = v;

p->nextarc = G->adjlist[u].firstarc;    //采用头插法插入结点p

G->adjlist[u].firstarc = p;

}

void DispAdj(AdjGraph* G)  //输出邻接表G

{

int i;

ArcNode* p;

for (i = 1; i <= G->n; i++)

{

p = G->adjlist[i].firstarc;

printf("%3d: ", i);

while (p != NULL)

{

printf("%3d[%d]→", p->adjvex, p->weight);

p = p->nextarc;

}

printf("∧\n");

}

}

void DestroyAdj(AdjGraph*& G)      //销毁图的邻接表

{

int i;

ArcNode* pre, * p;

for (i = 1; i < G->n; i++)         //扫描所有的单链表

{

pre = G->adjlist[i].firstarc;  //p指向第i个单链表的首结点

if (pre != NULL)

{

p = pre->nextarc;

while (p != NULL)         //释放第i个单链表的所有边结点

{

free(pre);

pre = p; p = p->nextarc;

}

free(pre);

}

}

free(G);                       //释放头结点数组

}

//------------------------------------------------------------

//深度优先遍历算法

int visited[MAXV] = { 0 };

void DFS(AdjGraph* G, int v, int d)

{

ArcNode* p;

visited[v] = 1;                   //置已访问标记

//printf("%d  ", v);//输出被访问顶点的编号

id[v] = dfs_clock;//记录第一次出现的位置

vs[dfs_clock] = v;

depth[dfs_clock++] = d;

p = G->adjlist[v].firstarc;        //p指向顶点v的第一条弧的弧头结点

while (p != NULL)

{

if (visited[p->adjvex] == 0)   //若p->adjvex顶点未访问,递归访问它

{

DFS(G, p->adjvex, d + 1);

vs[dfs_clock] = v;//类似 回溯

depth[dfs_clock++] = d;

}

p = p->nextarc;                //p指向顶点v的下一条弧的弧头结点

}

}

//---------------------------------------------------------

void find_depth(AdjGraph* G)

{

dfs_clock = 1;

memset(vs, 0, sizeof(vs));

memset(id, 0, sizeof(id));

memset(depth, 0, sizeof(depth));

DFS(G, 1, 0);//遍历

}

void RMQ_init(int NN)//预处理 区间最小值 预处理ST表,数组中共NN个元素

{

for (int i = 1; i <= NN; i++)

dp[i][0] = i;//初始化

for (int j = 1; (1 << j) <= NN; j++)

{

for (int i = 1; i + (1 << j) - 1 <= NN; i++)

{

int a = dp[i][j - 1];

int b = dp[i + (1 << (j - 1))][j - 1];

if (depth[a] <= depth[b])//f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);这里把a,b注入dp数组中,所以dp里面存的是不同结点,达到比较深度,从而得出结点的目的

dp[i][j] = a;//比如dp[1][3]是指从第一个位置开始,长度为2^3的序列里深度最小的结点

else

dp[i][j] = b;

}

}

}

int query(int L, int R)//查询操作  ans = max(f[l][k], f[r - (1 << k) + 1][k]);

{

//查询L <= i <= R 里面使得depth[i]最小的值 返回对应下标

int k = 0;

while ((1 << (k + 1)) <= R - L + 1) k++;

int a = dp[L][k];

int b = dp[R - (1 << k) + 1][k];

if (depth[a] <= depth[b])

return a;

else

return b;

}

int LCA(int u, int v)

{

int x = id[u];//比较大小 小的当作左区间 大的当作右区间

int y = id[v];

if (x > y)

return vs[query(y, x)];

else

return vs[query(x, y)];

}

void solve(int a, int b, int c)

{

int d = LCA(a, b);      //找出a和b结点的最近公共祖先为d

int ac = LCA(a, c);     //找出a和c结点的最近公共祖先为ac

int bc = LCA(b, c);     //找出b和c结点的最近公共祖先为bc

bool flag = false;

if (ac == c && bc == c) {      //如果ac==c并且bc==c,说明c结点是a和b结点的公共祖先

if (c == d) {      //如果c==d,说明c就是a和b的最近公共祖先,c必定在a和b的路径上

flag = true;

}

else

flag = false;                                     //如果c!=d,说明c不是a和b的最近公共祖先,a和b的路径上不包括c

}

else if (ac == c || bc == c) {                               //c是a的祖先或者是b的祖先,说明c在a到d的路径上或者在b到d的路径上

flag = true;                                          //此时c一定是a和b路径上的点

}

else {

flag = false;                                         //如果c不是a的祖先,也不是b的祖先,则a和b的路径上不会经过c点

}

if (flag)

cout << "村子" << c << "在村子" << a << "和村子" << b << "的路径上" << endl;

else

cout << "村子" << c << "不在村子" << a << "和村子" << b << "的路径上" << endl;

}

void input()

{

printf("下标:  ");

for (int i = 1; i < dfs_clock; i++)

printf("%d  ", i);

printf("\n");

printf("vs:    ");

for (int i = 1; i < dfs_clock; i++)

printf("%d  ", vs[i]);

printf("\n");

printf("depth: ");

for (int i = 1; i < dfs_clock; i++)

printf("%d  ", depth[i]);

printf("\n");

printf("下标:  ");

for (int i = 1; i <= (e+1); i++)

printf("%d  ", i);

printf("\n");

printf("id:    ");

for (int i = 1; i <= (e + 1); i++)

printf("%d  ", id[i]);

printf("\n");

}

int main()

{

AdjGraph* G;

cout << "输入村庄数和边数" << endl;

cin >> n >> e;

Init_CreateAdj(G,n, e);            //建立邻接表

cout << "下面开始自动生成n个结点的树:"<<endl;

srand((unsigned)time(0)); // 初始化随机种子

for (int i = 2; i <= n; ++i)

{

int fa = random(i - 1) + 1;

AddNode(G, i, fa);

AddNode(G, fa, i);

}

printf("图G的邻接表:\n");

DispAdj(G);                    //输出邻接表G

find_depth(G);//DFS遍历整个树 求出所需要的信息

RMQ_init(dfs_clock - 1);

input();

int m, a, b, c;

cout << "输入样例结束,请输入查询次数:" << endl;

cin >> m;

cout << "请输入A,B,C" << endl;

while (m--) {

cin >> a >> b >> c;                   //输入结点编号a,b和c

solve(a, b, c);              //询问c结点是否在a和b结点的路径上

}

DestroyAdj(G);

return 0;

}

四,程序验证

程序运行结果:

.算法的时间复度

由于本题采用了ST算法,ST算法的时间包括复杂度是O(nlogn)的建表和O(1)的查询。另外本题采用邻接表的存储结构,在进行一次深度优先遍历时,最坏时可能需要将链表中所有结点都遍历完(尤其是有向图中),此时时间复杂度自然就是O(e)了。

.存在的问题及体会

在本次实验报告中,选择了第三个课题“神秘国度的爱情故事”。

在一开始选择实现的算法的时候,我主要考虑了能够简单解决问题的暴力(即每次查询均进行广度优先遍历)算法,这个算法简答明了易懂,但效率是低效的,然后我上网查询了相关资料决定采用LCA+ST算法,LCA+ST算法可以在预处理一遍N个结点后,很稳定的在O(ln(N))的时间复杂度找出任意两个结点之间的最近公共祖先,然后通过AB,AC,BC之间的最近公共祖先的关系可以判断C是否在A和B结点的路径上的问题。我的做法是基于dfs深度优先搜索的,在深度优先的过程中,把结点的深度信息和结点出现的次序即遍历的次序分别存在三个数组里,所以LCA+ST的做法实际是牺牲了空间来争取时间。如果村庄的数量极其庞大可能程序会出现问题。因此这是需要解决的问题。

数据结构是计算机专业学生核心的课程,学好这门课程需要有耐性,有恒心,不断努力,不断实践。最后衷心感谢老师这一学期来的教导!

参考网站:https://blog.csdn.net/chenzhenyu123456/article/details/47359859

https://blog.csdn.net/forever_dreams/article/details/81127189

数据结构课程设计 神秘国度的爱情故事相关推荐

  1. 数据结构课程设计-神秘国度的爱情故事-LCA:tarjan+离线/树链剖分/暴力

    1.无脑暴力dfs:   O(n*m) 2.LCA/tarjan+离线处理: O(n+m) 3.LCA/树链剖分: O(nlogn+m)~O(nlogn+mlogn) 4.LCA/倍增思想(有空再补) ...

  2. 【广州大学】数据结构课程设计:神秘国度的爱情故事

    数据结构课程设计报告 广州大学 计算机科学与网络工程学院 计算机系 19级网络工程专业网络194班 超级菜狗 (学号:19062000) (班内序号:xxx) 完成时间:2021年1月11日 一.课程 ...

  3. 神秘国度的爱情故事——广州大学课程设计

    神秘国度的爱情故事 [问题描述] 某个太空神秘国度中有很多美丽的小村,从太空中可以想见,小村间有路相连,更精确一点说,任意两村之间有且仅有一条路径.小村 A 中有位年轻人爱上了自己村里的美丽姑娘.每天 ...

  4. 神秘国度的爱情故事 数据结构课设-广州大学

    神秘国度的爱情故事 数据结构课设-广州大学 ps:本次课设程序不仅需要解决问题,更需要注重代码和算法的优化和数据测试分析      直接广度优先实现的方法时间复杂度为O(QN),优化后的方法是lca+ ...

  5. 神秘国度的爱情故事--数据结构课程设计

    结论:最开始N个结点的树,处理M组数据,采用深度优先搜索,总时间复杂度为O(NM).优化方法是找最近公共祖先(lca)的倍增法.N个结点的树,每次找最近公共祖先的时间复杂度为O(logN),处理M组数 ...

  6. 数据结构课程设计——机票售卖系统(C++)

    引言 这学期最后的数据结构课程设计需要我们完成一个简单的小程序,我选择了一个机票售卖系统,实现了一些基本的功能:因为时间给的比较短,又赶在复习周补课,所以并没有什么突出的地方,我就在这里聊聊我的代码实 ...

  7. 数据结构课程设计---最长公共子串

    数据结构课程设计,由用户输入两个字符串串X和Y,再由用户输入一个任意的字符串Z,实现以下功能: ①如果字符串Z是字符串X的子串,则显示Z在X中的位置并记录,如果字符串Z是字符串Y的子串,则显示Z在Y中 ...

  8. 设树采用孩子兄弟表示法存放.用类c语言设计算法计算树的高度.,(数据结构课程设计分类题目.doc...

    (数据结构课程设计分类题目 线性表 顺序表: 1.设有一元素为整数的线性表L=(a1,a2,a3,-,an),存放在一维数组A[N]中,设计一个算法,以表中an作为参考元素,将该表分为左.右两部分,其 ...

  9. c语言数据结构五子棋实验报告,数据结构课程设计-五子棋

    数据结构课程设计-五子棋 姓 名: 学 院: 计算机与通信学院 班 级: 通信工程 101 班 指导老师: 目录一.需求分析 31.1 开发背景 .32.2 功能简介 .3二.系统设计 42.1 函数 ...

最新文章

  1. spi nor flash使用汇总
  2. 十分钟学习nginx
  3. 逆向入门--第一次的HelloWorld
  4. if js 判断成绩等级_javascript://8种方法根据分数判断等级
  5. vb 读取mysql所有表名_vb怎么列举出一个mdb数据库里面所有表名?
  6. Linux用户配置文件(第二版)
  7. Atom Latex Settings
  8. tensorflow英语怎么读_英语不行?你可以试试TensorFlow官方中文版教程
  9. 计算机课作业在线管理,iwork学生作业在线系统
  10. Nginx与TCP协议的关系
  11. 【解决】联想拯救者/MT7921网卡 ubuntu里 wifi/蓝牙 无法识别连接
  12. Linux使用代理服务器上网
  13. Java程序员面试分类真题(后附答案解析)
  14. Photoshop制作电影胶片效果
  15. borderColor与CGColor/UIColor
  16. 科技云报道:新基建已到来,网络安全建设跟上了吗?
  17. 统计打印字符串arg中每个字符出现的次数
  18. Linux/服务器中文件下载
  19. 小马哥----高仿三星note3 n9002 9006主板型号A202 刷机后修复返回键失灵实例说明
  20. 后处理方法NMS、Soft-NMS、 Softer-NMS、WBC、DIoUNMS 、NMS替代算法Confluence

热门文章

  1. SQL中十六进制和字符串之间的转换
  2. 子元素自动撑开父元素空间
  3. vue project vlog
  4. html 发言样式,HTML样式
  5. 淘宝商城 入住费用
  6. 为用户提供确定性——互联网平台建设
  7. office正在更新,请稍后(无法正常启动,错误0xc0000142)
  8. vmware安装openEuler+基本环境部署
  9. 阿迪达斯进博会展示首款碳足迹低于3千克运动鞋;霍尼韦尔携30余项产品和解决方案亮相进博会 | 美通社头条...
  10. 谐音单词背诵,持续补充中......欢迎留言添加