|  정렬이란

- 특정 값을 기준으로 데이터를 순서대로 배치하는 방법

- 기본 : 버블, 삽입, 선택

- 비교적 속도가 빠른 정렬 : 합병, 힙, 퀵, 트리

- 하이브리드 정렬 : 팀, 블록병합, 인트로

- 기타 : 기수, 계수(카운팅), 셸, 보고

 

|  정렬의 시간복잡도 요약

안정성이라는 것은, 어떤 데이터 { 1, 2, 3, 1, 1, 1 } 을 정렬한다고 할 때,

  같은 숫자 1에 대해서 { 1, 1, 1, 1 } 의 순서가 유지되도록 정렬되는 것을 '안정성이 있다'라고 한다.

- 아래의 보조 메모리는 추가적으로 드는 메모리를 이야기하는데,

  버블이나 삽입, 선택의 경우 swap시에 드는 변수 1개만 필요하기 때문에 보조메모리가 1개만 소요된다.

정렬 방법 시간 복잡도 보조 메모리 안정성
Ω(n) Θ(n) O(n)
버블 정렬 n n^2 n^2 1 o
삽입 정렬 n n^2 n^2 1 o
선택 정렬 n^2 n^2 n^2 1 x
합병 정렬 n log⁡n n log⁡n n log⁡n n o
정렬 n log⁡n n log⁡n n log⁡n 1 x
정렬 n log⁡n n log⁡n n^2 log⁡n x
트리 정렬 n log⁡n n log⁡n n^2 n x
기수 정렬 dn (*d:자릿수) dn dn n+k o
계수 정렬 n+k (*k:max) n+k n+k n+k o
셸 정렬 n log⁡n gap에 따라 다름 n^2 1 x

 

|  버블정렬 / 삽입 정렬 / 선택 정렬

// 버블 정렬
public static void bubbleSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        for (int j = 0; j < arr.length - i; j++) {
            if (arr[j] > arr[j + 1]) {
                swap(arr, j, j + 1);
            }
        }
    }
}

// 삽입 정렬
public static void insertionSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        for (int j = i - 1; j >= 0; j--) {
            if (arr[j] > arr[j + 1]) {
                swap(arr, j, j + 1);
            } else {
                break;
            }
        }
    }
}

// 선택 정렬
private static void selectionSort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
        int min = i;
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[j] < arr[min]) {
                min = j;
            }
        }
        swap(arr, i, min);
    }
}

 

|  합병정렬 / 힙 정렬 / 퀵 정렬 / 트리 정렬

1. 합병정렬

public static void mergeSort(int[] arr, int[] tmp, int left, int right) {
    if (left < right) {
        int mid = (left + right) / 2;
        mergeSort(arr, tmp, left, mid);
        mergeSort(arr, tmp, mid + 1, right);
        merge(arr, tmp, left, right, mid);
    }
}

public static void merge(int[] arr, int[] tmp, int left, int right, int mid) {
    int lCursor = left;
    int rCursor = mid + 1;
    int idx = left;

    while (lCursor <= mid || rCursor <= right){
        if (lCursor <= mid && rCursor <= right) {
            if (arr[lCursor] <= arr[rCursor]) {
                tmp[idx++] = arr[lCursor++];
            }else {
                tmp[idx++] = arr[rCursor++];
            }
        } else if (lCursor <= mid && rCursor > right) {
            tmp[idx++] = arr[lCursor++];
        } else {
            tmp[idx++] = arr[rCursor++];
        }
    }

    for (int i = left; i <= right; i++) {
        arr[i] = tmp[i];
    }
}

2. 힙 정렬

public static void heapSort(int[] arr) {
    // 가장 끝의 자식이 있는 부모노드를 잡아서 최대힙구조로 정렬
    for (int i = arr.length / 2 - 1; i >= 0; i--) {
        heapify(arr, i, arr.length);
    }

    // 가장 앞의 노드가 최대값임을 이용하여 나머지 정렬
    for (int i = arr.length - 1; i > 0; i--) {
        swap(arr, 0, i);
        heapify(arr, 0, i);
    }        
}

public static void heapify(int[] arr, int parentIdx, int size) {
    int leftIdx = parentIdx * 2 + 1;
    int rightIdx = parentIdx * 2 + 2;
    int maxIdx = parentIdx;

    if (leftIdx < size && arr[maxIdx] < arr[leftIdx]) {
        maxIdx = leftIdx;
    }

    if (rightIdx < size && arr[maxIdx] < arr[rightIdx]) {
        maxIdx = rightIdx;
    }

    if (maxIdx != parentIdx) {
        swap(arr, parentIdx, maxIdx);
        heapify(arr, maxIdx, size);
    }
}

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

3. 퀵 정렬

- pivot 값은 처음값이거나 중앙값이 될 수 있다. (상황에 맞게 선택하면 된다)

- 퀵 정렬은 pivot을 기준으로 좌우 정렬을 하고, pivot의 양 옆을 다시 분할하여 정렬하는 것을 반복한다.

public static void quickSort(int[] arr, int left, int right) {
    // 탈출문
    if (left >= right) {
        return;
    }

    // 피봇값 지정
    int pivot = partition(arr, left, right);

    // 피봇값의 좌우를 다시 퀵소트
    quickSort(arr, left, pivot - 1);
    quickSort(arr, pivot + 1, right);
}

public static int partition(int[] arr, int left, int right) {
    // 피봇값과 양 옆의 커서를 지정
    int pivot = arr[left];
    int lCursor = left;
    int rCursor = right;

    // 1. 양 끝의 커서가 서로 같기 전까지
    // 우측은 피봇값보다 더 작은 값을 찾고
    // 좌측은 피봇값보다 같거나 더 큰 값을 찾아 swap
    // 2. 양 끝의 커서가 서로 같아지면
    // 한쪽 커서와 피봇을 swap
    while (lCursor < rCursor) {
    	while (arr[rCursor] > pivot && lCursor < rCursor) {
            rCursor--;
        }
    
        while (arr[lCursor] <= pivot && lCursor < rCursor) {
            lCursor++;
        }

        swap(arr, lCursor, rCursor);
    }
    swap(arr, left, lCursor);

    return lCursor;
}

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

4. 트리 정렬

- 이진탐색트리를 만들어 정렬하는 방식으로, 알고리즘 복잡도는 NlogN이다.

- 이 부분은 비선형구조 > 이진탐색트리 포스팅에 적은 AVL, Red-Black을 확인해주세요.

 

|  기수 정렬 / 계수 정렬 / 셸 정렬

1. 기수 정렬

- 기수 : 자릿수를 의미한다.

- 기수정렬 : 일의 자리, 십의 자리, 백의 자리.. 순으로 각 자릿수에 해당되는 수를 큐에 담은 후 기존 배열에 넣는 방식

public static void radixSort(int[] arr) {
    // 각 자리에 큐를 담은 배열
    ArrayList<Queue<Integer>> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) { // 10진수 기준으로 자리 수 0 ~ 9
        list.add(new LinkedList<>());
    }

    // 최대 자릿수
    int maxLen = getMaxLen(arr);
    // 나누는 수
    int div = 1;
    // 배열에 넣어줄 인덱스
    int idx = 0;

    // 기수 정렬에 대한 반복문
    for (int i = 0; i < maxLen; i++) {
        // 해당 자릿수의 큐에 데이터를 추가
        for (int j = 0; j < arr.length; j++) {
            list.get((arr[j] / div) % 10).offer(arr[j]);
        }

        // 해당 자릿수의 큐를 가져와서, 기존 배열(arr)에 넣는다.
        for (int j = 0; j < 10; j++) {
            Queue<Integer> queue = list.get(j);

            while (!queue.isEmpty()) {
                arr[idx++] = queue.poll();
            }
        }

        // 그 다음 자릿수를 위한 준비작업
        idx = 0;
        div *= 10;
    }

}

