레이블이 Java인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Java인 게시물을 표시합니다. 모든 게시물 표시

2019년 12월 16일 월요일

도수 정렬



* 도수 정렬
요소의 대소를 판단하지 않고 빠르게 정렬할 수 있는 알고리즘이다.
도수분포표를 이용하기 때문에 if 대소비교문이 필요없이 for문만으로 정렬이 가능하다.




public class fSort {
    //배열 a중 최댓값을 구하여 max로 넘겨준다.
    static void fSort(int[] a, int n, int max) {
        int[] f = new int[max + 1];
        int[] b = new int[n];

        for (int i = 0; i < n; i++) f[a[i]]++; //도수분포표 만들기
        for (int i = 1; i <= max; i++) f[i] += f[i - 1]; //누적 도수분포표 만들기
        //f 배열에 있는 값을 -- 연산자를 사용한 f 배열은 누적도수분포표가 된 상태인데
        //해당 구간의 누적개수합산을 뜻한다.
        //인덱스를 1씩 빼서 동일한 값일 경우 앞서 저장한 부분의 앞칸에 저장되게 하기 위함이다.
        for (int i = n - 1; i >= 0; i--) b[--f[a[i]]] = a[i]; 
        for (int i = 0; i < n; i++) a[i] = b[i]; //a에 b 복사하기
        
    }
}

힙(Heap) 정렬




*힙(heap) 정렬 

선택 정렬을 응용한 알고리즘인 힙 정렬은 힙의 특성을 이용하여 정렬을 수행한다.
힙은 부모의 값이 자식의 값보다 항상 크다는 조건을 만족하는 완전이진트리이다.
부모의 값이 자식보다 항상 작아도 힙이라고 한다.
힙은 형제의 대소관계가 정해져 있지 않아서 부분순서트리라고도 한다.


public class HeapSort {
    // 배열 요소 a[idx1]과 a[idx2]의 값을 바꿉니다.
    static void swap(int[] a, int idx1, int idx2) {
        int t = a[idx1];
        a[idx1] = a[idx2];
        a[idx2] = t;
    }

    //a[left] ~ a[right]를 힙으로 만듭니다.
    static void downHeap(int[] a, int left, int right) {
        int temp = a[left]; //root값
        int child; //큰 값을 가진 노드 인덱스
        int parent; // 부모 인덱스

        //자식과 부모를 비교하여 큰 것을 위로 올린다.
        for (parent = left; parent < (right + 1) / 2; parent = child) { 
            int cl = parent * 2 + 1; //왼쪽 자식 인덱스
            int cr = cl + 1; //오른쪽 자식 인덱스
            child = (cr <= right && a[cr] > a[cl]) ? cr : cl; //큰 값을 가진 노드를 자식에 대입
            if (temp >= a[child]) //root가 더 크다면 반복문 중지
                break;
            a[parent] = a[child]; //큰 값을 위로 올림
        }
        a[parent] = temp;
    }

    //힙 정렬
    static void heapSort(int[] a, int n) {
        for (int i = (n - 1) / 2; i >= 0; i--) //a[i] ~ a[n - 1]를 힙으로 만들기
            downHeap(a, i, n - 1);

        for (int i = n - 1; i > 0; i--) {
            swap(a, 0, i); //가장 큰 요소와 아직 정렬되지 않은 부분의 마지막 요소를 교환
            downHeap(a, 0, i - 1); //a[0] ~ a[i - 1]을 힙으로 만들기
        }
    }
}

2019년 12월 6일 금요일

병합(merge) 정렬





* 병합 정렬

정렬의 마친 두 배열을 병합하여 분할 정복법에 따라 정렬하는 알고리즘을 병합 정렬이라고 한다.



1. 정렬을 마친 두 배열을 병합하기


public class MergeArray {
    //정렬을 마친 배열 a, b를 병합하여 배열 c에 저장
    static void merge(int[] a, int na, int[] b, int nb, int[] c) {
        int pa = 0;
        int pb = 0;
        int pc = 0;

        while (pa < na && pb < nb) //작은 값을 c 배열에 저장한다.
            c[pc++] = (a[pa] <= b[pb]) ? a[pa++] : b[pb++];
        while (pa < na) //a에 남아있는 요소를 복사
            c[pc++] = a[pa++];
        while (pb < nb) //b에 남아있는 요소를 복사
            c[pc++] = a[pb++];

    }
    public static void main(String[] args) {
        Scanner stdIn = new Scanner(System.in);
        int[] a = {2, 4, 6, 11, 13};
        int[] b = {1, 2, 3, 4, 9, 16, 21};
        int[] c = new int[13];

        System.out.println("두 배열의 병합");

        merge(a, a.length, b, b.length, c); //배열 a와 b를 병합하여 c에 저장

        System.out.println("배열 a와 b를 병합하여 배열 c에 저장했습니다.");
        System.out.println("배열 a : ");
        for (int i = 0; i < a.length; i++) {
            System.out.print("a[" + i + "] = " + a[i]);
        }
        System.out.println("배열 b : ");
        for (int i = 0; i < b.length; i++) {
            System.out.print("b[" + i + "] = " + b[i]);
        }
        System.out.println("배열 c : ");
        for (int i = 0; i < a.length; i++) {
            System.out.print("c[" + i + "] = " + c[i]);
        }

    }
}




2. 1번을 응용하여 분할 정복법에 따라 병합 정렬하기
배열을 2등분하여 앞부분과 뒷부분으로 나눈다.
앞부분과 뒷부분을 각각 정렬하고 병합한다.
여기서 재귀호출을 하게 되는데 그 앞부분과 뒷부분도 또다시 병합정렬을 이용하기 때문이다.




public class MergeSort {
    static int[] buff; //작업용 배열

    //a[left] ~ a[right] 을 재귀적으로 병합 정렬한다.
    static void __mergeSort(int[] a, int left, int right) {
        if (left < right) {
            int i;
            int center = (left + right) / 2;
            int p = 0;
            int j = 0;
            int k = left;

            __mergeSort(a, left, center); //배열의 앞부분을 병합 정렬한다.
            __mergeSort(a, center + 1, right); //배열의 뒷부분을 병합정렬한다.

            for (i = left; i <= center; i++) //버퍼에다 a[left]~a[center]를 복사한다.
                buff[p++] = a[i];

            while (i <= right && j < p) //버퍼와, a배열(center 뒷부분부터 right까지)을 서로 병합정렬한다.
                a[k++] = (buff[j] <= a[i]) ? buff[j++] : a[i++];

            while (j < p) //buff의 나머지 요소를 배열 a에 복사한다.
                a[k++] = buff[j++];
        }
    }

    //병합 정렬
    static void mergeSort(int[] a, int n) {
        buff = new int[n]; //작업용 배열 생성

        __mergeSort(a, 0, n - 1); //배열 전체를 병합 정렬한다.

        buff = null; //작업용 배열 해제
    }

    public static void main(String[] args) {
        Scanner stdIn = new Scanner(System.in);

        System.out.println("병합 정렬");
        System.out.print("요솟수:");
        int nx = stdIn.nextInt();
        int[] x = new int[nx];

        for (int i = 0; i < nx; i++) {
            System.out.print("x[" + i + "] : ");
            x[i] = stdIn.nextInt();
        }

        mergeSort(x, nx);

        System.out.println("오름차순으로 정렬했습니다.");
        for (int i = 0; i < nx; i++) {
            System.out.print("x[" + i + "]= " + x[i]);
        }

    }
}



3. 자바가 제공하는 sort 사용하기
- 기본 자료형 정렬
public class ArraySortTester {
    public static void main(String[] args) {
        Scanner stdIn = new Scanner(System.in);

        System.out.print("요솟수 : ");
        int num = stdIn.nextInt();
        int[] x = new int[num];

        for (int i = 0; i < num; i++) {
            System.out.print("x[" + i + "] :");
            x[i] = stdIn.nextInt();
        }

        //sort 메소드는 기본 자료형의 배열을 정렬한다.
        //퀵 정렬 알고리즘을 개선한 것으로 안정적이지 않다.
        //즉 배열에 같은 값이 존재하는 경우 같은 값 사이의 순서가 바뀔 수 있다.

        Arrays.sort(x);

        for (int i = 0; i < num; i++) {
            System.out.print("x[" + i + "] :");
        }
    }
}



