노드 n이 주어진 경우, 우리가 오른쪽으로가는 루트까지 백업하는 액세스 경로상의 다음 노드는 n의 이진 표현을 취하고 마지막 1을 제거함으로써 주어집니다.
인덱스 바이너리
OpenCV 프로젝트 : 7.1 - 컨투어
-
본문 폰트 크기 조정 본문 폰트 크기 작게 보기 본문 폰트 크기 크게 보기 가
컨투어(contour)는 우리말로 등고선, 윤곽선, 외곽선 등으로,
영상에서는 같은 색상이나 밝기의 연속된 점을 찾아 잇는 곡선을
객체 인식에 사용할 수 있는데 이것을 컨투어라고한다.
OpenCV는 컨투어와 관련해서 다음과 같은 함수를 제공한다.
dst, contours, hierarchy = cv2.findContours(src, mode, method, contours, hierarchy, offset)
mode = 컨투어 제공 방식
method = 근사 값 방식
contours = 검출된 컨투어 좌표 (파이썬 리스트)
hierarchy = 컨투어 계층 정보
cv2.drawContours(img, contours, contourIdx, color, thickness)
contours = 그림 그릴 컨투어 배열
contourIdx = 그림 그릴 컨투어 인덱스
thickness = 선 두께 (0 : 채우기)
다음은 도형의 컨투어를 찾아 그리는 예제이다.(2가지 method)
import numpy as np, cv2
img = cv2.imread( "C:/Users/yhk19/Desktop/openCV/img/shapes.jpg" )
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 스레시홀드로 인덱스 바이너리 바이너리 이미지로 만들어 검은색 배경에 흰색 전경으로 반전
ret, imthres = cv2.threshold(imgray, 127 , 255 , cv2.THRESH_BINARY_INV)
# 가장 바깥쪽 컨투어에 대해 모든 좌표 반환
im2, contour, hierarchy = cv2.findContours(imthres, cv2.RETR_EXTERNAL,
# 가장 바깥쪽 컨투어에 대해 꼭짓점 좌표만 반환
im2, contour2, hierarchy = cv2.findContours(imthres, cv2.RETR_EXTERNAL,
print ( '도형의 개수: <>(<>)' . format ( len (contour), len (contour2)))
# 모든 좌표를 갖는 컨투어 그리기, 초록색
cv2.drawContours(img, contour, - 1 , ( 0 , 255 , 0 ), 4 )
# 꼭짓점 좌표만을 갖는 컨투어 그리기, 초록색
cv2.drawContours(img2, contour2, - 1 , ( 0 , 255 , 0 ), 4 )
# 컨투어의 모든 좌표를 작은 파란색 점(원)으로 표시
for i in contour:
cv2.circle(img, tuple(j[ 0 ]), 1 , ( 255 , 0 , 0 ), - 1 )
# 컨투어의 꼭짓점 좌표를 작은 파란색 점(원)으로 표시
for i in 인덱스 바이너리 contour2:
cv2.circle(img2, tuple(j[ 0 ]), 1 , ( 255 , 0 , 0 ), - 1 )
cv2.imshow( 'CHAIN_APPROX_NONE' , img)
cv2.imshow( 'CHAIN_APPROX_SIMPLE' , img2)
cv2.CHAIN_APPROIX_NONE 은 컨투어를 표시하기 위한 모든 좌표를 담아서 반환하고,
cv2.CHAIN_APPROIX_SIMPLE 컨투어 꼭짓점 좌표만 담아서 반환한다.
컨투어의 개수는 도형의 개수와 같고,
모든 좌표를 담은 cv2.CHAIN_APPROIX_NONE 은 파란색 점이 모든 도형을 완전히 감싸지만,
꼭짓점 좌표를 담은 cv2.CHAIN_APPROIX_SIMPLE 은 파란색 점이 완전히 감싸지 못하고 있다.
다음은, 컨투어를 계층 트리로 담은 배열을 출력하는 예제이다.
import numpy as np, cv2
img = cv2.imread( "C:/Users/yhk19/Desktop/openCV/img/shapes_donut.jpg" )
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, imthres = cv2.threshold(imgray, 127 , 255 , cv2.THRESH_BINARY_INV)
im2, contour, hierarchy = cv2.findContours(imthres, cv2.RETR_EXTERNAL,
# 컨투어 개수와 계층 트리 출력
print ( len (contour), hierarchy)
# 모든 컨투어를 트리 계층으로 수집
im2, contour2, hierarchy = cv2.findContours(imthres, cv2.RETR_TREE,
# 컨투어 개수와 계층 트리 출력
print ( len (contour2), hierarchy)
# 가장 바깥 컨투어만 그리기(초록색)
cv2.drawContours(img, contour, - 1 , ( 0 , 255 , 0 ), 3 )
# 모든 컨투어 그리기(랜덤 색상)
for idx, cont in enumerate(contour2):
color = [ int (i) for i in np.random.randint( 0 , 255 , 3 )]
# 컨투어 인덱스마다 랜덤한 색상으로 그리기
cv2.drawContours(img2, contour2, idx, color, 3 )
# 컨투어 첫 좌표에 인덱스 숫자 표시
cv2.putText(img2, str (idx), tuple(cont[ 0 ][ 0 ]), cv2.FONT_HERSHEY_PLAIN,
cv2.imshow( 'RETR_EXTERNAL' , img)
cv2.imshow( 'RETR_TREE' , img2)
cv2.RETR_EXTERNAL을 이용해 가장 바깥 컨투어만 수집하고,
cv2.RETR_TREE를 이용해 모든 컨투어를 계층 구조로 수집했다.
반복문을 통해 랜덤 색상을 지정하여 컨투어 인덱스에 따라 색상을 그린다.
그리고 각 컨투어의 인덱스를 출력한다(hierarchy)
자식(First Child)
1번 인덱스는 삼각형 내부를 의미하고,
0번 인덱스는 1번 인덱스의 부모로 삼각형 외부를 의미한다.
이와 같이 컨투어의 외곽 요소와 자식 요소들을 순회하면서 컨투어 계층 정보를 활용할 수 있다.
1) 이미지 모멘트와 컨투어 속성
모멘트(moment)는 물리학에서 힘의 양을 기술할 때 사용하는 용어인데,
영상에서도 대상 물체의 양적인 속성을 표현할 때 모멘트라는 용어를 사용한다.
모멘트 식은 매우 복잡한데, OpenCV는 모멘트 계산을 위함 함수를 제공한다.
moment = cv2.moments(contour)
contour = 모멘트 계산 대상 컨투어 좌표
moment = 결과 모멘트(파이썬 딕셔너리)
m00, m01, m10, m11, m02, m12, m20, m21, m03, m30 : 공간 모멘트
mu20, mu11, mu02, mu30, mu21, mu12, mu03 : 중심 모멘트
nu20, nu11, nu02, nu30, nu21, nu03 : 정규화 중심 모멘트
또한, 컨투어를 통해 얻는 정보 중 넓이와 둘레 길이를 위한 별도의 함수도 제공한다.
retval = cv2.contourArea(인덱스 바이너리 contour, oriented=False) : 넓이 계산
contour = 넓이 계산을 위한 컨투어
oriented = 컨투어 방향 플래그(True/False)
retval = cv2.arcLength(curve, closed) : 둘레 계산
curve = 둘레 계산을 위한 컨투어
closed = 닫힌 호인지 여부 플래그
import numpy as np, cv2
img = cv2.imread( "C:/Users/yhk19/Desktop/openCV/img/shapes.jpg" )
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, imthres = cv2.threshold(imgray, 127 , 255 , cv2.THRESH_BINARY_INV)
im2, contours, hierarchy = cv2.findContours(imthres, cv2.RETR_EXTERNAL,
# 각 도형의 컨투어에 대한 루프
for c in contours:
# m10/m00, m01/m00 중심점 계산
cx = int (mmt[ 'm10' ] / mmt[ 'm00' ])
cy = int (mmt[ 'm01' ] / mmt[ 'm00' ])
l = cv2.arcLength(c, True)
cv2.circle(img, (cx, cy), 5 , ( 0 , 255 , 255 ), - 1 )
cv2.putText(img, "A:" . format (a), (cx, cy + 인덱스 바이너리 20 ), cv2.FONT_HERSHEY_PLAIN,
# 컨투어 시작점에 길이 그리기
cv2.putText(img, "L:" . format (l), tuple(c[ 0 ][ 0 ]), cv2.FONT_HERSHEY_PLAIN,
# 함수로 컨투어 넓이 계산해서 출력
print ( 'area:' . format (cv2.contourArea(c, False)))
cv2.imshow( 'center' , img)
area:29396.00
area:57600.00
area:49064.00
컨투어 함수로 각 도형의 중심점과 넓이 둘레 길이를 표시하는 예제이다.
도형의 넓이를 m01/m00, m10/m00 으로 구한것과
cv2.contourArea() 함수로 구한 것과 동일함을 알 수 있다.
또한, OpenCV는 컨투어를 이용해 해당 영역을 감싸는 도형 좌표를 계산하는 함수를 제공한다.
x,y,w,h = cv2.boundingRect(contour) : 좌표를 감싸는 사각형
x,y = 사각형 왼쪽 상단 좌표
rotateRect = cv2.minAreaRect(contour) : 좌표를 감싸는 최소한의 사각형
rotateRect = 회전한 사각형 좌표
vertex = cv2.boxPoints(rotateRect) : rotateRect로 꼭짓점 좌표 계산
vertex = 4개의 꼭짓점 좌표
center, radius = cv2.minEnclosingCircle(contour) : 좌표를 감싸는 최소한의 원
center = 원점 좌표(x, y)
area, triangle = cv2.minEnclosingTriangle(points) : 좌표를 감싸는 최소한의 삼각형
triangle = 3개의 꼭짓점 좌표
ellips = cv2.fitEllipse(points) : 좌표를 감싸는 최소한의 타원
ellips = 타원의 원점, 축의길이, 회전각도 좌표
line = cv2.fitline(points, distType, param, reps, aeps) : 중심점을 통과하는 직선
distType = 거리 계산 방식
param = distType에 전달할 인자(최적값 0)
reps = 반지름 정확도 (0.01 권장)
aeps = 각도 정확도 (0.01 권장)
line = vx, vy(정규화된 단위 벡터), x0, y0(중심점 좌표)
다음은, 위 함수들을 이용해 번개 모양을 감싸는 여러 도형을 출력하는 예제이다.
import numpy as np, cv2
# 이미지를 읽어서 바이너리 스케일로 변환
img = cv2.imread( "C:/Users/yhk19/Desktop/openCV/img/lightning.jpg" )
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, th = cv2.threshold(imgray, 127 , 255 , cv2.THRESH_BINARY_INV)
im, contours, hr = cv2.findContours(th, cv2.RETR_EXTERNAL,
contr = contours[ 0 ]
cv2.rectangle(img, (x, y), (x + w, y + h), ( 0 , 0 , 0 ), 3 )
box = cv2.boxPoints(rect) # 중심점과 각도를 4개의 꼭짓점 좌표로 변환
box = np.int0(box) # 정수로 변환
cv2.drawContours(img, [box], - 1 , ( 0 , 255 , 0 ), 3 )
(x, y), radius = cv2.minEnclosingCircle(contr)
cv2.circle(img, ( int (x), int (y)), int (radius), ( 255 , 0 , 0 ), 2 )
ret, tri = cv2.minEnclosingTriangle(contr)
cv2.polylines(img, [np.int32(tri)], True, ( 255 , 0 , 255 ), 2 )
cv2.ellipse(img, ellipse, ( 0 , 255 , 255 ), 3 )
# 중심점을 통과하는 직선 표시(빨간색)
[vx,vy,x,y] = cv2.fitLine(contr, cv2.DIST_L2, 0 , 0.01 , 0.01 )
cols, rows = img.shape[: 2 ]
cv2.line(img, ( 0 , 0 - x * (vy / vx) + y), (cols - 1 , (cols - x) * (vy / vx) + y), ( 0 , 0 , 255 ), 2 )
cv2.imshow( 'Bount fit shapes' , img)
실생활의 영상은 대부분 물체가 정확히 표현되는 경우보다는,
노이즈와 침식이 일어나는 경우가 많다.
그래서 정확한 컨투어보다 오히려 부정확하게 단순화한 컨투어가 쓸모 있는 경우가 많은데,
따라서 OpenCV는 오차범위 내 근사값으로 컨투어를 계산해주는 함수를 제공한다.
approx = cv2.approxPolyDP(contour, epsilon, closed)
contour = 대상 컨투어 좌표
epsilon = 근사 값 정확도, 오차 범위
closed = 컨투어의 닫힘 여부
다음은, 상처 투성이 사각형에서 컨투어를 찾는 예제이다.
import numpy as np, cv2
# 이미지를 읽어서 바이너리 스케일로 변환
img = cv2.imread( "C:/Users/yhk19/Desktop/openCV/img/bad_rect.jpg" )
imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, th = cv2.threshold(imgray, 127 , 255 , cv2.THRESH_BINARY)
temp, contours, hierachy = cv2.findContours(th, cv2.RETR_EXTERNAL,
contour = contours[ 0 ]
# 전체 둘레의 0.05로 오차범위 지정
epsilon = 0.05 * cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, epsilon, True)
cv2.drawContours(img, [contour], - 1 , ( 0 , 255 , 0 ), 3 )
cv2.drawContours(img2, [approx], - 1 , ( 0 , 255 , 0 ), 3 )
cv2.imshow( 'contour' , img)
cv2.imshow( 'approx' , img2)
cv2.arcLength()함수로 둘레길이를 구해 그의 0.05 만큼을 epsilon으로 설정해 근사 컨투어를 계산한다.
좌측은 원래 컨투어를 표시한 것이고, 우측은 근사 컨투어를 표시한 것인데
들쭉날쭉한 상처를 무시하고 정확히 직사각형을 잘 찾아내고 있다.
컨투어를 단순화하는 또 다른 방법은 볼록 선체(convex hull)를 만드는 것이다.
볼록 선체는 어느 한 부분도 오목하지 않은 상태를 말하는 것으로,
대상 객체를 완전히 포함하므로 객체의 외곽 영역을 찾는데 좋다.
OpenCV는 볼록 선체와 관련해서 다음과 같은 함수를 제공한다.
hull = cv2.convexhull(points, hull, clockwise, returnPoints) : 볼록 선체 찾기
clockwise = 방향 지정(True : 시계방향)
returnPoints = 인덱스 바이너리 결과 좌표 형식 선택(True/False)
retval = cv2.isContourConvex(contour) : 볼록 선체 만족 여부 확인
retval = True인 경우 볼록 선체 만족
defects = cv2.convexityDefects(contour, convexhull) : 볼록 선체 결함 찾기
인덱스 바이너리
블로그: 세그먼트 트리 (Segment Tree) 에서 풀어본 문제를 Fenwick Tree를 이용해서 풀어보겠습니다. Fenwick Tree는 Binary Indexed Tree라고도 하며, 줄여서 BIT라고 합니다.
Fenwick Tree를 구현하려면, 어떤 수 X를 이진수로 나타냈을 떄, 마지막 1의 위치를 알아야 합니다.
마지막 1이 나타내는 값을 L[i] 라고 표현하겠습니다. L[3] = 1 , L[10] = 2 , L[12] = 4 이 됩니다.
수 N개를 A[1] ~ A[N] 이라고 했을 때, Tree[i] 는 A[i] 부터 앞으로 L[i] 개의 합이 저장되어 있습니다.
아래 그림은 각각의 i 에 대해서, L[i] 를 나타낸 표입니다. 아래 초록 네모는 i 부터 앞으로 L[i] 개가 나타내는 구간입니다.
L[i] = i & -i 가 됩니다. 그 이유는 아래와 같습니다.
A = [3, 2, 5, 7, 10, 3, 2, 7, 8, 2, 1, 9, 5, 10, 7, 4]인 경우에, 각각의 Tree[i] 가 저장하고 있는 값은 다음과 같게 됩니다.
예를 들어, Tree[12] 에는 12부터 앞으로 L[12] = 4 개의 합은 A[9] + A[10] + A[11] + A[12] 가 저장되어 있습니다. Tree[7] 에는 7부터 앞으로 L[7] = 1 개의 합인 A[7] 이 저장되어 있습니다.
합 구하기
Tree 를 이용해서 A[1] + . + A[13] 은 어떻게 구할 수 있을까요?
13을 이진수로 나타내면 1101입니다. 따라서, A[1] + . + A[13] = Tree[1101] + Tree[1100] + Tree[1000] 이 됩니다. Tree 의 인덱스는 이진수입니다.
1101 -> 1100 -> 1000 는 마지막 1의 위치를 빼면서 찾을 수 있습니다. 이것을 코드로 작성해보면 다음과 같습니다.
모든 i 에 대해서, A[1] + . + A[i] 를 구하는 과정을 그림으로 나타내면 다음과 같습니다.
어떤 구간의 합 A[i] + . + A[j] 는 A[1] + . + A[j] 에서 A[1] + . + A[i-1] 을 뺀 값과 같습니다. 따라서, sum(j) - sum(i-1) 을 이용해서 구할 수 있습니다.
어떤 수를 변경한 경우에는, 그 수를 담당하고 있는 구간을 모두 업데이트해줘야 합니다. 아래와 같이 마지막 1의 값을 더하는 방식으로 구현할 수 있습니다.
바이너리 인덱스 트리 ( Binary Index Tree, BIT )
: 바이너리 인덱스 트리 ( Binary Index Tree, BIT )
펜윅 트리라고도 한다.
: 데이터의 업데이트가 가능한 상황에서의 구간 합
3번째 숫자를 6으로 바꾸고
2번째부터 5번째 수의 합을 구하면 17이 된다.
그리고 5번째 숫자를 2로 바꾸고
3번째부터 5번째 수의 합을 구하면 12가 된다.
데이터 개수 : N ( 1 데이터 변경 횟수 : M ( 1 구간 합 계산 횟수 : K ( 1
이러한 상황을 말한다.
M과 K는 같지 않을까 싶지만, 무튼 그러하다.
문제 링크에 찾아가보니 명확한 표시를 위해 K를 따로 변수로 둔 것 같다.
바이너리 인덱스 트리를 이용해서 해결하는 편이 더 좋다고 한다.
우선 보도록 하자.
: 특정 수 i의 0이 아닌 마지막 비트를 찾기.
만약 i==7 일때
비트 단위로 표시하면
i : 00000000 00000000 00000000 00000111
-i : 11111111 11111111 11111111 11111001
& 연산자는 비트연산자로 대응되는 두 비트가 둘 모두 1일때 1이된다.
( 1 & 1 = 1
1 & 0 = 0
0 & 0 = 1
AND연산을 생각하면 된다. )
: 이렇게 0이 아닌 마지막 비트를 구했다면, 이 값을 내가 저장하고 있는 값들의 개수로 보자.
이후 특정 값을 변경할 때 0이 아닌 마지막 비트만큼 더하면서 구간들의 값을 변경하여
업데이트를 수행할 수 있다.
이를 이용한 구간합을 구하는 방법은 이렇다.
글자만으로는 도저히 설명이 어려워 강의 그림을 캡쳐했다.
이를 그래프로 도식화 하면 이렇다.
: 좀 더 자세한 설명..
실제로 구현해보려 했을때, 사실 이 강의만 보고 나는 도저히 이해를 할 수 없었다.
아주 손쉽게 구간합을 구할 수 있는 메커니즘임은 자명해 보였으나
정확히는 ' 특정 값을 변경할 때 0이 아닌 마지막 비트만큼 더하면서 '
사설이지만 이게. 무슨 말인가? 알고리즘 공부를 하며 하루하루 스스로가 빠가사리라는 생각을 해왔지만
이건 너무하다는 생각이 들었다. 내가 2진법을 몰라서 못 알아듣는건가 싶기도하고
algorithm - 펜윅트리 - BIT:바이너리 인덱스 트리 사용?
이진 인덱싱 된 트리는 다른 데이터 구조와 비교하여 연구 할 이론이 거의 없거나 상대적으로 없습니다. 간결하게 가르쳐지는 유일한 곳 은 톱 코더 자습서 입니다. 튜토리얼은 모든 설명에서 완전하지만, 그런 나무 뒤에있는 직감이란 무엇인지 이해할 수 없습니까? 그리고 그게 정확함을 증명하는 방법?
증거가 설명하기가 복잡하다고 생각합니다. BIT를 사용할 때 어떤 접근 방식을 따르십니까?
@templatetypedef에 의해이 해답이 매우 명확하게 직관과 이진 색인 트리의 증명에 대해 설명하는 것으로 나타났습니다. 대답은 .
직관적으로, 바이너리 인덱스 트리를 자체적으로 표준 배열 표현의 최적화 된 이진 트리의 압축 된 표현으로 생각할 수 있습니다. 이 대답은 하나의 가능한 파생물로 이어집니다.
예를 들어 총 7 가지 요소에 대해 누적 빈도를 저장한다고 가정 해 봅시다. 번호가 배포 될 7 개의 양동이를 작성하여 시작할 수 있습니다.
이제, 누적 빈도가 다음과 같이 보일 것이라고 가정 해 봅시다.
이 버전의 배열을 사용하면 해당 지점에 저장된 숫자 값을 증가시키고 이후에 오는 모든 항목의 빈도를 증가시켜 요소의 누적 빈도를 증가시킬 수 있습니다. 예를 들어 누적 빈도를 3으로 늘리려면 다음과 같이 위치 3 또는 그 이후에 배열의 각 요소에 7을 더할 수 있습니다.
이 문제는이 작업을 수행하는 데 O (n) 시간이 걸리므로 n이 클 경우 상당히 느립니다.
이 작업을 개선하는 것에 대해 생각할 수있는 한 가지 방법은 버킷에 저장하는 것을 변경하는 것입니다. 주어진 점까지 누적 빈도를 저장하는 대신, 이전 빈 통에 비해 현재 빈도가 증가한 양을 저장하는 것을 생각할 수 있습니다. 예를 들어, 위의 버킷을 다음과 같이 다시 작성합니다.
이제 우리는 적절한 양을 그 양동이에 추가함으로써 O (1) 시간에 양동이 내의 빈도를 증가시킬 수 있습니다. 그러나 모든 작은 버킷의 값을 합산하여 버킷의 총계를 다시 계산해야하기 때문에 조회를 수행하는 총 비용은 이제 O (n)이됩니다.
여기에서부터 바이너리 인덱스 트리에 이르기까지 필요한 첫 번째 주요 통찰은 다음과 같습니다. 특정 요소 앞에 오는 배열 요소의 합계를 지속적으로 다시 계산하는 것이 아니라, 특정 요소 이전에 모든 요소의 총 합계를 미리 계산한다면 순서의 포인트? 우리가 그렇게 할 수 있다면, 사전 계산 된 합계의 올바른 조합을 합산하여 누적 합계를 계산할 수 있습니다.
이를 수행하는 한 가지 방법은 표현을 버킷 배열에서 노드의 이진 트리로 변경하는 것입니다. 각 노드에는 해당 노드의 왼쪽에있는 모든 노드의 누적 합계를 나타내는 값이 주석으로 표시됩니다. 예를 들어, 다음 노드에서 다음과 같은 이진 트리를 생성한다고 가정 해보십시오.
이제는 노드와 왼쪽 하위 트리를 포함하여 모든 값의 누적 합계를 저장하여 각 노드를 보강 할 수 있습니다. 예를 들어, 우리의 값이 주어지면 우리는 다음을 저장합니다 :
이 트리 구조를 감안할 때 점까지 누적 합계를 쉽게 결정할 수 있습니다. 아이디어는 다음과 같습니다 : 카운터를 초기에 0으로 유지 한 다음 문제의 노드를 찾을 때까지 정상적인 이진 검색을 수행합니다. 우리가 그렇게 할 때 우리는 또한 다음과 같이 또한 우리가 올바르게 움직일 때마다 현재 값을 카운터에 더합니다.
예를 들어, 3의 합계를 찾으려고한다고 가정합니다. 그렇게하기 위해 다음을 수행합니다.
- 루트 (4)에서 시작하십시오. 카운터는 0입니다.
- 노드 (2)로 왼쪽 이동. 카운터는 0입니다.
- 노드 (인덱스 바이너리 인덱스 바이너리 3)로 이동하십시오. 카운터는 0 + 6 = 6입니다.
- 노드 (3)를 찾습니다. 카운터는 6 + 15 = 21입니다.
이 프로세스를 역으로 실행하는 것도 상상할 수 있습니다. 주어진 노드에서 시작하여 카운터를 해당 노드의 값으로 초기화 한 다음 트리를 루트로 이동하십시오. 올바른 하위 링크를 따라갈 때마다 도착한 노드의 값을 추가하십시오. 예를 들어 3의 빈도를 찾으려면 다음을 수행 할 수 있습니다.
- 노드 (3)에서 시작하십시오. 카운터는 15입니다.
- 노드 (2)로 이동하십시오. 카운터는 15 + 6 = 21입니다.
- 노드 (1)로 이동하십시오. 카운터는 21입니다.
노드의 빈도를 늘리려면 (그리고 암시 적으로 뒤 따르는 모든 노드의 빈도) 트리의 왼쪽 하위 트리에 해당 노드를 포함하는 노드 집합을 업데이트해야합니다. 이렇게하려면 다음을 수행합니다. 해당 노드의 빈도를 증가시킨 다음 트리의 루트까지 걷기 시작합니다. 왼쪽 자식으로 데려가는 링크를 따라갈 때마다 현재 값을 더함으로써 마주 치는 노드의 빈도를 증가시킵니다.
예를 들어 노드 1의 빈도를 5 씩 증가 시키려면 다음을 수행합니다.
노드 1에서 시작하여 주파수를 5 씩 증가시켜
위쪽으로 왼쪽 자식 링크를 따라 갔으므로이 노드의 빈도도 증가시킵니다.
그것은 왼쪽 자식 인덱스 바이너리 링크 였으므로이 노드도 증가시킵니다.
마지막 단계는 이것을 바이너리 인덱스 트리로 변환하는 것이고 여기서 바이너리 숫자로 재미있는 일을하게됩니다. 이 트리의 각 버킷 인덱스를 이진수로 다시 작성해 보겠습니다.
여기서 우리는 아주 아주 멋진 관찰을 할 수 있습니다. 이 이진수 중 하나를 가져 와서 숫자에 설정된 마지막 1을 찾은 다음 그 비트를 없애고 그 뒤의 모든 비트와 함께 놓습니다. 이제 다음이 남아 있습니다.
정말로 멋진 관찰이 있습니다. 0을 "왼쪽"으로, 1을 "오른쪽"으로 사용하면 각 숫자의 남은 비트가 정확하게 루트에서 시작하여 그 숫자로 내려가는 방법을 정확하게 설명합니다. 예를 들어, 노드 5는 바이너리 패턴 101을가집니다. 마지막 1은 최종 비트이므로 10을 얻으려면 인덱스 바이너리 마지막 비트가됩니다. 루트에서 시작하여 오른쪽 (1)으로 이동 한 다음 왼쪽 (0)으로 이동하면 끝납니다. 노드 5에서 업!
이것이 중요한 이유는 우리 조회 및 업데이트 작업이 노드에서 루트까지의 액세스 경로 및 왼쪽 또는 오른쪽 하위 링크를 따르는 지 여부에 의존하기 때문입니다. 예를 들어 조회 중에 우리가 따라야 할 왼쪽 링크에 신경 쓴다. 업데이트하는 동안 우리는 우리가 따라야 할 올바른 링크에 관심이 있습니다. 이 바이너리 인덱스 된 트리는 인덱스의 비트를 사용하여이 모든 것을 효율적으로 수행합니다.
핵심 트릭은이 완벽한 이진 트리의 다음과 같은 속성입니다.
노드 n이 주어진 경우, 우리가 오른쪽으로가는 루트까지 백업하는 액세스 경로상의 다음 노드는 n의 이진 표현을 취하고 마지막 1을 제거함으로써 주어집니다.
예를 들어 노드 7의 액세스 경로 인 111을 살펴보십시오. 오른쪽 포인터를 따라가는 루트에 대한 액세스 경로의 노드는 다음과 같습니다.
- 노드 7 : 111
- 노드 6 : 110
- 노드 4 : 100
이 모든 것이 올바른 연결 고리입니다. 우리가 노드 3에 대한 액세스 경로 인 011을 취하고 우리가 올바르게가는 노드를 보면 우리는
- 노드 3 : 011
- 노드 2 : 010
- (노드 4 : 왼쪽 링크를 따르는 100)
즉, 다음과 같이 노드까지의 누적 합계를 매우 효율적으로 계산할 수 있습니다.
- 노드 n을 2 진수로 기록하십시오.
- 카운터를 0으로 설정하십시오.
- n ≠ 0 인 동안 다음을 반복하십시오.
- 노드 n의 값을 더하십시오.
- n에서 맨 왼쪽 1 비트를 제거하십시오.
마찬가지로 업데이트 단계를 어떻게 수행할지 생각해 봅시다. 이를 위해 우리는 루트까지 액세스 경로를 따라 가면서 왼쪽 링크를 따라 갔던 모든 노드를 업데이트하려고합니다. 기본적으로 위의 알고리즘을 수행하여이 작업을 수행 할 수 있지만 모든 1을 0과 0을 1로 전환하면됩니다.
이진 인덱싱 된 트리의 마지막 단계는이 비트 방식의 속임수 때문에 트리를 명시 적으로 더 이상 저장하지 않아도된다는 점입니다. 모든 노드를 길이 n의 배열에 저장 한 다음 bitwise twiddling 기법을 사용하여 트리를 암시 적으로 탐색 할 수 있습니다. 사실 그것은 비트 단위의 인덱스 된 트리가하는 것입니다. 노드를 배열에 저장 한 다음이 비트 트릭을 사용하여이 트리에서 위쪽으로 걷는 것을 효율적으로 시뮬레이션합니다.
Find, install and publish Python packages with the Python Package Index
The Python Package Index (PyPI) is a repository of software 인덱스 바이너리 for the Python programming language.
PyPI helps you find and install software developed and shared by the Python community. Learn about installing packages.
Package authors use PyPI to distribute their software. Learn how to package your Python code for PyPI.
Trending projects
Trending projects as downloaded by the community
SimplyNews 1.0
Python scrabing news from any news website
getips 0.1.2
Small python tool to get all IP addresses from list of domains.
quark-sphinx-theme 0.6.0
A Sphinx theme designed for QTextBrowser
pyduinocoin 1.0.5
pyDuinoCoin is a simple python integration for the DuinoCoin REST API, that allows developers to communicate with DuinoCoin Master Server.
interactions-pipeline 0.4
Interactions Pipeline Workflow Package.
New releases
Hot off the press: the newest project releases
pilli 2022.7.28
The whistle (Finnish: pilli, German: Der Pfiff) parsing random DAFIF like data.
0 개 댓글