본문 바로가기

(179)

프로그래머스 코딩 테스트 연습문제: 타겟 넘버 (DFS/BFS)

문제출처: https://programmers.co.kr/learn/courses/30/lessons/43165 코딩테스트 연습 - 타겟 넘버 | 프로그래머스 n개의 음이 아닌 정수가 있습니다. 이 수를 적절히 더하거나 빼서 타겟 넘버를 만들려고 합니다. 예를 들어 [1, 1, 1, 1, 1]로 숫자 3을 만들려면 다음 다섯 방법을 쓸 수 있습니다. -1+1+1+1+1 = 3 +1-1+1+1+1 = 3 +1+1-1+1+1 = 3 +1+1+1-1+1 = 3 +1+1+1+1-1 = 3 사용할 수 있는 숫자가 담긴 배열 numbers, 타겟 넘버 target이 매개변수로 주어질 때 숫자를 적절히 더하고 빼서 타겟 넘 programmers.co.kr 해당 문제는 고득점 Kit 문제에서 DFS/BFS 탐색 기법으..

[알고리즘] 순열(Permutation)

참고: https://gorakgarak.tistory.com/522 순열(Permutation) 알고리즘 1,2,3 와 같은 숫자들이이 있다. 이것을 중복하지 않고 순서를 끌어내는 방법을 생각해보자 1-2-3 1-3-2 2-1-3 2-3-1 3-1-2 3-2-1 여섯가지 방법이 존재한다. 이제 숫자 네개인 1,2,3,4를 한번 섞어본다. 1-2-3-4.. gorakgarak.tistory.com 프로그래머스의 코딩 테스트 문제 중에서 완적 탐색 기법 lever2 단계인 '소수 찾기'를 풀다가 '순열'의 개념이 필요하게 되었다. 직접 구현해보려고 했으나, 쉽지않아서 구글링을 통해서 학습하였다. (알고리즘이 직관적으로 이해하기가 쉽지 않아 꽤 오랜 시간 손 코딩을 하면서 따라가며 이해하였다) 참고: htt..

[알고리즘] 너비 우선 탐색 (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)이 사용된다...

유니콘 우아한 형제들의 조직문화....

이 글은 아래의 내용을 읽은 후 느낀 점을 간략하게 작성하였습니다. 출처: https://www.folin.co/storybook/504/chapter/509 storybook / 코드가 아니라 밸류를 만든다 : 유니콘 우아한형제들의 조직문화 다양한 지식과 경험을 지닌 Linker들이 공유하는 인사이트 www.folin.co 한국의 대표적인 유니콘 기업하면 떠오르는 것이 '배달의 민족', 그리고 그 앱을 만든 개발자들이 소속된 곳 '우아한 형제들'(이하 우형) 몰랐었던 사실이지만 배민하면 떠오르는 키워드는 '디자인과 마케팅' 그리고 'B급감성' 일 것이다. 이는 개발자의 길을 걷지 않은 나로서는 잘 알았던 사실이었다. 하지만 내가 개발자의 길을 목표하고 본격적으로 공부를 시작하면서 우아한 형제들을 알게 ..

기술적인 고민이 차단된 환경에서도... 발전할 수 있는 방법

* 이 글은 아래의 글을 읽고 느낀 점을 작성하였습니다. 출처: https://www.popit.kr/si-%ea%b0%9c%eb%b0%9c-10%eb%85%84%ec%b0%a8%ec%9d%b8%eb%8d%b0-%ec%bd%94%eb%93%9c-%ec%a2%80-%eb%b4%90%ec%a3%bc%ec%84%b8%ec%9a%94/ SI 개발 10년차인데 코드 좀 봐주세요 | Popit 최근 한국에 머무르는 시간이 길어지면서 Popit 저자 섭외 활동과 병행하여 개발자 멘토링을 꾸준히 하고 있습니다. SI 경력 10년 정도 되는 개발자와 만나서 이야기 한 내용을 대략적으로 정리해 보았습니다 [1] . 어느날 페이스북 메신저로 SI 분야에서 계속 있으면서 10년 정도의 경력을 가지고 있는데 자신이 만든 코드에..