// 데이터의 최대 자릿수 반환
public static int getMaxLen(int[] arr) {
    int maxLen = 0;
    for (int i = 0; i < arr.length; i++) {
        int len = (int)Math.log10(arr[i]); // 자릿수
        if (maxLen < len) {
            maxLen = len;
        }
    }
    return maxLen;
}

2. 계수 정렬

- 계수 : 관계가 있는 수, 상수를 의미한다.

- 계수정렬 : 상수 data 수열에서 최대값 + 1을 size로 배열에 생성해, 카운트한 후 다시 기존 배열에 넣는 방식

public static void countingSort(int[] arr) {
    // 최댓값의 + 1 만큼 배열을 잡아둔다
    int max = Arrays.stream(arr).max().getAsInt();
    int[] cntArr = new int[max + 1];

    // 숫자데이터에 해당되는 인덱스의 cnt를 하나 늘린다.
    for (int i = 0; i < arr.length; i++) {
        cntArr[arr[i]]++;
    }

    // cntArr을 한바퀴씩 돌면서 기존 배열(arr)에 해당 인덱스의 갯수만큼 삽입
    int idx = 0;
    for (int i = 0; i < cntArr.length; i++) {
        while (cntArr[i] > 0) {
            arr[idx++] = i;
            cntArr[i] -= 1;
        }
    }
}

//최댓값 하나만 너무 크면 비효율적 -> HashMap을 통해 보완할 수 있다
public static void countingSort2(int[] arr) {
    // key : data, value : 갯수
    HashMap<Integer, Integer> map = new HashMap<>();
    for (int item : arr) {
        map.put(item, map.getOrDefault(item, 0) + 1);
    }

    // key를 배열에 담아서 정렬한 후, 기존 배열(arr)에 해당인덱스의 갯수만큼 다시 삽입한다.
    ArrayList<Integer> keyList = new ArrayList<>(map.keySet());
    Collections.sort(keyList);
    int idx = 0;
    for (int key : keyList) {
        int cnt = map.get(key);
        while (cnt > 0) {
            arr[idx++] = key;
            cnt--;
        }
    }
}

3. 셸 정렬

- 삽입 정렬을 보완한 형태로 

- 삽입 정렬은 idx 1부터 arr.length-1까지 처음부터 좌측의 데이터를 확인했다면

- 셸 정렬은 간격을 두고 간격의 거리만큼 떨어진 데이터를 확인하는 것을 반복한다.

- 이렇게 하게 되면 기존의 삽입 정렬 보다는 교환 횟수가 감소할 수 있다.

public static void shellSort(int[] arr) {
    // 초기 gap을 설정
    int gap = arr.length / 2;

    // 더 이상 간격이 없을 때까지 간격을 /2한다.
    for (int g = gap; g > 0; g /= 2) {
        // 초기 간격부터 간격의 길이만큼 증가하면서
        for (int i = g; i < arr.length; i++) {
            // 지정된 위치의 좌측 간격들의 데이터를 비교 & 교환한다.
            int tmp = arr[i]; // 짚은 데이터를 tmp에 넣고
            int j = 0; // 짚은 데이터의 좌측 데이터들을 확인
            for (j = i - g; j >= 0; j -= g) {
                // 큰 값이 나오면 좌측 데이터들을 하나씩 당긴다
                if (arr[j] > arr[j + g]) {
                    arr[j + g] = arr[j];
                } else {
                    break;
                }
            }
            arr[j + g] = tmp;
        }
    }
}

 

 

[ 출처 ]

- 부트캠프 강의를 듣고 정리한 내용입니다.

|  알고리즘이란?

알고리즘이란, 어떤 문제를 해결하기 위한 절차나 방법을 말한다.

- 알고리즘의 조건

입력 데이터의 입력
출력 처리 후 출력
명확성 동작의 흐름(flow)에 대한 명확성
유한성 정해진 시간 및 공간 내에서의 처리
효율성 같은 동작을 하더라도 시간 및 공간 면에서 보다 효율적이어야 한다.

 

|  알고리즘은 결국 정확성과 시간 복잡도, 공간 복잡도를 말한다.

- 어떻게 보다 적은 자원을 효과적으로, 정확하게 만들어 내느냐가 알고리즘의 핵심

 

|  알고리즘 정리 개요 

- 앞으로 정리할 알고리즘 목록입니다.

- (글 작성 후 링크 업로드 예정)

정렬  
이진탐색 / 투 포인터  
그리디 알고리즘  
분할 정복 / 다이나믹 프로그래밍  
백 트래킹  
최단 경로  
최소 신장 트리  

 

 

[ 참고 및 출처 ]

부트 캠프 강의를 들은 후 정리한 내용입니다.

|  들어가기 전에 

- 왜 우선순위 큐는 비선형 구조일까? 
: 큐는 선형구조로 1 : 1의 대응방식을 가진다. 앞에서 부터 데이터를 꺼내고, 뒤에서부터 데이터를 축척한다.
: 우선순위 큐는 이러한 큐에 우선순위 개념을 추가한 형태로, 우선순위를 따져 우선순위가 높은 데이터를 먼저 뽑는다.
: 우선순위 큐는 배열, 연결리스트, 힙으로 구현이 가능한데 이중에서 힙으로 구현을 하는 것이 가장 효율적이다.
: 따라서, 자바에서도 우선순위 큐는 힙으로 구현되었다. 힙은 완전 이진 트리 형태이며, 이는 1 : N 관계를 갖는 비선형구조이다.

자바에서는 우선순위 큐를 힙으로 구현한다. 왜?
: 위에서도 잠시 언급했던 것처럼, 힙으로 큐를 구현하는 것이 가장 효율적이기 때문에, 자바도 구현방식으로 힙을 쓴다.
  - 배열의 경우, 중간 삽입 또는 삭제 시 O(N)의 시간이 소요되고,
  - 연결리스트의 경우, 첫 위치에 삽입/삭제할 경우 O(1)이 걸리지만 그렇지 않다면 모든 구간을 돌아 O(N)이 소요된다.
  - 힙의 경우, 루트에 최소값 또는 최대값을 두며 하위 트리로 파생되기 때문에 삽입/삭제 모두 logN이 걸린다.
자료구조 삽입 삭제
순서 없는 배열 O(1) O(N)
순서 없는 연결리스트 O(1) O(N)
정렬된 배열 O(N) O(1)
정렬된 연결리스트 O(N) O(1)
O(logN) O(logN)
: 이처럼, 힙을 사용하면 삽입/삭제의 시간 복잡도를 줄일 수 있기에 자바도 우선순위 큐를 힙으로 구현한다.
: 참고로, 자바의 PriorityQueue 객체는 Queue와 달리 인터페이스가 아니라 일반 클래스이다.

 

|  힙이란?

- 사전적으로, "무언가를 차곡차곡 쌓아올린 자료구조"라는 뜻이다.

- 완전 이진 트리 형태의 자료구조

  ** 완전 이진 트리란? 마지막 레벨을 제외하고 모든 레벨의 노드가 꽉 채워진 상태인 트리

- 중복값을 허용하며, 반 정렬 상태(느슨한 정렬 상태)이다.

  ** 반 정렬 (또는 느슨한 정렬) 이란?
  : 큰 값이 상위에 있고, 작은 값이 하위에 있을 뿐, 이진 탐색 트리처럼 좌우가 정렬되어 있지 않다.
  ** 중복값을 허용한다는 것은?
  : 이진 탐색 트리와 달리, 힙은 중복값을 허용한다.

- 종류 : 최소힙, 최대힙

출처:https://gmlwjd9405.github.io/2018/05/10/data-structure-heap.html

 - 응용 분야 :

힙은 최대값과 최소값을 구해야할 때 가장 적절하다.
그렇기에 우선순위 큐를 구현하거나, 힙 정렬를 만드는 게 사용되며, 허프만 코드와 같은 알고리즘에 쓰인다.

 

