본문 바로가기

(3)

프로그래머스 코딩 테스트 연습문제: 여행경로

문제 출처: https://programmers.co.kr/learn/courses/30/lessons/43164 코딩테스트 연습 - 여행경로 | 프로그래머스 [[ICN, SFO], [ICN, ATL], [SFO, ATL], [ATL, ICN], [ATL,SFO]] [ICN, ATL, ICN, SFO, ATL, SFO] programmers.co.kr 프로그래머스 고득점 Kit 문제 DFS/BFS 문제 Level 3 (마지막 문제)를 푸는데 어려움이 많았다. 이전까지 풀었던 DFS/BFS와 달리 풀이에 어려움이 많았다. 문제 설명 주어진 항공권을 모두 이용하여 여행경로를 짜려고 합니다. 항상 ICN 공항에서 출발합니다. 항공권 정보가 담긴 2차원 배열 tickets가 매개변수로 주어질 때, 방문하는 공항..

[알고리즘] 너비 우선 탐색 (BFS, Breadth First Search)

참고: https://blog.naver.com/ndb796/221230944971 15. 너비 우선 탐색(BFS) 너비 우선 탐색(Breadth First Search, BFS)은 탐색을 할 때 너비를 우선으로 하여 탐색을 수행하는 ... blog.naver.com 너비 우선 탐색 (BFS)은 탐색을 할 때, 너비를 우선으로 하여 탐색을 수행하는 알고리즘이다. 특히 '맹목적인 탐색'을 하고자 할 때 사용할 수 있는 탐색 기법이다. 너비 우선 탐색은 최단경로를 찾아준다는 점에서 최단길이를 보장해야 할 때 많이 사용한다. (DFS가 스택을 사용했다면, BFS는 큐(Queue)를 이용한다. * 너비 우선 탐색이란 ? 루트노드(또는 다른 임의의 노드)에서 시작해서 인접한 노드를 먼저 탐색하는 방법이다. 시작 ..

[알고리즘] 깊이 우선 탐색 ( DFS, Depth-First Search)

참고: https://gmlwjd9405.github.io/2018/08/14/algorithm-dfs.html [알고리즘] 깊이 우선 탐색(DFS)이란 - Heee's Development Blog Step by step goes a long way. gmlwjd9405.github.io 깊이 우선 탐색은 탐색을 함에 있어서 보다 깊은 것을 우선적으로 탐색하는 알고리즘이다. 조금 쉽게 얘기하자면 루트노드(또는 임의의 노드)에서 시작해서 다음 분기 (branch)로 넘어가기 전에 해당 분기를 완벽하게 탐색하는 방법이다. 이러한 깊이 우선 탐색은 맹목적으로 각 노드를 탐색할 때 주로 사용된다. 너비 우선 탐색(BFS)에서는 큐(Queue)가 사용되었다면, 깊이 우선 탐색에서는 스택(Stack)이 사용된다...

코딩 테스트

프로그래머스 코딩 테스트 연습문제: 여행경로

728x90

문제 출처: https://programmers.co.kr/learn/courses/30/lessons/43164

 

코딩테스트 연습 - 여행경로 | 프로그래머스

[[ICN, SFO], [ICN, ATL], [SFO, ATL], [ATL, ICN], [ATL,SFO]] [ICN, ATL, ICN, SFO, ATL, SFO]

programmers.co.kr

 

프로그래머스 고득점 Kit 문제 DFS/BFS 문제 Level 3 (마지막 문제)를 푸는데 어려움이 많았다. 
이전까지 풀었던 DFS/BFS와 달리 풀이에 어려움이 많았다. 

문제 설명

주어진 항공권을 모두 이용하여 여행경로를 짜려고 합니다. 항상 ICN 공항에서 출발합니다.

항공권 정보가 담긴 2차원 배열 tickets가 매개변수로 주어질 때, 방문하는 공항 경로를 배열에 담아 return 하도록 solution 함수를 작성해주세요.

