数据结构上机题4 最短路径

最短路

题目

  • ddl:12月9日。

  • 实验题目:

    • 给定图的边和边权值(非负数),求给定两顶点间的最短路径。边和边权值(格式为边的兩顶点和权值,勿直接输入邻接矩阵)、两顶点由窗口(文件也可以)输入。输出路径和路径总权值。
  • 基本要求(8分):

    • 无向图、顶点以自然数命名
  • 扩展:

    • 有向图;
    • 路径除起终顶点外,过另外几个给定顶点;
    • 顶点支持隨意命名;
    • 图形界面;
    • 等等….

说明

1.打开ShortestPaths.exe,选择有向图模式或无向图模式

2.点击帮助按钮查看如何操作

3.点击导入文件可导入事先建好的图,.udg为无向图文件,.dg为有向图文件,只能导入运行程序目录下的文件,只输入文件名(不用输入后缀名,会根据当前模式读取对应文件)

4.导入后,左键单击某个顶,呈选中状态(绿色),然后按住SHIFT,再左键单击另一个顶,将求出最短路径,路径高亮加粗显示(黄色)。

5.ShortestPaths文件夹内为工程源代码,在VS2010下创建,ShortestPaths.cpp绘制界面和处理键鼠操作,graph.h为图数据结构声明和基本操作

截图

代码

注:需要easyx图形库

graph.h