|  힙의 구현

- 힙은 배열로 가장 잘 구현할 수 있다.

- 힙의 삽입은 아래와 같이 가장 끝에 데이터를 삽입하고 각 구간을 비교하여 교체하는 방식이며,

- 힙의 삭제는 최상단의 루트를 삭제하는 것으로, 가장 끝의 노드를 루트에 놓고 비교하여 교체하는 방식을 취한다.

삽입 (1) 트리의 가장 끝 위치에 데이터를 삽입
(2) 삽입한 위치의 부모노드와 비교하여 작으면 부모와 자리를 교체한다 (작지 않을 때까지 반복)
삭제 (1) 루트를 꺼내서 삭제한다.
(2) 트리의 가장 끝 위치의 노드를 루트에 삽입
(3) 루트부터 시작해서 자식과 비교하여 자식보다 크면 교체한다. (크지 않을 때까지 반복)

[ 코드 - 더보기 ]

더보기

1. 최소힙 구현하기

import java.util.ArrayList;

class MinHeap{
    ArrayList<Integer> tree;

    public MinHeap(){
        this.tree = new ArrayList<Integer>();
        this.tree.add(0); // dummy data
    }

    // 삽입 : (1) 트리의 가장 뒤에 삽입 (2) 삽입 위치에서부터 부모와 비교
    public void add(int data){
        this.tree.add(data);

        int curIdx = this.tree.size() - 1; // 현재 위치
        int parentIdx = curIdx / 2; // 부모 위치
        while (curIdx > 1 && tree.get(parentIdx) > tree.get(curIdx)) {
            int tmp = tree.get(parentIdx);
            tree.set(parentIdx, tree.get(curIdx));
            tree.set(curIdx, tmp);
            curIdx = curIdx / 2;
        }
    }

    // 삭제 : (1) 루트를 삭제 (2) 루트에 가장 끝 노드를 삽입 (3) 루트에서부터 비교
    public int remove(){

        if (this.tree.size() == 1){
            System.out.println("this heap is empty!");
            return -1;
        }

        // (1)-(2)
        int min = this.tree.get(1);
        int last = this.tree.remove(this.tree.size() - 1);
        this.tree.set(1, last);

        // (3)
        int curIdx = 1;
        while(true){
            int leftIdx = curIdx * 2;
            int rightIdx = curIdx * 2 + 1;
            int childIdx = -1;

            if (rightIdx < this.tree.size()) { // 자식노드가 둘 다 있는 경우
                childIdx = tree.get(leftIdx) < tree.get(rightIdx) ? leftIdx : rightIdx;
            } else if (leftIdx < this.tree.size()) { // 자식노드가 하나인 경우
                childIdx = leftIdx;
            } else { // 자식이 하나도 없을 때
                break;
            }
            
            if (tree.get(curIdx) < tree.get(childIdx)){
                break;
            } else {
                int tmp = tree.get(curIdx);
                tree.set(curIdx, tree.get(childIdx));
                tree.set(childIdx, tmp);
                curIdx = childIdx;
            }
        }

        return min;
    }
}

 

2. 최대힙 구현하기

import java.util.ArrayList;

class MaxHeap{
    ArrayList<Integer> tree;

    public MaxHeap(){
        this.tree = new ArrayList<Integer>();
        this.tree.add(0); // dummy data
    }

    // 삽입 : (1) 트리의 가장 뒤에 삽입 (2) 삽입 위치에서부터 부모와 비교
    public void add(int data){
        this.tree.add(data);

        int curIdx = this.tree.size() - 1; // 현재 위치
        int parentIdx = curIdx / 2; // 부모 위치
        while (curIdx > 1 && tree.get(parentIdx) < tree.get(curIdx)) {
            int tmp = tree.get(parentIdx);
            tree.set(parentIdx, tree.get(curIdx));
            tree.set(curIdx, tmp);
            curIdx = curIdx / 2;
        }
    }

    // 삭제 : (1) 루트를 삭제 (2) 루트에 가장 끝 노드를 삽입 (3) 루트에서부터 비교
    public int remove(){

        if (this.tree.size() == 1){
            System.out.println("this heap is empty!");
            return -1;
        }

        // (1)-(2)
        int max = this.tree.get(1);
        int last = this.tree.remove(this.tree.size() - 1);
        this.tree.set(1, last);

        // (3)
        int curIdx = 1;
        while(true){
            int leftIdx = curIdx * 2;
            int rightIdx = curIdx * 2 + 1;
            int childIdx = -1;

            if (rightIdx < this.tree.size()) { // 자식노드가 둘 다 있는 경우
                childIdx = tree.get(leftIdx) > tree.get(rightIdx) ? leftIdx : rightIdx;
            } else if (leftIdx < this.tree.size()) { // 자식노드가 하나인 경우
                childIdx = leftIdx;
            } else { // 자식이 하나도 없을 때
                break;
            }

            if (tree.get(curIdx) > tree.get(childIdx)){
                break;
            } else {
                int tmp = tree.get(curIdx);
                tree.set(curIdx, tree.get(childIdx));
                tree.set(childIdx, tmp);
                curIdx = childIdx;
            }
        }

        return max;
    }
}

 

우선순위 큐란?

- 우선순위가 있는 큐이다.

- 모든 데이터가 우선순위가 있으며, Dequeue를 할 때에 우선순위가 높은 순으로 출력된다.

  * 만약, 우선순위가 같은 경우에는 FIFO 순으로 출력된다.

■ 우선순위 큐를 어떻게 쓸 수 있을까?

- 우선순위 큐는 데이터를 삽입할때, 정해진 기준대로 오름차순/내림차순으로 데이터를 넣을 수 있고

- 최소값 또는 최대값부터 데이터를 뽑을 수 있다.

- 삽입/삭제 시간복잡도는 그럼 얼마나 될까? 앞에서 힙에서 언급했듯 logN이 걸린다.

- 이 점을 이용해서 K번째로 가장 큰 수를 뽑는다거나, 강도가 다른 돌을 부딪혀 남는 돌을 다시 넣거나, 

  중복된 수가 있는 수열에서 빈도수 기준으로 정렬하되 같은 빈도면 더 작은 수가 앞에 오게 정렬하는 등

  순서에 따라 넣고 빼는 일이 잦거나 재정렬이 필요한 데이터들에서 사용할 수 있다.

 

Comparable 인터페이스

- 우선순위 큐를 사용할 때, 우선순위의 기준이 되는 객체에 Comparable인터페이스를 사용하면

  오름차순 또는 내림차순으로 데이터를 enqueue/dequeue할 수 있다.
- Comparable인터페이스를 사용하는 방법은 크게 두가지이다.


[1] 객체에 Comparable를 구현하는 방법

class Student implements Comparable<Student>{
    String name;
    int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public int compareTo(Student o) {
        // this.Student(new)와 o(old) Student를 비교
        // 1 : 변경하지 않음, -1 : 변경
        return this.score >= o.score ? 1 : -1;
        // return this.score - o.score; -- 같은 말이다.
    }
}

[2] 람다식을 사용하여 비교 연산 메소드(compareTo)를 바로 구현하는 방법

import java.util.PriorityQueue;

class Student{
    String name;
    int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
}

class test{
    public static void main(String[] args) {
        PriorityQueue<Student> pq = new PriorityQueue<>(
            (Student newSt, Student oldSt) ->  newSt.score - oldSt.score
        );

        for (int i = 0; i < 10; i++) {
            pq.add(new Student("Student"+(i+1), i * 10));
        }

        while(!pq.isEmpty()) {
            Student st = pq.poll();
            System.out.println(st.name + " : " + st.score);
        }
    }
}

[+] 문자열을 Comparable를 통해 사전순으로 offer/poll하는 방법

/* 생략 (위의 Student 객체 그대로 사용) */