코딩 테스트

프로그래머스 코딩 테스트 연습문제: 타겟 넘버 (DFS/BFS)

728x90

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

 

코딩테스트 연습 - 타겟 넘버 | 프로그래머스

n개의 음이 아닌 정수가 있습니다. 이 수를 적절히 더하거나 빼서 타겟 넘버를 만들려고 합니다. 예를 들어 [1, 1, 1, 1, 1]로 숫자 3을 만들려면 다음 다섯 방법을 쓸 수 있습니다. -1+1+1+1+1 = 3 +1-1+1+1+1 = 3 +1+1-1+1+1 = 3 +1+1+1-1+1 = 3 +1+1+1+1-1 = 3 사용할 수 있는 숫자가 담긴 배열 numbers, 타겟 넘버 target이 매개변수로 주어질 때 숫자를 적절히 더하고 빼서 타겟 넘

programmers.co.kr

 

해당 문제는 고득점 Kit 문제에서 DFS/BFS 탐색 기법으로 풀 수 있는 문제라고 명시되어있다. 

문제 설명:

n개의 음이 아닌 정수가 있습니다. 이 수를 적절히 더하거나 빼서 타겟 넘버를 만들려고 합니다. 예를 들어 [1, 1, 1, 1, 1]로 숫자 3을 만들려면 다음 다섯 방법을 쓸 수 있습니다.

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

사용할 수 있는 숫자가 담긴 배열 numbers, 타겟 넘버 target이 매개변수로 주어질 때 숫자를 적절히 더하고 빼서 타겟 넘버를 만드는 방법의 수를 return 하도록 solution 함수를 작성해주세요.

제한 사항

  • 주어지는 숫자의 개수는 2개 이상 20개 이하입니다.
  • 각 숫자는 1 이상 50 이하인 자연수입니다.
  • 타겟 넘버는 1 이상 1000 이하인 자연수입니다.

 

블로그에서 DFS와 BFS 알고리즘을 정리한 적이 있으나, 막상 실제로 써먹기가 쉽지 않았다. (결국 ..40분동안 삽질만 하다가 찾아보았다)

참고: https://dreamhollic.tistory.com/entry/Programmers-%EA%B9%8A%EC%9D%B4%EB%84%88%EB%B9%84-%EC%9A%B0%EC%84%A0-%ED%83%90%EC%83%89DFSBFS-27-%ED%83%80%EA%B2%9F-%EB%84%98%EB%B2%84

 

Programmers > 깊이/너비 우선 탐색(DFS/BFS) > #27 타겟 넘버 [JAVA]

타겟 넘버 1. 문제 설명 n개의 음이 아닌 정수가 있습니다. 이 수를 적절히 더하거나 빼서 타겟 넘버를 만들려고 합니다. 예를 들어 [1, 1, 1, 1, 1]로 숫자 3을 만들려면 다음 다섯 방법을 쓸 수 있습니다. -1+1+..

dreamhollic.tistory.com

 

예제처럼 단순히 (같은 숫자) 1이 5개 주어진 경우에 초점을 맞춰서 풀었다가. 많은 오류를 범하였다. 
이 문제는 BFS가 아닌 DFS의 개념을 이용한다. DFS는 stack 개념을 사용하거나 또는 재귀를 이용하여 구현할 수 있다. 
참고한 코드는 재귀메소드를 이용하여 구현하였다. 

연산과정을 하나의 Tree로 생각해 볼 때, '+' 연산자를 왼쪽 자식노드로 '-' 연산자를 오른쪽 자식노드로 내려가는 과정을 재귀 메소드를 사용하여 코드로 표현하였다.