제한 사항

  • 모든 공항은 알파벳 대문자 3글자로 이루어집니다.
  • 주어진 공항 수는 3개 이상 10,000개 이하입니다.
  • tickets의 각 행 [a, b]는 a 공항에서 b 공항으로 가는 항공권이 있다는 의미입니다.
  • 주어진 항공권은 모두 사용해야 합니다.
  • 만일 가능한 경로가 2개 이상일 경우 알파벳 순서가 앞서는 경로를 return 합니다.
  • 모든 도시를 방문할 수 없는 경우는 주어지지 않습니다.

입출력 예

tickets                                                                                              return

[[ICN, JFK], [HND, IAD], [JFK, HND]] [ICN, JFK, HND, IAD]
[[ICN, SFO], [ICN, ATL], [SFO, ATL], [ATL, ICN], [ATL,SFO]] [ICN, ATL, ICN, SFO, ATL, SFO]

입출력 예 설명

예제 #1

[ICN, JFK, HND, IAD] 순으로 방문할 수 있습니다.

예제 #2

[ICN, SFO, ATL, ICN, ATL, SFO] 순으로 방문할 수도 있지만 [ICN, ATL, ICN, SFO, ATL, SFO] 가 알파벳 순으로 앞섭니다.

나의 접근 방법은 tickets의 도착지를 알파벳순으로 먼저 정렬한 후에, 출발지가 ICN인 경우에 시작점으로 등록하고, 그 후에 순차적으로 연결되는 항공편을 찾는 방법이었는데, 사용 유무를 표시한 후에 DFS를 실행했을 시에, 해당 경로로 더 이상 진행이 불가능할 경우에 다시 이전 단계로 돌아와야 했는데, (using이라는 boolean 배열을 tickents 행의 길이만큼 선언한 후 시도) 그 해결책을 찾지 못해서 결국 커뮤니티에서 답을 찾았다. 먼저 내가 짠 코드를 먼저 보겠다. (눈이 아플지도 모르니, 주의하자)

● 나의 풀이

public class Solution {

    boolean[] using;
    static ArrayList<String> list;
    static int start;
    static ArrayList<String> banpick;

    public String[] solution(String[][] tickets) {
        String[] answer = {};
        using = new boolean[tickets.length]; //모두 false 로 초기화
        list = new ArrayList<>();
        banpick = new ArrayList<>();
        start = 0;

        Arrays.sort(tickets, new Comparator<String[]>() {
            @Override
            public int compare(String[] o1, String[] o2) {
                return o1[1].compareTo(o2[1]);
            }
        });

        dfs(tickets, start);

        answer = new String[list.size()];
        for (int i = 0; i < list.size(); i++) {
            answer[i] = list.get(i);
            System.out.print(answer[i] + " ");
        }

        return answer;
    }

    public void dfs(String[][] tickets, int startPoint) {

        if (list.size() == 0) {
            System.out.println("start:" + start + "-----");
            if (tickets[startPoint][0].equals("ICN")) {
                list.add(tickets[startPoint][0]); // "ICN"이다.
                System.out.println(tickets[startPoint][0] + "추가 size:" + list.size());
                list.add(tickets[startPoint][1]);
                System.out.println(tickets[startPoint][1] + "추가 size:" + list.size());
                using[startPoint] = true;
            } else {
                dfs(tickets, ++start);
                return;
            }
        }

        int cnt = 0;
        boolean[] check = new boolean[tickets.length];//false로 초기화
        //이전 내용 저장

        for (int i = 0; i < tickets.length; i++) {
            if (!using[i] && tickets[startPoint][1].equals(tickets[i][0])) {
                if (!banpick.contains(tickets[i][0])) {
                    cnt++;
                    check[i] = true;
                }
            }
        }

        boolean[] befusing = using;
        ArrayList<String> beflist = list;
        // 다음으로 넘어가는 티켓이 없는경우 이전단계로 회기한다.
        if (cnt == 0 && list.size() != tickets.length + 1) {
            //이전단계로 돌아가고 문제가 발생한 부분 뛰어넘게 만들기....
            banpick.add(tickets[startPoint][1]);
            System.out.println("도착지 = 출발지 일치x clear 했음");
            list.clear(); //좋은 방법이 아닌것같다.
            list = beflist;
            using = new boolean[tickets.length];

            dfs(tickets, start);

            banpick.clear();
        }

        if (list.size() == tickets.length + 1) {
            System.out.println("size:" + list.size());
            return;
        }

        for (int i = 0; i < check.length; i++) {
            if (check[i]) {
                list.add(tickets[i][1]);
                System.out.println(tickets[i][1] + " 추가 size:" + list.size());
                using[i] = true;
                dfs(tickets, i);
            }
        }

    }
}

 