#include <string.h>
#define INF 99999999
#define MAX_VERTEX_NUM 100
#define MAX_NAME 10
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define Status int
typedef enum {DG,DN,UDG,UDN}GraphType;
typedef struct ArcCell{
    int w;//边的权值,0为不连通
}GArc,ArcArray[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct Vertex{
    TCHAR name[MAX_NAME];//顶的名称
    int x,y;//用于绘制该顶时的位置
    int l;//dijkstra算法需要
    int state;//=0正常,=1选中
}GVex,VexArray[MAX_VERTEX_NUM];
typedef struct Graph{
    ArcArray arcs;
    VexArray vexs;
    int arcnum,vexnum;
    GraphType type;
}MGraph;
typedef struct DijkstraResult{
    int vexbegin;//表示这个结果是从该顶到其他顶的最短距离
    int prev[MAX_VERTEX_NUM];//最短路径上每个顶的前一个顶
    int dist[MAX_VERTEX_NUM];//每个顶到vexbegin的最短距离
}DijRes;
Status CreateGraph(MGraph* g){
    g->arcnum=0;
    g->vexnum=0;
    memset(g->vexs,0,sizeof(GVex)*MAX_VERTEX_NUM);
    memset(g->arcs,0,sizeof(GArc)*MAX_VERTEX_NUM*MAX_VERTEX_NUM);
    return OK;
}
Status DestoryGraph(MGraph *g){
    g->arcnum=0;
    g->vexnum=0;
    memset(g->vexs,0,sizeof(GVex)*MAX_VERTEX_NUM);
    memset(g->arcs,0,sizeof(GArc)*MAX_VERTEX_NUM*MAX_VERTEX_NUM);
    return OK;
}
int LocateVex(MGraph g,TCHAR vexname[]){//获取顶在图中的位置,即在顶数组中的下标,未找到返回-1 
    int i;
    for(i=0;i<g.vexnum;i++){
        if(!_tcscmp(g.vexs[i].name,vexname)) return i;
    }
    return -1;
}
Status InsertVex(MGraph *g,TCHAR vexname[],int x,int y){//添加一个顶,不允许为空,不允许重名 
    _tcscpy_s(g->vexs[g->vexnum].name,vexname);
    g->vexs[g->vexnum].x=x;
    g->vexs[g->vexnum].y=y;
    g->vexs[g->vexnum].l=0;
    g->vexnum++;
    return OK; 
}
Status DeleteVex(MGraph *g,TCHAR vexname[]){
    int i=LocateVex(*g,vexname),j;
    if(i==-1) return ERROR;
    _tcscpy_s(g->vexs[i].name,g->vexs[g->vexnum-1].name);//最后一个顶覆盖要删除的顶 
    _tcscpy_s(g->vexs[g->vexnum-1].name,_T(""));
    for(j=0;j<MAX_VERTEX_NUM;j++){
        g->arcs[i][j].w=g->arcs[g->vexnum-1][j].w;
        g->arcs[g->vexnum-1][j].w=0;
    }
    return OK; 
}

DijRes Dijkstra(MGraph g,int v){//求其他各顶到顶v的最短路径
    DijRes r={};
    int flag[MAX_VERTEX_NUM]={0};//标志是否已找到最短路径
    int i,j,mindist,minu;
    for(i=0;i<g.vexnum;i++){//初始化每个顶到v的距离
        if(g.arcs[v][i].w){//连通则初始距离为边权
            r.dist[i]=g.arcs[v][i].w;
            r.prev[i]=v;
        }
        else{//不连通为无穷
            r.dist[i]=INF;
            r.prev[i]=-1;
        }
    }
    r.dist[v]=0;//v到v为0
    flag[v]=1;//v标志为已找到
    for(i=1;i<g.vexnum;i++){//遍历vexnum-1次,每次找出一个顶点的最短路径
        mindist=INF+1;
        minu=-1;
        for (j=0;j<g.vexnum;j++){//求出未标记的顶中到v最短的
            if(flag[j]==0 && r.dist[j]<mindist){
                mindist=r.dist[j];
                minu=j;
            }
        }
        flag[minu]=1;
        for (j=0;j<g.vexnum;j++){//根据新找到的顶u更新其他各顶最短距离
            int sum=mindist+(g.arcs[minu][j].w==0?INF:g.arcs[minu][j].w); // 边权为0不连通,以无穷计算
            if (flag[j]==0 && (sum<r.dist[j])){
                r.dist[j]=sum;
                r.prev[j]=minu;
            }
        }

    }

    return r;
}

ShortestPaths.cpp

    // MinRoad.cpp : 定义控制台应用程序的入口点。
    #define SPACE 20 //按钮间隔大小
    #define HEIGHT 480
    #define WIDTH 800
    #define BUTTONHEIGHT 40 //按钮大小
    #define BUTTONWIDTH 150 //按钮大小
    #define CONTROLWIDTH BUTTONWIDTH+2*SPACE //控制区大小
    #define VEXRADIUS 20 //顶半径
    #define ARROWLEN 10 //箭头斜边的长
    #define PI 3.1415926
    #define HPI 1.5707963

    #include <graphics.h>//easyx图形库头文件
    #include <windows.h>
    #include <stdio.h>
    #include <math.h>
    #include "graph.h"//图
    TCHAR runpath[256];//运行路径
    int MODE=0;//0为无向图,1为有向图
    int vsel=-1;//当前选中顶
    int vmov=-1;//当前移动顶
    MGraph g;//定义一个图
    void DrawButton(int left,int top,LPCTSTR text){

        setbkmode(TRANSPARENT);//设置字体背景透明
        setfillcolor(RGB(136,247,104));//设置填充颜色
        setlinestyle(PS_SOLID,4);//设置线型线宽
        setlinecolor(RGB(249,102,102));//设置线颜色
        settextstyle(25, 0, _T("宋体"),0,0,FW_BOLD,0,0,0);//设置字体,大小,粗细
        settextcolor(RGB(0,0,255));//设置字体颜色

        RECT r;
        r.left=left; r.top=top; r.right=left+BUTTONWIDTH; r.bottom=top+BUTTONHEIGHT;
        fillroundrect(left,top,left+BUTTONWIDTH,top+BUTTONHEIGHT,5,5);//带边框填充
        drawtext(text,&r,DT_CENTER|DT_VCENTER|DT_SINGLELINE);//输出字
    }
    void InitUI(){
        initgraph(WIDTH, HEIGHT);   // 创建绘图窗口
        int t=SPACE;
        DrawButton(SPACE,t,(LPCTSTR)L"帮  助");
        t+=SPACE+BUTTONHEIGHT;
        DrawButton(SPACE,t,(LPCTSTR)L"导出文件");
        t+=SPACE+BUTTONHEIGHT;
        DrawButton(SPACE,t,(LPCTSTR)L"导入文件");
        t+=SPACE+BUTTONHEIGHT;
        DrawButton(SPACE,t,(LPCTSTR)L"关  于");
        line(CONTROLWIDTH,0,CONTROLWIDTH,HEIGHT);
        HRGN rgn = CreateRectRgn(CONTROLWIDTH,0,WIDTH,HEIGHT);
        setcliprgn(rgn);//之后的绘图区域只在该矩形内有效
        DeleteObject(rgn);    

    }
    void DrawVex(int i){
        setbkmode(TRANSPARENT);//设置字体背景透明
        setfillcolor(RGB(255,188,121));//设置填充颜色
        setlinestyle(PS_SOLID,4);//设置线型线宽

        if(g.vexs[i].state==0)
            setlinecolor(RGB(129,29,228));//设置线颜色
        else
            setlinecolor(RGB(92,185,0));//设置线颜色(选中状态)
        settextstyle(30, 0, _T("Arial"),0,0,FW_BOLD+100,0,0,0);//设置字体,大小,粗细
        settextcolor(RGB(255,0,0));//设置字体颜色

        RECT r;
        r.left=g.vexs[i].x-VEXRADIUS; r.top=g.vexs[i].y-VEXRADIUS; 
        r.right=g.vexs[i].x+VEXRADIUS; r.bottom=g.vexs[i].y+VEXRADIUS;
        fillcircle(g.vexs[i].x,g.vexs[i].y,VEXRADIUS);
        drawtext((LPCTSTR)(g.vexs[i].name),&r,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
    }
    double GetAngle(int x1,int y1,int x2,int y2){//从(x1,y1)到(x2,y2)的向量的方位角,以x轴为0度,顺时针,弧度值
        double a=atan(((double)(y2-y1))/(x2-x1));//得到-pi/2~pi/2的弧度值
        if(x2>x1) return a;
        else if(x2==x1) return (y2>=y1)?HPI:-HPI;//pi/2和-pi/2,两点重合这里会返回pi/2
        else return (y2>=y1)?a+PI:a-PI;
    }
    void DrawArc(int i,int j,int mode,int inroad){//画边;mode=0,无向;mode=1,有向,i到j;inroad=1则换一种颜色画边
        if(!inroad){
            setlinestyle(PS_SOLID,3);//设置线型线宽
            setlinecolor(RGB(0,196,166));//设置线颜色
        }
        else{
            setlinestyle(PS_SOLID,4);//设置线型线宽
            setlinecolor(RGB(255,250,28));//设置线颜色
        }
        settextstyle(23, 0, _T("Arial"),0,0,FW_BOLD,0,0,0);//设置字体,大小,粗细
        TCHAR tw[10]={};

        _stprintf_s(tw,_T("%d"),g.arcs[i][j].w);//即使对于无向图也通用,因为arcs[i][j]和arcs[j][i]一样

        if(!mode){//无向图,直接连线
            line(g.vexs[i].x,g.vexs[i].y,g.vexs[j].x,g.vexs[j].y);
            outtextxy((g.vexs[i].x+g.vexs[j].x)/2,(g.vexs[i].y+g.vexs[j].y)/2,tw);
        }
        else{//有向图,画边时两个方向的线需要错开,不重叠,并且带箭头
            double a=GetAngle(g.vexs[i].x,g.vexs[i].y,g.vexs[j].x,g.vexs[j].y);//从顶i到j的向量的方位角
            double da=30.0/180.0*PI;//30度角对应的弧度,
            int x1=g.vexs[i].x+cos(a-da)*VEXRADIUS,y1=g.vexs[i].y+sin(a-da)*VEXRADIUS;
            int x2=g.vexs[j].x+cos(a+da-PI)*VEXRADIUS,y2=g.vexs[j].y+sin(a+da-PI)*VEXRADIUS;
            line(x1,y1,x2,y2);//画i到j的边的直线,下面画箭头

            double len1_2=sqrt(pow((long double)(g.vexs[i].x-g.vexs[j].x),2)+pow((long double)(g.vexs[i].y-g.vexs[j].y),2))/2;//该边1/2长
            int arrx=x1+len1_2*cos(a),arry=y1+len1_2*sin(a);//箭头端点所在坐标
            double db=2.617994;//150度角对应的弧度
            line(arrx,arry,arrx+cos(a-db)*ARROWLEN,arry+sin(a-db)*ARROWLEN);//箭头一侧边
            line(arrx,arry,arrx+cos(a+db)*ARROWLEN,arry+sin(a+db)*ARROWLEN);//箭头一侧边
        outtextxy((x1+x2)/2,(y1+y2)/2,tw);
    }
}
void DrawMGraph(){
    clearcliprgn();//清空绘图区
    int i,j;

    for(i=0;i<g.vexnum;i++){//先画边,再画顶不然边会部分叠在顶上
        for(j=0;j<g.vexnum;j++){
            if(g.arcs[i][j].w) DrawArc(i,j,MODE,0);
        }
    }
    for(i=0;i<g.vexnum;i++){
        DrawVex(i);
    }
}

void OnButtonClick(int i){
    FILE* fp;
    TCHAR filename[32]=_T("test");
    TCHAR filepath[256];
    switch(i){
        case 1:    MessageBox(GetHWnd(),_T(\
"1.点击空白处添加新顶.\n2.在某顶上按住右键可移动该顶.\n3.左键单击某个顶可以选中.\n4.选中某顶后,ctrl+左键单击另一顶可添加边.\n5.选中某顶后,shift+左键单击另一顶可求最短路径."\
),_T("Help"),MB_OK|MB_ICONINFORMATION);
                break;

        case 2:    if(InputBox(filename,32,_T("文件将保存在当前运行目录下,无向图后缀名为.udg,有向图后缀名为.dg\n请输入文件名(不带后缀名):"),_T("保存文件"),(LPCTSTR)filename,0,0,0)){
                    _stprintf_s(filepath,_T("%s%s%s"),runpath,filename,MODE?_T(".dg"):_T(".udg"));//生成保存路径

                    if(!_tfopen_s(&fp,filepath,_T("wb+"))){
                        fwrite(&g,sizeof(g),1,fp);
                        fclose(fp);
                        MessageBox(GetHWnd(),_T("导出成功!"),_T("提示"),MB_OK|MB_ICONINFORMATION);
                    }
                    else{
                        MessageBox(GetHWnd(),_T("文件不存在!!"),_T("错误"),MB_OK|MB_ICONWARNING);
                    }
                }
                break;
        case 3:    if(InputBox(filename,32,_T("将打开当前运行目录下的文件,无向图后缀名为.udg,有向图后缀名为.dg\n请输入文件名(不带后缀名):"),_T("打开文件"),(LPCTSTR)filename,0,0,0)){
                    _stprintf_s(filepath,_T("%s%s%s"),runpath,filename,MODE?_T(".dg"):_T(".udg"));//生成保存路径
                    //MessageBox(GetHWnd(),filepath,_T("aaa"),MB_OK|MB_ICONINFORMATION);
                    if(!_tfopen_s(&fp,filepath,_T("rb+"))){
                        fread(&g,sizeof(g),1,fp);
                        fclose(fp);
                        DrawMGraph();
                    }
                    else{
                        MessageBox(GetHWnd(),_T("文件不存在!!"),_T("错误"),MB_OK|MB_ICONWARNING);
                    }
                }
                break;
        case 4:    MessageBox(GetHWnd(),_T("ShortestPaths V1.0\nCode by LinXiang(PB16020923)"),_T("About"),MB_OK|MB_ICONINFORMATION);
                break;
    }
}
int IsInRange(int a,int up,int down){
    return a<=up && a>=down;
}
int GetPosInVex(int x,int y){//获取当前坐标下的顶下标,没有返回-1
    int i,xu=x+VEXRADIUS,xd=x-VEXRADIUS,yu=y+VEXRADIUS,yd=y-VEXRADIUS;
    //反过来判断某个顶在不在x,y范围内,这样节省计算,不用算每个顶边界
    for(i=0;i<g.vexnum;i++){
        if(IsInRange(g.vexs[i].x,xu,xd) && IsInRange(g.vexs[i].y,yu,yd)) return i;
    }
    return -1;
}
void SetVexSelected(int i){
    g.vexs[i].state=1;
    DrawVex(i);//更新为选中状态
    vsel=i;//记下当前选中顶
}
void SetVexNoSelected(){
    g.vexs[vsel].state=0;
    DrawVex(vsel);//更新为正常选中状态
    vsel=-1;//
}
void GetRunPath(TCHAR *text,TCHAR *path){//获取当前运行路径
    int pos;//最后一个\的位置 
    int i=0;
    while(*(text+i)){
        if(*(text+i)=='\\') pos=i;
        i++;
    }
    _tcscpy_s(path,256,text);
    path[pos+1]=0;
}
int _tmain(int argc,TCHAR *argv[])
{
    GetRunPath(argv[0],runpath);

    if(IDYES==MessageBox(GetHWnd(),_T("选择‘是’为进入 有向图 模式,\n选择‘否’为进入 无向图 模式。"),_T("选择模式"),MB_YESNO|MB_ICONQUESTION))
        MODE=1;
    else MODE=0;
    InitUI();
    CreateGraph(&g);
    while(true){
        MOUSEMSG m = GetMouseMsg();// 获取一条鼠标消息
        switch(m.uMsg){
            int vi;
            case WM_MOUSEMOVE://移动
                if(vmov!=-1){
                    g.vexs[vmov].x=m.x;
                    g.vexs[vmov].y=m.y;
                    DrawMGraph();//重绘全部
                }
                break;
            case WM_RBUTTONDOWN://右键按下,开始移动 
                vi=GetPosInVex(m.x,m.y);
                if(vi!=-1)
                    vmov=vi;
                break;
            case WM_RBUTTONUP://右键弹起,结束移动 
                if(vmov!=-1)
                    vmov=-1;
                break;
            case WM_LBUTTONDOWN://按下左键
                if(m.mkCtrl){//同时按下CTRL
                    vi=GetPosInVex(m.x,m.y);
                    if(vi!=-1 && vsel!=-1 && vi!=vsel){//按下ctrl选择了一顶,并且之前已经选中一顶,而且两顶不同,则添新边
                        TCHAR tn[10]=_T("1");
                        if(InputBox(tn,128,_T("Input a arc weight"),_T("Add a arc"),(LPCTSTR)tn,0,0,0)){//输入了权

                            if(!MODE){//无向图
                                g.arcs[vi][vsel].w=g.arcs[vsel][vi].w=_ttoi(tn);
                                DrawArc(vsel,vi,MODE,0);
                            }
                            else{//有向图
                                g.arcs[vsel][vi].w=_ttoi(tn);
                                DrawArc(vsel,vi,MODE,0);
                            }

                        }
                    }
                }
                else if(m.mkShift){//同时按下SHIFT
                    vi=GetPosInVex(m.x,m.y);
                    if(vi!=-1 && vsel!=-1 && vi!=vsel){//按下ctrl选择了一顶,并且之前已经选中一顶,而且两顶不同,则求最短路径
                        DrawMGraph();//重绘全部,避免图中有画过其他路径
                        DijRes r=Dijkstra(g,vsel);//求出vel到各顶最短路径
                        int tmp=vi;//倒推路径,先画边
                        while(r.prev[tmp]!=-1){
                            DrawArc(r.prev[tmp],tmp,MODE,1);
                            tmp=r.prev[tmp];
                        }
                        tmp=vi;//倒推路径,再画顶
                        while(tmp!=-1){
                            DrawVex(tmp);
                            tmp=r.prev[tmp];
                        }
                    }
                }
                else{//仅仅按下左键
                    if(m.x<CONTROLWIDTH){//控制区
                        if(m.y%(SPACE+BUTTONHEIGHT)>SPACE)
                            OnButtonClick(m.y/(SPACE+BUTTONHEIGHT)+1);
                    }
                    else{//绘图区
                        int vi=GetPosInVex(m.x,m.y);
                        if(vi!=-1){//按下了某个顶
                            if(vsel!=-1) //如果有选中的,先取消选中状态
                                SetVexNoSelected();
                            SetVexSelected(vi);
                        }
                        else{//按下了空白区域
                            if(vsel!=-1){//如果有选中的,取消选中状态
                            SetVexNoSelected();
                            break;
                        }

                        //以下添加新顶
                                //TCHAR根据是否有UNICODE宏分别对应char和wchar_t
                                //tn相当于TCHAR* 也就是LPTSTR
                        TCHAR tn[MAX_NAME]=_T("0");
                        tn[0]+=g.vexnum;
                        if(InputBox(tn,128,_T("Input a name:"),_T("Add a vex"),(LPCTSTR)tn,0,0,0)){
                                //_T将字符串常量转为unicode版本,(LPCTSTR)把字符串变量转为常量
                            InsertVex(&g,tn,m.x,m.y);
                            DrawVex(g.vexnum-1);
                        }
                    }
                }
            }
            break;
        }
    }
    closegraph();// 关闭绘图窗口
    return 0;
}
文章目录
  1. 最短路
    1. 题目
    2. 说明
    3. 截图
  2. 代码
|