- 클래스 객체 배열의 정렬 (병합 정렬)
클래스 객체 배열의 정렬에서 사용하는 알고리즘은 병합 정렬을 개선한 것으로 안정적인 결과가 보장된다.


아래 메소드들은 기본 오름 차순 정렬이다. static void sort(Object[] a)
static void sort(Object[] a, int fromIndex, int toIndex) : fromIndex 부터 toIndex 까지만 정렬한다.



public class SortCalendar {
    public static void main(String[] args) {
        GregorianCalendar[] x = {
                new GregorianCalendar(2017, Calendar.NOVEMBER, 1),
                new GregorianCalendar(1963, Calendar.OCTOBER, 18),
                new GregorianCalendar(1985, Calendar.APRIL, 5),
        };

        Arrays.sort(x);

        for (int i = 0; i < x.length; i++)
            System.out.printf("%04d년 %02d월 %02d일\n",
                    x[i].get(Calendar.YEAR),
                    x[i].get(Calendar.MONTH) + 1,
                    x[i].get(Calendar.DATE)
            );
    }
}


결과 출력
1963년 10월 18일
1985년 04월 05일
2017년 11월 01일


만약 정렬 시에 원하는 기준이 있을 경우 그 키를 기준으로 하는 Comparator를 구현 하면 된다. 아래 메소드들은 기본 오름 차순 정렬이다.


static void sort(T[] a, Comparator c)
static void sort(T[] a, int fromIndex, int toIndex, Comparator c) : T를 부모로 갖는 클래스이어야 한다.


public class PhyscExamSort {
    static class PhyscData {
        String name;
        int height;
        double vision;

        PhyscData(String name, int height, double vision) {
            this.name = name; this.height = height; this.vision = vision;
        }
        public String toString() {
            return name + " " + height + " " + vision;
        }

        //키 오름차순용 comparator
        //Comparator의 compare 메소드를 오버라이드하여 기준을 정함.

        static final Comparator HEIGHT_ORDER = new HeightOrderComparator();

        private static class HeightOrderComparator implements Comparator {
            @Override
            public int compare(PhyscData d1, PhyscData d2) {
                return (d1.height > d2.height) ? 1 :
                        (d1.height < d2.height) ? -1 : 0;
            }
        }
    }
    public static void main(String[] args) {
        PhyscData[] x = {
                new PhyscData("이나령", 162,0.3),
                new PhyscData("전서현", 122,1.3),
                new PhyscData("홍길동", 172,0.8),
                new PhyscData("유지훈", 182,0.9),
        };
        Arrays.sort(x, PhyscData.HEIGHT_ORDER); //HEIGHT_ORDER 를 사용하여 정렬한다.
    }
}


2019년 12월 5일 목요일

퀵(quick) 정렬






* 퀵 정렬

일반적으로 많이 사용되고 있는 아주 빠른 정렬 알고리즘.
어느 한 요소를 선택하는데 이를 피벗(pivot) 이라고 한다.
그 요소보다 작은 그룹과 큰 그룹을 나누고 각 그룹에서의 피벗 설정과 그룹 나눔을 반복하여 모든 그룹이 1개의 요소가 되면 정렬을 마친다.


1. 배열을 피벗으로 나누어보는 코드

//배열 요소 a[idx1]과 a[idx2]의 값을 바꿉니다.
static void swap(int[] a, int idx1, int idx2) {
    int t = a[idx1];
    a[idx1] = a[idx2];
    a[idx2] = t;
}

//배열을 나눕니다.
static void partition(int[] a, int n) {
    int pl = 0; //point left 왼쪽 커서
    int pr = n - 1; //point right 오른쪽 커서
    int x = a[n / 2]; //피벗(가운데 위치의 값)

    do {
        while (a[pl] < x) pl++; //왼쪽커서의 값이 피벗보다 작은경우 계속 검사
        while (a[pr] > x) pr--; //오른커서의 값이 피벗보다 큰경우 계속 검사
        if (pl <= pr)
            swap(a, pl++, pr--); // 스왑한다.
    } while (pl <= pr);

    System.out.println("피벗의 값은 " + x + "입니다.");

    System.out.println("피벗 이하의 그룹");
    for (int i = 0; i <= pl - 1; i++)
        System.out.print(a[i] + " ");
    System.out.println();

    if (pl > pr + 1) {
        System.out.println("피벗과 일치하는 그룹");
        for (int i = pr + 1; i <= pl - 1; i++)
            System.out.print(a[i] + " ");
        System.out.println();
    }
    
    System.out.println("피벗 이상의 그룹");
    for (int i = pr + 1; i < n; i++)
        System.out.print(a[i] + " ");
    System.out.println();
}




2. 재귀적으로 퀵 소트를 구현 (재귀호출)

static void quickSort(int[] a, int left, int right) {
    int pl = left;
    int pr = right;
    int x = a[(pl + pr) / 2]; //피벗

    do {
        while (a[pl] < x) pl++;
        while (a[pr] > x) pr--;
        if (pl <= pr)
            swap(a, pl++, pr--);
    } while (pl <= pr);

    if (left < pr) quickSort(a, left, pr); //왼쪽끝이 오른커서보다 작으면 왼쪽그룹을 계속 나눈다.
    if (pl < right) quickSort(a, pl, right); //오른끝이 왼쪽커서보다 크면 오른그룹을 계속 나눈다.
}



3. 비재귀적으로 퀵소트를 구현 (스택)

    //비재귀버전
    static void quickSort2(int[] a, int left, int right) {
        IntStack lstack = new IntStack(right - left + 1);
        IntStack rstack = new IntStack(right - left + 1);

        lstack.push(left);
        rstack.push(right);

        while (lstack.isEmpty() != true) {
            int pl = left = lstack.pop();
            int pr = right = rstack.pop();
            int x = a[(pl + pr) / 2]; //피벗

            //그룹 분할
            do {
                while (a[pl] < x) pl++;
                while (a[pr] > x) pr--;
                if (pl <= pr)
                    swap(a, pl++, pr--);
            } while (pl <= pr);

            if (left < pr) {
                // 왼쪽 그룹 범위의 인덱스를 푸시합니다.
                lstack.push(left);
                rstack.push(pr);
            }

            if (pl < right) {
                // 오른쪽 그룹 범위의 인덱스를 푸시합니다.
                lstack.push(pl);
                rstack.push(right);
            }
        }
    }




2019년 12월 4일 수요일

셸 정렬 (shell sort)





셸(shell) 정렬은 단순 삽입 정렬의 장점을 살리고 단점을 보완한 정렬 알고리즘이다.

참고: 단순삽입정렬



//기본 셸 정렬
static void shellSort(int[] a, int n) {
    //h : 요소사이의 간격을 뜻한다. h 간격의 요소를 비교하여 삽입정렬을 진행한다.
    for (int h = n / 2; h > 0; h /= 2) //h는 계속 2로 나눈 몫이 된다.  ex) h = 4, 2, 1
        for (int i = h; i < n; i++) { //i = 4, 5, 6.. 로 차례로 증가
            int j;
            int tmp = a[i]; //a[4] 값을 임시 저장한다.
            //j = 0부터 시작. a[i-h]값이 tmp보다 클때만 for문을 돈다.
            //증분은 j에서 h값을 빼준다.
            for (j = i - h; j >= 0 && a[j] > tmp; j -= h)
                a[j + h] = a[j]; //복사
            a[j + h] = tmp;
        }
}


위 코드의 문제점은 h 수열이 서로 배수가 될 경우 동일한 요소들끼리만 섞인다는 점이다.


