반응형
이번 글에서는 백준 28279번 문제를 통해
deque를 개념을 익혀보자


문제 핵심

  • N : 명령 수
  • 1 ~ 8 까지의 명령어 종류

풀이 과정

다음 Deque를 통해 저번 백준 스택 문제와 같은 형태로 푸는 쉬운 문제이다.

1. 명령어 정의

1 ~ 8에 해당하는 명령어를 정의한다.

Java에는 deque가 존재하지만, 문제에 맞게 처리해야 하기 때문에 다시 정의해준다.

    private static Deque<Integer> queue;

    private static int isEmpty(){
        return queue.isEmpty() ? 1 : 0;
    }

    private static void addFirst(int num) {
        queue.addFirst(num);
    }

    private static void addLast(int num) {
        queue.addLast(num);
    }

    private static int size(){
        return queue.size();
    }

    private static int removeFirst() {
        return queue.isEmpty() ? -1 : queue.removeFirst();
    }

    private static int removeLast() {
        return queue.isEmpty() ? -1 : queue.removeLast();
    }

    private static int getFirst() {
        return queue.isEmpty() ? -1 : queue.getFirst();
    }

    private static int getLast() {
        return queue.isEmpty() ? -1 : queue.getLast();
    }

2. 명령어 분류 처리

 명령어 처리 1 ~ 8 까지의 명령어에 해당하는 함수를 실행한다.

            int command = Integer.parseInt(stringTokenizer.nextToken());

            switch (command) {
                case 1:
                    addFirst(Integer.parseInt(stringTokenizer.nextToken()));
                    break;
                case 2:
                    addLast(Integer.parseInt(stringTokenizer.nextToken()));
                    break;
                case 3:
                    stringBuilder.append(removeFirst() + "\n" );
                    break;
                case 4:
                    stringBuilder.append(removeLast() + "\n" );
                    break;
                case 5:
                    stringBuilder.append(size() + "\n" );
                    break;
                case 6:
                    stringBuilder.append(isEmpty() + "\n" );
                    break;
                case 7:
                    stringBuilder.append(getFirst() + "\n" );
                    break;
                case 8:
                    stringBuilder.append(getLast() + "\n" );
                    break;
            }

 

 

 

아직까지는 실버 문제로 개념만 이해한다면,
문제 조건에 맞게 풀기만 한다면,
아주 쉽게 풀 수 있다.

이후에 해결할 골드이상의 문제에 대비해 이와 같은 문제를 많이 풀어보길 바란다.

전체코드

package org.example;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.StringTokenizer;

public class Main {
    private static Deque<Integer> queue;

    private static int isEmpty(){
        return queue.isEmpty() ? 1 : 0;
    }

    private static void addFirst(int num) {
        queue.addFirst(num);
    }

    private static void addLast(int num) {
        queue.addLast(num);
    }

    private static int size(){
        return queue.size();
    }

    private static int removeFirst() {
        return queue.isEmpty() ? -1 : queue.removeFirst();
    }

    private static int removeLast() {
        return queue.isEmpty() ? -1 : queue.removeLast();
    }

    private static int getFirst() {
        return queue.isEmpty() ? -1 : queue.getFirst();
    }

    private static int getLast() {
        return queue.isEmpty() ? -1 : queue.getLast();
    }


    public static void main(String[] args) throws IOException {
        queue = new ArrayDeque<>();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(bufferedReader.readLine());
        StringBuilder stringBuilder = new StringBuilder();
        StringTokenizer stringTokenizer;

        for (int i = 0; i < N; i++) {
            stringTokenizer = new StringTokenizer(bufferedReader.readLine());
            int command = Integer.parseInt(stringTokenizer.nextToken());

            switch (command) {
                case 1:
                    addFirst(Integer.parseInt(stringTokenizer.nextToken()));
                    break;
                case 2:
                    addLast(Integer.parseInt(stringTokenizer.nextToken()));
                    break;
                case 3:
                    stringBuilder.append(removeFirst() + "\n" );
                    break;
                case 4:
                    stringBuilder.append(removeLast() + "\n" );
                    break;
                case 5:
                    stringBuilder.append(size() + "\n" );
                    break;
                case 6:
                    stringBuilder.append(isEmpty() + "\n" );
                    break;
                case 7:
                    stringBuilder.append(getFirst() + "\n" );
                    break;
                case 8:
                    stringBuilder.append(getLast() + "\n" );
                    break;
            }
        }
        System.out.println(stringBuilder);
    }
}
반응형
반응형
이번 글에서 백준 11866번 문제를 통해 요세푸스 순열을 활용,
이를 통해 원형 큐와 결합하여 사용해보자.

문제 핵심

    • N : 사람 수
    • K : 건너뛸 수
    • ( 1 <= K <= N <= 1,000 )

풀이 과정

다음 문제는 요세푸스를 순열을 통해 한 사람씩 제거해 나가는 문제이다.

다음 글을 통해 개념을 참고하면 좋을 것 같다. 비교적 간단하다.

 

플라비우스 요세푸스 순열

요세푸스 순열은 유대인 역사가 플라비우스 요세푸스의 이야기에서 유래한 수학적 문제이다.요세푸스의 이야기1세기 로마 제국 시대, 유대인은 로마에 대항하여 대규모 반란을 일으켰다.이 때,

p-coding.tistory.com

 

1. 반복

해당 문제는 K의 배수마다 값을 꺼내오면 된다.

때문에 이전 값들은 모두 다시 큐의 뒤에 넣어주고 그 과정을 마지막 한 사람이 남을 때까지 반복해준다. 

        while(1 < queue.size()) {
            count++;
            if(count % K == 0){
                stringBuilder.append(queue.remove() + ", ");
                continue;
            }
            queue.add(queue.remove());
        }

 

2. 출력

백준의 문제 출력 조건에 따라 마지막 값은 기호와 함께 처리해 준다.

        stringBuilder.append(queue.remove() + ">");
        System.out.println(stringBuilder);

