Untitled

  1. priority queue 는 우선순위 큐로, 큐인데 우선순위가 있는 아이템들을 저장하는 큐임.

  2. 주의할 점! 일반적인 큐의 경우 먼저 들어간 것이 먼저 나오는 선입선출이었다면, 우선 순위 큐의 경우 우선 순위가 높은 아이템이 먼저 나오게 됨.

  3. priority queue 는 가장 흔한 자료구조임. 스택이나 선입선출 큐도 priority queue 로 구현이 가능함. 왜냐면, 우선순위를 어떻게 주느냐에 따라서 꺼내는 것이 맨 처음에 들어온 것일 수도 잇고 ,맨 마지막에 들어온 것으로 만들 수도 있으니까. 즉 priority queue 가 좀 더 일반적인 개념이라고 생각하면 됨.

  4. 주로 시뮬레이션 돌릴 때 많이 쓰임. 이벤트가 발생한 시간을 우선순위로 만들면, 시간 순에 따라 이벤트를 동작하게끔 만들 수 잇어서. 또 운영체제가 일이 많을 때 그 일들을 스케줄하는 것은 이 우선순위 큐에 따라서 스케줄 하게 됨. 사실 당연한 말.. 네트워크에 트래픽이 많이 몰려있을 때 중요한, 우선순위가 높은 것부터 내보내야 함.

  5. delete(q)는 가장 우선 순위가 높은 아이템을 지우고, 중요한 것은 그 아이템의 값을 리턴하게 됨!! (리턴값이 있다는 것이 중요함)

  6. 우선순위 큐는 크게 두 가지 종류로 나눌 수 있음. 하나는 minimum priority queue 다른 하나는 maximum priority queue. minimum 은 말 그대로 우선순위가 가장 작은 것부터 뽑아내겠다는 거고 maximum 은 우선순위가 가장 큰 것부터 뽑아낸다는 것.

  7. 우선순위 큐는 크게 세 가지로 구현이 가능함. 하나는 배열로 구현, 다른 하나는 연결 리스트, 마지막으로는 힙으로 구현 가능한데, 여기서 힙이 가장 효율적임. 왜냐면 Insertion operation 과 deletion Operation 모두 시간 복잡도가 O(logn) 이기 때문임.

  8. 그럼 heap 이라는 자료구조는 무엇이냐? 우선 heap = complete binary tree 임. 즉 터미널 노드 전까지는 모든 자식 노드가 두 개여야 하고, 터미널 노드는 왼쪽부터 꽉 채워져 있어야 함.

  9. 그리고 나서 heap 은 Max heap 과 min heap 으로 나뉨. max 힙은 부모 노드의 키값이 자식 노드의 키값보다 크거나 같은 경우의 complete binary tree 를 의미하고, min 힙은 부모 노드의 키값이 자식 노드의 키값보다 작거나 같은 complete binary tree 를 의미함. 이때 binary search tree 와 힙은 완전 다름에 주의해야 함. 이진 탐색 트리는 좌우를 기준으로 크기가 나뉘었음. heap 은 부모 자식 관계!

  10. 힙은 complete binary tree 이기 때문에 힙의 높이는 O(log2n)임. 터미널 노드를 제외한 모든 노드에서 노드의 개수는, 힙의 레벨을 i 라고 할 때, 2^i-1 이라는 것도 알아두자. (레벨은 높이랑은 또 다른 얘기인 듯..? 그래서 뿌리 노드는 레벨 1 부터 시작하고 높이는 0 인 것 같아..? )

  11. 이때, heap 의 경우 항상! complete 트리라고 했다. 그말인 즉슨, heap 을 구현할 때 배열을 사용하면 아주 편리하다. complete 이거나 full 이라는 보장만 있다면 배열로 구현하는 것이 매우 편리하다.

  12. 먼저 각각의 노드에 배열의 인덱스를 붙인다. 뿌리노드가 1이고 왼쪽 자식에서 오른쪽 자식 순으로 레벨을 하나씩 키워가면 인덱싱을 해 준다.

  13. 그렇게 되면, 부모 노드 인덱스 = 자식 노드 인덱스 / 2 가 되고, 왼쪽 자식 노드 인덱스는 부모 노드 인덱스 * 2, 오른쪽 자식 노드 인덱스는 부모 노드 인덱스 * 2 + 1 이 된다.

  14. Max heap 에서의 Insertion 절차.

    1. 새로운 노드는 힙에 있는 맨 마지막 노드의 바로 오른쪽에 삽입이 된다.
    2. 그럼 그 새로운 노드는 자기가 있는 곳에서부터 필요하다면 뿌리 노드까지 계속해서 비교와 자리바꿈을 반복한다.
    3. 그렇게 되면 어차피 최대 시간 복잡도 해 봐야, 트리의 높이이기 때문에 시간복잡도는 O(log2n) 이 나온다.
    4. 만약 부모의 키값이 자식의 키값보다 크거나 같다면 해당 작업을 중단한다.
  15. 이걸 쉐도 코드로 구현을 해 보면, 우선 인자로 넘겨줘야 하는 것은 heap 이랑 새로 추가할 노드의 키값.

    1. heap 에 노드 하나를 추가하므로, 힙 사이즈 1 늘려주고
    2. 1 늘어난 힙 사이즈의 인덱스를 i 에 넣어준다 .
    3. A[i] 즉 새로운 배열 속 노드에 원하던 키값을 대입해서 자리를 만들어 준다.
    4. 거슬러 올라가는 작업을 하면 되는데, i 가 1 일때는 뿌리 노드이기 때문에 부모노드가 없어서 하면 안되고, 부모 노드가 크거나 같으면 조건을 만족하는 것이니 부모노드가 자식 노드보다 더 작을 때까지만 반복한다. 그리고 A[i] 와 A[parent(i)] 의 위치를 서로 바꿔준 다음에 (근데 사실 여기서, 부모 노드를 자식 노드에 다운받는 것만 하면, 어차피 위로 올라가게 되어 있으니 굳이 자식 노드를 부모 노드에 올려줄 필요가 없음) i 에 2를 나눈 몫을 저장하면 됨. (부모 인덱스) 그리고 그렇게 와일문을 다 하고 빠져 나오면, 해당하는 인덱스를 가진 배열 요소에 넣고 싶던 키값을 넣어주면 끝!
  16. Deletion in Max heap

    1. 가장 큰 키 값을 가진 노드를 삭제하는 것.
    2. 절차
      1. 뿌리 노드를 먼저 제거한다.
      2. 맨 마지막 노드를 뿌리 노드 자리에 옮겨 놓고
      3. 부모 노드 ≥ 자식 노드 조건을 만족할 때까지 부모 자식 관계를 바꿔준다. (필요하다면 terminal 노드까지 반복 )
      4. 삭제도 마찬가지로 시간복잡도 O(log2(n))
    3. 쉐도 코드
    4. 삭제하고 싶은 키값은 A[1] 이니까, 넣어주고 (여기서 주의할 점은, 힙을 만드는 배열에서 뿌리노드의 인덱스값은 0이 아니라 1이라는 것! ) A[1] 에는 heap의 사이즈를 인덱스로 가진는 A[heapsize] 를 넣어준다. 즉 자리를 옮겨주고, heap size 는 1을 뺀 값을 저장하면 된다. 이때 주의할 점은 인덱스가 1 부터 시작하기 때문에 맨 마지막 노드의 인덱스가 heapsize - 1 이 아니라 heapsize 그 자체라는 것!
    5. i 가 1 일 때는 뿌리 노드인데, 이 경우는 어떻게..?

    Untitled

    쨋든 i 가 2부터 라고 하면, i 즉 인덱스가 heap size 보다 작거나 같을 때까지만 탐색을 하는데, 만약 i 가 heap size 보다 그냥 작고, (맨 마지막 노드가 아니고) 오른쪽 자식이 왼쪽 자식보다 더 크다면 오른쪽 자식의 인덱스를 largest 에 저장하고, 왼쪽 자식의 값이 더 크다면 왼쪽 자식의 인덱스를 largest 에 저장한다. 만약 i 가 heapsize 그 자체인 경우에도, largest 에 i 값 그 자체를 저장하게 된다. (비교 대상이 없으므로) ? 부모가 더 크거나 같으면 아닌지..?ㅇㅇ 맞음) 만약 부모가 자식 중 더 큰 값 보다 더 크다면, 반복문을 빠져 나오고, 그렇지 않다면, largest 를 인덱스로 갖는 놈과 부모의 자리를 바꾼 다음에, i 의 값을 largest 의 자식의 인덱스로 바꿔준다. 즉 곱하기 2 를(맞는지..? ㅇㅇ 맞음) 해준다.

    1. 참고로 max heap 만들기 전에 항상 Init 을 해주자. heapsize 를 0으로 정해주면 끝이다.

    2. 이렇게 햇을 때, max heap 에 삽입하는데 걸리는 시간 복잡도는 O(nlog2n)이다. 왜냐면, ? 이유를 모르겠.. 근데 이를 더 줄여서 log(n)으로 만드는 방법이 있다.

    3. Building Max heap (교과서엔 없지만 중요함! 이걸 이용하면, O(nlogn)을 O(n)으로 줄일 수 있어서

      1. 배열이 주어지면, 먼저 그냥 모든 엘리멘트들을 힙에 넣어준다.
      2. 그렇게 되면 터미널 노드에 있는 모든 노드들(n/2 내림한 거 + 1 인덱스부터 n 인덱스까지)이 heap 속성을 만족하게 된다. 자식 노드가 아예 없기 때문에. 따라서 터미널 노드 하나 위의 노드들부터 조건을 만족시키면 된다.
      3. 따라서 맨 아래 노드서부터 차례대로 노드들이 속성을 만족하게끔 이동시키면 된다. 아래에서부터 차곡차곡 조건을 만족시키면서 올라가기 때문에 아래에서부터 위로 올라가는 실행 순서가 i의 자식 노드들이 모두 힙 조건을 만족한다는 것을 보장한다. 따라서 아래에서 위로 올라가는 순서가 매우 중요함.
      4. 시간 복잡도
        1. 하나의 서브트리에서 엘리멘트를 움직이는 동작은 총 O(h)번 일어난다. h는 서브트리의 높이! 따라서 O(log2m) (m은 서브트리에 있는 노드의 총 개수) 따라서 우리는 각 노드의 서브트리들에서 생긴 이동을 모두 합해주면 된다 .
        2. 그럼 시그마 h 가 0부터 log2n의 내림 까지 (트리의 높이) 더해주는데, 해당 높이에 존재하는 노드의 개수 곱하기 그 노드가 있는 곳의 트리의 높이의 서브트리에서 일어나는 이동의 수 를 더해주면 된다. 그렇게 되면 결과가 O(n)이 된다.
      5. 힙의 적용 예시로, heap sort 가 있다.
        1. 힙을 이용한 분류 알고리즘이다.
        2. 먼저 맥스 힙에 n개의 요소들을 정렬해서 넣어준다. (O(nlog2n)) ?왜..?
        3. 다음으로 맥스 힙에 있는 루트 노드를 제거한 다음에 그 값을 배열에 집어 넣는 것을 n 번 반복한다. O(nlog2n) ?왜..?
        4. 따라서 1번과 2번을 합친 시간 복잡도는 O(nlog2n 이다.) 근데 이때 만약 1번에서 맥스 힙을 만들 때, build max heap 을 사용하면, 시간 복잡도는 O(n) + O(nlog2n) 이 된다. 여전히 시간 복잡도는 O(nlog2n)이지만 훨씬 빠르다.
        5. 힙 분류 알고리즘이 유용할 때: 전체의 분류된 데이터가 필요한 게 아니라, 몇 개의 분류된 데이터가 필요할 때 유용하다. (가장 큰 데이터 몇 개)
        6. 코드로 구현을 하면,
          1. 먼저 init 해주고
          2. i=0부터 n까지, (? 왜 1부터가 아닌지.>?) 맥스힙에 요소를 추가해서 맥스힙을 만들어주고,
          3. i 가 n-1부터 0까지 delete 함수 호출해서 그 결과를 배열에 저장해 주면 된다.
      6. 힙의 적용 예시 중 다른 것으로 이산 이벤트 시뮬레이션이 있다. 이때 이산 이벤트 시뮬레이션의 경우, 일의 발생에 의해서 수행이 된다. 즉 이벤트의 발생을 기준으로 시뮬레이션을 돌리게 된다. 우리가 그 전에 했던 은행 시뮬레이션의 경우에는 이산 시간 시뮬레이션이다. (시간의 흐름에 따라 이벤트를 발생시킴으로써 수행된다.) 이산 시간 시뮬레이션과 달리 이산 이벤트 시뮬레이션은 시간을 일부러 1씩 증가시키거나 하지 않는다.
      7. 모든 시간의 진행은 이벤트 발생 시에만 일어난다. 이벤트들은 우선순위 큐에 저장이 되어 있고, 이벤트 시간에 기반해서 진행이 된다. 예를 들어 아이스크림 가게 시뮬레이션이 있다.
        1. 손님이 아이스크림 가게를 방문했다. 만약 앉을 수 있는 자리가 없다면, 그들은 그냥 떠나버릴 것이다. 우리의 목표는 얼마나 많은 의자가 있어야지 이익을 최대화할 수 있을까를 계산하는 것이다.
        2. element 구조체의 필드는 다음의 네 가지이다.
          1. 손님 그룹의 id (팀마다 다름)(팀을 식별하기 위함)
          2. 이벤트의 타입
            1. 도착 : 손님이 아이스크림 가게에 도착을 했다!
              1. 만약 이 이벤트ㅔ서 손님의 수가 남아있는 의자의 수보다 작다면,(?같은 경우도 손님 받아야 하는 것 x..?) 손님을 받는다. 그리고 남아있는 의자의 수에서 손님의 수를 빼준다. 만약 남아있는 의자의 수가 손님의 수와 같거나 작다면, 손님은 주문하지 않고 바로 떠남 이벤트를 실행한다.
            2. 주문 : 손님이 아이스크림을 주문했다!
              1. 손님의 수만ㅋ늠 주문을 받는다. 그리고 잠시 있다가 떠남 이벤트를 실행한다.
            3. 떠남 : 손님이 아이스크림 가게를 떠났다.
              1. 남아있는 의자의 수를 떠난 그룹의 손님 수만큼 증가시킨다.
          3. 이벤트가 언제 일어났는지를 저장하는 “키” (얘를 기준으로 우선순위에서 꺼냄. 즉 얘가 키 노드)
          4. 그 그룹에 있는 손님의 수
        3. 이때 이벤트 타입에 있는 “랜덤” 변수는 다음과 같다.
          1. 도착
            1. 게스트들의 도착 시간(키값)
            2. 하나의 그룹에 속한 손님의 수
          2. 주문
            1. 도착한 뒤에 주문하기까지 걸리는 시간(대기시간 등 포함)
            2. 손님들이 주문할 아이스크림의 개수
          3. 떠남
            1. 떠나기 전에 사람들이 샵에 머무는 시간(아이스크림 먹고 수다 떠는..)
        4. 주의할 것~: 시간이 짧은 것이 먼저 진행되어야 하기 때문에, (먼저 온 손님이 먼저 가야하니) min 힙이 사용된다라는 것!
        5. 예시
          1. id 0 번인 5명으로 구성된 0번 팀이 “도착” 이벤트를 가지고, 도착 시간은 1을 가지고 힙에 들어옴. 그 다음으로 2명으로 구성된 id 1번 팀이 “도착” 이벤트를 가지고 도착 시간은 3을 가지고 힙에 들어옴. 처음에 이용 가능한 의자는 총 10개임.
          2. 키값에 의해서, 키값이 더 작은 것이 먼저 힙을 빠져 나오게 됨. 즉 0번 팀이 먼저 와서 키값이 1이니 얘를 먼저 뺌. 그럼 남아있는 자리수와 구성원의 수를 비교함. 0번 팀은 총 5명인데 남은 의자는 10개 즉 수용할 수 있음. 그럼 이벤트 타입을 도착에서 “주문”으로 바꿔주고 order 하는데 걸리는 시간 (인당 1 총 5명이니 5) 을 기존의 키값에 더해줌. 그리고 나서 남은 의자의 개수가 줄었으니, 10개에서 손님의 수만큼인 5를 빼줘야 함.
          3. 그렇게 키값이 6이 되고 타입이 “주문” 이 된 채로 다시 힙에 들어감. 그럼 이번에는 1번 팀의 키값이 3이니 더 작음 그래서 1번 팀을 빼내옴. 마찬가지로 남아 있는 의자수랑 손님 수 비교, 손님 수가 더 작으니 수용해서 타입을 “주문” 으로 바꾸고, 키값을 (한 사람당 걸리는 주문 시간이 1이니까, 두 명이니까 +2)(3+2 =) 5로 바꾼 다음에 남은 의자 수가 5개에서 2명 자리 줄어든 3개 가 됨. 그리고 얘도 다시 힙에 넣음
          4. 그렇게 되면 키값이 또 1번 팀이 더 작음. 그러니까 또 빼옴. 그렇게 되면, 이번에는 남은 의자수랑 비교할 필요 없이 바로 타입을 “떠남” 으로 바꾸고, 떠날 때까지 걸리는 시간(랜덤)을 기존의 키값에 더해줌 (5 + 랜덤수 (ex.7)) = 12 가 됨. 그리고 나서 다시 힙에 넣음!! 에 주의! 떠남으로 바뀌는 순간 가는 게 아니라, 떠남으로 바뀌고 대기하다가 떠남.
          5. 또 키값 비교. 이번에는 0번팀이 더 작음. 그래서 0번 팀을 추출함. 이번에는 남은 의자 수 비교하지 말고 바로 타입 “떠남” 으로 만들어 버리고, key 값에 랜덤한 수를 더함. (6 + 8) = 14. 이렇게 타입이랑 키값만 바꾸고 다시 바로 힙으로 들어가야 함. 의자수 늘리면 안됨. 떠남 이벤트가 끝난 다음에 의자수 늘려야 함!
          6. 그럼 이제 다시 1번 팀의 키값이 더 작음. 따라서 1번 팀 추출. 이제는 떠나야 할 시간이 왓음. 따라서 남은 의자수에 1번 팀의 팀원 수를 더해줌.
          7. 힙에 남은 게 0번 팀 하나니까 추출. 마찬가지로 이제 떠나야 할 시간. 남은 의자 수에 5명을 더해줌.
        6. 코드
          1. element 구조체와, HeapType 구조체를 선언해 줌.
            1. 힙타입 구조체에는, element 요소를 저장하는 배열 heap[] 이 선언되어 있고, int 형 변수 heap_size 가 정의되어 있음. 즉 HeapType 을 들고 다니면, 힙 배열과 사이즈를 알 수 있음.
            2. 변수들
    4. Huffman code