[Java] 알고리즘 - 지하철 노드 탐색 (연결리스트, 트리 탐색 응용)


Development note/Java  2015.06.20 02:02

안녕하세요. 명월입니다.


이번 포스트는 지하철 노드 탐색에 대해서 작성해 보겠습니다.

사실 알고리즘에는 지하철 노드 탐색이라는 알고리즘은 없습니다. (혹시 있는데 제가 모를 수도 있습니다..) 비슷한 알고리즘은 있을 거라고는 생각되지만, 현재로써는 모르겠네요.

갑자기 이걸 왜 만드는가 하는 거에 대해서 회사 퇴근길에 문득 지하철 노선표를 보다가 이걸 탐색하는 알고리즘을 만들어 볼까는 생각이 들었습니다. 하나의 지하철 노선은 연결리스트라고 했을 때 환승역에서는 트리구조로 되어있는 탐색입니다.

쉽게 생각하면 연결리스트 + 트리구조라고 생각됩니다.

실제로는 이런 알고리즘을 쓰지 않고 모든 걸 데이터베이스로 처리할 겁니다. 검색할 때마다 알고리즘 구동시키면 엄청나게 느릴 테니깐요..

그래도 재미로 한 번 작성해보고 싶다는 생각해 만들어 봤습니다.


구성은 예외처리를 빼고 역정보를 가지고 있는 클래스(노드)와 노선 클래스로 구성되겠습니다.

노드는 연결리스트를 응용한 것으로 전 역과 다음 역에 대한 포인터를 가지고 노선 클래스에서 탐색하는 것입니다.

import java.util.ArrayList;

public class Station {
  //전역(복수가 될수 있다.환승역 예:동대문운동장)
  private final ArrayList<Station> prev;
  //다음역(복수가 될수 있다.환승역 예:동대문운동장)
  private final ArrayList<Station> next;
  //역코드(임의)
  private String code = null;
  //역이름
  private String name = null;
  //생성자
  public Station(){
    prev = new ArrayList<Station>();
    next = new ArrayList<Station>();
  }
  //이하 프로퍼티
  public void setCode(String code){
    this.code = code;
  }
  public String getCode(){
    return this.code;
  }
  public void setName(String name){
    this.name = name;
  }
  public String getName(){
    return this.name;
  }
  public Station getPrev(int index){
    return this.prev.get(index);
  }
  public void addPrev(Station value){
    if(!this.prev.contains(value)){
      this.prev.add(value);
    }
  }
  public Station getNext(int index){
    return this.next.get(index);
  }
  public void addNext(Station value){
    if(!this.next.contains(value)){
      this.next.add(value);
    }
  }
  public String toString(){
    return "[CODE]" + code + "[NAME]"+name;
  }
  public int getPrevCount(){
    return this.prev.size();
  }
  public int getNextCount(){
    return this.next.size();
  }
}

역 클래스에는 전역과 다음 역이 동대문 같은 경우는 여러 개의 노선이 중첩되어 있으니 ArrayList로 복수가 됩니다. 그 외에 역 코드, 역 이름 정보가 있습니다.


다음은 노선 탐색 클래스입니다.


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Stack;

/**
 * 지하철 노선 탐색 알고리즘 
 */