이번 문제는 큐의 개념과 요세푸스 순열을 이해하고 있다면, 
결합하여 아주 쉽게 풀 수 있는 문제이다.
요세푸스 문제는 재귀로도 풀 수 있는 만큼 한 번 시도해보는 것도 좋다.

전체코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        Queue<Integer> queue = new LinkedList<Integer>();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer stringTokenizer = new StringTokenizer(bufferedReader.readLine());
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("<");
        int N = Integer.parseInt(stringTokenizer.nextToken());
        int K = Integer.parseInt(stringTokenizer.nextToken());


        for(int i = 0; i < N; i++) {
            queue.add(i + 1);
        }
        int count = 0;

        while(1 < queue.size()) {
            count++;
            if(count % K == 0){
                stringBuilder.append(queue.remove() + ", ");
                continue;
            }
            queue.add(queue.remove());
        }

        stringBuilder.append(queue.remove() + ">");
        System.out.println(stringBuilder);

    }
}
반응형
반응형
요세푸스 순열은
유대인 역사가 플라비우스 요세푸스의 이야기에서 유래한 수학적 문제이다.

요세푸스의 이야기

1세기 로마 제국 시대, 유대인은 로마에 대항하여 대규모 반란을 일으켰다.
이 때, 요세푸스는 유대 반군의 장군 중 한명으로 로마군에 의해 포위당했다.
전투에서 패배한 요세푸스와 동료들은 로마군에 항복하는 대신 자결하는 것을 택했다.
하지만 자결하는 것은 두려웠는지 원형으로 앉았고, 특정 규칙에 따라 순차적으로 서로 죽여주는 방식을 택했다.
1. 특정 수의 인원을 건너 뛰고 다음 사람을 제거한다.
2. 마지막 한 사람이 남을 때까지 반복.
그러나 요세푸스는 동료들의 뜻에 반대하여 살고 싶었고, 위 규칙을 이용하여 마지막 한 사람이 되어 로마군에 항복하였다.

 

공식

일반적인 공식은 다음과 같다. 

n : 인원 수

k : 건너뛸 수

재귀함수 형식을 띄고 있다.

 

 

예시)

f( 7, 3 ) 인 경우

idx f( N, K ) ( N + K ) mod N  result
1 (7, 3) (0 + 3) % 7 3
2 (6, 3) (3 + 3) % 6 0
3 (5, 3) (0 + 3) % 5 3
4 (4, 3) (1 + 3) % 4 0
5 (3, 3) (1 + 3) % 3 1
6 (2, 3) (0 + 3)  % 2 1
7 (1, 3) N = 1 0

 

코드

재귀 코드로 구현하면 다음과 같이 구현할 수 있다.

package org.example;

public class Main {
    public static int josephus(int N, int K) {
        if (N == 1) return 0;
        return (josephus(N - 1, K) + K) % N; // 재귀 호출
    }

    public static void main(String[] args) {
        int N = 7;
        int K = 3;
        System.out.println("Last survivor: " + (josephus(N, K) + 1));
    }
}

 

결과 ( 마지막 생존 자리 )

4

 

이번에는 재귀함수로 해결하였지만, 원형 큐 등 다양한 방법들이 있다.
생존을 위해서 역사가의 새로운 순열 공식
순차적인 제거 문제에서 다양하게 사용되고 있다.
반응형
반응형
자료형 변환시 주로 ParseInt가 권장된다.
하지만 valueOf 함수도 있는데 왜 저걸 권장할까?

 

ParseInt()

Java 라이브러리에서 ParseInt()는 다음과 같이 정의하고 있다.

public static int parseInt(String s) throws NumberFormatException {
    return parseInt(s,10);
}

 

 

기본 자료형( int )로 반환하며, 오버로딩(Overloading) 되어서 다시 재정의가 이루어진다.

 

 

다음은 오버로딩 ( Overloading ) 전체 코드이다. 문자열( s )를 진법( radix )을 통해 변환이 이루어진다.

public static int parseInt(String s, int radix)
            throws NumberFormatException
{
    /*
     * WARNING: This method may be invoked early during VM initialization
     * before IntegerCache is initialized. Care must be taken to not use
     * the valueOf method.
     */

    if (s == null) {
        throw new NumberFormatException("Cannot parse null string");
    }

    if (radix < Character.MIN_RADIX) {
        throw new NumberFormatException("radix " + radix +
                                        " less than Character.MIN_RADIX");
    }

    if (radix > Character.MAX_RADIX) {
        throw new NumberFormatException("radix " + radix +
                                        " greater than Character.MAX_RADIX");
    }

    boolean negative = false;
    int i = 0, len = s.length();
    int limit = -Integer.MAX_VALUE;

    if (len > 0) {
        char firstChar = s.charAt(0);
        if (firstChar < '0') { // Possible leading "+" or "-"
            if (firstChar == '-') {
                negative = true;
                limit = Integer.MIN_VALUE;
            } else if (firstChar != '+') {
                throw NumberFormatException.forInputString(s, radix);
            }

            if (len == 1) { // Cannot have lone "+" or "-"
                throw NumberFormatException.forInputString(s, radix);
            }
            i++;
        }
        int multmin = limit / radix;
        int result = 0;
        while (i < len) {
            // Accumulating negatively avoids surprises near MAX_VALUE
            int digit = Character.digit(s.charAt(i++), radix);
            if (digit < 0 || result < multmin) {
                throw NumberFormatException.forInputString(s, radix);
            }
            result *= radix;
            if (result < limit + digit) {
                throw NumberFormatException.forInputString(s, radix);
            }
            result -= digit;
        }
        return negative ? result : -result;
    } else {
        throw NumberFormatException.forInputString(s, radix);
    }
}

 

1. 초기 예외 검사

문자열(s)가 null이면 변환이 불가 함으로 NumberFormatException 예외 발생

if (s == null) {
    throw new NumberFormatException("Cannot parse null string");
}

 

radix(진법)이 2~36 진법 범위 내에서만 변환 가능함으로 Min, Max 값 범위 예외 처리

