인덱스 바이너리

마지막 업데이트: 2022년 7월 24일 | 0개 댓글
  • 네이버 블로그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 트위터 공유하기
  • 카카오스토리 공유하기
정렬된 리스트

인덱스 바이너리

이러한 경우는 단순히 생각할 것이다. 1 x y가 들어오면 인덱스 바이너리 간단히 상수시간 내로 연산이 가능하겠지만, 0 x y에서 많은 시간을 소요하게 될 것이다. 다음과 같은 예제를 보면 단순히 생각하던 이들이 깨달음을 얻으며 “시간이 매우 오래 걸리는구나!”라고 소리친 후, 어떻게 해야 할지 깊은 고민에 빠질 것이다.

왜 시간이 오래 걸리는지는 간단히 생각해볼 수 있다. 우선 명령 수는 10만개이다. 간단히 Sequential Search(쭉 다 찾아보는 방법)을 이용한다면 한 개의 명령 계산만 해도 10만 번이 걸린다. 근데 명령 수는 10만개이다. 따라서 10만*10만>1억이므로 1초안에 계산이 될 리가 전혀 없다!

우리는 여기서 개선점이 필요함을 느꼈다. 어떻게 해야 할까? 바로 인덱스트리를 사용하는 것이다.(binary indexed tree는 합밖에 구하지 못한다.)

2. Indexed Tree

2-1. 인덱스트리 개요
인덱스 트리가 완전히 구성된 1번 슬라이드를 참고하자. 제일 아래에 있는 2가 4와 비교하여 부모로 올라갔다. 이 연산엔 무슨 의미가 있을까? 부모노드는 모든 자식노드들의 값보다 같거나 작을 것이다. 따라서 일정 구간의 최솟값은 그 구간의 부모노드에 있을 것이다.

이해가 잘 안 갈수도 있으니 예제로 설명하겠다. “0 0 1”이라는 명령어를 받았다고 해보자. a[0]~a[1]의 최솟값을 찾으라는 연산이다. 다 인덱스 바이너리 찾을 수도 있지만, 그들의 공통 부모노드를 하나 찾으면 그걸로 끝이다. 따라서 2가 인덱스 바이너리 나올 것이다. 또 다른 명령어 “0 0 3”을 넣었다고 해보자. a[0]~a[3]이 포함된 공통 인덱스 바이너리 부모노드를 찾으면 역시 또 2가 나올 것이다.

2-2. 삽입 (insert(x,y) : x는 x번째에 y가 들어가야 한다는 것을 의미)

삽입은 제일 밑바닥(즉, leap node)에서부터 시작한다. x번째 leap node의 인덱스를 갖고 시작한다. 노드에 값을 갱신할 때에는 아래의 세 규칙을 따른다. I) 노드에 아무런 값도 없을 경우 : 값을 넣고 부모노드로 가고, 부모노드에 대해 재실시. II) 노드에 값이 있을 때 값이 작은 경우 : 값을 넣고 부모노드로 가고, 부모노드 재실시. III) II에 위배되는 노드이거나 값을 넣은 후 부모가 없을 경우 : 루프를 종료.

2-3. 대푯값 찾기 (find(sp,ep) : sp~ep를 대표하는 값을 찾는다.)
2-4. 값 고치기 (insert(x,y), 삽입과 같이 그대로 써준다.)
  • Q1. 최솟값이 아닌 구간 합을 구하려면 어떻게 구현할까? 최댓값은 또 어떻게 구현할까? 직접 구현해보자.
  • Q2. 배열로 구현하려고 한다. 최대 N개의 데이터가 들어온다고 한다면, 배열을 가장 작게 잡았을 때 배열의 크기를 얼마로 잡아야겠는가? 계산해보도록 하자.

    hint : 트리의 depth가 d일 때, 그 깊이에는 최대 2^d의 데이터가 들어갈 수 있다. (제일 위의 depth를 0으로 가정했을 때이다.) 부등식을 응용해보자. (로그나 지수 등을 배우지 않은 학생에게는 어려울 수 있다. 그러나 알고 가도록 하자.)

    Q2 답 : 2^

문서 II

Peter M. Fenwick 에 따르면 이 구조는 처음 자료의 압축에 사용되어 졌다. 지금은 빈도수를 저장하고 저장된 빈도수 테이블에서 구간의 합을 구하는데 종종 사용되어 진다.

인덱스 바이너리

특정 구간, 구간합을 구할 때 빠르게 구할 수 인덱스 바이너리 있는 트리이다.

펜윅트리(Fen Wick Tree) 또는 BIT (Binary Indexed Tree) 바이너리 인덱스 트리 라 부르기도 한다.

특정 구간의 합을 구할 수 있는데 시작은 1번째 값부터 n번째 값까지 구할 수 있다.