//셸 정렬 개선 버전
//h값 수열을 서로 배수가 되지 않도록 h값을 변경
static void shellSort2(int[] a, int n) {
    int h;
    //h의 초기값을 미리 계산해놓는다.
    //초기값은 너무 크면 의미가 없으므로 배열의 요솟수 n을 9로 나눈 몫을 넘지 않도록 정한다.
    //그리고 h간격을 3배한 후 1을 더하면 요소가 충분히 섞일수 있도록 하는 초기값을 구할 수 있다.
    for (h = 1; h < n / 9; h = h * 3 + 1)
        ;
    
    //h를 3으로 나눈 몫을 가지고 간격이 점차 줄어들어
    for ( ; h > 0; h /= 3) //마지막엔 h가 1이 남게 될 것이다.
        for (int i = h; i < n; i++) {
            int j;
            int tmp = a[i];
            for (j = i - h; j >= 0 && a[j] > tmp; j -= h)
                a[j + h] = a[j];
            a[j + h] = tmp;
        }

}


2019년 12월 3일 화요일

버블/단순 선택/단순 삽입 정렬





정렬에서 사용하는 swap 메소드는 아래와 같이 구현하면 된다.

// a[idx1]와 a[idx2]의 값을 바꿉니다.
    static void swap(int[] a, int idx1, int idx2) {
        int t = a[idx1];
        a[idx1] = a[idx2];
        a[idx2] = t;
    }



1. 버블(Bubble) 정렬


    //length가 n 인 배열 a 을 버블정렬한다.
    static void bubbleSort(int[] a, int n) {
        //정렬을 마친 부분을 저장한다.
        int k = 0; // a[k]보다 앞쪽은 정렬은 마친 상태

        while (k < n - 1) {
            int last = n - 1; // 마지막으로 요소를 교환한 위치
            for (int j = n - 1; j > k; j--) //
                if (a[j - 1] > a[j]) {
                    swap(a, j - 1, j);
                    last = j; 
                }
            k = last; 
        }
    }

    // 양방향 버블 정렬(셰이커 정렬)
    static void shakerSort(int[] a, int n) {
        int left = 0;
        int right = n - 1;
        int last = right;

        while (left < right) {
            for (int j = right; j > left; j--) {
                if (a[j - 1] > a[j]) {
                    swap(a, j - 1, j);
                    last = j;
                }
            }
            left = last;

            for (int j = left; j < right; j++) {
                if (a[j] > a[j + 1]) {
                    swap(a, j, j + 1);
                    last = j;
                }
            }
            right = last;
        }
    }


2. 단순 선택 정렬
가장 작은 요소부터 선택해 알맞은 위치로 옮겨서 순서대로 정렬하는 알고리즘이다.


// 1. 가장 작은 요소의 값을 선택.
// 2. 아직 정렬하지 않은 부분의 첫번째 요소를 교환한다.
static void selectionSort(int[] a, int n) {
    for (int i = 0; i < n - 1; i++) {
        int min = i;
        for (int j = i + 1; j < n; j++)
            if (a[min] > a[j]) //최소값의 인덱스를 for 루프를 통해 찾았으면
                min = j; //해당 j(인덱스)를 min 에 저장
        swap(a, i, min); //아직 정렬되지 않은 부분과 min을 교환한다.

    }
}


3. 단순 삽입 정렬
선택한 요소를 그보다 더 앞쪽의 알맞은 위치에 삽입하는 작업을 반복하여 정렬하는 알고리즘이다.

public class InsertionSort {
    //단순 삽입 정렬
    static void insertionSort(int[] a, int n) {
        for(int i = 1; i < n; i++) {
            int j;
            int tmp = a[i]; //2번째 요소부터 선택하고 임시저장
            //tmp와 배열의 값을 하나씩 비교하여 앞이 더 크면 그 값을 뒤로 복사한다.
            for (j = i; j > 0 && a[j - 1] > tmp ; j--)
                a[j] = a[j - 1];
            //a[j - 1] <= tmp 인 경우 for 문을 탈출
            a[j] = tmp; // j인덱스 자리에 tml 값을 삽입
        }
    }

    public static void main(String[] args) {
        Scanner stdIn = new Scanner(System.in);

        System.out.println("단순 삽입 정렬");
        System.out.print("요솟수:");
        int nx = stdIn.nextInt();
        int[] x = new int[nx];

        for (int i = 0; i < nx; i++) {
            System.out.print("x[" + i + "] : ");
            x[i] = stdIn.nextInt();
        }

        insertionSort(x, nx); //배열 x를 단순 삽입 정렬 합니다.

        System.out.println("오른차순으로 정렬했습니다.");
        for (int i = 0; i < nx; i++) {
            System.out.print("x[" + i + "]= " + x[i]);
        }
    }
}

2019년 11월 30일 토요일

8퀸 문제 (재귀 호출)




8개의 퀀을 8x8판에 배치시키는데 서로가 잡힐 수 없도록 배치하는 가지수를 찾아내는 것이다.

퀸은 자신과 같은 열에 있는 다른 퀸을 공격하므로 각 열에 퀸을 1개만 배치해야 한다.
pos라는 배열은 각 배열의 인덱스가 열이 되고, 각 인덱스의 값들이 행이 된다.
즉 한 줄로 8x8의 배치 형태를 표시할 수 있다.


문제에 접근하기 앞서 순차적으로 문제에 접근을 해보자.
이것은 단순히 하나의 열에 퀸이 있다면 그 열에는 퀸이 있을 수 없다는 규칙만을 적용하여 도출된 계산이다.
pop[0]에 0이 있다면 퀸은 최상단좌측에 한개가 위치한 것인데 0열은 또 다른 퀸이 들어갈 수 없으므로 그 다음 열에 퀸이 들어갈 경우의 수가 8가지가 된다.
이런식으로 계산을 하면 $8^8$ 가지의 퀸의 배치 경우의 수가 도출된다.
이것은 단순 가지 뻗기 방식의 퀸의 배치방법이다.

* 가지 뻗기 식의 퀸의 배치

public class QueenB {
    static int[] pos = new int[8]; //각 열의 퀸의 위치를 숫자로 저장

    //각 열의 퀸의 위치를 출력합니다.
    static void print() {
        for (int i = 0; i < 8; i++)
            System.out.printf("%2d", pos[i]);
        System.out.println();
    }

    //i열에 퀸을 놓습니다.
    static void set(int i) { //i:열
        for (int j = 0; j < 8; j++) {
            pos[i] = j; //j:행, 퀸을 j행에 배치합니다.
            if (i == 7)
                print(); //배치된 퀸들을 출력한다.
            else
                set(i + 1); //다음 열에 퀸을 배치한다.
        }
    }
    public static void main(String[] args) {
        set(0); //0열에 퀸을 배치한다.
    }
}


여기서 각 열에 퀸을 1개를 배치하는 조건 뿐만이 아니라 각 행에도 퀸을 한개씩만 배치하여야 하는 조건을 추가해 보자.


public class QueenBB {
    static boolean[] flag = new boolean[8]; //각 행에 퀸을 배치했는데 체크하는 배열
    static int[] pos = new int[8]; //각 열의 퀸의 위치

    //각 열의 퀸의 위치를 출력합니다.
    static void print() {
        for (int i = 0; i < 8; i++)
            System.out.printf("%2d", pos[i]);
        System.out.println();
    }

    static void set(int i) {
        for (int j = 0; j < 8; j++) {
            if(flag[j] == false) { //j행에 퀸을 배치 하지 않았다면
                pos[i] = j; //퀸을 j행에 배치한다.
                if(i == 7) 
                    print();
                else{
                    flag[j] = true; //j행에 배치했으니 flag를 true로 변경
                    set(i + 1); //그리고 다음 열에 퀸을 배치할 때 0행에는 배치가 되지 않는다.
                    flag[j] = false; //그리고 다시 j행은 false 로 변경한다
                }
            }
        }
    }
    public static void main(String[] args) {
        set(0); //0열에 퀸을 배치한다.
    }
}


