题目描述:

邮递员的工作是每天在邮局里选出邮件,然后送到他所管辖的客户中,再返回邮局。自然地,若他要完成当天的投递任务,则他必须要走过他所投递邮件的每一条街道至少一次。问怎样的走法使他的投递总行程为最短?这个问题就称为中国邮路问题。
编程要求:
层次一:只求解用户输入的图形的中国邮路问题
要求用户输入图形,求解输入的图形的中国邮路问题,要求能显示图形和最终结果。
层次二:加入图形编辑器
系统自动生成图形,系统求解生成的图形的中国邮路问题,要求能显示图形和最终结果。
层次三:附加要求
能够图形显示求解过程。

思路:

随机选取点添边,同时构建并查集检验图的连通性,若生成的图非连通则添边,若仍不满足条件,则重新构建直到满足条件。
将符合要求的图写入文件。通过Floyd算法获得距离数组和路径数组,用于后续匹配和查找。获得奇点下标以及奇点个数,用状压dp
进行最小权匹配,获得奇点对。在原图中添加边消除奇点获得欧拉图,用DFS找到一条欧拉回路。在欧拉回路中,完善不直接相连奇点的
路径,获得实际路径。获取点的坐标,固定邮局A点,根据结点个数确定权值,判断两点间距离,如果太小则重新选取,最终得到分布均匀的图。
画出所有结点,令点按路径移动,最终连线,动态演示完毕。

数据结构定义:

#define INF 65535            //定义无穷大
#define MaxVerNum  100      //最大结点数目
#define eps 1e-3            //精度,在精度之内则符合要求typedef char elementType; //定义图中顶点的数据类型
typedef int eInfoType;      //定义 eInfo 的数据类型,即权值的数据类型typedef struct eNode        //定义边链表的结点结构
{int adjVer;                //邻接顶点信息,此处为顶点编号,从 1 开始eInfoType eInfo;       //边链表中表示边的相关信息,比如表的权值struct eNode* next;     //指向边链表中的下一个结点。bool visited;
}EdgeNode;                  //边链表结点类型typedef struct vNode       //定义顶点表的结点结构
{elementType data;      //顶点表数据类型EdgeNode* firstEdge;   //指向边结点
}VerNode;                   //顶点表结点类型typedef struct oNode {     //奇点对存储结构int node_1;int node_2;
}OddNode;typedef struct Euler {     //路径结点定义int point;              //结点下标struct Euler* next;       //指向下一个路径结点
}Node;typedef  struct {         //定义结点基本信息的结构int x, y;              //横、纵坐标elementType name;        //结点名称A ~ Zbool exist;              //是否已获得坐标
}vN;stack<int> Q;                                                 //全局栈,用于获得结点之间的路径
int bg, to;                                                     //边的起点和终点
int VerNum;                                                     //顶点数
int odd_num = 0, lf = 0;                                      //奇点个数
int* dp;                                                        //全局指针,根据奇点个数动态分派内存
bool isIP = true;                                              //用于判断VerNum是否已有值
VerNode VerList[MaxVerNum];                                     //顶点表
int degree[MaxVerNum];                                          //表示结点度数
int tree[MaxVerNum];                                            //并查集,用于判断图的连通性
int path[MaxVerNum][MaxVerNum];                                 //路径数组
eInfoType dist[MaxVerNum][MaxVerNum];                           //距离矩阵
eInfoType AdjMatrix[MaxVerNum][MaxVerNum];                      //邻接矩阵
int* odd_point;                                                 //记录奇点
int odd_n[MaxVerNum];                                           //用于记录奇点个数及奇点是第几个结点
OddNode odd_match[MaxVerNum];                                   //奇点间配对

函数定义:

void initialize();                                               //初始化函数
int findroot(int x);                                            //建立并查集
bool add(int m, int n, int w, bool instant);                    //添加边
void free();                                                    //释放顶点表的边链表及动态创建的奇点表
void connected();                                               //若生成的图非连通,则合并
bool Legal();                                                   //判断图是否符合条件
void odd();                                                     //不符合条件则重新生成图
void writetofile();                                             //将符合要求的图写入文件用以检验结果的准确性
void Floyd();                                                   //获得任意两点距离矩阵以及路径
int get_oddedge(int x);                                         //获得需要添边的奇点对
void extend_edge();                                             //在原图中添加边以获得欧拉图
bool IsExtend(int x, int y);                                    //判断是否是添加的边
void search_euler(int u);                                       //寻找一条欧拉回路并记录
void get_path(Node* p);                                         //获得实际路径
void getarc(int& arc, int x, int y);                            //根据点的位置获得合适的角度
void getWeight(int& weight);                                    //根据点的数目确定点与点的距离
void getXY(vN* xy);                                             //获取点的坐标
void generate(vN* xy, int i, EdgeNode* p);                      //产生点的坐标
void adjustXY(vN* xy);                                          //调整点的坐标,使各点之间保持一定距离
void paint(vN* xy);                                             //绘制出所有结点
void drawline(vN* xy);                                          //绘制点之间的边
void show(vN* xy, Node* Euler);                                 //动态演示求解过程(路径)
void getd(double& d, double k);                                 //因斜率不同选取合适的步长
void flag(int x, int y);                                        //到达一个结点则高亮一次,用于确定邮递员的位置
void move(int x1, int y1, int x2, int y2, vN* xy);              //演示邮递员在图上的移动过程

获取可行的求解路径:

initialize();vN xy[MaxVerNum];Node* Euler = (Node*)malloc(sizeof(Node));cout << "Loading a Graph with " << VerNum << " nodes" << endl;connected();odd();writetofile();//将顶点表储存的符合要求的的图写入文件,用于验证Floyd();extend_edge();search_euler(1);get_path(Euler);

图形化求解路径:

void getarc(int& arc, int x, int y)                                                  //分区域获得角度,使分布均匀
{int n;if (x >= 75 && x <= 530){if (y >= 45 && y < 288){arc = rand() % 90 + 270;}else if (y >= 288 && y < 572){n = rand() % 2 + 1;if (n == 1)arc = rand() % 90;elsearc = rand() % 90 + 270;}else if (y >= 572 && y <= 815){arc = rand() % 90;}}else if (x > 530 && x <= 985){if (y >= 45 && y < 288){arc = rand() % 180 + 180;}else if (y >= 288 && y < 572){arc = rand() % 360;}else if (y >= 572 && y <= 815){arc = rand() % 180;}}else if (x > 985 && x <= 1450){if (y >= 45 && y <= 288){arc = rand() % 90 + 180;}else if (y > 288 && y <= 572){arc = rand() % 180 + 90;}else if (y > 572 && y <= 815){arc = rand() % 90 + 90;}}
}void getWeight(int& weight)                                                             //根据点的个数确定距离
{switch (VerNum){case 24:case 23:weight = 140;break;case 22:weight = 145;break;case 21:weight = 150;break;case 20:case 19:weight = 155;break;case 18:weight = 160;break;case 17:case 16:;case 15:weight = 170;break;case 14:case 13:case 12:case 11:case 10:case 9:weight = 180;break;}
}void generate(vN* xy, int i, EdgeNode* p)                                          //在以某点为圆心的圆上取得一点
{int weight, arc;int X = 0, Y = 0;if (!xy[p->adjVer].exist){getWeight(weight);while (X < 75 || X > 1450 || Y < 45 || Y > 815){getarc(arc, xy[i].x, xy[i].y);X = int(xy[i].x + weight * cos(arc * 3.14 / 180) + 0.5);Y = int(xy[i].y + weight * sin(arc * 3.14 / 180) + 0.5);}xy[p->adjVer].x = X;xy[p->adjVer].y = Y;xy[p->adjVer].exist = true;}
}void getXY(vN* xy)                                                                 //获得结点XY坐标
{int i, k;int X = 0, Y = 0;bool all;EdgeNode* p = NULL;xy[1].x = 757;xy[1].y = 430;xy[1].name = 'A';xy[1].exist = true;for (i = 2; i <= VerNum; i++){xy[i].name = char(i - 1) + 'A';xy[i].exist = false;}for (i = 1; i <= VerNum; i++){if (!xy[i].exist)continue;p = VerList[i].firstEdge;while (p){if (p->eInfo >= 4 && p->eInfo <= 20)generate(xy, i, p);p = p->next;}}while (1){all = true;for (i = 2; i <= VerNum; i++){if (!xy[i].exist){all = false;p = VerList[i].firstEdge;while (p){if (xy[p->adjVer].exist && p->eInfo >= 4 && p->eInfo <= 20)break;p = p->next;}if (p){k = p->adjVer;p = VerList[k].firstEdge;while (p && p->adjVer != i)p = p->next;generate(xy, k, p);}}}if (all)break;}adjustXY(xy);
}void adjustXY(vN* xy)                                                              //调整结点坐标使分布均匀
{int i, j, weight;bool all;int X = 0, Y = 0;while (1){all = true;;for (i = 1; i < VerNum; i++){for (j = i + 1; j <= VerNum; j++){getWeight(weight);if (sqrt((xy[i].x - xy[j].x) * (xy[i].x - xy[j].x) + (xy[i].y - xy[j].y) * (xy[i].y - xy[j].y)) < 135.0){all = false;while (sqrt((xy[i].x - xy[j].x) * (xy[i].x - xy[j].x) + (xy[i].y - xy[j].y) * (xy[i].y - xy[j].y)) < 135.0){X = rand() % 1375 + 75;Y = rand() % 770 + 45;xy[j].x = X;xy[j].y = Y;//cout << "X: " << X << "Y: " << Y << endl;}}}}if (all)break;}}void paint(vN* xy)                                                                    //显示结点和相关信息
{int i;EdgeNode* p = NULL;setfillcolor(GREEN);settextstyle(20, 10, _T("黑体"));setbkmode(TRANSPARENT);setcolor(BLACK);for (i = 1; i <= VerNum; i++){fillcircle(xy[i].x, xy[i].y, 8);outtextxy(xy[i].x, xy[i].y + 6, xy[i].name);}
}void drawline(vN* xy)                                                              //显示点之间的边
{int i;EdgeNode* p = NULL;setlinecolor(BLACK);setlinestyle(PS_SOLID, 1);for (i = 1; i <= VerNum; i++){p = VerList[i].firstEdge;while (p){if (p->eInfo >= 4 && p->eInfo <= 20)line(xy[i].x, xy[i].y, xy[p->adjVer].x, xy[p->adjVer].y);p = p->next;}}
}void getd(double& d, double k)                                                     //根据斜率调整步长
{k = fabs(k);if (k >= 0 && k < 2){d = 51.0;}else if (k >= 2 && k < 5){d = 23;}else if (k >= 5 && k < 10){d = 19.0;}else if (k > 10 && k <= 15){d = 5.0;}else if (k > 15 && k <= 20){d = 2.5;}else if (k > 20 && k <= 30){d = 1.75;}else if (k > 30 && k <= 80){d = 1.0;}else if (k > 80 && k <= 120){d = 0.5;}else if (k > 120 && k <= 250){d = 0.35;}else if (k > 250 && k <= 400){d = 0.20;}else if (k > 400 && k <= 500){d = 0.10;}else if (k > 500 && k <= 800){d = 0.08;}else if(k > 800 && k <= 1200){d = 0.05;}else{d = 0.01;}
}void flag(int x, int y)                                                                //每到达一点,高亮显示位置
{setfillcolor(BROWN);fillcircle(x, y, 8);
}void move(int x, int y, int x1, int y1, vN* xy)                                        //邮递员移动函数
{double d, k, x0, y0, x2, y2;x0 = x, y0 = y, x2 = x1, y2 = y1;k = (y1 - double(y)) / (x1 - double(x));getd(d, k);if (x < x1){BeginBatchDraw();                                                          //开始批量绘图while (x0 < x2){paint(xy);outtextxy(5, 5, _T("注:图中距离与实际权值并非一一对应,点的相对位置与真实位置未必相符,详情参考C:\\Users\\pc\\Desktop\\CPP.txt"));int j = int(k * (x0 - double(x2)) + y2 + 0.5);setfillcolor(DARKGRAY);fillcircle(int(x0 + 0.5), j, 2);Sleep(500);FlushBatchDraw();                                                      //显示if (x0 + d >= x2){x0 = x2;flag(int(x0 + 0.5), int(k * (x0 - double(x2)) + y2 + 0.5));}elsex0 += d;}EndBatchDraw();                                                              //结束批量绘图}else{BeginBatchDraw();while (x0 > x2){paint(xy);outtextxy(5, 5, _T("注:图中距离与实际权值并非一一对应,点的相对位置与真实位置未必相符,详情参考C:\\Users\\pc\\Desktop\\CPP.txt"));setfillcolor(DARKGRAY);int j = int(k * (x0 - double(x2)) + y2 + 0.5);fillcircle(int(x0 + 0.5), j, 2);Sleep(500);FlushBatchDraw();if (x0 - d <= x2){x0 = x2;flag(int(x0 + 0.5), int(k * (x0 - double(x2)) + y2 + 0.5));}elsex0 -= d;}EndBatchDraw();}
}void show(vN* xy, Node* Euler)                                                     //动态演示求解过程
{Node* p = NULL, * q = NULL;p = Euler->next;q = p->next;while (q){move(xy[p->point].x, xy[p->point].y, xy[q->point].x, xy[q->point].y, xy);p = q;q = q->next;}paint(xy);drawline(xy);
}

完整代码实现:

#include <iostream>
#include<graphics.h>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include<conio.h>
#include <ctime>
#include <cmath>
#include <cstdio>
#include <stack>using namespace std;/*设计思想:
随机选取点添边,同时构建并查集检验图的连通性,若生成的图非连通则添边,若仍不满足条件,则重新构建直到满足条件。
将符合要求的图写入文件。通过Floyd算法获得距离数组和路径数组,用于后续匹配和查找。获得奇点下标以及奇点个数,用状压dp
进行最小权匹配,获得奇点对。在原图中添加边消除奇点获得欧拉图,用DFS找到一条欧拉回路。在欧拉回路中,完善不直接相连奇点的
路径,获得实际路径。获取点的坐标,固定邮局A点,根据结点个数确定权值,判断两点间距离,如果太小则重新选取,最终得到分布均匀的图。
画出所有结点,令点按路径移动,最终连线,动态演示完毕。
*/#define INF 65535         //定义无穷大
#define MaxVerNum  100      //最大结点数目
#define eps 1e-3            //精度,在精度之内则符合要求typedef char elementType; //定义图中顶点的数据类型
typedef int eInfoType;      //定义 eInfo 的数据类型,即权值的数据类型typedef struct eNode        //定义边链表的结点结构
{int adjVer;                //邻接顶点信息,此处为顶点编号,从 1 开始eInfoType eInfo;       //边链表中表示边的相关信息,比如表的权值struct eNode* next;     //指向边链表中的下一个结点。bool visited;
}EdgeNode;                  //边链表结点类型typedef struct vNode       //定义顶点表的结点结构
{elementType data;      //顶点表数据类型EdgeNode* firstEdge;   //指向边结点
}VerNode;                   //顶点表结点类型typedef struct oNode {     //奇点对存储结构int node_1;int node_2;
}OddNode;typedef struct Euler {     //路径结点定义int point;              //结点下标struct Euler* next;       //指向下一个路径结点
}Node;typedef  struct {         //定义结点基本信息的结构int x, y;              //横、纵坐标elementType name;        //结点名称A ~ Zbool exist;              //是否已获得坐标
}vN;stack<int> Q;                                                 //全局栈,用于获得结点之间的路径
int bg, to;                                                     //边的起点和终点
int VerNum;                                                     //顶点数
int odd_num = 0, lf = 0;                                      //奇点个数
int* dp;                                                        //全局指针,根据奇点个数动态分派内存
bool isIP = true;                                              //用于判断VerNum是否已有值
VerNode VerList[MaxVerNum];                                     //顶点表
int degree[MaxVerNum];                                          //表示结点度数
int tree[MaxVerNum];                                            //并查集,用于判断图的连通性
int path[MaxVerNum][MaxVerNum];                                 //路径数组
eInfoType dist[MaxVerNum][MaxVerNum];                           //距离矩阵
eInfoType AdjMatrix[MaxVerNum][MaxVerNum];                      //邻接矩阵
int* odd_point;                                                 //记录奇点
int odd_n[MaxVerNum];                                           //用于记录奇点个数及奇点是第几个结点
OddNode odd_match[MaxVerNum];                                   //奇点间配对void initialize();                                               //初始化函数
int findroot(int x);                                            //建立并查集
bool add(int m, int n, int w, bool instant);                    //添加边
void free();                                                    //释放顶点表的边链表及动态创建的奇点表
void connected();                                               //若生成的图非连通,则合并
bool Legal();                                                   //判断图是否符合条件
void odd();                                                     //不符合条件则重新生成图
void writetofile();                                             //将符合要求的图写入文件用以检验结果的准确性
void Floyd();                                                   //获得任意两点距离矩阵以及路径
int get_oddedge(int x);                                         //获得需要添边的奇点对
void extend_edge();                                             //在原图中添加边以获得欧拉图
bool IsExtend(int x, int y);                                    //判断是否是添加的边
void search_euler(int u);                                       //寻找一条欧拉回路并记录
void get_path(Node* p);                                         //获得实际路径
void getarc(int& arc, int x, int y);                            //根据点的位置获得合适的角度
void getWeight(int& weight);                                    //根据点的数目确定点与点的距离
void getXY(vN* xy);                                             //获取点的坐标
void generate(vN* xy, int i, EdgeNode* p);                      //产生点的坐标
void adjustXY(vN* xy);                                          //调整点的坐标,使各点之间保持一定距离
void paint(vN* xy);                                             //绘制出所有结点
void drawline(vN* xy);                                          //绘制点之间的边
void show(vN* xy, Node* Euler);                                 //动态演示求解过程(路径)
void getd(double& d, double k);                                 //因斜率不同选取合适的步长
void flag(int x, int y);                                        //到达一个结点则高亮一次,用于确定邮递员的位置
void move(int x1, int y1, int x2, int y2, vN* xy);              //演示邮递员在图上的移动过程void initialize()                                                                    //初始化函数
{int count = 0, i, m, n, weight, EdgeNum;bool succeed;if (isIP)                                                                        //是否需要再次生成结点个数{VerNum = rand() % 16 + 9;                                                  //结点个数在9 ~ 24isIP = false;}for (i = 1; i < VerNum + 1; i++)                                             //初始化顶点表{VerList[i].data = char(i - 1) + 'A';VerList[i].firstEdge = NULL;}odd_point = (int*)malloc((VerNum + 1) * sizeof(int));                          //动态数组memset(degree, 0, sizeof(degree));                                                //初始化dgree数组memset(tree, -1, sizeof(tree));                                                 //初始化tree数组EdgeNum = rand() % (VerNum * 2 - VerNum + 1) + VerNum - 1;                        //控制边的个数while (count < EdgeNum)                                                          //确定两结点以及权值{m = rand() % VerNum + 1;n = rand() % VerNum + 1;weight = rand() % 15 + 4;while (m == n){n = rand() % VerNum + 1;}succeed = add(m, n, weight, false);if (succeed)count++;}
}int findroot(int x)                                                                    //并查集的方式判断图连通性
{if (tree[x] == -1)return x;else{int temp = findroot(tree[x]);tree[x] = temp;return temp;}
}bool add(int m, int n, int weight, bool instant)                                   //添边,根据instant的值来确定何种形式的添边
{EdgeNode* p = NULL, * edge1 = NULL, * edge2 = NULL;int t, tempa, tempb;if (!instant)                                                                    //不允许重复边的添边方式{                                                                              //不符合条件则返回false         if (degree[m] > 3 && degree[n] > 3 && m < 1 || m > VerNum || n < 1 || n > VerNum)return false;p = VerList[m].firstEdge;                                                  //防重if (p){while (p->next && p->adjVer != n){p = p->next;}}if (p){if (p->adjVer == n){while (1){t = n;p = VerList[m].firstEdge;while (n == m || n == t)n = rand() % VerNum + 1;while (p->next && p->adjVer != n){p = p->next;}if (p->adjVer != n)break;}}}edge1 = new EdgeNode;                                                     //添加双向边edge1->adjVer = n;edge1->eInfo = weight;edge1->next = NULL;edge1->visited = false;if (VerList[m].firstEdge == NULL)VerList[m].firstEdge = edge1;elsep->next = edge1;p = VerList[n].firstEdge;if (p){while (p->next){p = p->next;}}edge2 = new EdgeNode;edge2->adjVer = m;edge2->eInfo = weight;edge2->next = NULL;edge2->visited = false;if (VerList[n].firstEdge == NULL)VerList[n].firstEdge = edge2;elsep->next = edge2;tempa = findroot(m);                                                      //建立并查集用以判断连通性tempb = findroot(n);if (tempa != tempb)                                                         //若祖先不同则合并子图tree[tempa] = tempb;}else                                                                            //重复边的添边{p = VerList[m].firstEdge;while (p->next){p = p->next;}edge1 = new EdgeNode;edge1->adjVer = n;edge1->eInfo = weight;edge1->next = NULL;edge1->visited = false;p->next = edge1;p = VerList[n].firstEdge;while (p->next){p = p->next;}edge2 = new EdgeNode;edge2->adjVer = m;edge2->eInfo = weight;edge2->next = NULL;edge2->visited = false;p->next = edge2;}degree[m]++;                                                                  //结点度数相应增加degree[n]++;return true;
}void connected()
{int i, ans = 0, a[80], weight;memset(a, -1, sizeof(a));                                                       //初始化用以存放独立子图祖先结点的数组for (i = 1; i < VerNum + 1; i++){                                                                                //判断连通性if (tree[i] == -1){a[ans] = i;ans++;}}i = 0;if (ans > 1)                                                                    //合并子图,最后仅有一个连通图{while (i < ans - 1){weight = rand() % 15 + 4;add(a[i], a[i + 1], weight, false);i++;}}
}bool Legal()                                                                       //判断子图是否合法
{int i, odd = 0;for (i = 1; i < VerNum + 1; i++)                                                //统计奇点个数{if (degree[i] % 2)odd++;}if (odd == 0 || odd > 2)return true;if (odd == 2){if (degree[1] % 2 == 1)                                                      //当奇点个数为2,若1不为奇点,则不符合要求return true;elsereturn false;}
}void odd()                                                                         //循环构建图,直到生成的图符合要求
{while (!Legal()){free();initialize();connected();}
}void writetofile()                                                                 //图写入文件以确认准确性
{FILE* fp;errno_t err;int i, weight;char a, b, str[80];EdgeNode* p = NULL;if ((err = fopen_s(&fp, "C:\\Users\\pc\\Desktop\\CPP.txt", "w+")) != NULL){printf("Cannot open this file\n");exit(0);}for (i = 1; i < VerNum + 1; i++){a = VerList[i].data;p = VerList[i].firstEdge;while (p){b = char(p->adjVer - 1) + 'A';weight = p->eInfo;sprintf_s(str, "%c\t%c\t%d\n", a, b, weight);fputs(str, fp);p = p->next;}}fclose(fp);
}void Floyd()                                                                       //Floyd算法确定任意两点距离及路径
{int i, j, k;EdgeNode* p = NULL;for (i = 1; i < VerNum + 1; i++)for (j = 1; j < VerNum + 1; j++)AdjMatrix[i][j] = INF;for (i = 1; i < VerNum + 1; i++)                                               //将邻接表转换为邻接矩阵{p = VerList[i].firstEdge;while (p){AdjMatrix[i][p->adjVer] = p->eInfo;p = p->next;}}for (i = 1; i < VerNum + 1; i++)                                               //初始化路径数组{for (j = 1; j < VerNum + 1; j++){dist[i][j] = AdjMatrix[i][j];if (i != j && AdjMatrix[i][j] < INF)path[i][j] = i;elsepath[i][j] = -1;}}for (k = 1; k < VerNum + 1; k++)for (i = 1; i < VerNum + 1; i++)for (j = 1; j < VerNum + 1; j++){if (i != j && dist[i][k] + dist[k][j] < dist[i][j]){dist[i][j] = dist[i][k] + dist[k][j];path[i][j] = path[k][j];}}
}int get_oddedge(int x)                                                             //获得能最小权匹配的奇点对
{int i, j;int ans = INF, temp, ANS;EdgeNode* point = NULL;if (dp[x] || !x)return dp[x];for (i = 0; i < odd_num - 1; i++)                                                //遍历压缩状态中的所有'1'对,即所有的奇点对{if ((1 << i) & x){for (j = i + 1; j < odd_num; j++){if ((1 << j) & x){temp = x ^ (1 << i) ^ (1 << j);ANS = get_oddedge(temp) + dist[odd_n[odd_num - i]][odd_n[odd_num - j]];if (ans > ANS){ans = ANS;                                                   //记录权值最小的奇点对下标bg = odd_n[odd_num - i];to = odd_n[odd_num - j];}}}}}return dp[x] = ans;
}void extend_edge()                                                                 //添加奇点对的边
{int i, j = 0, a = 0, count = 1, n = 0;bool instant = false;EdgeNode* point = NULL;cout << "奇点有:" << endl;for (i = 1; i < VerNum + 1; i++)                                                //初始化奇点数组{if (degree[i] % 2){odd_num++;odd_n[odd_num] = i;odd_point[odd_num] = i;cout << i << "\t";}}cout << endl;lf = odd_num;cout << "奇点个数为:" << lf << endl;for (i = odd_num; i >= 1; i--)                                                   //根据奇点个数生成对应二进制数{a <<= 1;a++;}i = 0;while (a){dp = (int*)malloc((1 << odd_num) * sizeof(int));                         //创建并初始化dp数组memset(dp, 0, (1 << odd_num) * sizeof(int));instant = false;get_oddedge(a);                                                              //获取奇点对并记录odd_match[i].node_1 = bg;odd_match[i].node_2 = to;i++;for (j = 1; j <= odd_num; j++)                                               //更新奇点数组(添边后的奇点删除){if (odd_n[j] != bg && odd_n[j] != to){odd_n[count++] = odd_n[j];}}odd_num -= 2;count = 1;a = 0;for (int k = odd_num; k >= 1; k--){a <<= 1;a++;}point = VerList[bg].firstEdge;                                               //添边while (point)                                                               //判断两奇点是否直接相连{if (point->adjVer == to){instant = true;break;}point = point->next;}add(bg, to, dist[bg][to], instant);cout << bg << "\t" << to << "\t" << dist[bg][to] << endl;free(dp);                                                                   //释放dp数组}for (int i = 1; i <= VerNum; i++){if (degree[i] % 2)n++;}cout << "添边后的奇点个数为:" << n << endl;
}bool IsExtend(int x, int y)                                                            //是否为后来添加的边
{int i;for (i = 0; i < lf / 2; i++){if (odd_match[i].node_1 == x || odd_match[i].node_2 == x){if (odd_match[i].node_1 == y || odd_match[i].node_2 == y)return true;}}return false;
}void search_euler(int u)                                                            //DFS寻找欧拉回路
{EdgeNode* p = VerList[u].firstEdge;;EdgeNode* q = NULL;while (p){if (!p->visited){p->visited = true;q = VerList[p->adjVer].firstEdge;while (q && q->adjVer != u){q = q->next;}while (q && q->visited)                                                  //防止重边无法正确遍历{q = q->next;while (q && q->adjVer != u){q = q->next;}}if (!q)cout << "bug" << endl;if (q)q->visited = true;search_euler(p->adjVer);}p = p->next;}Q.push(u);                                                                       //邻接点都被遍历则入栈
}void get_path(Node* p)                                                             //获得可行路径
{Node* q = p, * t = NULL, * r = NULL;int x, k;bool instant = true;while (!Q.empty())                                                                //生成欧拉回路{t = new Node;x = Q.top();Q.pop();t->point = x;t->next = NULL;q->next = t;q = t;}if (p->next){r = q = p->next;t = q->next;}else{cout << "此题无解" << endl;exit(1);}cout << "欧拉回路为:" << endl;while (r){cout << char(r->point - 1 + 'A');if (r->next)cout << "->";r = r->next;}cout << endl;while (t)                                                                  //得到最终路径{instant = true;if (IsExtend(q->point, t->point))                                        //补全非直接相连奇点间的路径{k = path[q->point][t->point];while (k != q->point){Q.push(k);k = path[q->point][k];instant = false;}r = q;while (!Q.empty()){x = Q.top();Node* node = new Node;node->point = x;r->next = node;r = node;r->next = t;Q.pop();}r = NULL;if (!instant)t = q->next;else{q = q->next;t = q->next;}}else{q = t;t = t->next;}}if (p->next)q = p->next;cout << "最终路径为:" << endl;while (q){cout << char(q->point - 1 + 'A');if (q->next)cout << "->";q = q->next;}cout << endl;
}void free()                                                                            //释放动态分配的内存
{int i;EdgeNode* p = NULL, * u = NULL;free(odd_point);for (i = 1; i < VerNum + 1; i++){p = VerList[i].firstEdge;VerList[i].firstEdge = NULL;while (p){u = p->next;delete p;p = u;}}
}void getarc(int& arc, int x, int y)                                                    //分区域获得角度,使分布均匀
{int n;if (x >= 75 && x <= 530){if (y >= 45 && y < 288){arc = rand() % 90 + 270;}else if (y >= 288 && y < 572){n = rand() % 2 + 1;if (n == 1)arc = rand() % 90;elsearc = rand() % 90 + 270;}else if (y >= 572 && y <= 815){arc = rand() % 90;}}else if (x > 530 && x <= 985){if (y >= 45 && y < 288){arc = rand() % 180 + 180;}else if (y >= 288 && y < 572){arc = rand() % 360;}else if (y >= 572 && y <= 815){arc = rand() % 180;}}else if (x > 985 && x <= 1450){if (y >= 45 && y <= 288){arc = rand() % 90 + 180;}else if (y > 288 && y <= 572){arc = rand() % 180 + 90;}else if (y > 572 && y <= 815){arc = rand() % 90 + 90;}}
}void getWeight(int& weight)                                                             //根据点的个数确定距离
{switch (VerNum){case 24:case 23:weight = 140;break;case 22:weight = 145;break;case 21:weight = 150;break;case 20:case 19:weight = 155;break;case 18:weight = 160;break;case 17:case 16:;case 15:weight = 170;break;case 14:case 13:case 12:case 11:case 10:case 9:weight = 180;break;}
}void generate(vN* xy, int i, EdgeNode* p)                                          //在以某点为圆心的圆上取得一点
{int weight, arc;int X = 0, Y = 0;if (!xy[p->adjVer].exist){getWeight(weight);while (X < 75 || X > 1450 || Y < 45 || Y > 815){getarc(arc, xy[i].x, xy[i].y);X = int(xy[i].x + weight * cos(arc * 3.14 / 180) + 0.5);Y = int(xy[i].y + weight * sin(arc * 3.14 / 180) + 0.5);}xy[p->adjVer].x = X;xy[p->adjVer].y = Y;xy[p->adjVer].exist = true;}
}void getXY(vN* xy)                                                                 //获得结点XY坐标
{int i, k;int X = 0, Y = 0;bool all;EdgeNode* p = NULL;xy[1].x = 757;xy[1].y = 430;xy[1].name = 'A';xy[1].exist = true;for (i = 2; i <= VerNum; i++){xy[i].name = char(i - 1) + 'A';xy[i].exist = false;}for (i = 1; i <= VerNum; i++){if (!xy[i].exist)continue;p = VerList[i].firstEdge;while (p){if (p->eInfo >= 4 && p->eInfo <= 20)generate(xy, i, p);p = p->next;}}while (1){all = true;for (i = 2; i <= VerNum; i++){if (!xy[i].exist){all = false;p = VerList[i].firstEdge;while (p){if (xy[p->adjVer].exist && p->eInfo >= 4 && p->eInfo <= 20)break;p = p->next;}if (p){k = p->adjVer;p = VerList[k].firstEdge;while (p && p->adjVer != i)p = p->next;generate(xy, k, p);}}}if (all)break;}adjustXY(xy);
}void adjustXY(vN* xy)                                                              //调整结点坐标使分布均匀
{int i, j, weight;bool all;int X = 0, Y = 0;while (1){all = true;;for (i = 1; i < VerNum; i++){for (j = i + 1; j <= VerNum; j++){getWeight(weight);if (sqrt((xy[i].x - xy[j].x) * (xy[i].x - xy[j].x) + (xy[i].y - xy[j].y) * (xy[i].y - xy[j].y)) < 135.0){all = false;while (sqrt((xy[i].x - xy[j].x) * (xy[i].x - xy[j].x) + (xy[i].y - xy[j].y) * (xy[i].y - xy[j].y)) < 135.0){X = rand() % 1375 + 75;Y = rand() % 770 + 45;xy[j].x = X;xy[j].y = Y;//cout << "X: " << X << "Y: " << Y << endl;}}}}if (all)break;}}void paint(vN* xy)                                                                    //显示结点和相关信息
{int i;EdgeNode* p = NULL;setfillcolor(GREEN);settextstyle(20, 10, _T("黑体"));setbkmode(TRANSPARENT);setcolor(BLACK);for (i = 1; i <= VerNum; i++){fillcircle(xy[i].x, xy[i].y, 8);outtextxy(xy[i].x, xy[i].y + 6, xy[i].name);}
}void drawline(vN* xy)                                                              //显示点之间的边
{int i;EdgeNode* p = NULL;setlinecolor(BLACK);setlinestyle(PS_SOLID, 1);for (i = 1; i <= VerNum; i++){p = VerList[i].firstEdge;while (p){if (p->eInfo >= 4 && p->eInfo <= 20)line(xy[i].x, xy[i].y, xy[p->adjVer].x, xy[p->adjVer].y);p = p->next;}}
}void getd(double& d, double k)                                                     //根据斜率调整步长
{k = fabs(k);if (k >= 0 && k < 2){d = 51.0;}else if (k >= 2 && k < 5){d = 23;}else if (k >= 5 && k < 10){d = 19.0;}else if (k > 10 && k <= 15){d = 5.0;}else if (k > 15 && k <= 20){d = 2.5;}else if (k > 20 && k <= 30){d = 1.75;}else if (k > 30 && k <= 80){d = 1.0;}else if (k > 80 && k <= 120){d = 0.5;}else if (k > 120 && k <= 250){d = 0.35;}else if (k > 250 && k <= 400){d = 0.20;}else if (k > 400 && k <= 500){d = 0.10;}else if (k > 500 && k <= 800){d = 0.08;}else if(k > 800 && k <= 1200){d = 0.05;}else{d = 0.01;}
}void flag(int x, int y)                                                                //每到达一点,高亮显示位置
{setfillcolor(BROWN);fillcircle(x, y, 8);
}void move(int x, int y, int x1, int y1, vN* xy)                                        //邮递员移动函数
{double d, k, x0, y0, x2, y2;x0 = x, y0 = y, x2 = x1, y2 = y1;k = (y1 - double(y)) / (x1 - double(x));getd(d, k);if (x < x1){BeginBatchDraw();                                                          //开始批量绘图while (x0 < x2){paint(xy);outtextxy(5, 5, _T("注:图中距离与实际权值并非一一对应,点的相对位置与真实位置未必相符,详情参考C:\\Users\\pc\\Desktop\\CPP.txt"));int j = int(k * (x0 - double(x2)) + y2 + 0.5);setfillcolor(DARKGRAY);fillcircle(int(x0 + 0.5), j, 2);Sleep(500);FlushBatchDraw();                                                      //显示if (x0 + d >= x2){x0 = x2;flag(int(x0 + 0.5), int(k * (x0 - double(x2)) + y2 + 0.5));}elsex0 += d;}EndBatchDraw();                                                              //结束批量绘图}else{BeginBatchDraw();while (x0 > x2){paint(xy);outtextxy(5, 5, _T("注:图中距离与实际权值并非一一对应,点的相对位置与真实位置未必相符,详情参考C:\\Users\\pc\\Desktop\\CPP.txt"));setfillcolor(DARKGRAY);int j = int(k * (x0 - double(x2)) + y2 + 0.5);fillcircle(int(x0 + 0.5), j, 2);Sleep(500);FlushBatchDraw();if (x0 - d <= x2){x0 = x2;flag(int(x0 + 0.5), int(k * (x0 - double(x2)) + y2 + 0.5));}elsex0 -= d;}EndBatchDraw();}
}void show(vN* xy, Node* Euler)                                                     //动态演示求解过程
{Node* p = NULL, * q = NULL;p = Euler->next;q = p->next;while (q){move(xy[p->point].x, xy[p->point].y, xy[q->point].x, xy[q->point].y, xy);p = q;q = q->next;}paint(xy);drawline(xy);
}int main()
{initgraph(1545, 875, EW_SHOWCONSOLE);                                          //初始化带有控制台的窗口setbkcolor(WHITE);                                                             //调整背景为白色cleardevice();HWND hwnd = GetHWnd();                                                          //全屏显示SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) - WS_CAPTION);SetWindowPos(hwnd, HWND_TOP, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CXSCREEN), SWP_SHOWWINDOW);srand((signed)time(NULL));                                                       //确定随机种子initialize();vN xy[MaxVerNum];Node* Euler = (Node*)malloc(sizeof(Node));cout << "Loading a Graph with " << VerNum << " nodes" << endl;connected();odd();writetofile();//将顶点表储存的符合要求的的图写入文件,用于验证Floyd();extend_edge();search_euler(1);get_path(Euler);getXY(xy);show(xy, Euler);system("pause");closegraph();                                                                  //关闭窗口free();                                                                           //释放内存return 0;
}

写在最后:
个人认为本题在复杂度上能进行很大的优化,因为时间有限未能完成。当时尚未学习离散数学,图论算法如匈牙利算法,Fleury算法等并不了解。求欧拉回路也是采用的DFS算法,可以通过保存尾指针位置来提升效率,本文没有进行这样的优化。在图形动态演示方面,效果也不尽人意,但是也能说的过去,图的结点个数和位置是随机的,想要形成美观的演示是比较困难的,这也是一直困扰的问题。无论如何,这对于课程设计来说还是合格的。

中国邮路问题的解决(数据结构课程设计)相关推荐

  1. C/C++《数据结构课程设计》任务书[2022-12-27]

    C/C++<数据结构课程设计>任务书[2022-12-27] <数据结构课程设计>任务书 一.任务总体安排: 班级 设计时间 地点 指导老师 21软件开发 17周每周一至周五五 ...

  2. 大学数据结构课程设计题目

    数据结构课程设计题目 1.         飞机订票系统(限1 人完成) 任务:通过此系统可以实现如下功能: 录入: 可以录入航班情况(数据可以存储在一个数据文件中,数据结构.具体数据自定) 查询: ...

  3. 数据结构 课程设计报告

    <数据结构课程设计> 课程题目 电话客服模拟系统 课程编号 j1620102 学生姓名 严乾聪 学生学号 201311671424 所在专业 信息管理与信息系统 所在班级 信管1134班 ...

  4. 山东大学数据结构课程设计实验五(低风险出行系统)

    数据结构课程设计(五)--低风险出行系统 前言 题目要点 ①生成数据 ②要给定两种最短路解法 ③创立文件 ④模拟时间流动并与用户交互 代码讲解 源代码 写在最后 前言 数据结构课程设计第五题是每一个同 ...

  5. 数据结构课程设计—简单行编辑程序

    简单行编辑程序 一.引言 1.1 问题的提出 文本编辑程序是利用计算机进行文字加工的基本软件工具,实现对文本文件的插入.删除等修改操作.限制这些操作以行为单位进行的编辑程序称为行编辑程序. 被编辑的文 ...

  6. c语言数据结构课程设计停车场管理系统,数据结构课程设计报告停车场管理系统...

    <数据结构课程设计报告停车场管理系统>由会员分享,可在线阅读,更多相关<数据结构课程设计报告停车场管理系统(8页珍藏版)>请在人人文库网上搜索. 1.数据结构课程设计报告系 别 ...

  7. 学生搭配问题数据结构报告c语言,数据结构课程设计_学生搭配问题.doc

    数据结构课程设计_学生搭配问题 数据结构课程设计 题 目: 学生搭配问题 学 院: 班 级: 学 生 姓 名: 学 生 学 号: 指 导 教 师: 2012 年 12 月 3 日 课程设计任务书 姓名 ...

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

    数据结构 课程设计报告 广州大学 计算机科学与网络工程学院 计算机系 17级计科专业2班 2019年6月30日 广州大学学生实验报告 开课学院及实验室:计算机科学与工程实验室              ...

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

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

  10. c语言实现一元多项式程序报告设计,数据结构课程设计报告一元多项式的计算..doc...

    数据结构课程设计报告一元多项式的计算. 题目:一元多项式的计算 --链表 摘要(题目) 一元多项式计算 任务:能够按照指数降序排列建立并输出多项式: 能够完成两个多项式的相加.相减,并将结果输入: 目 ...

最新文章

  1. SD模块的几个增强(VA01-VA03,VA41-VA43)
  2. 在java中必须要有main吗_在一个Java应用程序中main方法必须被说明为_____。
  3. LeetCode Populating Next Right Pointers in Each Node II(dfs)
  4. 移动互联网教育领域或将出现新的风口?
  5. Java开发知识之Java面相对象
  6. python数据输出_python数据输出
  7. [python学习] 模仿浏览器下载CSDN源文并实现PDF格式备份
  8. JSONObject和JSONArray 以及Mybatis传入Map类型参数
  9. 单词短语搭配用法网站
  10. js hover 触发事件_为什么说JS的DOM操作很耗性能
  11. 【三维深度学习】点云上采样网络PU-Net
  12. 每日总结app_焊工日常工作的主要职责是什么?焊工证考试用什么APP复习?
  13. 建议简书评论区升级筛选/排序功能
  14. 【优化预测】基于matlab布谷鸟算法优化灰色模型预测【含Matlab源码 1244期】
  15. 二分类模型评估之 ROC曲线和PR曲线
  16. lol8月21号服务器维护,8月21日英雄联盟更新维护到几点 lol8.21更新维护公告
  17. SQL(oracle)常用命令
  18. Android 学习笔记(十二):安卓中的事件分发机制
  19. 前端CSS核心内容定位
  20. 福州IT企业之金庸群侠传

热门文章

  1. mysql php教程视频教程下载地址_最全138节Mysql数据库+PHP零基础到精通,视频教程下载...
  2. android+后台下载notification,Android实现Service下载文件,Notification显示下载进度的示例...
  3. 铜厨具的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  4. vector函数去除最后一个数据以及输出
  5. java 节气_谁有关于24节气的算法,最后有java实现的代码
  6. dw中未能检测到测试服务器,dw测试服务器找不到远端主机
  7. SpringThirdDay
  8. 在vmware环境下安装ubuntu
  9. 使用sealer-构建、交付、运行【kubernetes】-demo
  10. RuntimeException和Exception