class test{
    public static void main(String[] args) {
        PriorityQueue<Student> pq = new PriorityQueue<>(
            (Student newSt, Student oldSt) ->  newSt.name.compareTo(oldSt.name)
        );

        for (int i = 0; i < 10; i++) {
            char c = (char)('A' + (int)(Math.random()*32));
            pq.add(new Student((char)(c+i)+"_Student", i * 10));
        }

        /* 출력 생략 */
    }
}

 

|  힙과 우선순위 큐 문제

https://www.acmicpc.net/workbook/view/1819

 

문제집: 힙 쓰는 문제 (kcaladram)

 

www.acmicpc.net

https://www.acmicpc.net/step/13

 

우선순위 큐 단계

우선순위 큐를 응용하여 중앙값을 빠르게 찾는 문제

www.acmicpc.net

 

[ 참고 및 출처 ]

- 부트 캠프의 강의를 듣고 여러 자료들을 참조하여 정리한 내용입니다.

- https://namu.wiki/w/%ED%9E%99%20%ED%8A%B8%EB%A6%AC

- https://gmlwjd9405.github.io/2018/05/10/data-structure-heap.html

 

|  요약

  핵심 단어 핵심 특성 언제 쓰면 좋을까
트리 계층, Acyclic, 4가지의 순회 포화 이진 트리의 경우, 하위로 갈수록
노드의 갯수가 2^0, 2^1, 2^2..로 증가
계층 관계의 데이터를 다룰 때
그래프 네트워크, cyclic, DFS/BFS 루트가 없고, 각 정점들간 단방향 또는
양방향의 관계를 가질 수 있음
연관 관계를 가진 데이터들에서
최적의 결과를 찾아야할 때

 

|  트리와 그래프의 차이

  트리 그래프
개요 그래프의 한 종류 정점(vertex, 노드)과 간선(edge)으로 이루어진 자료구조
그림

출처 : 위키백과

출처 : 위키백과
방향 (단)방향 그래프  무(양)방향, (단)방향 그래프
사이클 Acyclic Cyclic
모델 계층 모델 네트워크 모델
루트 최상위 노드 없음
상-하위 관계 인접한 상하위 노드 없음
간선의 갯수 N개의 노드의 간선은 N-1개 그래프에 따라 다름
순회 PreOrder, InOrder, PostOrder
LevelOrder
DFS
BFS
경로 두 노드 간 경로는 하나(단방향) 두 노드 간 경로 여러 개 가능(양방향)
종류 이진 트리, 이진 탐색 트리,
균형 트리(AVL 트리, red-black트리),
이진 힙(최대힙, 최소힙) 등
-
예시 폴더 구조(디렉토리, 서브 디렉토리),
조직도, 가계도 등
지도, 지하철 노선도의 최단 경로, 
전기 회로의 소자들,
도로 (교차점과 일방 통행길), 선수 과목

 

■ 트리 개요

- 트리의 구조와 특징
- 이진 트리
- 이진 탐색 트리
- 균형 이진 탐색 트리

|  트리의 구조와 특징

출처:https://velog.io/@alstn3265/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%8A%B8%EB%A6%AC

[특징]
1. 모든 노드는 연결되어 있다.
2. 노드 간 이동 경로는 하나이다.(단방향)
3. 하나의 간선을 끊으면 2개의 sub-Tree로 나뉜다.
4. 이진트리에서, 노드가 N개인 트리의 간선 갯수는 N-1개이다.
5. 그래프와 달리 cycle이 존재하지 않는다(acyclic)
Basic 노드(Node, 또는 정점) data를 담고 있는 트리구조의 단위
간선(Edge) 노드의 연결선 (= link, branch)
구분 루트(Root) 꼭대기 노드
내부 노드(Internal) 단말을 제외한 모든 노드
단말(Leaf) 자식이 없는 노드
관계 부모(Parent) 연결된 두 노드 중 상위 노드
자식(Child) 연결된 두 노드 중 하위 노드
형제(Sibling) 같은 부모를 공유하는 노드
깊이 깊이(Depth) 루트에서 어떤 노드까지의 간선의 수 (ex. P에서 E까지 깊이 : 4)
레벨(Level) 트리의 특정 깊이를 가지는 노드 집합 (ex. level 1의 노드는 Q와 R)
높이(Height) 트리에서 가장 큰 레벨 값 (ex. 위에 트리는 4)
노드 갯수 크기(Size) 자신을 포함한 자식 노드의 갯수
간선 갯수 차수(Degree) 각 노드가 가진 간선 갯수 (ex. L의 차수는 3)
트리의 차수 트리의 최대 차수 (ex. 위 트리의 최대 차수는 L의 차수인 3)

 

|  이진트리(BT, Binary Tree)

이진트리 : 최대 2개의 자식을 가질 수 있는 트리

1. 이진 트리의 종류와 특징

포화 이진 트리(Perfect Binary Tree) 모든 레벨에서 노드가 꽉 채워진 트리
완전 이진 트리(Complete Binary Tree) 마지막 레벨을 제외하고
모든 레벨에서 노드가 꽉 채워진 트리

정 이진 트리(Full Binary Tree) 모든 노드가 0 또는 2개의 자식을 가진 트리
(자식이 없거나 2개 모두 있거나)
편향 트리(Skewed Binary Tree, 사향 트리) 한쪽으로 기울어진 트리
균형 이진 트리(Balanced Binary Tree) 모든 노드의 좌우 서브 트리 높이가
1 이상 차이나지 않는 트리

(ex. 옆 그림에서 A의 좌측은 2개, 우측은 1개의 간선이 있어 차이가 1이 난다)

- 관련 수식

N = 2^(h+1) - 1

* N : 포화트리 노드의 갯수
* h : 높이
h = logN
* N : 노드의 갯수
* h : 포화 또는 완전 트리 높이
maxH = N
*maxH : 이진 트리의 최대 가능 높이
*N : 노드의 갯수

 

2. 이진 트리의 순회(Traversal)

전위 순회(PreOrder) 현재 -> 왼쪽 -> 오른쪽 
중위 순회(IntOrder) 왼쪽 -> 현재 -> 오른쪽 
후위 순회(PostOrder) 왼쪽 -> 오른쪽 -> 현재
레벨 순회(LevelOrder) 각 레벨에서 왼쪽 -> 오른쪽
[ 이진 트리를 구현할 때의 Tip ]
- 배열을 통해 이진 트리를 구현한다고 가정할 때 위치를 찾는 방법
(1) 좌측 노드 = 나의 노드 x 2 + 0 --> 배열idx에서는 나의 노드 x 2 + 1
(2) 우측 노드 = 나의 노드 x 2 + 1 --> 배열idx에서는 나의 노드 x 2 + 2
(3) 부모 노드 = 나의 노드 / 2
- 연결리스트를 통해 구성할 수도 있다.

- 이진 트리 순회 구현  ▼ 더보기

더보기

1. 배열을 통한 이진트리 순회

// 배열을 통한 이진트리 구현
class BinaryTree{
    char[] arr;
    
    BinaryTree(char[] data){
    	this.arr = data;
    }
    
    public void preOrer(int idx){      
        System.out.print(arr[idx] + " ");
        
        int left = 2 * idx + 1;
        int right = 2 * idx + 2;
        
        if (left < this.arr.length){
        	preOrder(left);
        }
        
        if (right < this.arr.length){
            preOrder(right);
        }
    }
    
    public void inOrder(int idx){
    	int left = 2 * idx + 1;
        int right = 2 * idx + 2;
        
        if (left < arr.length){
           inOrder(left);
        }
        
        System.out.print(arr[idx] + " ");
        
        if (right < arr.lenght){
           inOrder(right);
        }
    }
    
    public void postOrder(int idx){
    	int left = idx * 2 + 1;
        int right = idx * 2 + 2;
        
        if (left < arr.length) {
           postOrder(left);
        }
        
        if (right < arr.length) {
           postOrder(right);
        }
        
        System.out.print(arr[idx] + " ");
    }
    