퀸은 가로 세로 뿐만아니라 대각선으로도 이동할 수가 있다.
따라서 퀸이 배치가 되면 배치된 퀸에서 대각선으로도 퀸을 배치하지 않도록 해야한다.




public class EightQueen {
    static boolean[] flag_a = new boolean[8]; //각 행에 퀸을 배치했는지 체크
    static boolean[] flag_b = new boolean[15]; //↗대각선 방향으로 퀸을 배치했는지 체크
    static boolean[] flag_c = new boolean[15]; //↘대각선 방향으로 퀸을 배치했는지 체크
    static int[] pos = new int[8];

    //각 열의 퀸의 위치를 출력합니다.
    static void print() {
        for (int i = 0; i < 8; i++)
            System.out.printf("%2d", pos[i]);
        System.out.println();
    }

    //i열의 알맞은 위치에 퀸을 배치
    static void set(int i) {
        for (int j = 0; j < 8; j++) {
            if (flag_a[j] == false &&
                    flag_b[i + j] == false &&
                    flag_c[i - j + 7] == false) {
                pos[i] = j;
                if (i == 7)
                    print();
                else {
                    flag_a[j] = flag_b[i + j] = flag_c[i - j + 7] = true;
                    set(i + 1);
                    flag_a[j] = flag_b[i + j] = flag_c[i - j + 7] = false;
                }
            }

        }
    }
    public static void main(String[] args) {
        set(0); //0열에 퀸을 배치한다.
    }

}

대각선 방향에 퀸이 배치가 되었는지 체크하는 배열의 길이가 15개인 이유는 8x8 정사각형의 대각선 방향은 2가지 인데 각 방향마다 15개의 사선이 그려진다.
맨 처음 퀸이 위치하면 가로와 대각선방향으로는 퀸이 위치해서는 안된다. ↗대각선 방향의 사선들 중에 하나의 ↗은 ↗에 배치된 퀸의 행(j)과 열(i)의 합이 동일하다.
고로 flag_b[i + j]를 true로 만든 후 경우의 수를 제거해야한다.
이러한 방법으로 ↘대각선 방향도 규칙을 찾아내어 공식으로 만들면 된다.







2019년 11월 28일 목요일

하노이의 탑(Tower of Hanoi) 재귀 호출 구현하기




자료구조 책을 공부하다가 재귀함수파트가 너무 어려워서 이해한것을 잊어버릴까봐 글로 남긴다.
책대로 해야하는 것은 알겠는데 실제로 정말 될까를 이해하는 과정이 너무 힘들었다.
재귀 함수 안에 재귀함수가 2개이상 호출되는 경우 머리로 순서가 연산이 잘 안된다.ㅋㅋ

지금 생각해보니 몇시간동안 너무 어렵게 생각한 것 같다.
공부한 김에 정리하고 나중에 추가로 수정을 해야할 것이다.

이 코드에서 계속 잘못 생각하고 있었던 부분은 move 메소드 자체였는데,
move 메소드에서 x, y가 의미하는 바는 x 기둥에서 y기둥으로 옮긴다는 것이다.

어쨌든 이 코드에서의 큰 줄기는 가장 아래 원반을 제외한 나머지 원반그룹들을 두번째 기둥으로 옮긴 후 아래 원반을 세번째 기둥에 옮긴다.
그리고 원반 그룹들을 세번째에 옮기면 된다.

큰원반은 작은원반 위로 올라가질 못한다.
원반을 옮기려면 최소한 가장 작은 원반부터 건드려야 한다.

결국 move 메소드는 n개의 원반을 x 기둥에서 y기둥으로 옮긴다.
요약하면 아래와 같다.
기둥 번호는 차례대로 1,2,3 이다.


1. 위에서부터 n-1개의 원반들을 1번째 기둥에서 2번째 기둥으로 옮긴다. (1 -> 2)
2. 맨 아래의 원반 1개를 3번째 기둥으로 옮긴다 (1 -> 3)
3. 2번째 기둥에 있던 n-1개의 원반들을 3번째 기둥으로 옮긴다. (2 -> 3)

그런데 n-1개의 원반들을 기둥에서 기둥으로 옮길 때 move 메소드를 또다시 호출하게 된다. 다만 x, y인자를 어떤기둥에서 옮겨지는지 제대로 넣어주어야 한다.

그리고 원반 개수가 1이 될 때 실제로 옮겼다고 텍스트를 출력하게 된다.



public class Hanoi {
    /**
     * n개의 원반을 x 기둥에서 y 기둥으로 옮김
     * @param no 원반 개수
     * @param x 시작 기둥
     * @param y 목표 기둥
     */
    static void move(int no, int x, int y) {
        //가장 아래의 원반을 뺀 윗 그룹의 원반들을
        //처음 기둥에서 중간 기둥으로 이동한다.
        //x,y가 1과 3일때 6-x-y는 중간 기둥 값이다.
        //그리고 가장 마지막 기둥을 세번째 기둥으로 옮긴다.
        if (no > 1)
            move(no - 1, x, 6 - x - y);
        
        //실제 옮겼다고 출력하는 부분
        System.out.println("원반[" + no + "]을 " + x + "기둥에서 " + y + "기둥으로 옮김");

        //그룹 원반들(no-1)을 중간 기둥에서 목표 기둥으로 이동한다.
        if (no > 1)
            move(no - 1, 6 - x - y, y);
    }

    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);

        System.out.println("하노이의 탑");
        System.out.println("원반 개수:");
        int n = s.nextInt();

        //기둥 번호를 순서대로 1,2,3 칭함
        //하노이탑의 목표: n개의 원반을 1번 기둥에서 3번 기둥으로 옮김
        move(n, 1, 3);
    }
}

유클리드 호제법으로 최대공약수 구하기




* 유클리드 호제법 개념 참고

재귀 호출을 이용하여 유클리드 호제법 알고리즘을 구현하였다.
두 수를 서로 나눈 나머지와 두 수중의 하나의 수는 서로 최대 공약수인 점을 이용하여,
나머지와 하나의 수를 계속 나누어 가다보면 나머지가 0이 되는 시점에서 나누어지는 수를 리턴한다.

public class EuclidGCD {
    //유클리드 호제법 알고리즘을 이용합니다.
    //정수 x,y의 최대 공약수를 구하여 반환합니다.
    static int gcd(int x, int y) {
        if(y == 0)
            return x;
        else
            return gcd(y, x % y);
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int x = sc.nextInt();
        int y = sc.nextInt();

        System.out.println("최대 공약수는 " + gcd(x, y) + "입니다.");
    }
}

2019년 11월 27일 수요일

Java Queue 구현하기



자료구조와 함께 배우는 알고리즘 실습 내용 (157P)
링버퍼로 큐를 구현한 예제

배열로 구현하되 링버퍼로 구현한 큐는 배열의 끝에 도달할 때 다시 처음으로 돌아가게 되는 구조이다. 따라서 디큐를 할 때 앞요소를 가리키는 포인터를 +1 만 해주면 된다.
데이터들을 전부 이동시키거나 할 필요가 없어 링으로 구현한 큐의 복잡도는 $O(1)$이 된다.




/**
 * Description: 링버퍼로 큐를 구현
 */
public class IntQueue {
    private int max; //큐의 용량
    private int front; //앞 요소
    private int rear; //마지막 요소
    private int num; //현재 데이터 수
    private int[] que; //큐 본체