public class Subway {
  // 역정보 리스트
  private final ArrayList<Station> stations;
  /**
   * 생성자 리스트
   */
  public Subway(){
    stations = new ArrayList<Station>();
  }
  /**
   * 역추가(다형성)
   * @param code 역 코드
   * @param name 역 이름
   */
  public void addStation(String code,String name) throws SubwayException{
    Station s = new Station();
    s.setCode(code);
    s.setName(name);
    addStation(s);
  }
  /**
   * 역추가(다형성)
   * @param station 역 클래스
   */
  public void addStation(Station station) throws SubwayException{
    //역코드가 중복일시 에러를 내보낸다
    if(getStation(station.getCode()) != null){
      throw new SubwayException("same code");
    }
    stations.add(station);
  }
  /**
   * 파일로 부터 역정보를 읽어드린다.(다형성)
   * @param file 파일
   */
  public void addStation(File file) throws SubwayException,IOException{
    //파일을 전부 읽어드린다.
    byte[] buffer = new byte[(int)file.length()];
    try(FileInputStream in = new FileInputStream(file)){
      in.read(buffer, 0, buffer.length);
    }
    String data = new String(buffer);
    // 구분자 \n로 역정보를 전부 읽어드린다.(라인 하나에 역하나)
    String[] lineBuffer = data.split("\n");
    // 데이터가 하나도 없으면 에러
    if(lineBuffer.length <= 0) {
      new IOException("document style wrong");
    }
    // 역을 클래스화
    for(String line : lineBuffer){
      // 구분자는 콤마[,]
      String[] buf = line.split(",");
      // 데이터 이상은 통과
      if(buf.length != 2){
        continue;
      }
      // 파일 형식은 코드,역이름
      addStation(buf[0],buf[1]);
    }
  }
  /**
   * 코드로 역을 찾는다
   * @param code
   */
  public Station getStation(String code){
    for(Station s: stations){
      if(s.getCode().equals(code)){
        return s;
      }
    }
    return null;
  }
  /**
   * 역 링크정보 읽기
   * @param file 파일
   */
  public void setLinkStation(File file) throws IOException{
    //파일을 전부 읽어드린다.
    byte[] buffer = new byte[(int)file.length()];
    try(FileInputStream in = new FileInputStream(file)){
      in.read(buffer, 0, buffer.length);
    }
    String data = new String(buffer);
    // 구분자 \n로 링크정보를 전부 읽어드린다.(라인 하나에 링크하나)
    String[] lineBuffer = data.split("\n");
    if(lineBuffer.length <= 0){
      new IOException("document style wrong");
    }
    // 링크 만들기
    for(String line : lineBuffer){
      // 구분자는 콤마[,]
      String[] buf = line.split(",");
      // 데이터 이상은 통과
      if(buf.length != 3){
        continue;
      }
      //buf[1]의 다음역은 buf[2]
      if("n".equals(buf[0])){
        setNextStation(buf[1],buf[2]);
      }
      //buf[1]의 전역은 buf[2]
      else if("p".equals(buf[0])){
        setPrevStation(buf[1],buf[2]);
      }
    }
  }
  /**
   * 다음역 세팅
   * @param point 기준 역 정보
   * @param next 다음 역 정보
   */
  public void setNextStation(Station point,Station next){
    point.addNext(next);
    next.addPrev(point);
  }
  /**
   * 다음역 세팅(다형성)
   * @param pointCode 역 코드
   * @param nextCode 역 코드
   */
  public void setNextStation(String pointCode,String nextCode){
    Station point = getStation(pointCode);
    Station next = getStation(nextCode);
    setNextStation(point,next);
  }
  /**
   * 전역 세팅
   * @param point 기준 역 정보
   * @param prev 전역정보
   */
  public void setPrevStation(Station point,Station prev){
    point.addPrev(prev);
    prev.addNext(point);
  }
  /**
   * 전역 세팅(다형성)
   * @param pointCode 전역 코드
   * @param prevCode 전역 코드
   */
  public void setPrevStation(String pointCode,String prevCode){
    Station point = getStation(pointCode);
    Station prev = getStation(prevCode); 
    setPrevStation(point,prev);
  }
  /**
   * 역 탐색
   * @param start 출발 역코드
   * @param end 도착 역코드
   * @return 노선 출력
   */
  public String search(String start,String end) throws SubwayException{
    Station startStation = getStation(start);
    Station endStation = getStation(end);
    return search(startStation,endStation);
  }
  /**
   * 역 탐색 (다형성)
   * @param start 출발 역정보
   * @param end 도착 역정보
   * @return 노선 출력
   */
  public String search(Station start,Station end) throws SubwayException{
    //모든 탐색 정보
    ArrayList<ArrayList<Station>> list = new ArrayList<ArrayList<Station>>();
    //탐색용 버퍼
    Stack<Station> buffer = new Stack<Station>();
    //역코드로 역을 탐색할때 없으면 에러처리
    if(getStation(start.getCode()) == null) {
      throw new SubwayException("Not Station");
    }
    //역코드로 역을 탐색할때 없으면 에러처리
    if(getStation(end.getCode()) == null) {
      throw new SubwayException("Not Station");
    }
    //경로 탐색
    nodeExplorer(start, end, buffer, list);
    
    //출역
    String ret = "";
    int index = 0;
    int size = 999999;
    //노드가 가장 적은 역이 어떤건지 찾음(최단 탐색)
    for(int i=0;i<list.size();i++){
      if(list.get(i).size() < size){
        size = list.get(i).size();
        index = i;
      }
    }
    //모든 경로를 출력한다.
    for(ArrayList<Station> item : list){
      ret += print(item);
    }
    ret += "\r\n\r\n";
    //최단 경로를 출력한다.
    ret += "Best root\r\n";
    ret += print(list.get(index));
    return ret;
  }
  private String print(ArrayList<Station> item){
    StringBuffer sb =new StringBuffer(); 
    sb.append("Size : "+item.size()+"**");
    for(Station s:item){
      if(sb.length() > 0){
        sb.append("->");
      }
      sb.append(s.toString());
    }
    sb.append("\r\n");
    return sb.toString();
  }
  /**
   * 노드 탐색(재귀적으로 탐색한다.)
   * @param point 현재 탐색 역
   * @param end 종착역
   * @param buffer 버퍼
   * @param list 노드리스트
   * 
   * 재귀의 구조
   * 도봉에서 석계를 간다고 가정할 때 처음 도봉의 전역, 다음역으로 재귀를 호출
   * 전역은 망월사 -> 회룡 -> 의정부 북부까지 갔는데 해당 역이 종착역을 만나지 못하면
   * pop으로 다시 도봉까지 돌아옴
   * 도봉역에서 방학-> 창동-> 노원 -> 상계 -> 당고개를 가지만 또 해당역이 종착을 못 만나고 
   * pop으로 돌아옴.그러나 중간에 노원에서 분기되었기 때문에
   * 노원에서 재탐색을 실시. 쌍문-> 수유 쪽의 경로를 탐색하게 됨
   * 그런 식으로 석계역을 만날 때까지 모든 경로를 탐색함. 
   */
  private boolean nodeExplorer(
      Station point,
      Station end,
      Stack<Station> buffer,
      ArrayList<ArrayList<Station>> list){
    //탐색역과 종착역이 같으면 도착함
    if(point == end){
      //탐색 노드 선언
      ArrayList<Station> root = new ArrayList<Station>();
      //노드 담기
      for(Station s:buffer){
        root.add(s);
      }
      //마지막역 담기
      root.add(point);
      //리스트에 추가
      list.add(root);
      //종료
      return true;
    }
    //현재역이 없으면 재 탐색
    if(point == null){
      return false;
    }
    //버퍼에 현재 역 담기
    buffer.push(point);
    //현재 역의 전역 개수만큼
    for (int i = 0; i < point.getPrevCount(); i++) {
      // 버퍼에 현재역이 있으면 돌아가기(지나간 역을 다시 지나가면 의미 없음)
      // 예)종각에서 시청을 갔는데 시청에서 다시 종각으로 돌아가면 의미 없음
      if(buffer.contains(point.getPrev(i))){
        continue;
      }
      //없으면 전역으로 이동
      if(!nodeExplorer(point.getPrev(i), end, buffer, list)){
        //재탐색이 되면 현재역은 경로가 아님
        if(buffer.size() > 0) {
          buffer.pop();
        }
      }
    }
    //현재 역의 다음역 개수만큼
    for (int i = 0; i < point.getNextCount(); i++) {
      // 버퍼에 현재역이 있으면 돌아가기(지나간 역을 다시 지나가면 의미없음)
      // 예)종각에서 시청을 갔는데 시청에서 다시 종각으로 돌아가면 의미 없음
      if(buffer.contains(point.getNext(i))) {
        continue;
      }
      if (!nodeExplorer(point.getNext(i), end, buffer, list)) {
        //재탐색이 되면 현재 역은 경로가 아님
        if(buffer.size() > 0) {
          buffer.pop();
        }
      }
    }
    //재탐색
    return false;
  }
  public String toString() {
    String ret = "";
    for (Station s : stations) {
      ret += s.toString() + "\r\n";
    }
    return ret;
  }
}