public class Solution{

   	static int answer = 0;

        public int solution(int[] numbers, int target) {

            dfs(target, numbers, 0);
            return answer;
        }

        public void dfs(int target, int[] numbers, int k) {
            if (k == numbers.length) {
                int sum = 0;
                for (int i = 0; i < numbers.length; i++) {
                    sum += numbers[i];
                }
                if (sum == target)
                    answer++;
                return;
            } else {
                numbers[k] *= 1;
                dfs(target, numbers, k + 1);

                numbers[k] *= -1;
                dfs(target, numbers, k + 1);
            }
        }
 }

 전역변수 answer를 선언하여, 결과값을 전역변수에 저장한다. (테스트 케이스 여러개 한번에 못 돌린다... 중첩되어 저장된다)

테스트 케이스 3개를 실행하였을 때, 정상적으로 결과값이 출력되었다.

numbers = { 1 , 1 , 1 , 1 , 1 } 
target = 1 
answer = 5

numbers = { 5 , 3 , 1 , 1 , 1 }
target = 1 
answer = 4

numbers = { 5 , 4 , 3 , 2 , 1 }
target = 3
answer = 3

728x90
알고리즘

[알고리즘] 순열(Permutation)

728x90

참고: https://gorakgarak.tistory.com/522

 

순열(Permutation) 알고리즘

1,2,3 와 같은 숫자들이이 있다. 이것을 중복하지 않고 순서를 끌어내는 방법을 생각해보자 1-2-3 1-3-2 2-1-3 2-3-1 3-1-2 3-2-1 여섯가지 방법이 존재한다. 이제 숫자 네개인 1,2,3,4를 한번 섞어본다. 1-2-3-4..

gorakgarak.tistory.com

 

프로그래머스의 코딩 테스트 문제 중에서 완적 탐색 기법 lever2 단계인 '소수 찾기'를 풀다가 '순열'의 개념이 필요하게 되었다.
직접 구현해보려고 했으나, 쉽지않아서 구글링을 통해서 학습하였다. (알고리즘이 직관적으로 이해하기가 쉽지 않아 꽤 오랜 시간 손 코딩을 하면서 따라가며 이해하였다) 

참고: https://jar100.tistory.com/16

 

프로그래머스 알고리즘 : 소수 찾기 (java)

프로그래머스 알고리즘 (java) 소수 찾기 프로그래머스 (소수 찾기) 코드 리뷰 각 종이 조각에 적힌 숫자가 적힌 문자열 numbers가 주어졌을 때, 종이 조각으로 만들 수 있는 소수가 몇 개인지 return 하는 문제입..

jar100.tistory.com

 

일반적으로 순열을 만들 때 ex. 1,2,3,4 

  • 1-2-3-4
  • 1-2-4-3
  • 1-3-2-4
  • 1-3-4-2
  • 1-4-2-3
  • 1-4-3-2
  • 2-1-3-4
  • .....

이렇게 숫자를 뒤에서부터 섞으면(= 순서를 변경), 순열의 모든 경우를 구할수 있다. 그렇다면 이를 알고리즘으로 어떻게 구현할 것인가?

위의 그림을 보자, 위 그림은 ABC의 알파벳을 재귀적으로 푸는 방법이다. ABC 중 첫번째 알파벳을 뭘로 할 것인가. 종류는 세 가지밖에 없다. 처음을 배열은 ABC 순서로 정해져 있다.

  • A
  • B
  • C

위와같은 경우에서 첫 번째가 A인 경우는 첫 번째[0] 인자인 A를 첫 번째[0] 인자 A와 바꾼 것이나 다름이 없다. (실질적으로는 바뀌는 건 없다)

첫 번째 인자가 B로 설정되어있는 경우는 첫 번째[0] 인자인 A를 두 번째[1] 인자 B와 바꾼 것이다.