    public void levelOrder(int idx){
    	for (int i = idx; i < arr.length; i++){
        	System.out.print(arr[i] + " ");
        }
        System.out.println("");
    }
}

public class Main{
   public static void main(String[] args){
      BinaryTree bt = new BinaryTree(new char[]{'A','B','C','D','E','F','G'});
      //     A
      //   B   C
      //  D E F G
   }
}

 

2. 연결리스트를 통한 이진트리 순회

// 노드의 연결(연결리스트)을 통한 이진트리 구현
class Node{
    char data;
    Node left;
    Node right;
    
    Node(char data){
    	this.data = data;
    }
}

class BinaryTree{
    Node root;
    
    BinaryTree(char[] datas){
    	Node nodes[] = new Node[datas.length];
    
        // 각각의 노드를 생성
    	for (int i = 0; i < datas.length; i++){
        	nodes[i] = new Node(datas[i]);
        }
        
        // 트리 형태로 노드를 연결
        root = nodes[0];
        for (int i = 0; i < nodes.length; i++){
        	int left = i * 2 + 1;
            int right = i * 2 + 2;
            
            nodes[i].left = nodes[left];
            nodes[i].right = nodes[right];
        }
    }
    
    public void preOrder(Node node){
    	if (node == null) {
        	return;
        }
        
        preOrder(node.left);
        System.out.print(node.data + " ");
        preOrder(node.right);
    }
    
    public void inOrder(Node node){
    	if (node == null) {
        	return;
        }
    
    	inOrder(node.left);
        System.out.print(node.data + " ");
        inOrder(node.right);
    }
    
    public void postOrder(Node node){
    	if (node == null){
        	return;
        }
        
        postOrder(node.left);
        postOrder(node.right);
        System.out.print(node.data + " ");
    }
    
    public void levelOrder(Node node){
    	Queue<Node> queue = new LinkedList();
        queue.add(node);
        
        while (!queue.isEmpty()){
			Node cur = queue.poll();
            System.out.print(cur.data + " ");
            
            if (cur.left != null) {
            	queue.offer(cur.left);
            }
            
            if (cur.right != null) {
            	queue.offer(cur.right);
            }
        }
    }
}

public class Main{
   public static void main(String[] args){
      BinaryTree bt = new BinaryTree(new char[]{'A','B','C','D','E','F','G'});
      //     A
      //   B   C
      //  D E F G
   }
}

 

3. 가중치가 있는 포화이진트리의 루트~리프까지의 가중치 총합을 동일하게 만드는 최소합

BinaryTree{
    int h; //높이
    int sum; //가중치의합
    int[] w; //가중치 배열
    
    BinaryTree(int h, int[] w){
    	this.h = h;
        this.w = new int[(int)(Math.pow(2, (h+1)))];
        
        for (int i = 2; i < this.w.length; i++){
        	this.w[i] = w[i-2];
        }
    }
    
    // 하위 좌우 중 더 큰 가중치에 맞춘 후 -> 상위 좌우 중 더 큰 가중치 합에 맞춘다.
    public int dfs(int h, int idx){
    	if (h == this.h){
        	res += this.w[idx]; //리프 가중치 추가
        	return this.w[idx];
        }
        
        int leftW = dfs(h + 1, idx * 2 + 1); // 좌측 서브 트리의 가중치 합
        int rightW = dfs(h + 1, idx * 2 + 2); // 우측 서브 트리의 가중치 합
        res += this.w[idx] + Math.abs(leftW - rightW); 
        return this.w[idx] + Math.max(leftW, rightW); // 서브 트리에서 더 큰 가중치 합 반환
    }
}

public class Main{

	public static void solution(int h, int[] w) {
    	BinaryTree bt = new BinaryTree(h, w);
		bt.dfs(0, 1);
        System.out.println(bt.res);
    }

	public static void main(String[] args){
    	int h = 2;
        int[] w = new int[]{1, 1, 2, 1, 3, 1};
        solution(h, w);
    }
}

 

|  이진 탐색 트리(BST, Binary Search Tree)

이진 탐색 트리 : 루트부터 시작하여 좌측에는 더 작은 키가, 우측에는 더 큰 키가 들어간 형태

* 모든 서브 트리 또한 이진 탐색 트리이며,
* 중복된 키는 허용하지 않는다.

1. 이진 탐색 트리의 탐색 시간 복잡도

탐색 균형 상태 O(logN)
불균형 상태 O(N)   

2. 이진 탐색 트리의 구현 - 탐색, 삽입과 삭제 (▼ 더보기)

더보기
탐색* root에서부터 탐색.
찾으려는 key값이 현재 노드보다 작은 경우 좌측으로 이동, 큰 경우 우측으로 이동.

찾으려는 key값과 현재 노드의 키값이 같은 경우 탐색 완료
O(logN)
삽입 root에서부터 key값을 비교.
삽입하는 key값이 현재 노드보다 작은 경우 좌측으로 이동, 큰 경우 우측으로 이동.
이동한 현재 위치에 노드가 없는 경우(null), 그 위치에 노드를 추가
O(logN)
삭제 case1 삭제 노드가 리프인 경우 : 부모노드에서 리프방향을 null 처리 O(logN)
case2 삭제 노드의 자식이 하나 있는 경우 : 부모노드에서 내 방향(ex.left)에 자식을 연결
case2 삭제 노드의 자식이 둘인 경우 : 
(1) 부모노드에서 내 방향에 내 좌측자식에서 가장 큰 노드를 연결
(2) 부모노드에서 내 방향에 내 우측자식에서 가장 작은 노드를 연결

 * 여기서의 탐색은 루트에서부터의 탐색으로, 이진 탐색 트리를 실제로 조회할 때는 4가지 순회 중 하나를 선택하는 것이 더 유리하다.(ex. preOrder/InOrder/....)

class Node{
    int key;
    Node left;
    Node right;
    
    Node(int key, Node left, Node right){
    	this.key = key;
        this.left = left;
        this.right = right;
    }
}

class BinarySearchTree{

    Node root;
	
    BinatySearchTree(int key){
    	this.root = new Node(key, null, null);
    }
    
    // 삽입
    public void add(int key){
    	if (root == null){
        	root = new Node(key, null, null);
        } else {
        	Node cur = this.root;
            Node pre = cur;
            
            while (cur != null) {
            	pre = cur;
            
            	if (key < cur.key){
                	cur = cur.left;
                    
                    if (cur == null){
                    	pre.left = new Node(key, null, null);
                        break;
                    }
                }
                
                if (key > cur.key){
                	cur = cur.right;
                    
                    if (cur == null) {
                    	pre.right = new Node(key, null, null);
                        break;
                    }
                }
            }
        }
    }
    
