<Zhuuu_ZZ>Spark项目实战-航班飞行网图分析

本文档介绍了如何使用SparkGraphX处理航班飞行数据,构建航班飞行网图并进行深入分析。首先,通过加载CSV数据并创建顶点和边的RDD来构建属性图。接着,统计了机场和航线的数量,找出了最长的飞行航线、最繁忙的机场以及最重要的飞行航线。最后,通过解决单源最短路径问题寻找最便宜的飞行航线。整个过程涉及到了PageRank算法和SSSP算法的实现。

一 项目技能

  • Spark GraphX API
    • vertices、edges、triplets、
    • numEdges、numVertices
    • inDegrees、outDegrees、degrees
    • mapVertices、mapEdges、mapTriplets
  • Spark GraphX PageRank
  • Spark GraphX Pregel

二 项目需求

  • 探索航班飞行网图数据
  • 构建航班飞行网图
  • 使用Spark GraphX完成下列任务
    • 统计航班飞行网图中机场的数量
    • 统计航班飞行网图中航线的数量
    • 计算最长的飞行航线(Point to Point)
    • 找出最繁忙的机场
    • 找出最重要的飞行航线(PageRank)
    • 找出最便宜的飞行航线(SSSP)

三 数据探索

下载数据

链接: 航班飞行网图数据.提取码:gvyd

数据格式

  • 文件格式为CSV,字段之间分隔符为“,”
  • 依次为:#日、周#、航空公司、飞机注册号、航班号、起飞机场编号、起飞机场、到达机场编号、到达机场、预计起飞时间(时分)、起飞时间、起飞延迟(分钟)、到达预计时间、到达时间、到达延迟(分钟)、预计飞行时间、飞行距离
    在这里插入图片描述

四 项目实战

构建航班飞行网图

  • 创建属性图Graph[VD,ED]
    • 装载CSV为RDD,每个机场作为顶点。关键字段:起飞机场编号、起飞机场、到达机场编号、到达机场、飞行距离
    • 初始化顶点集airports:RDD[(VertexId,String)],顶点属性为机场名称
    • 初始化边集lines:RDD[Edge],边属性为飞行距离
  val flight: RDD[Array[String]] = sc.textFile("in/project/fly.csv").repartition(1).map(_.split(","))
    //flatMap的返回值需要是TraversableOnce,即可反复迭代的,如数组集合等
    //一行数据进来会取下标5,6做一个元素,再取下标7,8做另外一个元素,然后所有元素返回进入一个集合中使维度相同
    //flatMap是扁平化函数,如“hello world”,“hello spark”进入.flatMap(_.split(","))会是hello,world,hello,spark而进入map(_.split(","))则是Array(hello, world), Array(hello,spark)
    //也就是flatMap会切割成一个个独立的元素,并把这些元素放入一个集合中使之成为一个维度。
 val vertex: RDD[(VertexId, String)] = flight.flatMap(x=>Array((x(5).toLong,x(6)),(x(7).toLong,x(8)))).distinct()
 val lines: RDD[Edge[PartitionID]] = flight.map(x=>(x(5).toLong,x(7).toLong,x(16).toInt)).distinct().map(x=>Edge(x._1,x._2,x._3))

 val graph: Graph[String, PartitionID] = Graph(vertex,lines)

统计航班飞行网图中机场与航线的数量

  • 机场数量
  • 航线数量
println("机场数量:"+graph.numVertices)
println("航线数量:"+graph.numEdges)

计算最长的飞行航线

  • 最大的边属性
    • 对triplets按飞行距离排序(降序)并取第一个
graph.triplets.sortBy(x => x.attr * (-1)).take(2).foreach(x=>println("最长的航线:"+x))

找出最繁忙的机场

-哪个机场到达航班最多

  • 计算顶点的入度并排序
graph.degrees.sortBy(x=>x._2,false).take(1).foreach(println)

找出最重要的飞行航线

  • PageRank
    • 收敛误差:0.05
graph.pageRank(0.05).vertices.sortBy(-_._2).take(1).foreach(println)
//等价
 graph.pageRank(0.05).vertices.takeOrdered(1)(Ordering.by(-_._2)).foreach(println)