세 번째 인자가 C로 설정되어있는 경우는 첫 번째[0] 인자인 A를 세 번째[2] 인자 C와 바꾼 것이다.

첫 번째[0] 인자는 이렇게 바뀌고 있다.

  • [0] <-> [0]
  • [0] <-> [1]
  • [0] <-> [2]

이제 첫번째 인자가 고정되었으니, 두 번째 인자와 세 번째 인자에 대해 고민을 할 필요가 있다. 위와 정확히 마찬가지로 같다.

(첫 번째[0] 인자가 [0]으로 고정된 경우)

  • [0][1] <-> [0][1]   즉, 두 번째 인자[1]인 B와 두번째 인자[1]인 B를 바꾼다. (실질적으로 바뀌는건 없다) ABC
  • [0][1] <-> [0][2]  두번째 인자[1]인 B와 두번째 인자[2]인 C를 바꾼다. ACB

이제 마지막[2] 인자를 살펴본다.

  • [0][1] <-> [0][1]의 경우에 --> [0][1][2] <-> [0][1][2]  (바뀌는 건 없다) ABC
  • [0][1] <-> [0][2]의 경우에 --> [0][1][2] <-> [0][2][1] (바뀌는 건 없다)  ACB

이제 A에 대한 경우를 모두 살펴보았고 첫 번째[0] 인자가 [0] <-> [1] , [0] <-> [2]인 경우도 이런 방향으로 알고리즘을 재귀적으로
짠다면, 그림과 같이 ABC, ACB, BAC, BCA, CBA, CAB 순으로 튀어나오게 될 것이다. (순서대로 나오지 않는다!) 

nPk(고등학교 수학 시간에 nPr을 많이 썼던 거 같지만..)의 순열을 구해야 하는 문제, 즉 n개중 k개로 이루어진 순열을 구하려고 한다. 샘플에서는 1,23,4 네 개로 이루어진 배열을 가지고 4개로 이루어진 순열을 구해보고자 한다.

다음과 같은 시그니처를 사용한다.

perm ( int[] arr , int depth , int n , int k )

배열 arr은 계속해서 데이터를 들고 다니면서 교환 작업을 하고 있는지에 대한 변수이다. 맨 처음 깊이라면 depth = 0의 위치에서 작업하고 있을 것이며, 이는 첫 번째[0]가 첫번째[0] 또는 두 번째[1] 또는 세 번째[2] 또는 네 번째[3] 인자를 교환하는 중이다.
depth는 0에 대한 것을 다 끝마치고 1로 넘어가는 것이 아니라 0,1,2,3,2,3,1,2,3과 같은 형태로 내부적으로 변하고 있다. (물론 프로그램의 시작점에서는 0으로 넣어주어야 한다)