    // 삭제
    public void remove(int key){
    	// 탐색 (해당 key값을 가진 노드 탐색)
        Node cur = this.root;
        Node parent = null;
        
        while (cur != null){
        	if (cur.key == key){
            	break;
            }
            
            parent = cur;
            if (key < cur.key) {
            	cur = cur.left;
            } else {
            	cur = cur.right;
            }
        }
        
        if (cur == null){ //없을 때
        	System.out.println("BinarySearchTree doesn't have key " + key);
        }
        
        // 세가지 case에 따라 삭제 진행
        Node child = null;
        Node leftRightMax = null;
        Node ex;
        
        // case 1 : 삭제 노드가 leaf 일 때
        if (cur.left == null && cur.right == null){
        	if (cur == this.root){
            	this.root = null;
            } else {
            	if (parent.left == cur){
                	parent.left = null;
                } else {
                	parent.right = null;
                }
            }
        } 
        // case 2 : 삭제 노드에 자식이 하나 있을 때
        else if (cur.left != null && cur.right == null || cur.left == null && cur.right != null) {
        	// 자식 위치를 먼저 확인
            if (cur.left != null) {
            	child = cur.left;
            } else {
            	child = cur.right;
            }
            
            // 자식 노드를 현재 위치로 올린다.
            if (parent == null){ // = cur 가 root
            	this.root = child;
            } else {
            	if (parent.left == cur){
                    parent.left = child;
                } else {
                    parent.right = child;
                }
            }
        }
        // case 3 : 삭제 노드에 자식이 둘 있을 때
        else {
        	// 나의 좌측 노드 중 가장 큰 노드를 찾는다.
            ex = cur;
            leftRightMax = cur.left;
            
            while (now.right != null){
            	ex = leftRightMax;
            	leftRightMax = leftRightMax.right;
            }
            
            // 나의 위치에 찾은 노드를 넣어준다.
            ex.right = leftRightMax.left; // 먼저, 찾은 노드에 좌측노드가 있는 경우
            leftRightMax.left = cur.left;
            leftRightMax.right = cur.right;
            
            if (parent == null){
            	this.root = leftRightMax;
            } else {
            	if (parent.left == cur) {
                	parent.left = leftRightMax;
                } else {
                	parent.right = leftRightMax;
                }
            }
        }
    }
    
}

 

|   균형 이진 트리 (추가 정리 예정)

(1) 이진 탐색 트리의 편향 발생

(2) 균형 이진 탐색 트리 종류

  AVL 트리 red-black 트리
공통점    
차이점    

 

■ 그래프 개요

- 그래프의 구조와 특징
- 그래프의 탐색
- 그래프의 구현

|  그래프의 구조와 종류

출처:https://medium.com/@sarinyaswift/intro-to-the-graph-data-structure-a8277c6a2ad9

Basic 정점(Vertex, 노드) 데이터를 담고 있는 노드
간선(Edge) 연결선 = link, brank
관계 인접 정점(Adjacent vertex) 간선 하나로 바로 연결된 정점
간선 갯수

정점의 차수(Degree) 무방향 그래프에서 하나의 정점에 인접한 정점의 수 (ex. a노드의 차수는 2)
* 무방향 그래프의 모든 정점 차수의 합 = 전체 간선의 수 x 2
진입 차수(In-degree) 들어오는 간선의 수 
진출 차수(Out-degree) 나가는 간선의 수
경로 경로 길이(Path length) 어떤 경로를 구성하는 데 사용된 간선의 수
단순 경로(Simple path) 어떤 경로를 가기 위해 특정 정점을 반복해서 가지 않아도 되는 경우
사이클 사이클(Cycle) 단순 경로의 시작과 끝 정점이 동일한 경우

 

출처:https://medium.com/@sarinyaswift/intro-to-the-graph-data-structure-a8277c6a2ad9
출처:위키백과(완전그래프)

종류 내용 정점 A와 B의 간선 표현 / 비고
무방향 그래프(Undirected) 간선에 방향이 없는 그래프 (A, B) = (B, A)
방향 그래프(Directed) 간선에 방향이 있는 그래프 <A, B> != <B, A>
가중치 그래프(Weighted) 간선에 값(이동 비용)이 있는 그래프 -
완전 그래프(Complete) 모든 정점이 서로 연결된 그래프 정점이 N개일 때, 간선의 수 n(n-1)/2

 

|  그래프의 탐색

DFS(깊이 우선 탐색) = depth first search, Stack 또는 재귀를 사용하여 깊이 탐색
BFS(너비 우선 탐색) = breath first search, Queue를 사용하여 같은 레벨의 노드를 탐색

 

■ 그래프 탐색의 핵심

(1) 정점(Vertex)
(2) 간선(Edge, 곧 연결상태) : 어느 방향으로, 몇 개의 데이터가 연결되어 있는가
(3) 요구사항이 무엇인가? 
ex.
- DFS
  : 만약 root~leaf까지의 가중치를 구해야하거나, 
  : 바둑판과 같은 2차원 배열 or x축과 y축 좌표의 특정 위치에서 사방위로 움직일때
  : 네트워크 방식으로 서로 복잡하게 연결된 정점들을 새로운 자료구조로 재구성할 경우
- BFS
  : 어떤 정점 X에서 파생되는 여러개의 정점들이 있고, 각 하위 정점이 같은 규칙으로 파생될때
    특정 타켓까지 구하기 위해서 얼만큼의 시도가 필요한가 (== level를 묻는 문제)

 

■ 인접 행렬 VS 인접리스트

- 간선 정보를 저장하는 자료구조로 인접 행렬과 인접 리스트를 주로 같이 설명하는데,

- 실제로 코딩테스트 문제를 풀다보면 HashSet를 쓰거나 HashMap을 통해서 간선 정보를 저장하기도 한다.

- DFS나 BFS를 써서 문제를 풀 때는 상황에 따라 가장 적절한 자료구조를 사용하는 것이 필요.

  인접 행렬 인접 리스트
내용 2차원 배열로 인접 간선을 저장 연결리스트(LinkedList[])로 인접 간선을 저장
장단점 간선 정보 확인/업데이트가 빠름
메모리 사용량이 높다(간선)
정점의 추가/삭제가 빠르다.
메모리 사용량이 적다.(간선)
간선 정보 확인이 오래걸린다.
언제 유리한가 정점의 갯수가 적고, 간선 갯수가 많을 때 정점의 갯수가 많고, 간선 갯수는 적을 때
시간복잡도





특정 간선 탐색 O(1) O(degree(V)) *V: 차수 (특정 정점의 간선들)
정점 차수 계산 O(N) O(degree(V)) 
전체 정점 탐색 O(N^2) *N : 정점 갯수 O(E) *E : 간선 갯수
공간복잡도 N x N N + E

 

|  그래프 문제

https://school.programmers.co.kr/learn/courses/30/parts/14393

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

https://school.programmers.co.kr/learn/courses/30/parts/12421

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

[ 참조 및 출처 ]

부트 캠프 수업을 들은 후 여러 참고 자료를 통해 내용을 정리하였습니다.

https://medium.com/@sarinyaswift/intro-to-the-graph-data-structure-a8277c6a2ad9

https://velog.io/@alstn3265/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%8A%B8%EB%A6%AC

https://velog.io/@jeewoo1025/%ED%8A%B8%EB%A6%AC-Tree

|  파일 시스템 

운영체제가 저장매체에 파일을 쓰기 위한 자료구조 및 알고리즘

1) 파일은 어떻게 만들어진 걸까?

- 0과 1만으로 이루어진 비트 데이터를 그대로 저장하면 관리하기 어렵다.

- 따라서 블록으로 (ex. 4kb) 데이터를 나눠 저장한다. (블록에 고유 번호 부여)

- 블록의 고유 번호도 구분하기 어려우니, 추상적인 객체가 필요하다. (객체 : 파일)

- 그렇게 파일이 만들어졌다.

 

2) 저장 방법

- 가능하면 연속적인 공간에 저장하는 것이 좋지만, 외부 단편화*나 파일 사이즈 변경 문제로 어렵다.

- 그렇기에 자료구조로 연결리스트를 사용한다.

* 외부 단편화 : 총 메모리 공간이 남았으나, 남아있는 공간이 연속적이지 않아서 발생하는 현상

외부단편화,출처:https://code-lab1.tistory.com/54

3) 다양한 파일 시스템

- 인도우는 FAT이라는 자료구조에 블록 위치를 기록

- 리눅스는 inode 방식 - 일종의 인덱스 블록 기법 - 을 사용

* 가장 기본 파일 시스템 방식은 inode이다.

 

|  inode 파일 시스템 구조

수퍼 블록 파일 시스템 정보 (파일 시스템 전반의 메타 데이터)
아이노드 블록 파일 상세 정보 (파일 각각의 메타 데이터)
데이터 블록 실제 데이터

- 파일은 inode 고유값과 자료구조로 주요 정보를 관리한다.