找出最便宜的飞行航线

  • 定价模型
    • price = 180.0 + distance * 0.15
  • SSSP问题
    • 从初始指定的源点到达任意点的最短距离
  • pregel
    • 初始化源点(0)与其它顶点(Double.PositiveInfinity)
  • 初始消息(Double.PositiveInfinity)
  • vprog函数计算最小值
  • sendMsg函数计算进行是否下一个迭代
  • mergeMsg函数合并接受的消息,取最小值
   //定义图中起始顶点id
    val srcVertexId=12478L
    //修改边属性,使之成为价格
    //修改顶点属性,起始顶点为0,其余全部为正无穷大
    val initialGraph=graph.mapEdges(e=>180.0+e.attr*0.15)
    .mapVertices((id,prop)=>{
      if(id==srcVertexId)
        0
      else
        Double.PositiveInfinity
    })
    //调用pregel
  val pregelGraph: Graph[Double, Double] = initialGraph.pregel(
      Double.PositiveInfinity,
      Int.MaxValue,
      EdgeDirection.Out
    )(
      //接收消息函数:接收下面sendMsg的信息
      (vid: VertexId, vd: Double, distMsg: Double) => {
        //返回相同VertexId的情况下,发送顶点的属性加上边属性和与目标顶点属性的最小值
        val minDist: Double = math.min(vd, distMsg)
       // println(s"顶点${vid},属性${vd},收到消息${distMsg},合并后的属性${minDist}")
        minDist
      },
      //发送消息函数:先发送后接受,所以先执行这一步
      (edgeTriplet: EdgeTriplet[Double, Double]) => {
        if (edgeTriplet.srcAttr + edgeTriplet.attr < edgeTriplet.dstAttr) { //如果发送顶点的属性加上边属性小于目标顶点属性
        //  println(s"顶点${edgeTriplet.srcId} 给 顶点${edgeTriplet.dstId} 发送消息 ${edgeTriplet.srcAttr + edgeTriplet.attr}")
          //则返回一个(目标顶点id,发送顶点属性加上边属性)
          Iterator[(VertexId, Double)]((edgeTriplet.dstId, edgeTriplet.srcAttr + edgeTriplet.attr))
        } else { //否则返回空,即消息发送失败
          Iterator.empty
        }
      },
      //合并消息函数:指有两个及以上的激活态顶点给同一个顶点发送消息,且都发送成功,则执行完sendMsg后调用mergeMsg再执行vprog
      (msg1: Double, msg2: Double) =>{
       // println("mergeMsg:",msg1,msg2) 
        math.min(msg1, msg2) //返回各自激活态顶点属性加上各自边的属性之和的最小值进入vprog函数
      }
    )
    pregelGraph.vertices.sortBy(_._2).take(3).foreach(println)
 