n은 총 배열 안에 들어있는 숫자를 뜻하며 고정값이다. (예제는 1,2,3,4 모두를 사용해 순열을 만드므로 4로 고정된다

k는 몇 개를 뽑아내서 순열을 만들 것인지를 뜻하며 고정값이다. 샘플을 1,2,3,4 모두를 사용해 순열을 만드므로 4로 고정된다.

public void perm(char[] arr, int depth, int n , int k ) {
        if (depth == k) {
            // 한번 depth 가 k로 도달하면 사이클이 돌았음. 출력함.
            print(arr);
            return;
        } else {
            for (int i = depth; i < n; i++) {
                swap(arr, i, depth);
                perm(arr, depth + 1, k, sosuList);
                swap(arr, i, depth);
            }
        }
    }

 

실제적인 코드의 구현은 위와 같다. 일단 depth를 검사해 k와 같으면 더 이상 순열을 만들지 않는다. 우리가 원하는 것은 k개의 숫자가 있는 순열이기 때문이다. 4에 도달하면 출력해야 하는 숫자 네 개가 모두 세팅되었다는 뜻이므로 출력해주면 된다

depth에 따라서 for문의 시작점이 다르다. depth가 0이라면 1 XXX, 2 XXX, 3 XXX, 4 XXX를 뽑아줘야 하므로 총 네 번의 루프가 돌아야 하며, 이는 i < n이라는 조건에서 n은 4로 고정이고 i는 depth에 따라 0부터 시작하기 때문에 0,1,2,3 네 번이 도는 것이다.

다만,

(depth 0) 1 XXX이 만들어지면, (2 XXX를 만드는 것이 아니라)
(depth 1) 2XXX를 만드는 것이 아니라 12XX, 13XX, 14XX를 만들고
(depth 2) 바로 다음으로 123X
(depth 3) 마지막으로 1234가 채워져 지며
(depth 4) 출력한다

perm (arr, depth + 1 , n , k ) 함수에  ( {1,2,3,4} , 4 , 4 , 4 ) 인자가 채워져서 호출된다. 그렇지만, depth는 이제 k와 같으므로 바로 return을 통해, perm문을 끝내고 다음 문장인 swap()을 실행하게 된다. 
마지막으로 의문인 점은 왜 swap 함수를 두 번 쓰는 것인가?인데, 깔끔해 보이지 않음에도, swap() 함수는 꼭 필요하다. 재귀 함수를 쓰면서 perm() 함수에 자꾸 걸려서 뒤쪽 swap() 함수가 드디어 호출된다. 두 번째 swap()은 전 단계의 분기점에서의 배열의 순서를 기억하고 이를 초기화하는 작업에 지나지 않는다. 트리구조에서 depth는 0,1,2,3,2,3,1,2,3.... 이런 식으로 한두 칸씩 뒤로 돌아가면서 다시 계산을 하는데 그 지점에서의 배열 안의 숫자 순서를 기억하고 있어야 한다. swap() 없이 바로 사용하고자 한다면, 분기로 돌아가서 다시 교환을 하는 게 아니라, 이미 다른 분기에서 망가뜨려버린 배열의 순서를 그대로 이용하게 되어, 황당한 순열들이 튀어나오게 된다. 

perm 메서드를 사용하기 위해서는 준비작업적 함수가 두 가지 필요하다.

  1. 배열의 특정 위치를 교환하는 함수 (필수)
  2. 배열을 출력하는 함수 (선택)

배열의 특정위치를 교환하는 함수가 필요한 이유는 , C의 경우에는 포인터가 있어 주소 값을 바로 찾아서 바꿔버릴 수 있어 조금 더 편리하게 값들을 바로 바꿀 수 있는 반면, 다른 언어들은 주소에 직접 접근하지 않고 참조형 변수로 접근하거나, 배열 자체를 전달해 이의 값을 바꾸도록 하기 때문이다. 그렇다고 포인터를 남발하면 남발할수록 프로그램은 파악하기도 어렵고 버그가 자주 나니 지양하는 편이 낫다.

배열의 특정 위치를 교환하는 함수는 swap 메서드이고 다음과 같다. 이렇게 교환을 하면 굳이 포인터를 넘기지 않아도 알아서 배열 안에 있는 값들이 교환된다. (다만 리턴 값이 없기 때문에 이러한 프로그램 방식은 좋지 않다)

public void swap(int[] arr, int i, int j) { 
		int temp = arr[i]; 
  	  	arr[i] = arr[j]; 
  	  	arr[j] = temp; 
    }

 

배열을 출력하는 함수는 그냥 배열을 출력하는 함수이므로 중요하지 않기에 생략하겠다.

최종적인 자바 코드는 아래와 같다.

package algo; public class Permutation { 
	public static void main(String[] args) { 
    	int[] arr = { 1, 2, 3, 4 }; 
        
        perm(arr, 0, 4, 4); 
    } 
        
    public static void perm(int[] arr, int depth, int n, int k) {
        if (depth == k) { // 한번 depth 가 k로 도달하면 사이클이 돌았음. 출력함.
        	print(arr,k); return; 
        } 
        for (int i = depth; i < n; i++) {
        	swap(arr, i, depth);
            perm(arr, depth + 1, n, k);
            swap(arr, i, depth); 
        } 
    } // 자바에서는 포인터가 없기 때문에 아래와 같이, 인덱스 i와 j를 통해 바꿔줌. 
            
    public static void swap(int[] arr, int i, int j) {
            
    	int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
                
    } 
            
    public static void print(int[] arr, int k) { 
    
    	for (int i = 0; i < k; i++) { 
        	
            if (i == k - 1) 
            	System.out.println(arr[i]); 
            else 
            	System.out.print(arr[i] + ","); 
         } 
    } 
}

이러한 순열( 조합 또한)은 코딩 테스트 문제를 풀면서 요구하는 경우가 더러 있었고, 정해진 시간에서 갑자기 떠올리기가 쉽지 않았다. 
지금까지 코딩 테스트를 망했었던 이유들을 분석하면서 이런 알고리즘들을 차례차례 정리해뒀다가 그 원리를 잘 떠올려서 적시적소에 써먹도록 해야겠다.

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
회고/Other's

유니콘 우아한 형제들의 조직문화....

728x90

이 글은 아래의 내용을 읽은 후 느낀 점을 간략하게 작성하였습니다.

출처: https://www.folin.co/storybook/504/chapter/509

 

storybook / 코드가 아니라 밸류를 만든다 : 유니콘 우아한형제들의 조직문화

다양한 지식과 경험을 지닌 Linker들이 공유하는 인사이트

www.folin.co

한국의 대표적인 유니콘 기업하면 떠오르는 것이 '배달의 민족', 그리고 그 앱을 만든 개발자들이 소속된 곳 '우아한 형제들'(이하 우형) 

몰랐었던 사실이지만 배민하면 떠오르는 키워드는 '디자인과 마케팅' 그리고 'B급감성' 일 것이다. 

이는 개발자의 길을 걷지 않은 나로서는 잘 알았던 사실이었다. 하지만 내가 개발자의 길을 목표하고 본격적으로 공부를 시작하면서 우아한 형제들을 알게 되었을 때, 우형은 '개발 잘하는 곳' , '바람직한 코드 리뷰 문화' 등 개발자로서 최고의 대우를 받는 곳 (우아한 형제들 뜻이 우와~!한 시림들이 만들어가는 우아~! 한 세상이라고 한다)이라는 소문을 많이 들었다. 나도 개발자로서 첫 발을 내디딘 계기가 우형의 교육 코스인 우아한 테크 코스를 목표로 하면서였기에 우아한 형제들이 더 남다르게 느껴졌을지도 모른다. (결과적으로 떨어졌지만 지금의 선택에 후회는 없다) 

이 글은 우아한 형제들이 개발로써 유명해지게 된 뒷 배경을 CTO인 김범준 부사장님이 말하고 있다. 

우아한 형제들이 개발로서 유명해진 이유?  감히 상상도 하기 어려울 정도의 거창한 계획과 시행착오가 있을 것이라 생각했지만, 김범준 부사장님의 답의 간단했다.  '서로 가진 지식을 나눈다'  내용인 즉슨 개발은 과거의 코드를 이해하고, 그 위에서 코드를 얹는 과정인데, 이 과정에서 여러명의 개발자가 서로 리뷰를 해주며 집단 지성을 발휘하는 것인데, 그렇기 때문에 서로 가진 지식을 나누는 것이 잘 나누는 것이 중요하다는 것이었다.

지금까지 내가 가진 지식을 주변 다른 (예비) 개발자들에게 나누고자 시도해본 적도 없었던 것 같았고, 오히려 정보력 싸움(?)이라는 이기적인 생각에 아는 내용도 주변에 굳이 알려주려고 하지 않았던 것 같다. 좀 더 주변 개발자들과 상생하는 관계가 되기 위해서 소통하는 노력을 해야겠다는 생각을 갖게 되었다. 

 

728x90
회고/Other's

기술적인 고민이 차단된 환경에서도... 발전할 수 있는 방법

728x90

* 이 글은 아래의 글을 읽고 느낀 점을 작성하였습니다.

출처: https://www.popit.kr/si-%ea%b0%9c%eb%b0%9c-10%eb%85%84%ec%b0%a8%ec%9d%b8%eb%8d%b0-%ec%bd%94%eb%93%9c-%ec%a2%80-%eb%b4%90%ec%a3%bc%ec%84%b8%ec%9a%94/

 

SI 개발 10년차인데 코드 좀 봐주세요 | Popit

최근 한국에 머무르는 시간이 길어지면서 Popit 저자 섭외 활동과 병행하여 개발자 멘토링을 꾸준히 하고 있습니다. SI 경력 10년 정도 되는 개발자와 만나서 이야기 한 내용을 대략적으로 정리해 보았습니다 [1] . 어느날 페이스북 메신저로 SI 분야에서 계속 있으면서 10년 정도의 경력을 가지고 있는데 자신이 만든 코드에 대해 리뷰를 해줄 수 없냐는 요청을 받았습니다. 코드 리뷰로 해결할 수 있는 문제가 아니다. 이 메시지를 받고 출장 중이던 북경에서

www.popit.kr

 

웹 개발자를 꿈꾸며, 국비지원교육을 수강하기 시작하면서 나도 이런저런 고민들을 많이 했고 현업에서 주도적인 개발을 할 수 있는 환경에서 적극적으로 참여하며 개발하고, 새로운 기술을 능동적으로 받아들이는 그런 이상적인 개발자가 되고 싶었다. 그런 나의 목표와 부합하는 곳이 기업들에 발주를 받아서 솔루션을 제공하는 SI업체보다는 자체 서비스를 제작해서 배포하는 회사라는 것도 알게 되었다. 국비지원교육과 남는 시간마다 다른 공부들을 이어갔고, 공부를 하면 할수록 매력적인 회사라고 느껴지는 기업들이 많다는 것을 알게 되었다. 특히 내 눈에 들어오는 곳들은 대부분 스타트업들이었다. (처음에는 젊은 기업에 대한 로망이 있어서라는 착각을 했었는데... 나의 성장에 엄청난 밑거름이 될 것 환경이 대다수였기 때문이라고 생각한다)  

SI업체만큼은 어떻게든 피해가겠다는 생각을 마음속으로 하고 있지만, 늦게 출발한 만큼 취업에 있어서 리스크가 있다고 생각하기에 (슬프지만 안 그러면 세상이 너무 불공평하지 않은가?) SI업체라도 가서 경력을 쌓는 것이 낫지 않을까 하는 생각이 들 때가 있다. 학생 시절 꽤 팔랑거렸던 귀 녀석이 제 습관을 못 버린 것 같다. SNS나 개발자 커뮤니티 사이트들에 올라오는 회고록은 되도록 챙겨보는 편인데, SI업체의 늪에 빠졌다 탈출한 일대기가 심심치 않게 나온다.  이러한 글들은 약해진 마음을 다시 잡는 좋은 자극제가 되어준다. 그리고 나의 직장에서 내가 성장하는데 한계가 생겨도, 계속해서 성장해나갈 수 있는 좋은 방향성들을 제시해준다. 앞으로 내가 어디로 갈지 거취는 전혀 정해진 바도 없고 예측할 수 없지만, 어떤 기업을 가더라도 계속해서 성장할 수 있다는 것(어떤 장애물이 있더라도... 극복할 수 있다는 것)을  알게 되었다.

728x90

'회고 > Other's' 카테고리의 다른 글

유니콘 우아한 형제들의 조직문화....  (1) 2019.10.02