XXXX学院
工科课程设计 -《数据结构》
题 目: 地图着色问题
学 号: XXXXXX
姓 名: XXX
班 级: 软件工程二班
指导教师: XX老师
日 期: 2018年 12月
对数据结构课程内容的复习,把所学知识应用到实际问题中去,提升自身思考能力和编码能力,为之后的学习打下基础。
设计地图着色软件,对湖南省地图中的地级市进行着色,要求相邻地级市所使用的颜色不同,并保证使用的颜色最少。
Jdk1.8
运用JFrame类构造基本窗口,在窗口中显示绘图结果。
根据问题要求,可通过遍历所有节点来实现总体着色,也可将问题划分为多个小问题进行解决,可使用DFS递归来解决问题。又考虑到算法时间复杂度,在此用BFS解决,通过BFS遍历所有节点,判断节点是否被着色,如果被着色直接跳过,若没被着色就通过统计该节点的邻接节点颜色来推断该节点颜色,逐一涂色,问题解决。
运用队列来进行所有节点的储存,进行BFS之前,把一号节点先放入队列,在遍历一号节点的邻接节点是,把没有被着色的节点放入队列中,在把该节点标记为已着色。由于队列是一个先进先出的数据结构,所以才可在此问题中被使用。队列的数据结构正好符合着色问题中,优先遍历的需求。
3.1.1界面图
初始界面图
点击”开始”按钮后界面图
3.1.2界面设计内容
使用JFrame类创建窗口,在窗口中添加JPanel画板,重写JLabel类MyLabel,在MyLabel类中添加标签中心点的Point属性,Point属性可以确定标签在何位置,重写JLabel的构造函数,使其更方便的构造标签内容和位置。通过重写JPanel类中的paintComponent()函数,在两个标签之间画线。
重写的JLabel的构造函数如下:
private Point center;//添加一个中心点坐标的成员变量
MyLabel(int x,int y,String name){//构造函数
setName(name);
setCenter(new Point(x + 50,y + 25));
setBounds(x, y, 100, 50);
setFont(new Font(null, Font.PLAIN, 20));
setHorizontalAlignment(CENTER);
setVerticalAlignment(CENTER);
setOpaque(true);
setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, Color.BLACK));
}
重写的paintComponent()函数代码如下:
JPanel mapPanel = new JPanel(null){
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;//绘图类转为二维绘图类
g2.setStroke(new BasicStroke(2.0f));//设置画笔
for (int i=1;i<=13;i++){//通过循环寻找有路径的两个节点
for(int j=1;j<=13;j++){
if(mapLoad[i][j] == 1){
//画直线
DrawLine(g2, mapMember.get(i-1).getCenter(), mapMember.get(j-1).getCenter());
}
}
}
}
};
通过使用java类库中的Queue队列封装类作为主要数据结构,用链表实现该队列,构造该数据结构的方法为:
Queue<Integer>queue = new LinkedList<>();
节点和颜色则使用java类库中Vector向量封装类作为数据结构保存,构造该数据结构的方法为:
Vector<MyLabel> mapMember = new Vector<>();
Vector<Color> colors = new Vector<>();
路径则使用最基本的二维数组储存,由于数据不是太多,开15*15大小的二维数组即可,构造方法为:
mapLoad = new int[15][15];
以上就是最主要的几个数据结构。
在类中添加DrawColor()函数作为BFS算法接口,定义一个队列,把第一号节点放入队列中,通过一个循环判断该节点的邻接点是否有颜色,循环代码如下:
while(!queue.isEmpty()){//判断队列是否为空,非空则代表还有节点未被着色
int colorRes = 100;//颜色代码
int deal = queue.poll();//从队列中取出一个元素并从队列中删除该元素
System.out.println(mapMember.get(deal-1).getName());
int[] vis = new int[15];//记录邻接点有哪些颜色代码
for(int i=1;i<=13;i++){//遍历所有节点
if(mapLoad[deal][i] == 1){//如果i是该点的邻接点则记录颜色代码
vis[color[i]]++;//颜色代码+1
if(isQueue[i] == 0){//如果邻接点没有被着色则放入队列
queue.offer(i);//放入队列
isQueue[i] = 1;//标记为被着色
}
}
}
for(int i=1;i<=13;i++){//遍历所有颜色代码,取出一个没
if(vis[i] == 0) //有被使用过并且是最小的颜色代码
colorRes = Integer.min(colorRes, i);
}
color[deal] = colorRes;//把该节点标记选出的最小颜色代码
mapMember.get(deal-1).setBackground(colors.get(colorRes-1));//着色
}
此循环即可实现地图着色功能,并确保用最少的颜色。
基本着色功能已经实现,考虑到人机交互问题,是否应该增加选择省份等选项和着色动画。增加选择省份选项则要知道所有省份的地级市邻接表,工作量巨大。着色动画需要添加多线程以及更多java绘图编码知识,所以暂时还没有实现添加省份和着色动画等功能。
通过在BFS算法中输出遍历节点顺序:张家界市->常德市->怀化市->岳阳市->益阳市->邵阳市->娄底市->长沙市->永州市->衡阳市->湘潭市->株洲市->郴州市,以及笔算和查阅关于完全问题之一GCP问题之后可证实此算法符合问题内容要求。
此程序采用傻瓜式操作模式,点击“开始”按钮即可运行程序并在界面框中看到结果。
通过该课程设计让我加深了对队列和向量两个数据结构的认识,能够更加熟练的运用它们,对今后的编码有一定的帮助。
全部代码
1.Start类
import org.jb2011.lnf.beautyeye.BeautyEyeLNFHelper;
import javax.swing.*;
import java.awt.*;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Vector;
public class Start {
private static MyLabel zhangjiajie;
private static MyLabel changde;
private static MyLabel yueyang;
private static MyLabel huaihua;
private static MyLabel yiyang;
private static MyLabel changsha;
private static MyLabel shaoyang;
private static MyLabel loudi;
private static MyLabel xiangtan;
private static MyLabel zhuzhou;
private static MyLabel yongzhou;
private static MyLabel hengyang;
private static MyLabel chenzhou;
private static int[][] mapLoad;
private static Vector<MyLabel> mapMember;
private static Vector<Color> colors;
public static void main(String[] args) {
try {
BeautyEyeLNFHelper.frameBorderStyle = BeautyEyeLNFHelper.FrameBorderStyle.translucencyAppleLike;
BeautyEyeLNFHelper.launchBeautyEyeLNF();
} catch (Exception e) {
e.printStackTrace();
}
JFrame frame = new JFrame("地图着色v1.0");
frame.setLayout(null);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(400, 200, 1200, 825);
frame.setVisible(true);
JPanel panel = new JPanel(null);
panel.setBounds(0, 0, 1200, 825);
JButton button = new JButton("开始");
button.setBounds(50, 25, 150, 50);
/*13个地级市*/
zhangjiajie = new MyLabel(110, 50, "张家界市");
changde = new MyLabel( 320, 50, "常德市");
yueyang = new MyLabel( 610, 80, "岳阳市");
huaihua = new MyLabel( 80, 200, "怀化市");
yiyang = new MyLabel(480, 160, "益阳市");
changsha = new MyLabel( 820, 150, "长沙市");
shaoyang = new MyLabel( 200, 340, "邵阳市");
loudi = new MyLabel( 350, 260, "娄底市");
xiangtan = new MyLabel( 600, 340, "湘潭市");
zhuzhou = new MyLabel(800, 390, "株洲市");
yongzhou = new MyLabel( 220, 490, "永州市");
hengyang = new MyLabel( 470, 410, "衡阳市");
chenzhou = new MyLabel( 600, 510, "郴州市");
InitMapMessage();
/*连线*/
JPanel mapPanel = new JPanel(null){
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;//绘图类转为二维绘图类
g2.setStroke(new BasicStroke(2.0f));//设置画笔
for (int i=1;i<=13;i++){//通过循环寻找有路径的两个节点
for(int j=1;j<=13;j++){
if(mapLoad[i][j] == 1){
//画直线
DrawLine(g2, mapMember.get(i-1).getCenter(), mapMember.get(j-1).getCenter());
}
}
}
}
};
mapPanel.setBounds(50, 100, 1050, 600);
mapPanel.setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, Color.BLACK));
button.addActionListener(e -> {
DrawColor();
});
mapPanel.add(zhangjiajie);
mapPanel.add(changde);
mapPanel.add(yueyang);
mapPanel.add(changsha);
mapPanel.add(huaihua);
mapPanel.add(yiyang);
mapPanel.add(shaoyang);
mapPanel.add(loudi);
mapPanel.add(xiangtan);
mapPanel.add(zhuzhou);
mapPanel.add(yongzhou);
mapPanel.add(hengyang);
mapPanel.add(chenzhou);
panel.add(button);
frame.add(mapPanel);
frame.add(panel);
}
private static void DrawLine(Graphics2D g2,Point p1,Point p2){
g2.drawLine(p1.x, p1.y, p2.x, p2.y);
}
private static void InitMapMessage(){
colors = new Vector<>();
colors.add(Color.RED);
colors.add(Color.GREEN);
colors.add(Color.YELLOW);
colors.add(Color.CYAN);
mapMember = new Vector<>();
mapMember.add(zhangjiajie);
mapMember.add(changde);
mapMember.add(yueyang);
mapMember.add(huaihua);
mapMember.add(yiyang);
mapMember.add(changsha);
mapMember.add(shaoyang);
mapMember.add(loudi);
mapMember.add(xiangtan);
mapMember.add(zhuzhou);
mapMember.add(yongzhou);
mapMember.add(hengyang);
mapMember.add(chenzhou);
mapLoad = new int[15][15];
mapLoad[1][2] = mapLoad[2][1] = 1;
mapLoad[1][4] = mapLoad[4][1] = 1;
mapLoad[2][4] = mapLoad[4][2] = 1;
mapLoad[2][3] = mapLoad[3][2] = 1;
mapLoad[2][5] = mapLoad[5][2] = 1;
mapLoad[3][5] = mapLoad[5][3] = 1;
mapLoad[3][6] = mapLoad[6][3] = 1;
mapLoad[4][5] = mapLoad[5][4] = 1;
mapLoad[5][6] = mapLoad[6][5] = 1;
mapLoad[4][7] = mapLoad[7][4] = 1;
mapLoad[4][8] = mapLoad[8][4] = 1;
mapLoad[5][8] = mapLoad[8][5] = 1;
mapLoad[6][8] = mapLoad[8][6] = 1;
mapLoad[8][9] = mapLoad[9][8] = 1;
mapLoad[6][9] = mapLoad[9][6] = 1;
mapLoad[6][10] = mapLoad[10][6] = 1;
mapLoad[9][10] = mapLoad[10][9] = 1;
mapLoad[7][8] = mapLoad[8][7] = 1;
mapLoad[8][12] = mapLoad[12][8] = 1;
mapLoad[9][12] = mapLoad[12][9] = 1;
mapLoad[10][12] = mapLoad[12][10] = 1;
mapLoad[10][13] = mapLoad[13][10] = 1;
mapLoad[7][12] = mapLoad[12][7] = 1;
mapLoad[7][11] = mapLoad[11][7] = 1;
mapLoad[11][12] = mapLoad[12][11] = 1;
mapLoad[11][13] = mapLoad[13][11] = 1;
mapLoad[12][13] = mapLoad[13][12] = 1;
}
@SuppressWarnings("ConstantConditions")
private static void DrawColor(){
Queue<Integer>queue = new LinkedList<>();
int[] isQueue = new int[15];
int[] color = new int[15];
queue.offer(1);
isQueue[1] = 1;
while(!queue.isEmpty()){//判断队列是否为空,非空则代表还有节点未被着色
int colorRes = 100;//颜色代码
int deal = queue.poll();//从队列中取出一个元素并从队列中删除该元素
System.out.println(mapMember.get(deal-1).getName());
int[] vis = new int[15];//记录邻接点有哪些颜色代码
for(int i=1;i<=13;i++){//遍历所有节点
if(mapLoad[deal][i] == 1){//如果i是该点的邻接点则记录颜色代码
vis[color[i]]++;//颜色代码+1
if(isQueue[i] == 0){//如果邻接点没有被着色则放入队列
queue.offer(i);//放入队列
isQueue[i] = 1;//标记为被着色
}
}
}
for(int i=1;i<=13;i++){//遍历所有颜色代码,取出一个没
if(vis[i] == 0) //有被使用过并且是最小的颜色代码
colorRes = Integer.min(colorRes, i);
}
color[deal] = colorRes;//把该节点标记选出的最小颜色代码
mapMember.get(deal-1).setBackground(colors.get(colorRes-1));//着色
}
}
}
2.MyLabel类
import javax.swing.*;
import java.awt.*;
public class MyLabel extends JLabel {
private Point center;//添加一个中心点坐标的成员变量
MyLabel(int x,int y,String name){//构造函数
setName(name);
setCenter(new Point(x + 50,y + 25));
setBounds(x, y, 100, 50);
setFont(new Font(null, Font.PLAIN, 20));
setHorizontalAlignment(CENTER);
setVerticalAlignment(CENTER);
setOpaque(true);
setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, Color.BLACK));
}
public Point getCenter() {
return center;
}
private void setCenter(Point center) {
this.center = center;
}
}