if (radix < Character.MIN_RADIX) {
    throw new NumberFormatException("radix " + radix +
                                    " less than Character.MIN_RADIX");
}
if (radix > Character.MAX_RADIX) {
    throw new NumberFormatException("radix " + radix +
                                    " greater than Character.MAX_RADIX");
}

2. 부호 판별 및 기본 설정

부호 +(43), -(45)은 0(48)보다 아래이기 때문에 부호를 찾고 '+'은 무시 나머지 경우에만 처리한다.

'-'인 경우 음수로 설정하고, limit를 int 자료형의 변환이기 때문에 Integer.MIN_VALUE 로 설정한다.

        if (firstChar < '0') { // Possible leading "+" or "-"
            if (firstChar == '-') {
                negative = true;
                limit = Integer.MIN_VALUE;
            } else if (firstChar != '+') {
                throw NumberFormatException.forInputString(s, radix);
            }

 

길이가 1이라면 앞 부호 하나만 있기 때문에 예외 처리

        if (len == 1) { // Cannot have lone "+" or "-"
            throw NumberFormatException.forInputString(s, radix);
        }

3. 숫자로 변환

먼저 자릿수를 가져오고, digit가 0보다 작거나 오버플로우가 나는지 체크한다.

사실 이 코드만 보고 이해하기는 어려울 수 있다. 성공과 실패의 두 가지 예시가 있다.

        int multmin = limit / radix;
        int result = 0;
        while (i < len) {
            // Accumulating negatively avoids surprises near MAX_VALUE
            int digit = Character.digit(s.charAt(i++), radix);
            if (digit < 0 || result < multmin) {
                throw NumberFormatException.forInputString(s, radix);
            }
            result *= radix;
            if (result < limit + digit) {
                throw NumberFormatException.forInputString(s, radix);
            }
            result -= digit;
        }

 

ex) 성공한 연산 10진수 "2147483647"

 multmin = -2147483647 / 10 = -214748364

이 값을 통해 미리 오버플로우 발생을 예측한다.

i s.charAt(i++) digit result result *= radix result -= digit
1 ' 2 ' 2 0 0 * 10 = 0 0 - 2 = -2
2 ' 1 ' 1 -2 -2 * 10 = -20 -20 - 1 = -21
3 ' 4 ' 4 -21 -21 * 10 = -210 -210 - 4 = -214
4 ' 7 ' 7 -214 -214 * 10 = -2140 -2140 - 7 = -2147
5 ' 4 ' 4 -2147 -2147 * 10 = -21470 -21470 - 4 = -21474
6 ' 8 ' 8 -21474 -21474  * 10 = -214740 -214740 - 8 = -214748
7 ' 3 ' 3 -214748 -214748  * 10 = -2147480 -2147480 - 3 = -2147483
8 ' 6 ' 6 -2147483 -2147483  * 10 = -21474830 -21474830 - 6 = -21474836
9 ' 4 ' 4 -21474836 -21474836  * 10 = -214748360 -214748360 - 4 = -214748364
10 ' 7 ' 7 -214748364 -214748364  * 10 = -2147483640 -2147483640 - 7 = -2147483647

 

마지막 결과에서 -2147483647을 부호판별 후 반환한다. -(-2147483647) = 2147483647

ex) 실패한 연산 10진수 "2147483648"

8 ' 6 ' 6 -2147483 -2147483  * 10 = -21474830 -21474830 - 6 = -214748 36
9 ' 4 ' 4 -21474836 -21474836  * 10 = -214748360 -214748360 - 4 = -214748364
10 ' 8 ' 8 -214748364 -214748364  * 10 = -2147483640 result < limit + digit (Exception)

 

다음과 같은 결과로 ( result < limt + digit ) 조건이 참이 되어 예외가 발생하게 된다.

result = -2147483640
limit = -2147483647
digit = 8

limit + digit = -2147483647 + 8 = -2147483639

 

 

valueOf()

Java 라이브러리에서 valueOf()는 다음과 같이 정의하고 있다.

    public static Integer valueOf(String s) throws NumberFormatException {
        return Integer.valueOf(parseInt(s, 10));
    }

 

객체형으로 반환하고 있고, 여기서도 오버로딩( Overloading ) 했던 parseInt()가 보인다.

 

또 하나 valueOf 메서드도 오버로딩( Overloading ) 되었는데 다음과 같다.

1. 미리 생성된 값 IntegerCache.low(-128) ~ IntegerCache.high(127) 사이면 새 객체를 생성하지 않고 반환한다.

2. 그 값이 아니라면 새로 객체를 생성하여 반환한다.

( 미리 생성되는 값 범위는 JVM option에 따라 조정 가능 )

    @IntrinsicCandidate
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

 

 

(추가) parseInt() 사용 예

예제)

System.out.println(Integer.parseInt("123", 10));   // 123
System.out.println(Integer.parseInt("-123", 10));  // -123
System.out.println(Integer.parseInt("101", 2));    // 5 (2진수 변환)
System.out.println(Integer.parseInt("7F", 16));    // 127 (16진수 변환)
System.out.println(Integer.parseInt("Z", 36));     // 35 (36진수에서 'Z'는 35)
System.out.println(Integer.parseInt("007", 8));    // 7 (8진수)

예외발생)

System.out.println(Integer.parseInt("abc", 10));  // 예외 발생 (숫자가 없음)
System.out.println(Integer.parseInt("12345", 2)); // 예외 발생 (2진수 범위 초과)
System.out.println(Integer.parseInt("", 10));     // 예외 발생 (빈 문자열)
System.out.println(Integer.parseInt(null, 10));   // 예외 발생 (null)

 

결국 valueOf() 메서드도 parseInt()를 사용하며
Integer를 사용하지 않고, 정수만을 반환받기 원한다면 parseInt() 메서드를 사용해야 한다.
Integer객체를 통해 불필요하게 메모리를 사용하게 된다.
반응형
반응형
이번 글에서는 2164번 문제를 통해
큐의 기본 개념을 활용해보자.
사실 이번문제는 거의 브론즈 이하급으로 쉬운 문제이다.

 