    //실행 시 예외 : 큐가 비어있음
    public class EmptyIntQueueException extends RuntimeException{
        public EmptyIntQueueException() { }
    }
    //실행 시 예외 : 큐가 가득 참
    public class OverflowIntQueueException extends RuntimeException{
        public OverflowIntQueueException() { }
    }
    //생성자
    public IntQueue(int capacity){
        num = front = rear = 0;
        max = capacity;
        try{
            que = new int[max];
        }catch (OutOfMemoryError e){
            max = 0;
        }
    }
    //큐에 데이터를 인큐
    public int enque(int x) throws OverflowIntQueueException {
        if (num >= max)
            throw new OverflowIntQueueException();
        que[rear++] = x;
        num++;
        if(rear == max)
            rear = 0;
        return x; //인큐한 데이터를 리턴
    }
    //큐에 데이터를 디큐
    public int deque() throws EmptyIntQueueException {
        if(num <= 0)
            throw new EmptyIntQueueException();
        int x = que[front++];
        num--;
        if (front == max)
            front = 0;
        return x;
    }
    //큐에서 데이터를 피크(프런트 데이터를 들여다봄)
    public int peek() throws EmptyIntQueueException {
        if(num <= 0)
            throw new EmptyIntQueueException();
        return que[front];
    }
    //큐에서 x를 검색하여 인덱스를 반환, 찾지 못하면 -1
    public int indexOf(int x) {
        //i는 num의 수만큼만 반복하면 된다.
        for(int i = 0; i < num; i++) { 
            /** 
             * 실제 시작지점에서부터의 인덱스를 구하기 위함.
             * 링형태의 인덱스값을 구해야 하므로 
             * max로 나누어 떨어질때 0부터 시작할 수 있도록 한다.
             */
            int idx = (i + front) % max;
            if(que[idx] == x)
                return idx;
        }
        return -1;
    }
    //큐를 비움
    public void clear() {
        num = front = rear = 0;
    }
    //큐의 용량의 반환
    public int capacity() {
        return max;
    }
    //큐에 쌓여있는 데이터 수를 반환
    public int size() {
        return num;
    }
    //큐가 비어있나요?
    public boolean isEmpty() {
        return num <= 0;
    }
    //큐가 가득 찾나요?
    public boolean isFull() {
        return num >= max;
    }
    //큐 안의 모든 데이터를 프런트 -> 리어 순으로 출력
    public void dump() {
        if (num <= 0)
            System.out.println("큐가 비어 있습니다.");
        else{
            for (int i = 0; i < num ; i++)
                System.out.print(que[(i + front) % max] + " ");
            System.out.println();
        }
    }
}

2019년 8월 9일 금요일

Java Reflection - Attributes




* Java Reflection


자바의 reflection은 자바 런타임 중에 클래스의 멤버들(속성)을 조사할수 있는 굉장히 유용한 도구이다.


참조 문서:https://docs.oracle.com/javase/tutorial/reflect/class/classMembers.html


아래는 클래스에 선언된 메소드들의 리스트를 가져오는 코드이다.

public class Solution {

        public static void main(String[] args){
        
            Class student = Student.class;
            Method[] methods = student.getDeclaredMethods();

            ArrayList methodList = new ArrayList<>();
            for(Method m : methods){
                methodList.add(m.getName());
            }
            Collections.sort(methodList);
            for(String name: methodList){
                System.out.println(name);
            }   
        }
}

2019년 8월 8일 목요일

Java Lambda Expressions (람다 표현식)




* Lambda Expressions (람다 표현식)
 

 익명 클래스(Anonymous class)는 만약 구현해야 할 인터페이스안의 메소드가 단 하나밖에 없는 경우처럼 간단한 경우에 실제 코드상에 해당 메소드를 구현하는 방법은 코드의 가독성이 떨어지고 다루기 불편해 보인다.

 이런 경우에서 자바 8부터 지원되는 람다 표현식은 익명 클래스의 목적은 그대로 사용하면서 간소화한 문법으로 변경된 것이다.

참고 람다표현식 API문서 :https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

아래의 hackerrank 문제는 isOdd, isPrime, isPalindrome 메소드들은 
PerformOperation 객체를 리턴하는데 람다표현식을 이용하여 작성하였다. 

PerformOperation 인터페이스는 구현해야 할 메소드가 한 개밖에 없어 심플하다.
이런 케이스에 람다표현식을 사용하게 되면 가독성이 좋아질 것이다.



interface PerformOperation { 
    boolean check(int a);
}
class MyMath {
    public static boolean checker(PerformOperation p, int num) {
       return p.check(num);
    }

    public static PerformOperation isOdd(){ //짝수, 홀수 찾기
       //람다 표현식을 이용하여 인터페이스의 check 메소드를 구현하고 인스턴스까지 생성할 수 있다. 
       return (int num) -> num % 2 != 0; 
    }

    public static PerformOperation isPrime(){ //소수 찾기
         return (int num) -> {
             for(int i = 2 ;i < Math.sqrt(num) ; i++){
                 if(num % 2 == 0){
                    return false;
                 }else{
                    continue;
                 }
             }
             return true;
             
         };
    }

    public static PerformOperation isPalindrome(){ //앞, 뒤 어느 쪽으로 읽어도 같아지는 숫자 찾기 
         return (int num) -> { 
             int r,sum = 0 , temp;  
             temp = num;
             while(num > 0){
                r = num % 10 ; //나머지, 일의 자리 부터 저장됨
                sum = (sum*10) + r; //거꾸로 뒤집어서 숫자를 만들어감   
                
                num /= 10;
             }

            if(temp == sum)
                return true;
            else
                return false;
             
         }; // 조건식이 여러 줄로 된 경우 중괄호로 묶고 마지막에 세미콜론을 넣는다.
    }
}
public class Solution {

    public static void main(String[] args) throws IOException {
        MyMath ob = new MyMath();
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int T = Integer.parseInt(br.readLine());
        
        PerformOperation op;
        boolean ret = false;
        String ans = null;
        
        while (T-- > 0) {
            String s = br.readLine().trim();
            StringTokenizer st = new StringTokenizer(s);
            int ch = Integer.parseInt(st.nextToken());
            int num = Integer.parseInt(st.nextToken());
            if (ch == 1) {
                op = ob.isOdd();
                ret = ob.checker(op, num);
                ans = (ret) ? "ODD" : "EVEN";
            } else if (ch == 2) {
                op = ob.isPrime();
                ret = ob.checker(op, num);
                ans = (ret) ? "PRIME" : "COMPOSITE";
            } else if (ch == 3) {
                op = ob.isPalindrome();
                ret = ob.checker(op, num);
                ans = (ret) ? "PALINDROME" : "NOT PALINDROME";
            }
            System.out.println(ans);
        }
    }
}

2019년 8월 1일 목요일

Java MD5, SHA-256 암호화




* MD5
출력값 길이 : 128bit (16진수로 32byte)


* SHA-256
출력값 길이 : 256bit (16진수로 64byte)




public class Solution {