기본적으로 연결리스트를 만들거나 파일을 읽어드려서 메모리상의 노선도를 만드는 함수 등이 있습니다.

그러나 이 클래스의 핵심은 search 함수와 nodeExplorer 함수입니다.

search 함수는 외수에서 어느역에서 어느역까지 검색을 할 것인가에 대한 호출이고 nodeExplorer 는 실제 노드 탐색이 이루어집니다.

출발역에서 한 역씩 이동할 때마다 검색을 하네요. 그리고 그 역에 대한 정보는 Stack으로 저장을 하고요. 왔던 역을 다시 들리게 되면 그건 잘 못 온 노선으로 그냥 pass합니다.


그리고 text 파일을 만들어서 데이터를 넣고 검색을 해 보겠습니다.

1,의정부북부 - 1호선
2,의정부 - 1호선
3,회룡 - 1호선
4,망월사 - 1호선
5,도봉산 - 1호선
6,도봉 - 1호선
7,방학 - 1호선
8,창동 - 1호선+4호선
9,녹천 - 1호선
10,월계 - 1호선
11,성북 - 1호선
12,석계 - 1호선
13,신이문 - 1호선
14,외대앞 - 1호선
15,회기 - 1호선
16,청량리(지하) - 1호선
17,제기동 - 1호선
18,신설동 - 1호선+2호선
19,동묘앞 - 1호선
20,동대문 - 1호선+4호선
21,종로5가 - 1호선
22,종로3가 - 1호선+3호선
23,종각 - 1호선
24,시청 - 1호선
25,서울역 - 1호선+4호선
26,남영 - 1호선
27,용산 - 1호선
28,노량진 - 1호선
29,대방 - 1호선
30,신길 - 1호선
31,영등포 - 1호선
32,신도림 - 1호선+2호선
33,구로 - 1호선
34,구일 - 1호선
35,개봉 - 1호선
36,오류동 - 1호선
37,온수 - 1호선
38,역곡 - 1호선
39,소사 - 1호선
40,부천 - 1호선
41,중동 - 1호선
42,송내 - 1호선
43,부계 - 1호선
44,부평 - 1호선
45,백운 - 1호선
46,동암 - 1호선
47,간석 - 1호선
48,주안 - 1호선
49,도화 - 1호선
50,재물포 - 1호선
51,도원 - 1호선
52,동인천 - 1호선
53,인천 - 1호선
54,청량리(지상) - 중앙선
55,왕십리 - 중앙선
56,응봉 - 중앙선
57,옥수 - 중앙선
58,한남 - 중앙선
59,서빙고 - 중앙선
60,이촌 - 중앙선+4호선
61,가리봉 - 1호선
62,독산 - 1호선
63,시흥 - 1호선
64,석수 - 1호선
65,관악 - 1호선
66,안양 - 1호선
67,명학 - 1호선
68,금정 - 1호선
69,군포 - 1호선
70,부곡 - 1호선
71,성균관대 - 1호선
72,화서 - 1호선
73,수원 - 1호선
74,세류 - 1호선
75,병점 - 1호선
76,용두- 2호선
77,신답 - 2호선
78,용답 - 2호선
79,성수 - 2호선
80,건대입구 - 2호선
81,구의 - 2호선
82,강변 - 2호선
83,성내 - 2호선
84,잠실 - 2호선
85,신천 - 2호선
86,종합운동장 - 2호선
87,삼성 - 2호선
88,선릉 - 2호선
89,역삼 - 2호선
90,강남 - 2호선
91,교대 - 2호선+3호선
92,서초 - 2호선
93,방배 - 2호선
94,사당 - 2호선+4호선
95,낙성대 - 2호선
96,서울대입구 - 2호선
97,봉천 - 2호선
98,신림 - 2호선
99,신대방 - 2호선
100,구로디지털단지 - 2호선
101,대림 - 2호선
102,문래 - 2호선
103,당산 - 2호선
104,합정 - 2호선
105,홍대입구 - 2호선
106,신촌 - 2호선
107,이대 - 2호선
108,아현 - 2호선
109,충정로 - 2호선
110,을지로입구 - 2호선
111,을지로3가 - 2호선+3호선
112,을지로4가 - 2호선
113,동대문운동장 - 2호선+4호선
114,신당 - 2호선
115,상왕십리 - 2호선
116,왕십리 - 2호선
117,한양대 - 2호선
118,뚝섬 - 2호선
119,도림천 - 2호선
120,양천구청 - 2호선
121,신정네거리 - 2호선
122,까치산 - 2호선
123,대화 - 3호선
124,주엽 - 3호선
125,정발산 - 3호선
126,마두 - 3호선
127,백석 - 3호선
128,대곡 - 3호선
129,화정 - 3호선
130,원당 - 3호선
131,삼송 - 3호선
132,지축 - 3호선
133,구파발 - 3호선
134,연신내 - 3호선
135,불광 - 3호선
136,녹번 - 3호선
137,홍제 - 3호선
138,무악재 - 3호선
139,독립론 - 3호선
140,경복궁 - 3호선
141,안국 - 3호선
142,충무로 - 3호선+4호선
143,동대입구 - 3호선
144,약수 - 3호선
145,금호 - 3호선
146,옥수 - 3호선
147,압구정 - 3호선
148,신사 - 3호선
149,잠원 - 3호선
150,고속터미널 - 3호선
152,남부터미널 - 3호선
153,양재 - 3호선
154,매봉 - 3호선
155,도곡 - 3호선
156,대치 - 3호선
157,학여울 - 3호선
158,대청 - 3호선
159,일원 - 3호선
160,수서 - 3호선
151,당고개 - 4호선
161,상계 - 4호선
162,노원 - 4호선
163,쌍문 - 4호선
164,수유 - 4호선
165,미아 - 4호선
166,미아삼거리 - 4호선
167,길음 - 4호선
168,성신여대입구 - 4호선
169,한성대입구 - 4호선
170,혜화 - 4호선
171,명동 - 4호선
172,회현 - 4호선
173,숙대입구 - 4호선
174,삼각지 - 4호선
175,신용산 - 4호선
177,동작 - 4호선
178,총신대입구 - 4호선
179,남태령 - 4호선
180,선바위 - 4호선
181,경마공원 - 4호선
182,대공원 - 4호선
183,과천 - 4호선
184,정부과천청사 - 4호선
185,인덕원 - 4호선
186,평촌 - 4호선
187,범계 - 4호선
188,금정 - 4호선