/*
(12478,0.0)
(10821,207.6)
(10721,208.05)
#include<stdio.h> #include<iostream> #include<stdlib.h> using namespace std; #define maxsize 100 #define error -1 #define overflow -2 #define false -3 #define ok 1 typedef int VexTexType; typedef int ArcType; typedef int Status; typedef int QElemType; int Visited[maxsize] = {0}; typedef struct{ VexTexType vex[maxsize]; ArcType arcs[maxsize][maxsize]; int vexnum,arcnum; }AMGraph; typedef struct QNode{ int data; struct QNode *next; }QNode,*QueuePtr; typedef struct{ QueuePtr front; QueuePtr rear; }LinkQueue; //队列函数 //初始化队列 Status InitQueue(LinkQueue &Q) { Q.front = (QueuePtr)malloc(sizeof(QNode)); if(Q.front == 0) { exit(overflow); } Q.rear = Q.front; Q.front->next = 0; return ok; } //队列插入元素 Status EnQueue(LinkQueue &Q,int p) { if(Q.front == 0) { return error; } QueuePtr q; q = (QueuePtr)malloc(sizeof(QNode)); q->next = 0; q->data = p; Q.rear->next = q; Q.rear = q; return ok; } //出队函数 Status DeQueue(LinkQueue &Q,int &p) { if(Q.front == 0) { return error; } if(Q.front == Q.rear) { return false; } QueuePtr q; q = Q.front->next;//让q指向队列的首元结点 p = q->data; Q.front->next = q->next; if(q = Q.rear) { Q.rear = Q.front;//保留尾指针,防止尾指针被删除 } free(q); return ok; } //定位函数 Status LocateVex(AMGraph G,int g) { int i; for(i = 0;i < G.vexnum;i++) { if(g == G.vex[i]) { return i;//将顶点在数组中的位置返回回来 } } return error; } //创建无向 Status CreatUDN(AMGraph &G) { int i,j,v1,v2,k; cout<<"请输入的顶点数和边数:"<<endl; cin>>G.vexnum>>G.arcnum; cout<<"为顶点数组赋值:"<<endl; for(i = 0;i < G.vexnum;i++) { cin>>G.vex[i];//为顶点数组赋值 0对应1 } for(i = 0;i < G.vexnum;i++) { for(j = 0;j < G.vexnum;j++) { G.arcs[i][j] = 0;//先将矩阵中全部元素赋值为0 } } for(k = 0;k < G.arcnum;k++) { cout<<"输入有关联的两个顶点:"<<endl; cin>>v1>>v2; i = LocateVex(G,v1); j = LocateVex(G,v2); G.arcs[i][j] = 1; G.arcs[j][i] = G.arcs[i][j]; } cout<<"无向构建完毕!"<<endl; return ok; } //输出邻接矩阵 Status Printf(AMGraph G) { int i,j; for(i = 0;i < G.vexnum;i++) { for(j = 0;j < G.vexnum;j++) { cout<<G.arcs[i][j]<<" "; } cout<<endl; } return ok; } //深度优先遍历 Status DFS(AMGraph G,int w) { int i,j; //int Visit[maxsize]; /*for(i = 0;i < G.vexnum;i++)//如何将数组中的元素赋值? { Visit[i] = 0;//将未访问过的结点全部赋值为0 }*/ cout<<G.vex[w]<<" "; Visited[w] = 1; for(j = 0;j < G.vexnum;j++) { if((G.arcs[w][j] == 1)&&(Visited[j] != 1)) { DFS(G,j); } } return ok; } //调用DFS函数 Status DFSTraverse(AMGraph G,int v) { int i; for(i = 0;i < G.vexnum;i++) { Visited[i] = 0; } for(v = 0;v < G.vexnum;v++) { if(Visited[v] == 0) { DFS(G,v); } } return ok; } /*//深度优先遍历 Status DFS(AMGraph G,int w) { int i,j; //int Visit[maxsize]; /*for(i = 0;i < G.vexnum;i++)//如何将数组中的元素赋值? { Visit[i] = 0;//将未访问过的结点全部赋值为0 } cout<<G.vex[w]<<" "; Visited[w] = 1; for(j = 0;j < G.vexnum;j++) { if((G.arcs[w][j] == 1)&&(Visited[j] != 1)) { DFS(G,j); } } return ok; }*/ //定位函数1 Status FirstAdjVex(AMGraph G,int u) { int i; for(i = 0;i < G.vexnum;i++) { if(G.arcs[u][i] == 1) { return i; } } return error; } //定位函数2 Status NextAdjVex(AMGraph G,int u,int w) { int i; for(i = w + 1;i < G.vexnum;i++) { if(G.arcs[u][i] == 1) { return i; } } return error; } //广度优先遍历 Status BFS(AMGraph G,int v) { int i; for(i = 0;i < G.vexnum;i++) { Visited[i] = 0; } for(v = 0;v < G.vexnum;v++) { if(Visited[v] == 0) { int u,w,temp,temp1,temp2; cout<<G.vex[v]<<" "; Visited[v] = 1; LinkQueue Q; InitQueue(Q); //EnQueue(Q,G.vex[v - 1]); temp = G.vex[v]; EnQueue(Q,temp); while(Q.front != Q.rear) { DeQueue(Q,u); /*cout<<"此时u的值为:"; cout<<u<<endl;*/ //temp1 = G.vex[u - 1];//想要的数据是第一行为0,但是此时却给的数值为1 temp1 = u - 1; /*cout<<"此时temp1的值为:"; cout<<temp1<<endl;*/ //cout<<"**********************"<<endl; //for(w = FirstAdjVex(G,G.vex[u - 1]);w > 0; w = NextAdjVex(G,G.vex[u - 1],w)) for(w = FirstAdjVex(G,temp1);w >= 0;w = NextAdjVex(G,temp1,w)) { //cout<<"**********************"<<endl; if(Visited[w] == 0) { cout<<G.vex[w]<<" "; Visited[w] = 1; temp2 = G.vex[w]; //EnQueue(Q,G.vex[w]); EnQueue(Q,temp2); } } } } }//1 2 3 4 5 6 return ok; } //主函数 int main() { int i,j,w,n; AMGraph G; LinkQueue Q; cout<<"********************"<<endl; cout<<"1 9 2 5 0 5 0 3 4 3"<<endl; cout<<"1.构建"<<endl; cout<<"2.输出邻接矩阵"<<endl; cout<<"3.深度优先遍历"<<endl; cout<<"4.广度优先"<<endl; cout<<"5.负数退出"<<endl; cout<<"********************"<<endl; do{ cout<<"请输入你的选择:"<<endl; cin>>n; switch(n) { case 1: CreatUDN(G); break; case 2: Printf(G); break; case 3: cout<<"请输入你想要遍历的起点:"<<endl; cin>>w; cout<<"深度优先遍历为:"<<endl; DFSTraverse(G,w); //DFS(G,w); for(i = 0;i < G.vexnum;i++) { Visited[i] = 0; } cout<<endl; break; case 4: cout<<"请输入你想要遍历的起点:"<<endl; cin>>w; BFS(G,w); for(i = 0;i < G.vexnum;i++) { Visited[i] = 0; } cout<<endl; break; case 5: n = -1; break; default : cout<<"请输入1~5的数!!"<<endl; break; } }while(n > 0); } 实验步骤
最新发布
06-08
### C++实现的深度优先遍历(DFS)和广度优先遍历(BFS) #### 1. 深度优先遍历 (DFS) 深度优先遍历是一种递归或栈辅助的方法,用于遍历或搜索树或。它从根节点开始(或者对于来说的某个起始节点),尽可能深地探索每个分支[^1]。 以下是C++实现深度优先遍历的代码示例: ```cpp #include <iostream> #include <vector> #include <stack> using namespace std; void dfs_recursive(int node, vector<vector<int>>& adj, vector<bool>& visited) { visited[node] = true; cout << node << " "; // 访问当前节点 for (int neighbor : adj[node]) { if (!visited[neighbor]) { dfs_recursive(neighbor, adj, visited); // 递归访问邻居节点 } } } void dfs_iterative(int start, vector<vector<int>>& adj, vector<bool>& visited) { stack<int> s; s.push(start); visited[start] = true; while (!s.empty()) { int node = s.top(); s.pop(); cout << node << " "; // 访问当前节点 for (int neighbor : adj[node]) { if (!visited[neighbor]) { s.push(neighbor); visited[neighbor] = true; // 标记为已访问 } } } } ``` - **递归实现**:通过函数调用栈来实现深度优先遍历。 - **非递归实现**:使用显式栈结构模拟递归过程。 #### 2. 广度优先遍历 (BFS) 广度优先遍历是一种队列辅助的方法,用于逐层遍历或树。它从起始节点开始,先访问所有相邻节点,再依次访问下一层节点。 以下是C++实现广度优先遍历的代码示例: ```cpp #include <iostream> #include <vector> #include <queue> using namespace std; void bfs(int start, vector<vector<int>>& adj, vector<bool>& visited) { queue<int> q; q.push(start); visited[start] = true; while (!q.empty()) { int node = q.front(); q.pop(); cout << node << " "; // 访问当前节点 for (int neighbor : adj[node]) { if (!visited[neighbor]) { q.push(neighbor); visited[neighbor] = true; // 标记为已访问 } } } } ``` - **队列实现**:使用队列存储待访问的节点,确保按层次顺序访问。 #### 实验步骤 1. 定义的邻接表表示形式 `adj`,其中 `adj[i]` 是一个包含节点 `i` 的所有邻居的列表。 2. 初始化一个布尔数组 `visited`,用于标记哪些节点已被访问。 3. 对于深度优先遍历,可以选择递归或非递归方法,分别调用 `dfs_recursive` 或 `dfs_iterative` 函数。 4. 对于广度优先遍历,调用 `bfs` 函数[^1]。 5. 输出遍历结果以验证正确性。 #### 示例运行 假设的邻接表如下: ```cpp vector<vector<int>> adj = { {1, 2}, // 节点0的邻居 {0, 2, 3}, // 节点1的邻居 {0, 1, 4}, // 节点2的邻居 {1, 4}, // 节点3的邻居 {2, 3} // 节点4的邻居 }; ``` 初始化 `visited` 数组并调用相应函数即可完成遍历。 ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值