    public static void main(String[] argh) {
        Scanner sc = new Scanner(System.in);
        String str = sc.next();
        sc.close();
        try {
            //MessageDigest의 인스턴스를 가져올 때 
            //MD5 혹은 SHA-256으로 변경하기만 하면 해당 알고리즘의 해시 값을 얻을 수 있다.
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(str.getBytes());
            
            byte[] digest = md.digest(); //해시된 바이트 배열
            
            for (byte b : digest) {
                System.out.printf("%02x", b);
            }
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}

바이트 배열의 요소 각각을 16진수로 변경해야 한다.
System.out.printf("%02x", b);
0 : 자리 수를 맞춰 주기 위한 0 padding 
2 : 너비, 2자리로 고정 
x : 16진수 포맷 출력

2자리로 하는 이유는 해시값이 들어있는 byte 배열의 요소값 하나 하나를 16진수로 변환하면서 4bit까지의 수만 들어있는 경우에는 한 자리가 부족해진다. 
따라서 32byte 고정 길이를 맞춰주기 위해 0을 패딩해주는 것이다.


2019년 7월 26일 금요일

java Varargs (가변인자)




* Varargs (가변인자)

 메소드의 매개변수의 개수를 동적으로 지정할 수 있는 방법이 있는데 메소드 오버로드를 하거나 가변인자(Variable argument)를 사용할 수 있다.

아래 코드는 add 메소드의 매개변수를 가변인자로 받아들여, int형 타입의 파라미터들의 개수를 동적으로 넣을 수 있다.




아래는 직접 작성한 코드이다.
class Add {
    void add(int... args){ //타입...변수명으로 사용한다.
        int sum = 0;
        String s = "";
        int[] arr = args; //args는 배열이므로 for문으로 이용해 값 하나 하나에 접근이 가능하다.
        for(int n : args)
            sum += n;
        
        for(int i = 0 ; i < arr.length; i++){
            s += arr[i];
            if(arr.length-1 == i)
                s += "=";
            else
                s += "+";
        }
        System.out.println(s+sum);
    }
}

hackerrank의 답안이 간결하여 가져왔다.
class Add{
    public void add(int ...arr){
        int sum = 0;
        String s = "";
        for(int i = 0; i < arr.length; i++){
            sum += arr[i];
            s += arr[i];
            if(i < arr.length-1) //마지막 순환이 아닌 경우에만 +기호를 붙인다.
                s += "+";
        }
        s += "=" + sum;
        System.out.println(s);
    }
}

2019년 7월 24일 수요일

Java Priority Queue (우선순위큐)




* Priority Queue, 우선순위 큐

우선순위큐는 말 그대로 큐의 성질을 가졌으나, 각 데이터가 우선순위를 갖고 있어 우선순위가 높은 순서대로 나오게 된다. 
 만약 우선순위가 동일하다면 큐에서 그들의 순서에 의해 처리된다.
 아래 문제는 이미 구현되어져 있는 자바 클래스인 PriorityQueue를 가지고 원하는 우선순위를 구현할 수 있는지에 대한 문제이다. 
 어떤 객체의 내부속성에 대해 특별한 기준으로 정렬을 하고 싶다면 Comparable 인터페이스의 compareTo 메소드를 구현하면 된다.
 아래 Student 클래스의 예시에서 우선순위대로 정렬의 기준을 구현하였다.
CGPA 점수가 높은 것이 우선순위가 더 높다고 하였으므로,비교대상점수가 더 높을 경우 원래라면 -1을 리턴하여 앞이 더 앞서다고 나타내야겠지만 1을 return 함으로써 그 순서가 역(내림차순)으로 정렬을 해야한다는 점을 알려준다.
이 부분이 이해가 되지 않을 경우 예전 게시글을 참고한다.

* 참고 : compareTo

compareTo 메소드는 매번 헷갈리는 부분이 있어 문제에 등장할 때마다 정리하게 된다.😓

결론은 정렬 기준의 순서대로 PriorityQueue에서 poll() 하는 경우 우선순위가 높은 것부터 나오게 된다.  




import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.*;
/*
 * Create the Student and Priorities classes here.
 */
class Student implements Comparable{
    int id;
    String name;
    double cgpa;

    public Student(int id, String name, double cgpa){
        this.id = id;
        this.name = name;
        this.cgpa = cgpa;
    }
    public int getID(){
        return id;
    }
    public String getName(){
        return name;
    }
    public double getCGPA(){
        return cgpa;
    }
    @Override
    public int compareTo(Student target){
        if(this.cgpa < target.getCGPA()){
            return 1;
        }else if(this.cgpa == target.getCGPA()){
            if( this.name.compareTo(target.getName()) < 0) { //String의 비교는 String에 내장된 것을 사용.
                return -1; //이름은 오름차순으로 정렬
            }else if( this.name.compareTo(target.getName()) == 0 ){
                if(this.id < target.getID()){
                    return -1;
                }else if(this.id == target.getID()){
                    return 0;
                }else{
                    return 1;
                }

            }else{
                return 1;
            }
        
        }else{
            return -1;
        }
    }

}

class Priorities{
    PriorityQueue q;

    List getStudents(List events){
        q = new PriorityQueue();
        List list = new ArrayList();
        //System.out.println(events.size());

        for(int i = 0 ; i < events.size();i++){
            String event = events.get(i);
            String[] eArr = event.split(" ");
            if(eArr[0].equals("SERVED")){
                q.poll();

            }else{
                q.offer(new Student(Integer.parseInt(eArr[3]),eArr[1],Double.parseDouble(eArr[2])));
            }
        }
            
        while(q.size()>0){
            list.add(q.poll());
        }
    
        return list; 
    }

}

public class Solution {
    private final static Scanner scan = new Scanner(System.in);
    private final static Priorities priorities = new Priorities();
    
    public static void main(String[] args) {
        int totalEvents = Integer.parseInt(scan.nextLine());    
        List events = new ArrayList<>();
        
        while (totalEvents-- != 0) {
            String event = scan.nextLine();
            events.add(event);
        }
        
        List students = priorities.getStudents(events);
        
        if (students.isEmpty()) {
            System.out.println("EMPTY");
        } else {
            for (Student st: students) {
                System.out.println(st.getName());
            }
        }
    }
}

2019년 7월 17일 수요일

Java Dequeue




* Dequeue 

Dequeue deck이라고 발음하기도 하는데, 양 끝이 열려있는 통로처럼 생긴 자료구조이다.
양 끝이 열려 있어 dequeue에 저장되어 있는 데이터들은 head와 tail 양 쪽에서 꺼내고 넣을 수 있는 특징이 있다.

참조: Dequeue API



 아래는 Hackerrank java Dequeue문제이다. 

n개의 int 값이 주어지며, 그 데이터들의 서브 배열의 길이는 m이다. m개의 서브 배열을 만들어가면서 서브 배열들 속에서 유니크한 데이터들의 수의 최대값을 구하는 문제이다.  


입력 예시)
6  =>n
3  => m
5 3 5 2 3 2 =>n개의 데이터

1번째 subArray: [5, 3, 5] - 유니크 데이터 개수: 2
2번째 subArray: [3, 5, 2] - 유니크 데이터 개수: 3
3번째 subArray: [5, 2, 3] - 유니크 데이터 개수: 3
4번째 subArray: [2, 3, 2] - 유니크 데이터 개수: 2

최대값은 3이 된다.


import java.util.*;
    public class test {
        public static void main(String[] args) {
            Scanner in = new Scanner(System.in);
            Deque deque = new ArrayDeque();
            int n = in.nextInt();
            int m = in.nextInt();

            int max = 0;
            Set set = new HashSet(); //유니크한 데이터만 저장하기 위한 HashSet

            for (int i = 0; i < n; i++) {
                int num = in.nextInt();
                deque.add(num); //dequeue에 값을 넣으면서
                set.add(num); //set에도 값을 넣는다.
                if(deque.size() == m){ //서브배열만큼 넣었을 때
                    if(max < set.size()){ 
                        max = set.size();
                    }
                    int first = deque.removeFirst(); //dequeue에서 첫번째 데이터를 지우고 그 값 저장한다.
                    if(!deque.contains(first)) set.remove(first); //point

                }
            }
            
            System.out.println(max);

        }
    }

포인트는 이 부분이다.

if(!deque.contains(first)) set.remove(first); 

dequeue에 first 값이 존재하지 않는다면, 그 값을 set에서 삭제한다.
dequeue에 첫 번째 값이 존재하지 않는다는 말은 first 값과 중복 된 값이 없다는 뜻이다. 
 즉 중복된 값이 없으므로 set 집합에서도 지워주어야 한다.
first 값을 삭제하고 난 후에도 first 값이 dequeue 안에 또 존재한다면, set에서는 삭제 처리를 하지 않아도 된다. 
set은 유니크한 데이터만 존재하므로 first 값이 이미 삭제 된 것이나 마찬가지이기 때문이다.

2019년 7월 12일 금요일

깊이 우선 탐색 (DFS, Depth-First Search)




DFS 깊이우선 탐색 알고리즘을 이용하여 풀수 있는 문제이다.
하나의 위치에서 갈 수 있는 모든 가능성에 대한 조사를 하고, 다음 위치로 넘어간다. 

* 문제 이해

n 길이의 1차 배열 arr 이 주어지고, arr[0]은 무조건 0으로 시작한다.  leap은 점프할수 있는 칸수이다.

1. 뒤로 이동
 * 만약 i-1이 존재하고, 그 인덱스의 값이 0이라면 i-1로 이동할 수 있다.
2. 앞으로 이동
 * arr[i+1]이 0이라면 한 칸을 앞으로 이동할 수 있다.
 * arr[i+leap]이 0이라면 i+leap으로 이동할 수 있다.
 * 만약 n-1에 위치하거나 i+leap>=n인 경우 win 이며 그 외는 fail이다.
 즉 인덱스 i부터 i+1 ,i-1, i+leap 번째 배열의 값이 0인 곳으로만 이동하면서 배열의 마지막에 도달하거나, 배열의 길이보다 높게 뛸 수 있으면 게임에서 이기게 된다.

문제를 풀기 위해서, 현재 위치에서 이동할 수 있는 조건들을 깊이 탐색을 해야하는 과정이 필요한데 이것은 재귀호출을 이용하여 탐색할 수 있다. 
 또한 다음 위치로 넘어가기 전에 현재 위치는 탐색을 완료했다는 표시를 반드시 해야 한다.
 그렇지 않으면 현재 위치로 다시 돌아올 경우 동일하게 탐색을 또 하기 때문에 무한루프에 빠지게 된다.



public class Solution {