문제 핵심

  • 큐를 활용한 카드이다.
  • N장의 카드는 위에서부터 아래로 오름차순으로 정렬되어 있다.
  •  
 

[ 자료구조 ] 큐 ( Queue ) - JAVA

오늘은 자료구조의 기본 구조 중 하나인 큐에 대해서 알아보고자 한다.가장 기본적인 구조로 선입선출 ( FIFO, First In First Out ) 방식을 가진다. 먼저 온 사람이 나가는 흔히 매장에 줄 서는 과정이

p-coding.tistory.com

 

풀이 과정

  • 2장의 카드를 한 번의 세트로 움직인다.
    • 한 장을 버리고 그 다음 장을 아래로 움직인다.
    • 값을 두 번 꺼내고 두 번째 값을 삽 하면 된다.

1. 큐 생성

        Queue<Integer> queue = new LinkedList<Integer>();
        
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(bufferedReader.readLine());
   

		for(int i = 1;i <= N; i++){
            queue.add(i);
        }

2. 과정 반복

        while (queue.size() > 1) {
            queue.poll();
            queue.add(queue.poll());
        }

 

기본적인 문제로 큐의 개념만 이해하고 있다면,
바로 쉽게 풀 수 있는 문제이다.

반응형
반응형
오늘은 자료구조의 기본 구조 중 하나인 큐에 대해서 알아보고자 한다.
가장 기본적인 구조로 선입선출 ( FIFO, First In First Out ) 방식을 가진다.
먼저 온 사람이 나가는 흔히 매장에 줄 서는 과정이라 볼 수 있다.
( Queue 단어 자체가 표 같은 것을 구매하기 위해 줄 서는 것을 의미 )

 

큐 ( Queue )

  • 선입 선출 ( FIFO, First In First Out )
  • 데이터가 들어오는 위치는 가장 뒤 Rear or Back 에서 이루어지고, 데이터가 나가는 위치인 가장 앞 Front, 양쪽에서 삽입 삭제가 이루어진다.
  • BFS ( 너비 우선 탐색 ), 프로세서 핸들링 ( CPU 헤드링 ) 등에 사용.
class Queue{
    private LinkedList<Integer> queue;

    public Queue(){
        queue = new LinkedList<>();
    }
    ....
}

큐의 기본 연산

1. Enqueue(value): 데이터 삽입

  • 큐의 맨 뒤(back)에 데이터를 추가하는 연산
    public void enQueue(int x){
        queue.addLast(x);
    }

 

2. Dequeue(): 데이터 제거

  • 큐의 맨 앞(front)에 있는 데이터를 제거하고 반환하는 연산
    public int depQueue(){
        if(queue.isEmpty()){
            return -1;
        }
        return queue.removeFirst();
    }

3. peek() : 최상단 데이터 확인

  • 큐의 맨 앞(front)에 있는 데이터를 반환하지만, 제거하지 않음
    public int peek(){
        if(queue.isEmpty()){
            return -1;
        }
        return queue.getFirst();
    }

4. isEmpty(): 큐이 비어 있는지 확인

  • 큐가 비어 있으면 true, 아니면 false 반환
    public boolean isEmpty(){
        return queue.isEmpty() ? true : false;
    }

로직 예시)

1. 빈 큐다

2. Enqueue ( 2 ) 를 통해 가장 앞 부터 채워 넣는다.

3. Enqueue ( 4 ) 를 통해 2 다음에(뒤에서부터) 채워 넣는다.

4. Dequeue 릍 통해 가장 앞에 있는 2를 꺼내고 4를 앞으로 옮긴다.

추가 - JAVA의 큐 관련 클래스

자바에서 큐는 Interface로 구현되어 ArrayDeque, LinkedList, PriorityQueue로 직접 구현할 수 있다.

그 밖에도 Blocking 즉 고정된 크기의 배열을 사용하는 큐도 존재한다.

이는 자바에서 원형 큐의 고정된 크기를 구현하기 위함으로 보인다.

이 글에서는 LinkedList로 정의된 큐와 달리 양쪽에서 삽입과 삭제가 이루어질 수 있는 양방향 큐이며, 메모리 동적인 요소를 가지고 있다.

 

ArrayDeque도 비슷하나 LinkedList와는 다르다. 배열로 사용되며 동적으로 변하긴 하지만, 배열을 다시 늘려야하는 번거로움이 있다. 추후 ArrayList와 LinkedList의 차이에 대해 포스팅할 예정이다.

DeQueue

 양방향에서 삽입/삭제가 이루어 질 수 있는 양방향 큐를 의미한다.

원형 큐

blocking으로 정의된 큐는 원형 큐라고 할 수 있으며 다음과 같은 형태을 가진다.

데이터가 삽입/삭제 됨에 따라 front와 back(rear)를 가리키는 위치가 계속 변하지만, 크기는 고정된 상태로 이루어진다.

다음과 같이 예시를 볼 수 있다.

PriorityQueue

우선순위 큐로 우선순위 비교는 Comparable interface를 활용하여 구현하거나 Comparator를 사용하여 설정할 수 있다.

중복 요소를 허용하고, 정렬된 순서로 요소를 처리하기 때문에 성능에 영향이 줄 수 있는 큐다.

PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.add(3);
queue.add(1);
queue.add(2);

System.out.println(queue.poll());  // 1 (가장 작은 값이 먼저 출력됨)
PriorityQueue<Integer> queue = new PriorityQueue<>(Comparator.reverseOrder());
queue.add(1);
queue.add(3);
queue.add(2);

System.out.println(queue.poll());  // 3 (가장 큰 값이 먼저 출력됨)
이미 많고 다양한 큐가 만들어져 있기 때문에
편한 방법과 
편한 클래스가 자바처럼 다양하게 존재하지만,
기본 자료구조를 이해하지 못한다면 활용하기가 어렵다.
때문에 그걸 사용하기 이전에 기본에 대한 이해를 점검하고 사용하도록 하자.
( ctrl + 클릭 을 통해 자바 클래스 내부를 확인 할 수 있다. )

 