- '파일이름:inode'로 이름과 inode번호를 매칭

- inode를 기반으로 파일에 접근한다. (inode 기반 메타데이터* 저장)

super block
↓ inode ↓ inode
fileA fileB
disk
block
disk
block
disk
block
disk
block
disk
block
disk
block

* inode 메타데이터 : 파일 권한, 소유자 정보, 파일 사이즈....등등등

출처:https://www.science.unitn.it/~fiorella/guidelinux/tlk/node96.html

더보기

ext2_inode 구조

  • i_blocks: 파일이 몇 개의 데이터 블록을 가지고 있는지 나타냄.
  • i_mode: 이 inode가 관리하는 파일의 속성 및 접근 제어 정보 유지
  • i_links_count: 이 inode를 가리키고 있는 파일 수(또는 링크 수)를 의미. (파일 이름 -- inode)
  • i_uid, i_gid: 파일을 생성한 소유자의 userID, groupID 의미.
  • i_atime, i_ctime, imtime: 파일의 접근 시간, 생성 시간, 수정 시간 의미.
  • i_block[15]: 파일에 속한 디스크 블록들의 위치를 관리.

[ 출처 ] https://velog.io/@jinh2352/inode-%EA%B5%AC%EC%A1%B0

 

|  가상 파일 시스템 (VFS)

가상적(virtual) 층으로 다양한 파일시스템에 일관된 방식으로 접근할 수 있게 하는 것

- 원래 EXT2, EXT4, XFS 파일 시스템들에 대해 각각에 맞는 고유 함수를 호출해야하나,

- VFS를 사용하면 open(), read(), write() 와 같은 동일 함수를 사용해 호출할 수 있다.

- 어느 파일 시스템으로 저장되었는지 상관 없이 일관된 인터페이스를 사용해서 파일에 접근 가능

 

|  가상 머신

하나의 하드웨어에 다수의 운영체제를 설치하고 개별 컴퓨터처럼 동작하게 한 것

- 하이퍼바이저, KVM(AWS), VMWare 등이 있다.

 

|  부팅 

- 컴퓨터를 켜서 동작시키는 절차

 

/ 확인하기

1. inode 파일 시스템 방식이 뭔가?

2. 가상 머신에 대해 설명하라

3. 외부 단편화에 대해 설명하라

 

[ 출처 ]

부트 캠프 강의를 듣고 책, 블로그 내용 등을 참조하여 정리하였습니다.

 

[ 참고 ] 

https://blog.naver.com/PostView.naver?blogId=n_cloudplatform&logNo=222481521174&parentCategoryNo=&categoryNo=11&viewDate=&isShowPopularPosts=false&from=postView 

https://hyoje420.tistory.com/53

https://code-lab1.tistory.com/54

|  쓰레드란?

- Light Weight Process

- 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위

출처:위키백과

 

|  프로세스와 쓰레드의 차이점

  프로세스 스레드
차이점 프로세스 간에 독립적이다 프로세스의 서브셋
각각의 프로세스가 독립적인 자원 가짐 프로세스 자원 공유
프로세스 자신만의 주소영역을 가짐 스레드는 주소 영역을 공유한다
IPC 기법으로 통신 가능 별도의 통신 기법이 필요 없다

 

|  스레드 장단점 정리

  내용 추가 설명
장점 성능 향상 멀티 스레드로 병렬 처리
응답성 향상 다수의 사용자의 요청이 오거나,
여러개의 처리와 동시에 요청이 이루어질 때 응답성이 향상
자원 공유 효율 프로세스 내부에서 스레드 간 자원 공유(IPC X)
단점 한 스레드가 프로세스 전체에 영향을 준다 멀티 프로세스의 경우, 하나의 프로세스가 문제가 있어도
다른 프로세스에 영향 주지X
동기화 이슈 동기화 코드를 적절히 추가해야한다.

 

|  스레드 동기화 이슈 이해

동기화(Synchronization) : 작업들 사이에 실행 시기를 맞추는 것

- 여러 개의 스레드가 하나의 자원에 동시에 접근, 수정할 때 동기화 이슈가 발생한다. 

 

|  동기화 이슈 해결 코드 작성 방법

임계 구역에 대한 접근을 막기 위해 Locking 매커니즘이 필요하다

  Mutual exclusion (상호 배제) = Mutex Semaphore (세마포어)
공통점 임계 구역에 대해 Locking(자바는 synchronized)을 걸어 동기화 이슈 해결
차이점 임계 구역에 하나의 스레드만 들어갈 수 있게 한다. 임계 구역에 여러 스레드가 들어갈 수 있게 한다
(Counter를 두어 동시 접근 가능 수 제어)

[ 자바 코드 예시 ]

class Shared { // 공유 자원
 
    // 임계 자원(공유 데이터)
    private static int sharedData = 0; 
 
    // 임계 영역(임계 자원을 사용하는 부분에 동기화 코드 추가)
    public synchronized void count(int n){
        sharedData = sharedDate + n;
    }
}

class Visitor extends Thread { // 공유 자원 방문자
	
    private int n;
    
    Visitor(int n) {
    	this.n = n;
    }
    
    @Override
    public void run(){
        for (int i = 0; i < 100; i++){
            MainThread.shared.count(n);
        }
    }
}

public class MainThread{

    public static Shared sharedData = new Shared();

    public static void main(String[] args) throws Exception{
        
        for (int i = 0; i < 100; i++){
            Visitor visitor = new Visitor(i);
            visitor.start(); // 누가 언제 sharedData를 갱신하는지 모른다.
        }
    }
}

 

|  데드락(교착 상태)과 스타베이션(기아 상태) 개념 이해

  데드락(교착상태) 스타베이션(기아 상태)
의미 무한 대기 상태 우선순위에 따른 자원 미할당 상태
언제? 여러 프로세스가 동일 자원 점유 요청할 때 여러 프로세스가 부족한 자원을 점유하려 경쟁할 때
어디서? 프로세스, 스레드 모두에서 발생 가능 프로세스에서만 발생
어떻게? 두 개 이상의 작업이
상대방의 작업이 끝나는 걸 기다림
특정 프로세스의 우선순위가 낮아
영원히 자원을 할당받지 못하게 됨
비고

내 폰의 어떤 앱이 갑자기 멈춰버렸다.
-> 운영체제가 보통 어느정도 시간이 지나면
    강제 종료시켜 버린다.
기아 상태는 우선순위를 변경하면 해결된다.
- 프로세스 우선순위 수시 변경
- 오래 기다린 프로세스 순위 변경
- 우선순위가 아닌 FIFO 기반 요청큐 사용

출처 - 위키 백과

 

|  확인하기

1. 프로세스 간에 어떻게 통신하는지, 쓰레드와 비교하여 설명하라

2. 프로세스와 쓰레드의 차이점

3. 언제 멀티 프로세스를 사용하고, 언제 쓰레드를 써야하나

4. 쓰레드 동기화란 무엇이며 왜 사용해야 하나

5. 뮤텍스와 세마포어의 차이점에 대해 설명하라

 

[ 출처 ]

부트 캠프 강의를 듣고 책, 블로그 내용 등을 참조하여 정리하였습니다.

 

[참고]

위키백과

https://jhnyang.tistory.com/3

https://donghoson.tistory.com/8?category=799810 

https://brunch.co.kr/@kd4/3

 

 요약

- 운영체제는 커널 + 기타 기능
- 운영체제는 시스템 콜을 제공 (쉘은 사용자와 OS간 인터페이스)
- 프로그램 언어별로 OS별 API를 제공
- 응용 프로그램에서 API로 시스템 콜 호출 시, 커널 모드로 변환, OS내부에서 명령 처리 후 리턴

 

|  운영체제란?

운영체제 (OS : Operating System) : 
사용자의 편의를 위한 환경을 제공하는 시스템 소프트웨어

출처:https://coding-factory.tistory.com/300