     public static boolean canWin(int leap, int[] game) {
        return isSolvable(leap, game, 0); //뛸 수 있는 숫자, 배열, 시작 인덱스
    }

    private static boolean isSolvable(int leap, int[] game, int i) {
        // Base Cases
        if (i >= game.length) { //현재 위치의 인덱스가 전체 배열의 길이보다 같거나 크면
            return true; //게임에서 이김
        } else if (i < 0 || game[i] == 1) { //i가 0보다 작거나, 현재 위치의 배열 값이 1이면
            return false; //게임에서 짐
        }

        game[i] = 1; // (핵심) 현재 체크(방문)한 배열 위치의 값을 1로 마킹한다.

        // 재귀호출.
        // 현 위치에서 점프하거나, 앞으로 한칸을 가거나, 뒤로 한칸 가는 모든 상황을 체크한다. 
        // 그 중 하나라도 true를 반환한다면 게임에서 이긴다.
        // 3가지 가능한 케이스가 재귀를 돌면서 그 안에서 또 각 위치에서 세가지 케이스를 확인하므로 모든 조건에 대한 탐색이 가능하게 한다.
        return isSolvable(leap, game, i + leap) ||
                isSolvable(leap, game, i + 1) ||
                isSolvable(leap, game, i - 1);
    }

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int q = scan.nextInt();
        while (q-- > 0) {
            int n = scan.nextInt();
            int leap = scan.nextInt();
            
            int[] game = new int[n];
            for (int i = 0; i < n; i++) {
                game[i] = scan.nextInt();
            }

            System.out.println( (canWin(leap, game)) ? "YES" : "NO" );
        }
        scan.close();
    }
}

2019년 6월 12일 수요일

Java Comparator 인터페이스를 이용한 숫자 정렬






* Comparator 이용하여 숫자 정렬하기


Comparator 인터페이스, compare 메소드, Array sort, API 문서 참고: 
https://docs.oracle.com/javase/7/docs/api/index.html?overview-summary.html


문제는 아래와 같이 주어진 String 문자열들을 내림차순으로 정렬해야 하는데 000.000 같은 경우 0.0으로 출력되면 안되고, 반드시 형태를 유지해야한다.

[sample input]

9
-100
50
0
56.6
90
0.12
.12
02.34
000.000




class Solution{
    public static void main(String []args){
        //Input
        Scanner sc= new Scanner(System.in);
        int n=sc.nextInt();
        String []s=new String[n+2];
        for(int i=0;i<n;i++){
            s[i]=sc.next();
        }
        sc.close();

        Arrays.sort(s,0,n, Collections.reverseOrder(new Comparator<String>() {
            @Override
            public int compare(String a1, String a2) {
                BigDecimal a = new BigDecimal(a1);
                BigDecimal b = new BigDecimal(a2);
                return a.compareTo(b);
            }
        }));
        //Output
        for(int i=0;i<n;i++)
        {
            System.out.println(s[i]);
        }
    }
}
기존 String 배열을 재정렬하는 부분의 코드를 분석해보자.

먼저 Array.sort로 들어오는 인자를 보면, s String 배열을 0번째부터 n번째까지 Comparator의 정렬 기준으로 정렬한다. n은 포함이 아니다.
Comparator 객체를 다시한번 reverseOrder 를 하여 문제가 요구하는대로 내림차순이 되도록 하였다.

Comparator 객체를 생성하면서 동시에 compare메소드를 오버라이드(재정의) 하여 새로운 정렬 기준을 작성한다.
BigDecimal 클래스를 이용하여 String 형으로 되어있는 숫자를 변환하였고, 0의 손실이 일어나지 않도록 하였다.
또한 BigDecimal 클래스의 compareTo 메소드는 a가 b보다 앞서있는 경우에는 음수를, 동일하면 0, a가 b보다 뒤에있다면 양수를 반환한다. 
그래서 이 메소드가 반환하는 값이 Comparator클래스에서 compare 메소드의 return 값이 된다.
BigDecimal의 compareTo 메소드는 Comparator에서 구현해야하는 compare의 조건과 일치한다.
Comparator 클래스에서 오버라이드를 하는 compare 메소드에는 주의해야 할 필수 조건이 있다.

1. sgn(compare(x,y)) 가 -sgn(compare(y,x))와 같아야 한다는 것이다.

* sgn?
In the foregoing description, the notation sgn(expression) designates the mathematical signum function, which is defined to return one of -1, 0, or 1 according to whether the value of expression is negative, zero or positive.

2. ((compare(x, y)>0) && (compare(y, z)>0)) 로부터 compare(x, z)>0 을 추정할 수 있어야 한다.
3. sgn(compare(x, z))==sgn(compare(y, z)) for all z 라면, 이것으로부터 compare(x, y)==0 임을 알 수 있어야 한다.

위 조건을 반드시 만족하도록 compare 메소드를 오버라이드 시켜야 제대로 된 정렬이 일어날 것이다.

2019년 6월 4일 화요일

정규표현식 예제




1. IP 주소 패턴

IP 주소는 0-255까지 숫자 범위가 있기 때문에 각 자리수별로 올 수 있는 숫자를 생각하여 정규표현식으로 표현해야 한다.
 아이피 숫자가 한자리 혹은 두자리 수 일 경우에는 아무런 숫자가 와도 된다. 
 세자리 숫자일 경우에는 생각할 꺼리가 많아지는데, 맨 앞자리가 0,1,2 인 경우에 따라 각각의 케이스들을 | (pipeline) 으로 연결해 주면 된다.
 아이피 숫자는 맨 앞자리가 0이어도 유효하기 때문에 (0|1)일 경우에는 뒤에 두자리가 [0-9] 아무 숫자가 와도 된다. 맨 앞자리가 2일 경우에는 두 가지 분기로 나누어야만 하는데, 
두번째 자리가 [0-4]이면은 일의 자리는 [0-9] 아무숫자나 와도 되지만 앞자리 두개가 25일 경우 일의 자리는 0-5까지만 와야 한다. 
 아래 예시에 IP 매칭에 대한 정규표현식을 작성하였는데 두 개는 동일한 내용이다. 
 두 번째가 [0-9]를 \\d로 표현했기 때문에 더 간결해 보인다.
\\. 이 부분도 점 그 자체로 검색해야 하므로 이스케이프 문자가 붙어있다.



String pattern = "([0-9]{1,2}|(0|1)[0-9][0-9]|2[0-4][0-9]|25[0-5])(\\.([0-9]|[0-9][0-9]|(0|1)[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}";

String pattern = "(\\d{1,2}|(0|1)\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d{1,2}|(0|1)\\d{2}|2[0-4]\\d|25[0-5])){3}";