반응형

'알고리즘' 카테고리의 다른 글

플라비우스 요세푸스 순열  (2) 2025.02.28
스택 ( 자료구조 ) - JAVA  (0) 2025.02.04
[ 알고리즘 ] 선택 정렬  (2) 2024.12.12
소수 찾기 ( 에라토스테네스의 체 ) - JAVA  (3) 2024.09.04
반응형
취업 준비하면서 공부를 하다 개발자로써는 사실 거의 의미 없다고들 하지만,
네트워크 관련 흥미가 생겨 공부하다 겸사겸사 하게 되었습니다.
준비해서 나쁠것도 없고, 시험이 쉬운편이라고 하니 한 번 해보시길 바랍니다.

 

 

네트워크 관리사 2급

  • 네트워크 관리사 2급은 네트워크 관련 업무 수행을 위한 일반적인 운용지식과 구축기술, NOS운영, Packet분석, Monitoring, 인터넷 기술, Protocol 등 기초 이론과 실무능력 검정 기준으로 한다.
  • 2급은 공인 민간 자격으로 국가공인 자격이다.
  • 자격발급기관은 한국정보통신자격협회에서 주관합니다.
  • 1급은 기준이 있으나 2급은 제한없이 응시 할 수 있습니다.
  • 필기 시험시간은 1급 60분 2급 50분입니다.
 

(사)한국정보통신자격협회

개요 및 검정기준 네트워크관리사란 서버를 구축하고 보안 설정, 시스템 최적화 등 네트워크구축 및 이를 효과적으로 관리할 수 있는 인터넷 관련 기술력에 대한 자격이다. 자격명칭 검정기준

www.icqa.or.kr

시험일정

1급은 서울에서만 응시할 수 있고 2회만 존재합니다.

2급은 매년 4회 응시할 수 있고 여러 지역에서 응시 가능합니다.

준비물

  • 신분증, 볼펜
  • 신분증이 없으면 응시가 절대 불가합니다.

가격

2급은 필기은 43,000원, 실기는 78,000원입니다.

 

합격

합격은 모두 100점 만점에 60점 이상입니다.

 

필기 공부법

  • 교재는 사실상 필요없는 수준입니다.
  • 만약 컴퓨터 관련과가 아닌상태여서 이론이 부족하시다면 교재사서 공부하시면 될 것 같습니다.
  • CBT 에서 문제 풀고 틀린 문제 위주로 공부했습니다.
 

최강 자격증 기출문제 전자문제집 CBT

전자문제집, CBT, 컴씨비티, 씨비티, 기사, 산업기사, 기능사, 컴활, 컴퓨터활용능력, 1급, 2급, 워드, 정보처리, 전기, 소방, 기계, 사무자동화, 정보기기, 제과, 제빵, 한국사, 공무원, 수능, 필기,

www.comcbt.com

필기 합격률은 평균 30% 정도지만, 많은 풀이와 개념을 이해한다면 아주 쉬운 시험입니다.
예를 들어 SSH에 대해 물어본다면 SSH가 무엇인지 무슨 포트를 사용하며 무슨 목적으로 사용되는지..

물론 시험을 통과하기만을 위한 공부법도 있지만,
저는 공부하면서 시험보는 만큼 문제에 나온 설명을 통해 유추하여 푸는 과정으로 공부했습니다.
그렇다고 시험 자체 난이도는 매우 쉬운편으로 속했기 때문에 시험 시간은 10분정도였습니다.

모두 합격하시길 바라며 다음에는 실기 합격으로 돌아오겠습니다.
반응형
반응형
이번 글에서는 4949번 문제를 통해 스택을 활용,
괄호의 균형을 맞춰완벽한 문장을 나타내 보자.


 

 