그러면 a번째 값부터 b번째 값까지 구하려면 ? [1,b] 번째 값까지 합 - [1,a-1] 번째 값까지 합을 빼게 되면 구할 수 있다.

먼저 코드를 보면, BIT 클래스를 만들었다.

1) class 변수, 그리고 생성자를 설명

2) update 메소드 설명

1) class 변수 , 생성자

1) class 변수 , 생성자

S는 사이즈를 뜻한다. 즉 트리의 크기, 그리고 배열의 크기를 저장한다.

T는 BIT 트리를 뜻한다. 0번째 인덱스를 사용하지 않으므로 n+1 로 [0. n] 크기의 배열을 만들어야 한다.

A는 각 인덱스 마다 값을 저장한다. 이 또한 0번째 인덱스를 사용하지 않으므로 n+1 로 [0. n] 크기의 배열을 만들어야 한다.

이때 BIT는 0번째 인덱스는 사용하지 않는다. 1번째 인덱스부터 사용한다. 그 이유는 각 구간합을 저장, 계산할 때

(-) 값으로 보수를 취하고 각 비트를 AND(&) 연산으로 곱하여 다음 인덱스 위치로 이동하여 저장된 구간합을 구하거나, 업데이트 할 수 있기 때문이다.

예를 들어, 총 9개 수열이 있고, 순서대로 1,2,3. ,9 까지 있다고 했을 때 A (Array) 와 T (BIT) 는 아래와 같이 표현할 수 있다.

menu

MySQL은 비교나 검색을 수행할 때 데이터의 타입이 서로 다를 경우, 내부적으로 타입이 같아지도록 자동 변환하여 처리합니다.

하지만 사용자가 명시적으로 타입을 변환할 수 있도록 다양한 연산자와 함수도 같이 제공하고 있습니다.

BINARY

BINARY 연산자는 뒤에 오는 문자열을 바이너리 문자열로 변환합니다.

BINARY 연산자를 이용하면 문자가 아닌 바이트를 기준으로 하여 비교나 검색 작업을 수행할 수 있습니다.

다음 예제는 BINARY 연산자를 이용하여 문자 'a'와 'A'를 비교하는 예제입니다.

SELECT BINARY ' a ' = 'A',

실행 결과

위의 예제처럼 BINARY 연산자를 이용하면, 비교하려는 문자의 바이트 값을 비교합니다.

따라서 문자 'a'와 'A'가 서로 다른 값으로 인식됩니다.

CAST() 함수는 인수로 전달받은 값을 명시된 타입으로 변환하여 반환합니다.

이때 변환하고자 하는 타입을 AS 절을 이용하여 직접 명시할 수 있습니다.

CAST ( expr AS type)

AS 절에서 사용할 수 있는 타입은 다음과 같습니다.

7. JSON (MySQL 5.7.8부터 제공됨)

9. SIGNED [INTEGER]

10. UNSIGNED [INTEGER]

다음 예제는 CAST() 함수를 이용하여 문자열 데이터를 UNSIGNED 타입으로 변환하는 예제입니다.

4 / CAST( '2' AS 인덱스 바이너리 UNSIGNED);

실행 결과

위의 예제에서 문자열 타입의 데이터를 묵시적으로 타입 변환하여 수행한 나눗셈 연산의 결과는 정수 타입으로 반환됩니다.

하지만 정수 타입끼리 나눗셈 연산을 한 결과는 실수 타입으로 반환됩니다.

따라서 문자열 타입의 데이터를 CAST() 함수를 사용하여, 명시적으로 타입 변환해야만 올바른 나눗셈 연산 결과를 얻을 수 있습니다.

CONVERT()

CONVERT() 함수도 CAST() 함수처럼 인수로 전달받은 값을 명시된 타입으로 변환하여 반환합니다.

CONVERT() 함수는 두 번째 인수로 변환하고자 하는 타입을 직접 전달할 수 있습니다.

1. CONVERT ( 인덱스 바이너리 expr , type )
2. CONVERT ( expr USING transcoding_name )

이진탐색 = 이분탐색 (Binary Search) - Java로 구현

블로그 이미지

ex 1) 10억 명이 정렬된 배열에서 이진 탐색을 이용해 특정 이름을 찾는다면 단 30번의 비교만으로 검색이 완료된다.

반면에 순차 탐색의 경우 평균 5억 번의 비교가 있어야 된다.

ex 2) 영어 사전에서 단어를 찾는 과정 역시 이진 탐색과 동일하다.

영어 사전을 펼쳐서 찾고자 하는 단어가 현재 페이지에 있는 단어보다 앞에 있는지, 뒤에 있는지를 결정한 다음,

단어가 있는 부분 만을 다시 검색한다.

이진 탐색의 구현

1. 탐색의 대상이 되는 자료들이 array[low] 에서부터 array[high]에 들어있다고 가정하자. (정렬되어 있어야 함)