이 풀이는 4개의 테스트 케이스 중에 4번 테스트 케이스를 통과하지 못해서 테스트를 통과하지 못했다.
(실제로임의로 만들어본 예제 String [][]= {{"ICN", "COO"}, {"COO", "ICN"}, {"COO", "ICN"}, {"ICN", "COO"}, {"ICN", "AAA"}};  예제도 통과하지 못했다) 

풀이 참고: https://youjourney.tistory.com/111

 

[프로그래머스] 여행경로 (java)(43164)

원본 문제 : https://programmers.co.kr/learn/courses/30/lessons/43164 문제 참고 : https://geehye.github.io/programmers-dfs-bfs-04/# 문제 설명 주어진 항공권을 모두 이용하여 여행경로를 짜려고 합니다...

youjourney.tistory.com

 

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Solution {
    
    static boolean visit[];
	static List<String> list = new ArrayList<>();
	static String route = "";
    
    public String[] solution(String[][] tickets) {
        visit = new boolean[tickets.length];
		
		for(int i = 0 ; i < tickets.length; i++) {
			
			String departure = tickets[i][0];
			String destination = tickets[i][1];
			
			if(departure.equals("ICN")) {
				visit[i] = true;
				route = departure + ",";
				dfs(tickets, destination, 1);
				visit[i] = false;
			}
		}
		
		Collections.sort(list);
		String[] answer = list.get(0).split(",");
		return answer;
    }
	
	public static void dfs(String[][] tickets, String end, int count) {
		
		route += end + ",";
		
		if(count==tickets.length) {
			list.add(route);
			return;
		}
		
		for(int i = 0 ; i < tickets.length ; i++) {
			String depart = tickets[i][0];
			String desti = tickets[i][1];
			
			if(end.equals(depart) && !visit[i]) {
				visit[i] = true;
				dfs(tickets, desti, count+1);
				visit[i] = false;
				route = route.substring(0, route.length()-4);
			}
		}
	}
}

 

728x90
알고리즘

[알고리즘] 너비 우선 탐색 (BFS, Breadth First Search)

728x90

참고: https://blog.naver.com/ndb796/221230944971

 

15. 너비 우선 탐색(BFS)

너비 우선 탐색(Breadth First Search, BFS)은 탐색을 할 때 너비를 우선으로 하여 탐색을 수행하는 ...

blog.naver.com

 

