@@ -38,6 +38,7 @@ public class TransportationService {
3838 private static final int ORIGIN_DESTINATION_WALK_DISTANCE = 1000 ; // 출발지/도착지에서 정류장까지 도보 연결 가능 거리 (m)
3939 private static final int MAX_NEARBY_NODES = 5 ; // 출발지/도착지 주변에서 고려할 최대 정류장 수
4040 private static final int MAX_DIJKSTRA_VISITS = 5000 ; // 다익스트라 알고리즘에서 방문할 수 있는 최대 노드 수 (무한 루프 방지)
41+ private static final int MAX_ROUTES = 5 ; // 찾을 최대 경로 수
4142
4243 public TransportationResponseDto findRoutes (TransportationRequestDto request ){
4344
@@ -54,27 +55,39 @@ public TransportationResponseDto findRoutes(TransportationRequestDto request){
5455 Node start = Node .createNode (-1L , origin .name (), DirectionType .FROM_WAYPOINT ,origin .latitude (), origin .longitude ());
5556 Node end = Node .createNode (-2L , destination .name (), DirectionType .TO_WAYPOINT ,destination .latitude (), destination .longitude ());
5657
57- // 3. 경로 찾기
58- List <TransportationResponseDto .Step > steps = findTransportationRoute (start , end );
58+ // 3. 여러 경로 찾기
59+ List <List < TransportationResponseDto .Step >> allRoutes = findMultipleTransportationRoutes (start , end );
5960
6061 // 4. 페이징 처리
6162 int startIndex = (request .cursor () != null ) ? request .cursor () : 0 ;
62- int pageSize = request .size () != null ? request .size () : steps . size ();
63- int endIndex = Math .min (startIndex + pageSize , steps .size ());
64- boolean hasNext = endIndex < steps .size ();
63+ int pageSize = ( request .size () != null ) ? request .size () : 5 ; // 기본값 5로 설정
64+ int endIndex = Math .min (startIndex + pageSize , allRoutes .size ());
65+ boolean hasNext = endIndex < allRoutes .size ();
6566 Integer nextCursor = hasNext ? endIndex : null ;
6667 TransportationResponseDto .PageInfo pageInfo = new TransportationResponseDto .PageInfo (nextCursor , hasNext );
6768
6869 // 경로를 찾지 못한 경우 처리
69- if (steps .isEmpty ()) {
70+ if (allRoutes .isEmpty ()) {
7071 throw new ApplicationException (PATH_NOT_FOUND );
7172 }
7273
73- return new TransportationResponseDto (steps , pageInfo );
74+ // 페이징된 경로들을 Route 객체로 변환
75+ List <TransportationResponseDto .Route > routeList = new ArrayList <>();
76+ List <List <TransportationResponseDto .Step >> pagedRoutes = allRoutes .subList (startIndex , endIndex );
77+ for (int i = 0 ; i < pagedRoutes .size (); i ++) {
78+ List <TransportationResponseDto .Step > route = pagedRoutes .get (i );
79+ TransportationResponseDto .Route routeObj = createRoute (route , startIndex + i + 1 );
80+ routeList .add (routeObj );
81+ }
82+
83+ return new TransportationResponseDto (routeList , pageInfo );
7484 }
7585
86+ private TransportationResponseDto .Route createRoute (List <TransportationResponseDto .Step > steps , int routeIndex ) {
87+ return new TransportationResponseDto .Route (routeIndex , steps );
88+ }
7689
77- private List <TransportationResponseDto .Step > findTransportationRoute (Node startTmp , Node endTmp ){
90+ private List <List < TransportationResponseDto .Step >> findMultipleTransportationRoutes (Node startTmp , Node endTmp ){
7891 // 1. 데이터 로드
7992 List <Node > nodes = new ArrayList <>(nodeRepository .findAll ());
8093 List <Edge > edges = new ArrayList <>(edgeRepository .findAllWithNodesAndRoute ());
@@ -91,13 +104,12 @@ private List<TransportationResponseDto.Step> findTransportationRoute(Node startT
91104 nodes .add (startTmp );
92105 nodes .add (endTmp );
93106
94- // 4. 그래프 빌드 및 최적 경로 찾기
107+ // 4. 그래프 빌드 및 여러 경로 찾기
95108 TransportationGraphDto graphData = buildGraph (nodes , edges , startTmp , endTmp );
96- return findOptimalRoute (graphData .graph (), startTmp , endTmp , graphData .weightMap (), nodes , nearestToStart , nearestToEnd );
109+ return findMultipleOptimalRoutes (graphData .graph (), startTmp , endTmp , graphData .weightMap (), nodes , nearestToStart , nearestToEnd );
97110 }
98111
99-
100- private List <TransportationResponseDto .Step > findOptimalRoute (
112+ private List <List <TransportationResponseDto .Step >> findMultipleOptimalRoutes (
101113 Map <Long , List <Edge >> graph ,
102114 Node startTmp ,
103115 Node endTmp ,
@@ -121,26 +133,179 @@ private List<TransportationResponseDto.Step> findOptimalRoute(
121133 return new ArrayList <>();
122134 }
123135
124- // 2. 다익스트라 알고리즘으로 최적 경로 찾기
125- List <TransportationResponseDto .Step > route = runDijkstra (graph , startNode , endNode , weightMap , nodes );
136+ // 2. 여러 경로 찾기
137+ List <List <TransportationResponseDto .Step >> allRoutes = findMultipleRoutes (graph , startNode , endNode , weightMap , nodes );
138+
139+ // 3. 경로 필터링 및 정렬
140+ return filterAndSortRoutes (allRoutes );
141+ }
142+
143+ private List <List <TransportationResponseDto .Step >> findMultipleRoutes (
144+ Map <Long , List <Edge >> graph ,
145+ Node start ,
146+ Node end ,
147+ Map <Pair <Long , Long >, Integer > weightMap ,
148+ List <Node > nodes ) {
149+
150+ List <List <TransportationResponseDto .Step >> routes = new ArrayList <>();
151+
152+ // 1. 기본 다익스트라로 첫 번째 경로 찾기
153+ List <TransportationResponseDto .Step > firstRoute = runDijkstra (graph , start , end , weightMap , nodes );
154+ if (!firstRoute .isEmpty ()) {
155+ routes .add (firstRoute );
156+ }
157+
158+ // 2. 효율적인 다중 경로 찾기 - 한 번의 탐색으로 여러 경로 생성
159+ if (!firstRoute .isEmpty ()) {
160+ List <List <TransportationResponseDto .Step >> alternativeRoutes = findAlternativeRoutesEfficiently (
161+ graph , start , end , weightMap , nodes , firstRoute
162+ );
163+ routes .addAll (alternativeRoutes );
164+ }
126165
127- if (!route .isEmpty ()) {
128- // 3. 대중교통 포함 여부 확인
129- boolean hasPublicTransport = route .stream ()
130- .anyMatch (step -> step .mode () == DirectionType .BUS || step .mode () == DirectionType .SUBWAY );
166+ return routes ;
167+ }
168+
169+ private List <List <TransportationResponseDto .Step >> findAlternativeRoutesEfficiently (
170+ Map <Long , List <Edge >> graph ,
171+ Node start ,
172+ Node end ,
173+ Map <Pair <Long , Long >, Integer > weightMap ,
174+ List <Node > nodes ,
175+ List <TransportationResponseDto .Step > firstRoute ) {
176+
177+ List <List <TransportationResponseDto .Step >> alternativeRoutes = new ArrayList <>();
178+
179+ // 첫 번째 경로에서 실제 사용된 엣지들을 추출
180+ Set <Pair <Long , Long >> usedEdges = extractActualEdgesFromRoute (firstRoute , graph );
181+
182+ // 최대 4개의 추가 경로 찾기
183+ for (int i = 0 ; i < 4 && alternativeRoutes .size () < MAX_ROUTES - 1 ; i ++) {
184+ // 실제 사용된 엣지들에만 패널티를 적용한 가중치 맵 생성
185+ Map <Pair <Long , Long >, Integer > penalizedWeightMap = createActualEdgePenalizedWeightMap (weightMap , usedEdges , i + 1 );
186+
187+ // 다익스트라로 새로운 경로 찾기
188+ List <TransportationResponseDto .Step > newRoute = runDijkstra (graph , start , end , penalizedWeightMap , nodes );
131189
132- if (! hasPublicTransport ) {
133- return new ArrayList <>() ;
190+ if (newRoute . isEmpty () ) {
191+ break ;
134192 }
135193
136- // 4. 환승 횟수 검증 (4회 이상 제외)
137- int transferCount = calculateTransferCount (route );
138- if (transferCount >= 4 ) {
139- return new ArrayList <>();
194+ // 첫 번째 경로와 동일한지 확인
195+ if (areRoutesIdentical (newRoute , firstRoute )) {
196+ continue ;
140197 }
198+
199+ // 새로운 경로에서 사용된 엣지들도 추가
200+ Set <Pair <Long , Long >> newUsedEdges = extractActualEdgesFromRoute (newRoute , graph );
201+ usedEdges .addAll (newUsedEdges );
202+
203+ alternativeRoutes .add (newRoute );
141204 }
142205
143- return route ;
206+ return alternativeRoutes ;
207+ }
208+
209+
210+
211+
212+
213+ private Set <Pair <Long , Long >> extractActualEdgesFromRoute (List <TransportationResponseDto .Step > route , Map <Long , List <Edge >> graph ) {
214+ Set <Pair <Long , Long >> usedEdges = new HashSet <>();
215+
216+ for (TransportationResponseDto .Step step : route ) {
217+ String fromName = step .from ();
218+ String toName = step .to ();
219+
220+ for (Map .Entry <Long , List <Edge >> entry : graph .entrySet ()) {
221+ Long nodeId = entry .getKey ();
222+ List <Edge > edges = entry .getValue ();
223+
224+ for (Edge edge : edges ) {
225+ Node fromNode = edge .getStartNode ();
226+ Node toNode = edge .getEndNode ();
227+
228+ if ((fromNode .getStationName ().equals (fromName ) && toNode .getStationName ().equals (toName )) ||
229+ (fromNode .getStationName ().equals (toName ) && toNode .getStationName ().equals (fromName ))) {
230+ usedEdges .add (Pair .of (fromNode .getId (), toNode .getId ()));
231+ usedEdges .add (Pair .of (toNode .getId (), fromNode .getId ()));
232+ }
233+ }
234+ }
235+ }
236+
237+ return usedEdges ;
238+ }
239+
240+ private Map <Pair <Long , Long >, Integer > createActualEdgePenalizedWeightMap (Map <Pair <Long , Long >, Integer > originalWeightMap , Set <Pair <Long , Long >> usedEdges , int routeIndex ) {
241+ Map <Pair <Long , Long >, Integer > penalizedWeightMap = new HashMap <>();
242+
243+ for (Map .Entry <Pair <Long , Long >, Integer > entry : originalWeightMap .entrySet ()) {
244+ Pair <Long , Long > edge = entry .getKey ();
245+ int weight = entry .getValue ();
246+
247+ if (usedEdges .contains (edge )) {
248+ int penalty = routeIndex * 100000 ;
249+ penalizedWeightMap .put (edge , weight + penalty );
250+ } else {
251+ penalizedWeightMap .put (edge , weight );
252+ }
253+ }
254+
255+ return penalizedWeightMap ;
256+ }
257+
258+ private boolean areRoutesIdentical (List <TransportationResponseDto .Step > route1 , List <TransportationResponseDto .Step > route2 ) {
259+ // 두 경로가 완전히 동일한지 확인
260+ if (route1 .size () != route2 .size ()) {
261+ return false ;
262+ }
263+
264+ for (int i = 0 ; i < route1 .size (); i ++) {
265+ TransportationResponseDto .Step step1 = route1 .get (i );
266+ TransportationResponseDto .Step step2 = route2 .get (i );
267+
268+ if (step1 .mode () != step2 .mode () ||
269+ !Objects .equals (step1 .from (), step2 .from ()) ||
270+ !Objects .equals (step1 .to (), step2 .to ()) ||
271+ !Objects .equals (step1 .routeName (), step2 .routeName ())) {
272+ return false ;
273+ }
274+ }
275+
276+ return true ;
277+ }
278+
279+ private List <List <TransportationResponseDto .Step >> filterAndSortRoutes (List <List <TransportationResponseDto .Step >> routes ) {
280+ return routes .stream ()
281+ .filter (route -> {
282+ // 대중교통 포함 여부 확인
283+ boolean hasPublicTransport = route .stream ()
284+ .anyMatch (step -> step .mode () == DirectionType .BUS || step .mode () == DirectionType .SUBWAY );
285+
286+ if (!hasPublicTransport ) {
287+ return false ;
288+ }
289+
290+ // 환승 횟수 검증 (4회 이상 제외)
291+ int transferCount = calculateTransferCount (route );
292+ return transferCount < 4 ;
293+ })
294+ .sorted (Comparator
295+ .<List <TransportationResponseDto .Step >>comparingInt (this ::calculateTransferCount )
296+ .thenComparingInt (this ::calculateWalkDistance ))
297+ .limit (MAX_ROUTES )
298+ .collect (Collectors .toList ());
299+ }
300+
301+ private int calculateWalkDistance (List <TransportationResponseDto .Step > route ) {
302+ return route .stream ()
303+ .filter (step -> step .mode () == DirectionType .WALK )
304+ .mapToInt (step -> {
305+ // 간단한 도보 거리 추정 (실제로는 정확한 거리 계산 필요)
306+ return 500 ; // 기본값
307+ })
308+ .sum ();
144309 }
145310
146311 private TransportationGraphDto buildGraph (List <Node > nodes , List <Edge > edges , Node startTmp , Node endTmp ) {
@@ -457,9 +622,9 @@ private List<TransportationResponseDto.Step> mergeConsecutiveRoutes(List<Edge> p
457622 return new ArrayList <>();
458623 }
459624 }
460- } catch (Exception e ) {
625+ } catch (Exception e ) {
461626 log .info ("버스 정보 조회 실패: {}" , e .getMessage ());
462- }
627+ }
463628 } else if (currentType == DirectionType .SUBWAY ) {
464629 try {
465630 if (currentEdge .getStartNode () != null ) {
0 commit comments