- 종류 : 윈도우, 유닉스 계열(리눅스), MacOS

리눅스커널구조,출처:https://flylib.com/books/en/3.475.1.15/1/

- 운영체제는 일반적으로는 커널여러가지가 추가된 상태를 의미하지만, 좁은 의미로 "커널(kernel)" 자체를 말합니다.

 

pf. General Purpose OS vs Embeded OS

General Purpose OS Embeded OS
컴퓨터에서 사용되는 OS

* 프로세스 실행시간에 민감하지 않고,
일반적인 목적으로 사용된다.
컴퓨터가 아닌, 다른 기종에서 특정한 작업을 실행하기 위해
만들어진 특수 목적의 OS

* 모바일, 비행기, TV, IOT 전자제품 등

 안드로이드는 Embeded OS

  아래 안드로이드 구조를 보면 안드로이드에는 리눅스 커널만 들어가고 

  일반적인 PC의 OS의 나머지 기능들은 추가되어 있지 않습니다. (정말 모바일을 위해 만들어진 녀석이에요)

  안드로이드는 리눅스 커널 위에 라이브러리와 프레임워크를 제공해서 안드로이드 앱이 작동하게 합니다.

출처:위키피디아

 

|  커널과 쉘

커널(코어, 핵심): 
: 컴퓨터 운영 체제의 핵심이 되는 컴퓨터 프로그램 [출처 : 위키백과]

출처:위키백과

쉘(Shell)
: 사용자가 운영체제 기능과 서비스를 조작할 수 있도록 시스템 콜을 하는 프로그램
: 사용자와 운영체제 사이에 일종의 다리 역할을 한다.

*시스템 콜의 user(shell) <-> provider(kernel)

 

 

- 쉘은 커널 위에서 동작하는 응용 프로그램 중 하나입니다.

- 사용자가 어떤 "명령"을 하면, 프로그램을 구동시킬 수 있는데, 그런 명령을 운영체제에 전달하는 역할을 하는 게 쉘이에요.

- 쉘 중에서도 유명한 쉘로 "리눅스 bash"가 있는데, 해당 내용은 이후 공부하면서 정리하려고 해요.

터미널(CLI)
그래픽 사용자 인터페이스(GUI)
사용자가 편리하게 사용할 수 있도록
입출력 등의 기능을 알기 쉬운 아이콘 따위의 그래픽으로 나타낸 것 
[출처 : 위키피디아]

 

|  시스템 콜

- 시스템 콜은 운영체제에게 어떤 기능을 사용하라고 요청하는 명령 또는 함수를 말하는데요.

- 시스템 콜이라고도 하고, 시스템 콜 인터페이스라고도 합니다.

- 쉽게 풀어서, 운영 체제를 깨워서, "이 작업을 해줘!" 라고 하는 명령을 말합니다.

- 시스템 콜은 커널이 제공하고 있고, 쉘은 시스템 콜을 사용해서 커널이 제공하는 서비스(기능)에 접근합니다.

시스템 콜 : 
운영 체제의 커널이 제공하는 서비스에 대해, 응용 프로그램의 요청에 따라 
커널에 접근하기 위한 인터페이스 [출처 : 위키백과]

 

|  API

- 프로그램 언어별로 운영체제에 맞는 API가 있습니다.

  : OS는 주로 C언어로 구성되어 있다고 합니다.

    그런데 생각해보면, 응용프로그램들은 자바, 자바스크립트, 기타 C언어가 아닌 언어들로 작성이 된 경우가 많아요.  

    그럼 어떻게 응용프로그램들은 다른 언어로 구성된 OS의 시스템콜을 호출할까요?

    그건 프로그램 언어별로 제공되는 API가 있기 때문입니다.

API(Application Programming Interface)
: 필요할 때 운영체제의 시스템 콜을 호출하는 형태로 만들어진 인터페이스이다.
: 쉽게 말해,함수,패키지,라이브러리이다.

ex. 
Java는 각 OS별로 서로다른 JDK를 통해 개발을 한다.
-> Java의 api 곧, 라이브러리 중 하나로 IO가 있는데, IO는 OS에 입출력을 위한 시스템콜을 호출한다

 

Q1.  왜 응용프로그램마다 운영체제별로 다른 프로그램을 제공할까요?

- 각각의 운영체제마다 시스템콜은 다릅니다. 그렇기에 각 운영체제에 맞는 API도 다를 수 밖에 없어요.

이클립스를_보면_운영체제별로_설치파일이_다르다

 

Q2. 자바 언어로 작성하면 왜 운영체제별로 다른 프로그램을 만들지 않아도 될까요?

- 자바에는 JRE라는 일종의 대안적 운영체제가 존재합니다.

- JRE는 가상머신(JVM)을 통해 데이터를 처리하고 저장합니다.

- 이러한 JRE는 운영체제 위에 덧씌워져 있는 것과 같이 동작하기 때문에,

  결국 자바로 작성한 프로그램은 하나의 소스코드로 여러 개의 운영체제에서 실행할 수 있는 것입니다.

 

|  커널 모드와 사용자 모드

출처:위키백과_cpu_protection_rigns

사용자 모드 응용 프로그램이 사용
커널 모드 OS가 사용

- 예를 들어, 카카X맵을 처음 설치해서 실행한다고 예를 들어볼게요.

  카카오X맵은 나의 실시간 위치를 확인하기 위해 [실시간 위치 정보 제공]에 대한 [권한]을 요청합니다.

  이러한 권한 요청을 하는 이유는 뭘까요? 그 이유가 바로 위의 사용자 모드와 연관이 되어 있습니다.

- 카카X맵(응용프로그램)을 키고 쓸 때 운영체제의 도움이 필요 없었다면 이 때는 사용자 모드인 상태입니다.

  그러나, 실시간 위치 정보는 운영체제(여기서는 모바일이라서 안드로이드이지만 무시할게요)에서 관리하는 정보입니다.

  따라서, 카카X맵이 실시간 위치 정보를 얻으려면 운영 체제에게 시스템 콜을 해야 하죠. 

- 이제, 시스템 콜이 이루어지고 운영체제가 작동하면 커널 모드가 시작됩니다.

  사용자의 눈으로 볼 때, 권한 요청이 수락되고 비로소 카카X맵에서 길찾기가 정상 작동됩니다. 

- 이렇듯, 응용 프로그램이 직접적으로 내장 기기의 파일이나 중요 기능을 건드리지 않도록 

  사용자 모드와 커널 모드를 분리하는 것은 매우 중요합니다.

 

** 더보기 : CPU protection rigns에 대한 자세한 설명 링크

 

|  운영체제의 역할

1. 시스템 자원 (하드웨어) 관리

2. 사용자와 컴퓨터 간 커뮤니케이션을 지원합니다.

3. 응용 프로그램을 제어합니다. (프로세스 관리, 파일 관리, 입출력관리, 네트워크 보안 등)

 

pf.
프로그램 = 소프트웨어,
소프트웨어 > 운영체제 + 응용 프로그램
응용 프로그램 > Application(PC), App(스마트폰용)

 

 

[ 추천된 책 ]

Operating System Concepts

 

[ 출처 및 참조 ]

부트캠프 수업을 들은 후 여러 자료를 참조하여 정리한 내용입니다.

https://www.techtarget.com/iotagenda/definition/embedded-operating-system

https://velog.io/@gndan4/OS-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B5%AC%EC%A1%B0-%EC%82%AC%EC%9A%A9%EC%9E%90-%EB%AA%A8%EB%93%9C%EC%99%80-%EC%BB%A4%EB%84%90-%EB%AA%A8%EB%93%9C

https://medium.com/@su_bak/os-%EC%BB%A4%EB%84%90-kernel-%EC%9D%B4%EB%9E%80-b6b8aae8d0b4

https://coding-factory.tistory.com/300

 

 

 

 

+ Recent posts