즉 어떤 시점에서 탐색되어야 할 범위는 인덱스 바이너리 low에서 high 까지가 된다.

맨 처음 low에는 0번 인덱스의 값, high에는 n-1번 인덱스의 값이 들어갈 것이다.

2. low와 high값에 의거해 중간값 mid 값은 (low + high) / 인덱스 바이너리 2이다.

즉, array[low] ~ array[high] 까지의 탐색은

array[low] ~ array[middle-1] + array[middle+1] + array[high]까지의 인덱스 바이너리 탐색이 된다.

3. array[mid] 값과 구하고자 하는 key값을 비교한다.

3-1. key > mid : 구하고자 하는 값이 중간값보다 높다면 low를 mid +1로 만들어 줌 (왼쪽 반을 버림)

3-3. key == mid : 구하고자 하는 값을 찾음 중간값 리턴

4. low > high가 될 때까지 1~3번을 반복하면서 구하고자 하는 값을 찾는다.

(이때까지 못 찾으면 탐색 실패 -1, false, error 등 return)

순환 호출을 이용한 이진 탐색 구현

반복을 이용한 이진 탐색 구현

반복 구조를 사용하는 것이 재귀 호출로 구현하는 것보다 효율적이다.

전체 코드

이진 탐색의 성능

이진 탐색은 탐색을 반복할 때마다 탐색 범위를 반으로 줄인다.

이러한 탐색 범위가 더 이상 줄일 수 없는 1이 될 때의 탐색 횟수를 k라고 한다면, 아래 표와 같다.

비교 범위
q0 n
1 n/2
2 n/4
. .
k n/2^k

표의 마지막 행에서 n/2^k = 1 이므로, k = log 2 n 임을 알 수 있다.

따라서 이진 탐색의 시간 복잡도는 O(log n) 이 된다.

* log 2n의 경우 밑이 10인 log n으로 계산한다.

컴퓨터가 이진수 시스템을 사용하기 때문에, 로그는 밑을 대부분 2로 사용한다. (즉, log 2 n, 때때로는 log n이라고 쓰임.)

그러나, 로그의 밑이 변할 때, logan와 logbn는 오로지 상수 승수에 따라서만 달라지기에 빅-오 표기법에서는 버림 한다.

인덱스 바이너리

이진탐색정렬된 배열 안에서 타깃 값의 인덱스를 찾는 탐색 알고리즘이다.

이진탐색은 배열의 중간 인덱스의 값을 타깃 값과 비교해 타깃 값의 인덱스를 찾는다.

배열

문제 상황을 가정해 보자. 우리는 숫자 X가 위의 배열에 존재하는지 살펴볼 것이다.

⓵ X = 21 , 3번 인덱스에 존재

⓷ X = 81 , 7번 인덱스에 존재

위의 상황을 해결하는 가장 간단한 방법은 배열을 전부 스캔해 X 값을 찾는 것이다.

0번 인덱스로부터 시작해, 타깃 값을 찾으면 인덱스 반환 및 종료를 하면 된다. 다만, 존재하지 않는 것을 알기 위해서는 8번 인덱스까지 스캔해야만 한다. 이 탐색 법은 최악의 경우, 배열의 사이즈만큼의 시간 복잡도가 필요하다.

다행히도, 이 상황을 선형 시간 복잡도로 해결할 수 있는 방법이 있다.

바로 0번 인덱스부터 스캔하는 것이 아닌, 중간 인덱스부터 스캔하는 것이다. 다만, 정렬된 리스트에서만 가능한 방법이다.

[Case 1] X 가 중간 인덱스 의 값이랑 같은지 확인한다. 만약, 같다면 찾았다.

[Case 2] X 가 중간 인덱스 의 값보다 작다면, ( 중간 인덱스 - 마지막 인덱스 ) 사이의 요소들은 버려도 된다. 👉🏻 탐색 범위가 반이 줄었다.

[Case 3] X 가 중간 인덱스 의 값보다 크다면, ( 0번 인덱스 - 중간 인덱스 ) 사이의 요소들은 버려도 된다. 👉🏻 탐색 범위가 반이나 줄었다.

정렬된 리스트

위 배열에서 인덱스는 0부터 8까지 있으므로, 중간 인덱스는 4이다. X = 13이라고 가정해보자.

[1] X는 중간 인덱스의 값보다 작은가? YES [범위가 반으로 줄었다] 👉🏻 만약 있어도 [2,인덱스 바이너리 6,13,21] 사이에 있을 것임

[2] 중간 인덱스는 6 또는 13이 될 수 있다. ( (0+3)/2 = 1.5 ) 이므로 👉🏻 ceil 하거나 floor 해서 아무거나 하면 된다.


0 개 댓글

답장을 남겨주세요