너비 우선 탐색 (BFS)은 탐색을 할 때, 너비를 우선으로 하여 탐색을 수행하는 알고리즘이다. 특히 '맹목적인 탐색'을 하고자 할 때 사용할 수 있는 탐색 기법이다. 너비 우선 탐색은 최단경로를 찾아준다는 점에서 최단길이를 보장해야 할 때 많이 사용한다. (DFS가 스택을 사용했다면, BFS는 큐(Queue)를 이용한다.

*  너비 우선 탐색이란 ?
 루트노드(또는 다른 임의의 노드)에서 시작해서 인접한 노드를 먼저 탐색하는 방법이다. 

  • 시작 정점으로부터 가까운 정점을 먼저 방문하고 멀리 떨어져있는 정점을 나중에 방문하는 순회방법
  • 즉 깊게(deep) 탐색하기 전에 넓게(wide) 탐색하는 것
  • 사용하는 경우: 두 노드 사이의 최단 경로 혹은 임의의 경로를 찾고 싶을 때
  • 너비 우선 탐색(BFS)이 깊이 우선 탐색(DFS)보다 좀 더 복잡하다

* 특징

  • 직관적이지 않다 -> 너비 우선 탐색(BFS)은 시작 노드에서 시작해서 거리에 따라 단계별로 탐색한다고 볼 수 있다
  • 너비 우선 탐색(BFS)은 재귀적으로 동작하지 않는다
  • 그래프 탐색의 경우 어떤 노드를 방문했었는지 여부를 반드시 검사해야 한다 -> 이를 검사하지 않으면 무한루프에 빠질 수 있다
  • BFS는 방문한 노드들을 차례로 저장한 후 꺼낼 수 있는 자료 구조인 큐(Queue)를 사용한다
    -> 즉, 선입선출(FIFO) 원칙으로 탐색
    -> 일반적으로 큐를 이용해서 반복적 형태로 구현하는 것이 가장 잘 동작한다

* 너비 우선 탐색(BFS)의 시간 복잡도

  • 인접 리스트로 표현된 그래프: O(N + E)
  • 인접 행렬로 표현된 그래프: O(N^2) 
  • 깊이 우선 탐색(DFS)와 마찬가지로 그래프 내에 적은 숫자의 간선만을 가지는 희소 그래프(Sparse Graph)의 경우
    인접 행렬보다 인접 리스트를 사용하는 것이 좋다

* BFS 구현 알고리즘

package queue;

import java.util.Iterator;
import java.util.LinkedList;

//인접 리스트를 이용한 방향성 있는 그래프 클래스
public class Graph {
    private int v; //노드의 개수
    private LinkedList<Integer> adj[]; //인접 리스트

    //생성자
    Graph(int v) {
        this.v = v;
        adj = new LinkedList[v];
        for (int i = 0; i < v; i++) //인접 리스트 초기화
            adj[i] = new LinkedList();
    }

    // 노드를 연결 v -> w
    void addEdge(int v, int w) {
        adj[v].add(w);
    }

    // s를 시작 노드 한 BFS로 탐색하면서 탐색한 노드들을 출력
    void BFS(int s) {
        // 노드의 방문 여부 판단 (초기값: false)
        boolean visted[] = new boolean[v];
        // BFS 구현을 위한 큐(Queue) 생성
        LinkedList<Integer> queue = new LinkedList<>();

        // 현재 노드를 방문한 것으로 표시하고 큐에 삽입(enqueue)
        visted[s] = true;
        queue.add(s);

        // 큐(Queue)가 빌 때 까지 반복
        while (queue.size() != 0) {
            // 방문한 노드를 큐에서 추출(dequeue)하고 값을 출력
            s = queue.poll();
            System.out.print(s + " ");

            // 방문한 노드와 인접한 모든 노드를 가져온다.
            Iterator<Integer> i = adj[s].listIterator();
            while (i.hasNext()) {
                int n = i.next();
                // 방문하지 않은 노드면 방문한 것으로 표시하고 큐에 삽입(enqueue)
                if (!visted[n]) {
                    visted[n] = true;
                    queue.add(n);
                }
            }
        }
    }
}
 //사용방법 --> Graph 클래스 객체 사용
 
    public static void main(String[] args) {

        Graph g = new Graph(4);

        g.addEdge(0,1);
        g.addEdge(0,2);
        g.addEdge(1,2);
        g.addEdge(2,0);
        g.addEdge(2,3);
        g.addEdge(3,3);

        g.BFS(2); //주어진 노드를 시작 노드로 BFS 탐색

    }
728x90
알고리즘

[알고리즘] 깊이 우선 탐색 ( DFS, Depth-First Search)

728x90

참고: https://gmlwjd9405.github.io/2018/08/14/algorithm-dfs.html

 

[알고리즘] 깊이 우선 탐색(DFS)이란 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

 

깊이 우선 탐색은 탐색을 함에 있어서 보다 깊은 것을 우선적으로 탐색하는 알고리즘이다.
조금 쉽게 얘기하자면 루트노드(또는 임의의 노드)에서 시작해서 다음 분기 (branch)로 넘어가기 전에
해당 분기를 완벽하게 탐색하는 방법이다. 이러한 깊이 우선 탐색은 맹목적으로 각 노드를 탐색할 때 주로 사용된다. 
너비 우선 탐색(BFS)에서는 큐(Queue)가 사용되었다면, 깊이 우선 탐색에서는 스택(Stack)이 사용된다. 
(사실 컴퓨터는 구조적으로 항상 스택의 원리를 사용하기 때문에, 스택을 사용하지 않아도 구현이 가능하다) 

깊이 우선 탐색은 탐색기법 자체로는 큰 의미가 없고 이 탐색법을 활용해서 문제를 해결하고 하는 것에 초점을 둔다. (작동원리에 대해서만 빠삭하게 이해하면된다)

 

●  DFS의 특징 

  • 사용하는 경우: 모든 노드를 방문하고자 하는 경우에 이 방법을 선택
  • 깊이 우선 탐색이 너비 우선 탐색보다 좀 더 간단하다
  • 단순 검색속도 자체는 너비 우선탐색에 비해서 느리다 
  • 자기 자신을 호출하는 순환 알고리즘의 형태를 가지고있다 (재귀)

전위 순회(Pre-OrderTraversals)를 포함한 다른형태의 트리 순회는 모두 DFS의 한 종류이다. 

전위 순회 개념 (출처: 위키피아)

이 알고리즘을 구현할 때 가장 큰 차이점은, 그래프 탐색의 경우 '어떤 노드를 방문했었는지 여부'를 반드시 검사해야한다. (이를 검사하지 않으면 무한루프에 빠질 위험성이 생긴다)

●  깊이 우선 탐색(DFS)의 구현 

  1. 순환 호출 이용
  2. 명시적인 스택 이용
    · 명시적인 스택을 사용하여 방문한 정점들은 스택에 저장하였다가 다시 꺼내서 작업한다 

● 깊이 우선 탐색(DFS)의 시간 복잡도 

  • DFS는 그래프(정점의 수: N, 간선의수: E)의 모든 간선을 조회한다
    * 인접 리스트로 표현된 그래프: O(N + E)
    * 인접 행렬로 표현된 그래프: O(N^2)
  • 즉 그래프 내에 적은 숫자의 간선만을 가지는 희소 그래프(Spare Graph)의 경우 인접 행렬보다 인접 리스트를 사용하는 것이 유리하다

● 순환 호출을 이용한 DFS구현

package stack;

import java.util.Iterator;
import java.util.LinkedList;

// 인접 리스트를 이용한 방향성 있는 그래프 클래스
public class Graph {
    private int v; // 노드의 개수
    private LinkedList<Integer> adj[]; // 인접 리스

    Graph(int v) {
        this.v = v;
        adj = new LinkedList[v];
        // 인접 리스트 초기화
        for (int i = 0; i < v; i++)
            adj[i] = new LinkedList<>();
    }

    // 노드를 연결 v -> w
    void addEdge(int v, int w) {
        adj[v].add(w);
    }

    // DFS에 의해 사용되는 함수
    void DFSUtil(int v, boolean[] visited) {
        // 현재 노드를 방문한 것으로 표시하고 값을 출력
        visited[v] = true;
        System.out.print(v + " ");

        // 방문한 노드와 인접한 모든 노드를 가져온다
        Iterator<Integer> i = adj[v].listIterator();
        while (i.hasNext()) {
            int n = i.next();
            // 방문하지 않은 노드면 해당 노드를 시작 노드로 다시 DFSUtil 호출(재귀)
            if (!visited[n])
                DFSUtil(n, visited);
        }
    }

    // 주어진 노드를 시작 노드로 DFS 탐색
    void DFS(int v) {
        // 노드의 방문 여 판단 (초깃: false)
        boolean visited[] = new boolean[this.v];

        // v를 시작 노드로 DFSUtil 순환 호출
        DFSUtil(v, visited);
    }

    // DFS 탐색
    void DFS() {
        // 노드의 방문 여부 판단 (초깃값: false)
        boolean visited[] = new boolean[this.v];

        // 비연결형 그래프의 경우, 모든 정점을 하나씩 방문
        for (int i = 0; i < this.v; i++) {
            if (visited[i] == false)
                DFSUtil(i, visited);


        }
    }
}

 

 public static void main(String[] args) {
        Graph g = new Graph(4);

        g.addEdge(0, 1);
        g.addEdge(0, 2);
        g.addEdge(1, 2);
        g.addEdge(2, 0);
        g.addEdge(2, 3);
        g.addEdge(3, 3);

        g.DFS(2); // 주어진 노드를 시작 노드로 DFS 탐색
        g.DFS(); // 비연결형 그래프의 경우
        
    }
728x90