위 파일 정보는 지하철 역입니다.

n,1,2
n,2,3
n,3,4
n,4,5
n,5,6
n,6,7
n,7,8
n,8,9
n,9,10
n,10,11
n,11,12
n,12,13
n,13,14
n,14,15
n,15,16
n,16,17
n,17,18
n,18,19
n,19,20
n,20,21
n,21,22
n,22,23
n,23,24
n,24,25
n,25,26
n,26,27
n,27,28
n,28,29
n,29,30
n,30,31
n,31,32
n,32,33
n,33,34
n,34,35
n,35,36
n,36,37
n,37,38
n,38,39
n,39,40
n,40,41
n,41,42
n,42,43
n,43,44
n,44,45
n,45,46
n,46,47
n,47,48
n,48,49
n,49,50
n,50,51
n,51,52
n,52,53
n,15,54
n,54,55
n,55,56
n,56,57
n,57,58
n,58,59
n,59,60
n,60,27
n,33,61
n,61,62
n,62,63
n,63,64
n,64,65
n,65,66
n,66,67
n,67,68
n,68,69
n,69,70
n,70,71
n,71,72
n,72,73
n,73,74
n,74,75
n,18,76
n,76,77
n,77,78
n,78,79
n,79,80
n,80,81
n,81,82
n,82,83
n,83,84
n,84,85
n,85,86
n,86,87
n,87,88
n,88,89
n,89,90
n,90,91
n,91,92
n,92,93
n,93,94
n,94,95
n,95,96
n,96,97
n,97,98
n,98,99
n,99,100
n,100,101
n,101,32
n,32,102
n,102,103
n,103,104
n,104,105
n,105,106
n,106,107
n,107,108
n,108,109
n,109,110
n,110,111
n,111,112
n,112,113
n,113,114
n,114,115
n,115,116
n,116,117
n,117,118
n,118,79
n,32,119
n,119,120
n,120,121
n,121,122
n,123,124
n,124,125
n,125,126
n,126,127
n,127,128
n,128,129
n,129,130
n,130,131
n,131,132
n,132,133
n,133,134
n,134,135
n,135,136
n,136,137
n,137,138
n,138,139
n,139,140
n,140,141
n,141,22
n,22,111
n,111,142
n,142,143
n,143,144
n,144,145
n,145,146
n,146,147
n,147,148
n,148,149
n,149,150
n,150,91
n,91,152
n,152,153
n,153,154
n,154,155
n,155,156
n,156,157
n,157,158
n,158,159
n,159,160
n,151,161
n,161,162
n,162,8
n,8,163
n,163,164
n,164,165
n,165,166
n,166,167
n,167,168
n,168,169
n,169,170
n,170,20
n,20,113
n,113,142
n,142,171
n,171,172
n,172,25
n,25,173
n,173,174
n,174,175
n,175,60
n,60,177
n,177,178
n,178,94
n,94,179
n,179,180
n,180,181
n,181,182
n,182,183
n,183,184
n,184,185
n,185,186
n,186,187
n,187,188