문제 핵심

  • 문자열은 ( ),  [ ] 이 두 소괄호와 대괄호가 짝을 이루어야 한다.
  • 1 : 1 매칭만 가능하다.
  • 공백 이후의 . 과 같은 괄호가 없는 문장도 균형 잡힌 문자열이다.
  • (, [ 이 나온 후에 ), ] 가 나와야 짝이 이루어 진다. 
    • ] [ 은 짝이 아닌 경우 

풀이 과정

먼저 우리가 찾아야 할 것은 괄호들이다.

이는 StringTokenizer를 활용해 볼 예정이다. ( 물론 chatAt()을 사용하면 더 빠르다. )

다음 자료구조 스택에 대한 글과 split과 StringTokenizer비교에 관한 글을 참고하면 좋을 것 같다.

 

 

스택 ( 자료구조 ) - JAVA

오늘은 자료구조의 기본 구조 중 하나인 스택에 대해서 알아보고자 한다.가장 기본적인 구조로 후입선출 ( LIFO, Last In First Out ) 방식을 가진다.접시를 쌓아올린다고 생각하면 된다.스택 ( Stack )후

p-coding.tistory.com

 

 

[ JAVA ] 문자열 자르기 Splite VS StringTokenizer

문자열을 특정 구분자로 분리할 수 있는 StringTokenizer와 Splite을 사용하던 중구체적인 차이점이 무엇인지 궁금하여 조사하게 되었다.메모리에서 장점을 가진다고 하지만 정확히 어떤구조로 작용

p-coding.tistory.com

 

 

1. 괄호 문자 저장

먼저 StringTokenizer를 통해 소괄호, 대괄호를 기준으로 문자를 나눈다.

그렇게 되면 구분자를 저장하는 StringTokenizer의 특성을 활용해 문자를 가져올 수 있다.

true로 설정하면 구분자도 Token에 포함된다.

stringTokenizer = new StringTokenizer(sentence,"([)]",true);

 

2. 괄호 문자 찾아 균형확인하기

1. 구분자에 포함된 토큰 중에 괄호를 찾는다.

2. (, [ 라면 스택에 넣는다

3 - 1. ], ) 라면 스택에서 꺼내 비교한다

3 - 2. 이 때, 동일한 모양의 괄호 즉, )일 때 ( 괄호가 나오지 않는다면 균형이 맞지 않는 문자열로 처리

while(stringTokenizer.hasMoreTokens()) {

    String token = stringTokenizer.nextToken();

    if (token.equals("(") || token.equals("[")) {
        push(token);
    }
    else if (token.equals(")")) {
        if (!pop().equals("(")) {
            isPerfect = false;
            break;
        }
    }
    else if (token.equals("]")) {
        if (!pop().equals("[")) {
            isPerfect = false;
            break;
        }
    }
}

 

 

3. 스택에 남아있는 값 확인

마지막으로 스택에 남아 있는 경우 즉, (() 와 같은 문자열이 들어온다면 ( 이 스택에 남아 균형이 맞지 않는다.

if (!delimStack.isEmpty()) isPerfect = false;

 

스택을 활용하여 푸는 문제로 괄호를 저장하고 저장한 값을 꺼내 올 때,
다양한 경우에 대비하여 반론이 없게 푸는 것이 중요하다.
이번 문제는 빠르게 풀 수도 있지만, charAt함수가 아닌 StringTokenizer를 사용하듯이
다양한 방법으로 풀어보는 것을 추천한다.



전체 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.StringTokenizer;

public class Main {

    private static LinkedList<String> delimStack;

    private static void push(String delim){
        delimStack.addLast(delim);
    }

    private static String pop(){
        return delimStack.isEmpty() ? " " : delimStack.removeLast();
    }


    public static void main(String[] args) throws IOException {
        delimStack = new LinkedList<>();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder stringBuilder = new StringBuilder();

        boolean isPerfect;
        StringTokenizer stringTokenizer;

        while (true) {
            isPerfect = true;
            delimStack.clear();
            String sentence = bufferedReader.readLine();

            if(sentence.equals(".")) break;

            stringTokenizer = new StringTokenizer(sentence,"([)]",true);


            while(stringTokenizer.hasMoreTokens()) {

                String token = stringTokenizer.nextToken();

                if (token.equals("(") || token.equals("[")) {
                    push(token);
                }
                else if (token.equals(")")) {
                    if (!pop().equals("(")) {
                        isPerfect = false;
                        break;
                    }
                }
                else if (token.equals("]")) {
                    if (!pop().equals("[")) {
                        isPerfect = false;
                        break;
                    }
                }
            }

            if (!delimStack.isEmpty()) isPerfect = false;


            stringBuilder.append(isPerfect ? "yes\n" : "no\n");
        }

        System.out.println(stringBuilder);
    }
}
반응형
반응형
인공지능 개발시 Tensorflow를 사용할 때 겪는 어려움이 상당하다.
환경 변수로 인해 머리가 너무 아프고 막상 설치한다고 해도
대부분 Jupyter NoteBook을 통해 개발하는데 에러 메시지가 정확하게 나오지 않는다.
때문에 그 모든 과정을 포스팅하려고 한다.

해당 컴퓨터 사양은 Window11 RTX 3070 환경이나 다른 환경도 할 수 있게끔 설명하도록 하겠습니다.
많은 분들이 힘드실 Cuda 버전에서는 Tensorflow가 윈도우 지원은 2.10.0 까지 하기 때문에
Cuda 12버전은 되지 않는다는 점 미리 말씀드립니다. ( 성공하신 분 있으면 댓글 달아주세요 )

가상환경을 쓰는 이유는 앞 서 말한 이유로 인해 이전 버전을 사용하기 때문에 기존의 Python과 라이브러리 충돌이 일어나지 않게끔 독립적으로 유지하기 위함이다. 때문에 귀찮더라도 모두 설치하기 바랍니다.


1.  GPU 드라이버 설치 ( Tensorflow호환성 체크 )

1 - 1. 전체 적인 호환성 체크

먼저 Cuda Toolkit 과 cuDNN 라이브러리, Tensorflow의 호환성을 체크해야 한다.

( 이는 3070기준이며 각자의 맞게 사용하기 바란다. ) 

 

먼저 내 그래픽 카드가 어떤 Cuda에 맞게 사용되는지 알아야 한다. 다음 사이트에서 두 가지 표를 보고 확인 가능하다.

 

CUDA - Wikipedia

From Wikipedia, the free encyclopedia Parallel computing platform and programming model In computing, CUDA (Compute Unified Device Architecture) is a proprietary[2] parallel computing platform and application programming interface (API) that allows softwar

en.wikipedia.org

 

내 그래픽 카드를 검색하고 맨 앞에 숫자를 체크한다.

 

그리고 다음 표를 보며 내 그래픽 카드가 사용 가능한 Cuda 를 체크한다.

8.6이 포함된 모든 Cuda 11.1 ~ 12.8 최신버전까지 모두 사용 가능하다.

 

Tensorflow가 버전에 따라 지원하는 Cuda 버전들을 확인한다.

 

Windows의 소스에서 빌드,Windows의 소스에서 빌드  |  TensorFlow

이 페이지는 Cloud Translation API를 통해 번역되었습니다. Windows의 소스에서 빌드,Windows의 소스에서 빌드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 소스에

www.tensorflow.org

 

다음은 Tensorflow에 대한 설치 가이드로 설치해야하는 버전이 나와 있다. 

다음과 같이 Window에서의 GPU 지원은 2.10 이하의 버전에서만 사용 가능하다.

 

cuDNN과 쿠다(Cuda)라고 적힌 부분에 11.2라고 적혀있지만

사실상 11.x버전을 지원하는 경우이며 11.0이나 10은 11.x, 10.x를 지원하지 않는 듯 하다.

때문에 지원하는 가장 최신 버전 선택했다.

 

1 - 2. 그래픽 카드 드라이버 환경 설치

참고할 점

여기서 드라이버를 먼저 설치 했더라면 아마 CUDA가 버전에 맞게 설치 되어있는 경우들이 있다.

최신 버전으로 업데이트 했더라면 12.8로 나올 것이다. 환경 변수를 바꾸는 방법도 있지만, 삭제하고 설치하는 것이 맘이 편하다.

제어판에 있는 다음 항목들을 모두 삭제하고 진행한다면 편할 것이다. 

Cuda Toolkit다운시에 모두 다운로드 된다.

 

Cuda Toolkit  설치

다음 사이트에서 Cuda 11.x 버전을 다운 받아 준다. ( 각자 맞게 다운로드 하길 바란다. ) 

 

CUDA Toolkit Archive

Previous releases of the CUDA Toolkit, GPU Computing SDK, documentation and developer drivers can be found using the links below. Please select the release you want from the list below, and be sure to check www.nvidia.com/drivers for more recent production

developer.nvidia.com

Cuda 라이브러리 다운

그리고 아까 확인했던 RTX 3070 기준 v8.6.0을 Cuda 11.x 버전에 맞게 다운로드 받고 압축을 풀어 준다.

 

라이브러리 복사

그러면 다음과 같은 경로에 Toolkit이 버전에 맞게 설치 되어있다.

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8

 

다운받았던 라이브러리 파일을 복사해서 넣어준다.

 

1 - 3. 환경 변수 설정

다음과 같이 환경 변수 설정에서 CUDA_PATH는 버전에 맞게 설치되어 있다. 

이제 시스템 변수의 path에서 새로 만들기를 통해 다음 세 줄을 하나씩 추가한다.

( 찾아보면 bin은 있을 것이다. )

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\include
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\lib

1 - 4. 설치 확인

다음 명령어를 통해 Cuda Version이 맞게 설치된 것을 알 수 있다.

> nvidia-smi

 

환경 변수에 인식된 Cuda 버전도 확인할 수 있다.

> nvcc --version

처음 말했던 삭제하고 설치하라는 이유가 간 혹 nvidia-smi 명령어에서는 12.8로 확인되고, nvcc --version에서는 11.8로 확인되는 경우가 있다. 드라이버가 함께 설치되어 인식하는 버전은 Cuda 12.8이고 환경 변수로 컴퓨터에서 인식하는 버전은 11.8인 경우이다.  



2. 아나콘다 설치

 

Anaconda | Built to Advance Open Source AI

Anaconda simplifies, safeguards, and accelerates open-source AI with a trusted platform, enabling secure scaling, real-time insights, and community collaboration.

www.anaconda.com

 

 

2 - 1. 다음과 같이 Free Download를 통해 사이트를 접속하고 이메일을 Submit하면 이메일로 다운로드 링크를 보내준다.

 

이후에 설치 창에서 Recommnad 추천하는 경우를 선택하고 설치해주는 것이 좋다.

 

2 - 2. Anaconda Prompt 실행 후 가상환경 생성

 

가상환경을 생성하기 위해서는 아까 봤던 tensorflow 표를 통해 python version을 설정하여 생성하면 된다.

 

 

일단 가상 환경에 설정할 Python은 3.7에서 3.10으로 나와 있지만,

3.8 ~ 3.9가 안정적이다라고 많은 평가가 있으며,

3.10 버전은 이후 세팅을 위한 과정에서 어려움을 겪을 수 있으니 3.8 ~ 3.9를 추천한다.

 

가상환경 생성

먼저 가상환경을 생성해 준다.

> conda create --name (사용할 가상환경 이름) python=3.9

 

생성된 가상환경을 활성화 준다. 다음과 같이 앞이 설정한 이름으로 바뀌는 것을 알 수 있다.

> conda activate ( 사용했던 가상환경 이름 )

 

라이브러리 설치 ( tensorflow )

최신 버전의 conda는 더이상 tensorflow-gpu=2.10.0의 설치 지원을 하지 않기 때문에 pip 설치로 대체한다. 

둘다 설치한다. 이후 버전은 통합하였지만 따로따로 사용해야 한다.

> pip install tensorflow-gpu==2.10.0
> pip install tensorflow==2.10.0

라이브러리 설치 ( numpy )

이렇게 설치 한다면 tensorflow에 필요한 라이브러리들이 자동적으로 설치된다.

이때 numpy가 최신버전으로 설치 되기 때문에 다운그레이드를 해야한다. 이 경우도 conda가 지원하지 않아 pip로 진행

1.23 버전이 다른 라이브러리 버전과 호환된다.

> pip install numpy==1.23

 

GPU 인식 확인

꼭 다음 명령어를 conda가상환경이 활성화 된 상태에서 하기 바란다.

jupyter Notebook or Visual Code에서는 인식하는 과정이 느리고 오류가 제대로 나오지 않기 때문에 확인이 어렵다

다음과 같은 코드를 입력했을 때 되지않는다 하더라도 에러 내용이 제대로 나오기 때문에 대처가 가능하다.

> python -c "import tensorflow as tf; print('TensorFlow Version:', tf.__version__); print('GPU 사용 가능 여부:', tf.config.list_physical_devices('GPU'))"

 

위와 같이 GPU 인식이 가능하다.

 

 

추가 에러 상황 - zlibwapi.dll error

다음 오류는 Visual code에서 Jupyter Notebook으로 사용했을 때 생기는 오류이다. ( 그 외에도 생길 수 있다. )

 

Could not load library cudnn_cnn_infer64_8.dll. Error code 193

the zlibwapi files are there, the paths are there. Still getting the same error. However the links you provided for downloading the 64 bit versions are NOT secure. It has been more than 2 months with this and it takes you 2 weeks to provide vage answers th

forums.developer.nvidia.com

다음 사이트를 통해 사양에 맞는 비트를 선택하고 설치한 파일 내에 zlibwapi.dll만 복사해서 다음 경로에 붙여넣기 하면된다.

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin

 

계속 CPU 기기만 인식해서 많은 블로그와 공식 가이드 문서들을 찾아보았지만 호환성에 대해서 정확한 내용이 거의 없었다. 때문에 Cuda버전들을 삭제하고 다시 설치 하는 수 많은 과정을 통해 이 글을 작성하게 되었다.
다른 분들께서는 이 글을 통해 인식이 되지 않는 문제 없이 한 번에 이루어지는 쾌거를 느끼시길 바란다.

추가적인 오류가 있다면 댓글로 남겨주시면 감사하겠습니다.
반응형
반응형
이번 글에서는 백준 28278번 문제를 통해
스택(자료구조)를 구현하고 활용해 보자.

문제 핵심

  • N : 명령의 수 ( 1 <= N <= 1,000,000 )
  • 명령 1 ~ 5 까지 주어진다.
  • 1. Push
  • 2. Pop
  • 3. Size
  • 4. IsEmpty
  • 5. Peek
  • 명령은 하나씩 주어진다.

풀이 과정

다음 문제는 스택을 구현하고 처리하는 과정이라고 할 수 있다.

 

1. 함수 구현하기

1 ~ 5의 명령어와 같이 스택에 필요한 함수들을 구현하자.

	private static int isEmpty() {
        if (list.isEmpty()) {
            return 1;
        }
        return 0;
    }

    private static Integer peek() {
        if (list.isEmpty()) {
            return -1;
        }
        return list.getFirst();
    }

    private static Integer pop() {
        if (list.isEmpty()) {
            return -1;
        }
        return list.removeFirst();
    }

    private static void push(int num) {
        list.addFirst(num);
    }

 

문제에 맞게 명령어들을 변형하여 함수를 구현하였다.

2. 구현한 함수 활용하기 ( Main 작성 )

  • 문제를 다음과 같이 풀었지만, 1 ~ 5의 단순한 숫자나 문자열로 처리할 경우 Switch문이 더 적합하며 범위 계산이나 null 값 계산 등이 아니라면 Switch문으로 처리하는 것이 좋아 보인다.
  • List 는 JAVA의 LinkedList<> 클래스를 활용하였다.
    • ArrayList or List를 활용해서 사용할 수 있기도 하지만, 미리 만들어두는 구조보다는 문제제목과 같은 자료구조에 맞춰서 사용하였다.
private static final LinkedList<Integer> list = new LinkedList<>();

    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder stringBuilder = new StringBuilder();

        int num = Integer.parseInt(bufferedReader.readLine());
        StringTokenizer stringTokenizer;

        String command;

        for (int i = 0; i < num; i++) {
            stringTokenizer = new StringTokenizer(bufferedReader.readLine());

            command = stringTokenizer.nextToken();

            if (command.equals("1")) {
                push(Integer.parseInt(stringTokenizer.nextToken()));
            } else if (command.equals("2")) {
                stringBuilder.append(pop())
                        .append("\n");
            } else if (command.equals("3")) {
                stringBuilder.append(list.size())
                        .append("\n");
            } else if (command.equals("4")) {
                stringBuilder.append(isEmpty())
                        .append("\n");
            } else if (command.equals("5")) {
                stringBuilder.append(peek())
                        .append("\n");
            }

        }
        System.out.println(stringBuilder);
    }

 

 

스택 ( 자료구조 ) - JAVA

오늘은 자료구조의 기본 구조 중 하나인 스택에 대해서 알아보고자 한다.가장 기본적인 구조로 후입선출 ( LIFO, Last In First Out ) 방식을 가진다.접시를 쌓아올린다고 생각하면 된다.스택 ( Stack )후

p-coding.tistory.com

 

백준에서는 여러 프로그래밍 언어 중 Java가 비교적 느린 편이기 때문에, 많은 사람이 속도 최적화에 집중하거나 단순히 문제를 해결하는 데만 초점을 맞춰 코드를 작성하는 경우가 많다. (물론 브론즈 ~ 실버 수준의 문제에서는 속도 차이가 크게 중요하지 않다.)

하지만 이러한 문제의 핵심은 스택을 직접 구현하고 이해하는 것에 있다.


예를 들어, 명령어가 4일 때 list.isEmpty() ? 1 : 0을 사용하면 간편하게 해결할 수 있다.
그러나 이는 LinkedList 클래스의 isEmpty() 메서드를 활용한 것이지,
우리가 구현해야 할 스택의 isEmpty()가 아니다.

비록 같은 기능을 수행하지만, 개념적으로 다른 의미를 가진다.


즉,
문제를 통과하는 것에만 집중하기보다는, 정확한 개념을 이해하고 접근하는 것이 더 중요하다.


전체 코드

물론 다음 코드도 완벽하지는 않다.

package org.example;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.StringTokenizer;

public class Main {

    private static final LinkedList<Integer> list = new LinkedList<>();

    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder stringBuilder = new StringBuilder();

        int num = Integer.parseInt(bufferedReader.readLine());
        StringTokenizer stringTokenizer;

        String command;

        for (int i = 0; i < num; i++) {
            stringTokenizer = new StringTokenizer(bufferedReader.readLine());

            command = stringTokenizer.nextToken();

            if (command.equals("1")) {
                push(Integer.parseInt(stringTokenizer.nextToken()));
            } else if (command.equals("2")) {
                stringBuilder.append(pop())
                        .append("\n");
            } else if (command.equals("3")) {
                stringBuilder.append(list.size())
                        .append("\n");
            } else if (command.equals("4")) {
                stringBuilder.append(isEmpty())
                        .append("\n");
            } else if (command.equals("5")) {
                stringBuilder.append(peek())
                        .append("\n");
            }

        }
        System.out.println(stringBuilder);
    }

    private static int isEmpty() {
        if (list.isEmpty()) {
            return 1;
        }
        return 0;
    }

    private static Integer peek() {
        if (list.isEmpty()) {
            return -1;
        }
        return list.getFirst();
    }

    private static Integer pop() {
        if (list.isEmpty()) {
            return -1;
        }
        return list.removeFirst();
    }

    private static void push(int num) {
        list.addFirst(num);
    }
}
반응형

+ Recent posts