2. 중복된 단어 찾기 

아래와 같은 문자열에서 여러번 중복된 단어들을 제거하고 첫번째 문자열만 남긴다. 대소문자의 구분은 없다.


test Matched matched matched matchedtest String string String matched


이걸 풀기 위해서 정규표현식의 grouping 을 이용하면 쉽게 풀린다. group() 관련 메소드는 API문서를 참고하면 된다.


연속으로 중복되는 단어들을 전부 찾아내고, 그 중에 첫번째 단어만 남겨야 한다.
패턴은 아래와 같다.


String pattern = "\\b(\\w+)(?:\\W\\1\\b)+";

하나씩 의미를 파악해보자면,

\\b(\\w+)  
\\b로 단어의 경계를 지정하고 한 글자 이상의 단어를 찾는다. 그 단어 한개가 첫번째 그룹이다. 

(?:\\W\\1\\b)+ 
(?:X) 이 문법은 이 괄호 안에 잡히는 대상은 그룹으로 잡지 않겠다는 의미이다. 또한 마지막에 + 로 되어있는데 이런 그룹이 연속으로 1개 이상이라는 의미이다.

\\W는 word가 아닌 문자열을 의미한다. 중복된 단어 사이에 공백이나 , . 등이 있는 경우도 포함하기 위함이다.
\\1 은 첫번째 그룹에서 가장 최근에 매치된 동일한 텍스트를 의미한다. 
만약 그룹이 여러개라면 \\n 이런 식으로 표현 가능하며, n번째 그룹에서 가장 최근에 매치된 텍스트를 골라낼 수 있다.
위 예제에서는 만약 단어 Matched가 매치되면 그 다음에 이 단어가 \\1자리에 위치하는 것이다.
마지막에 단어의 경계를 나타내는 \\b를 명시하지 않으면 위 예시에서 matchedtest에서 matched까지 찾아내게 된다.

이제 이 정규표현식을 통해 중복을 제거한다.
핵심은 하나의 문장을 입력받아서 하나의 문장을 Matcher가 검사하는데 하나의 문장 내에서도 while문에 Matcher로 find()를 하면서 나란히 중복되는 문자열을 찾는다. 
그리고 그 찾아낸 문자열의 m.group() 을 m.group(1) 로 replaceAll() 을 한다.

m.group()은 위 정규표현식에서 전체에 매칭되는 Full match를 의미한다.

test Matched matched matched matchedtest String string String matched

Matched matched matched 가 첫번째 m.group()이 되고 Matched는 첫번째 그룹의 값인 m.group(1)이 된다. 
String string String은 그 다음 while 루프에서 찾아지며 동일하게 첫번째 String으로 replaceAll이 될 것이다.




public class DuplicateWords {

    public static void main(String[] args) {

        String regex = "\\b(\\w+)(?:\\W\\1\\b)+";
        Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); //대소문자 구분없이 찾는다

        Scanner in = new Scanner(System.in);
        int numSentences = Integer.parseInt(in.nextLine());
        
        while (numSentences-- > 0) {
            String input = in.nextLine();
            
            Matcher m = p.matcher(input);
            
            // Check for subsequences of input that match the compiled pattern
            while (m.find()) {
                input = input.replaceAll(m.group(), m.group(1)); 
            }
            // Prints the modified sentence.
            System.out.println(input);
        }
        
        in.close();
    }
}


3. 아이디 제한 걸기

아이디 생성 조건은 다음과 같다.
- 문자열 길이는 8자 이상~ 30자 이하.
- 첫 시작 문자는 반드시 알파벳이어야 함.
- 알파벳 대소문자, 숫자, _ 가능
- 특수문자 불가

위 조건은 아래와 같이 심플하게 정규표현식으로 가능하다.


String pattern = "\\p{Alpha}[\\w]{7,29}";

7,29로 범위를 지정한 이유는 앞에 \\p{Alpha} 가 한자리를 차지하기 때문에 1씩 빼주었다. 



4. Html Tag 안의 텍스트 내용 추출하기

아래는 Tag의 내용을 추출하는 코드이다.
다만 태그의 열고 닫는 문법이 정확해야 그 내용을 추출해야한다. 만약 <tag>내용...<tag>이라면 올바른 태그 형식이 아니다.


public class Solution{
 public static void main(String[] args){
  
 Scanner in = new Scanner(System.in);
 int testCases = Integer.parseInt(in.nextLine());
 while(testCases>0){
     String line = in.nextLine();
   
            String pattern = "<(.+)>([^<>]+)</\\1>";
            Pattern p = Pattern.compile(pattern);
            Matcher m = p.matcher(line);
            boolean flag = false;
   while(m.find()){
                flag = true;
                System.out.println(m.group(2));
            }
            if(!flag)
                System.out.println("None");
   testCases--;
  }
 }
}

String pattern = "<(.+)>([^<>]+)</\\1>";

먼저 여는 태그 부분의 꺽쇠 안의 내용 그룹 한개와, content에 해당하는 부분을 2개의 그룹으로 만들고, 닫는 태그는 \\1을 이용하여 첫번째 그룹의 캡쳐된 문자열을 넣도록 하여 태그 문법이 맞도록 하였다.
content에는 < 혹은 >를 제외한 모든 문자가 들어가도록 되어있다. 
<h1><a>contents</a>invalid</h1> 이런 식의 형태에서 추출 해야 할 때 <a>태그 안에 있는 contents 문구는 유효하지만, invalid 문구는 유효하지 않도록 하는 것이 문제의 규칙이다.


2019년 6월 3일 월요일

java String split 메소드, 문자열 쪼개기




* String.split 메소드 정규표현식 이용하여 자르기



 Java에서 String을 특정 구분자로 잘라야 할 때 보통 split 메소드를 많이 쓰는데 그에 대한 예제이다.

 문자열 중에서 공백을 포함한 특수문자가 들어간 문자열은 모두 쪼개는 코드이다.
처음 문제를 풀 땐 어떻게 해서든 답을 맞춰야 하기 때문에,
아래와 같이 정규표현식에 특수문자를 일일이 지정해주고 쪼갠 후에 비어있는 배열의 값을 체크하여 전체 배열의 길이에서 빼는 임시 방편으로 땜빵(?)하는 모습이다.
빈 문자열 객체나 공백만 있는 문자열은 카운팅하지 않는 것이 문제의 조건이었다.


public class Solution {

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String s = scan.nextLine();
        // Write your code here.
        String[] arr = s.split("[\\s!,?._'@]");
        int n = 0;
         for(String t : arr){
            if(t.isEmpty())
                n++;
                continue; 
        }
        System.out.println(arr.length-n);

        for(String t : arr){
            if(t.isEmpty())
                continue;
            
            System.out.println(t);
        }
        scan.close();
    }
}


동일한 내용의 코드를 개선시킨 버전이다.

public class Solution {

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String s = scan.nextLine().trim();
        // Write your code here.

        String[] arr = s.split("[^\\p{Alpha}]+");
        int size = s.isEmpty() ? 0 : arr.length;

        System.out.println(size);        
        for(String t : arr){
            if(t.isEmpty())
                continue;
            System.out.println(t);
        }

        scan.close();
    }
}


정규표현식 부분을 아래와 같이 바꾸는 것이 가독성에 더 좋을 것이다.
문자가 아닌 것은 특수문자와 공백이다.


String[] arr = s.split("[^\\p{Alpha}]+");



또한 삼항 연산자를 이용하여 본래 문자열이 그냥 "" 라면 0을 대입한다.
 s = ""  (빈문자열) 을 위의 조건으로 split을 하게 되면 s는 그 어떤 것으로도 쪼개지지가 않기 때문에 그 자체가 arr에 들어가게 된다. 
 arr 배열은 {""} 가 되고, length가 1이 되므로 빈 문자열은 계산에서 빼기 위하여 삼항연산자로 s가 빈문자열 일 때의 처리를 해준 것이다.