위 정보는 연결 리스트 정보입니다.

그럼 Main을 작성해서 실행해 보도록 하겠습니다.

import java.io.File;

public class Main {
  public static void main(String[] args){
    try{
      String path = System.getProperty("user.dir");
      Subway subway = new Subway();
      //프로퍼티 역정보(역정보)취득
      subway.addStation(new File(path+"/station.txt"));
      //프로퍼티 링크정보(지하철 노선)취득
      subway.setLinkStation(new File(path+"/link.txt"));
      //의정부 북부에서 교대의 최단 노선을 구하여라
      String ret = subway.search("1","91");
      //결과출력
      System.out.println(ret);
    }catch(Throwable e){
      e.printStackTrace();
    }
  }
}

그럼 의정부역에서 교대역까지 검색해서 확인해 보겠습니다..


아래는 결과 내용입니다.

처리 시간이 상당히 걸리네요..

실제로 인터넷이나 모바일로 보는 노선 탐색은 이런 알고리즘을 작성하는 것이 아니라 데이터베이스를 기반으로 정보를 취득해서 표시할 겁니다.

뭐 처음 데이터를 만들 때는 이런 알고리즘을 이용했을지도 모르겠습니다.


소스 - github 바로가기


댓글 6개가 달렸습니다.
댓글쓰기

  1. 2015.06.23 21:54 |  수정/삭제  댓글쓰기

    비밀댓글입니다

    • 明月 v명월v
      2015.06.24 21:13 신고 |  수정/삭제

      안녕하세요... 블로그 방문 감사합니다...
      먼저 피보나치 힙 기반의 dijkstra는 뭔가요?? 첨듣는 알고리즘인데??
      피보나치는 수열의 명칭아닌가요?? 힙은 메모리의 논리적 기반이구요..
      dijkstra는 데이크스트라를 말씀하시는 건지?? 제가 만든 지하철 최단 경로와 데이크스트라 알고리즘이랑은 개념이 틀린 이야기 입니다만...
      데이크스트라는 각 도로의 길이를 설정해서 최단경로를 찾는 문제라고 기억을 하고 있습니다만.. 제가 만든건 모든 경로는 같은 길이라는 가정(실제로 지하철의 역간격읜 거의 비슷합니다. 3호선 빼고) 하에 노드를 가정 적게 거치는 경로, 즉 역을 가장 적게 지나치는 경로를 탐색해서 최단거리를 찾는 알고리즘입니다.
      모든 노드를 연결 리스트로 묶고 재귀함수를 이용한 트리 탐색으로 갈 수 있는 모든 경로를 구한 다음에 가장 적은 노드 수를 가진 것을 출력하는 방식입니다.
      핸드폰으로 보는 노선 탐색은 이런 알고리즘을 작성하는 것이 아니라 데이터베이스를 기반으로 정보를 취득해서 표시합니다" 에 대해서는 실제로 우리가 핸드폰이나 인터넷으로 지하철 시간표 및 걸리는 시간, 최단 거리를 나타내는 프로그램들은 이런 알고리즘을 사용해서 나타내는 것 이 아닌 종로에서 출발하면 모든 역의 최단 거리를 데이터 베이스에 집어넣고 검색하는 방식으로 개발합니다.
      이유는 성능 때문이겠지요... 알고리즘을 일일히 돌려 버리면 느려져 버리니깐요.... 도움이 되시길 바랍니다.

    • 明月 v명월v
      2015.06.24 21:15 신고 |  수정/삭제

      제가 예제 소스를 첨부하였으니 실제로 돌려 보시면 이해하시는데 도움이 되실 수 있습니다.

    • skin0011
      2016.04.24 14:39 |  수정/삭제

      NullPointerException이 뜨네요...
      java.lang.NullPointerException
      at Subway.setNextStation(Subway.java:101)
      at Subway.setNextStation(Subway.java:107)
      at Subway.setLinkStation(Subway.java:90)
      at SubwayFind_2605_2615.main(SubwayFind_2605_2615.java:14)

  2. 유진
    2018.06.21 12:05 |  수정/삭제  댓글쓰기

    글씨체가 엄청 이쁘네요! 글잘봤습니다~ 감사합니다.


  3. 2018.11.13 22:26 |  수정/삭제  댓글쓰기

    비밀댓글입니다