AVL 트리(매우 상세한 설명)

머리말

AVL 트리는 어디에서 왔습니까?
우리는 이진 트리를 검색하면 성능 저하 문제가 발생한다는 것을 알고 있습니다. 성능 저하 후에는 단일 분기가 되거나 단일 분기에 가까워집니다.
효율성은 O(N)이 되어 검색 효율성을 보장할 수 없습니다.

검색 효율성을 보장할 수 없다면 한 가지만 해야 하는데,
균형을 조절해야 한다는 것입니다.

AVL 트리의 개념

여기에 이미지 설명을 삽입하세요.
높이 균형 검색 이진 트리라고도 합니다.

위의 해결 방법을 보면서 우리는 왜 왼쪽과 오른쪽의 높이 차이가 같지 않고 1보다 크지 않은가?라는 질문에 대해 생각해 볼 것입니다.

할 수 없기 때문에 하나의 노드는 같을 수 있지만 두 개의 노드는 어떻게 해도 할 수 없습니다.
왜냐하면 당신의 값이 하나씩 삽입되고 데이터의 개수가 보장될 수 없기 때문입니다. 아래 그림과 같습니다.
여기에 이미지 설명을 삽입하세요.
일부 노드의 수는 보장될 수 없습니다.

AVL 트리를 구현하는 방법에는 여러 가지가 있는데 여기서는 균형을 제어하기 위해 다음 방법을 사용합니다.
이 접근 방식을 균형 요소라고 합니다.
균형을 제어하기 위해 균형 요소를 도입했습니다.
여기에 이미지 설명을 삽입하세요.

균형 요소는 왼쪽과 오른쪽, 오른쪽에서 왼쪽을 뺀 높이 차이입니다.
여기에 이미지 설명을 삽입하세요.
모든 노드에는 균형 요소가 있으며, 모든 노드 균형 요소가
0, 1, -1이면 AVL 트리입니다.

트리가 AVL 트리인지 확인하려면
균형 요소가 100% 정확하면 균형 요소를 살펴보세요.

AVL 트리 구현

AVL 트리 정의

키/값을 구현할 수 있으면 키를 구현할 수 있고 그 본질이 비슷하기 때문에 키/값 구조로 직접 정의합니다.
여기에 이미지 설명을 삽입하세요.

여기서는 처음으로 세 개의 포크 체인을 소개합니다. 즉, _left, _right 및 부모 외에
부모 없이도 가능하지만 구현하기가 매우 번거롭습니다.

Parnet은 추가 부담을 안겨줍니다. 단일 연결 목록을 이중 연결 목록으로 변경한 후 유지 관리해야 할 추가 포인터가 있습니다.
여기에는 세 갈래의 체인이 있고, 부모가 있기 때문에 위쪽과 뒤쪽으로 이동할 수 있지만 조정을 하면 삽입할 때 이 포인터를 유지해야 합니다.

이전에는 부모 포인터를 사용하지 않았지만 비용이 많이 들었고 나중에
회전을 할 때 매우 역겹다고 말하겠지만 이 포인터는 큰 편리함을 제공합니다.

여기서는 구조를 작성하기에는 너무 게으른 관계로 매개변수만 제공합니다.

끼워 넣다

먼저 값을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
AVL 트리에 값을 삽입하는 데는 트릭이 없으며 이전 기사의 검색 트리와 동일합니다.
재귀 또는 비재귀를 사용할 수 있습니다. 여기서는 비재귀를 사용하는 것이 좋습니다. 이러한 종류의 쓰기 루프는 매우
부드럽고 재귀를 사용할 필요가 없습니다.
여기에 이미지 설명을 삽입하세요.

여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
여기에 질문이 있습니다. 부모 포인터는 어떻게 연결됩니까?
우리는 한 단계 더 나아갈 수 있습니다. 백링크를 받으세요.
여기에 이미지 설명을 삽입하세요.

부모는 어떻게 연결되어 있나요?
루트에는 링크가 필요하지 않습니다. 루트에는 아버지가 없지만 다른 노드에는 부모가 있습니다.
다른 노드들이 그것보다 크면 오른쪽으로, 그것보다 작으면 왼쪽으로 가고, 삽입한 후에는
father의 왼쪽과 오른쪽에 연결되게 되는데, father에 연결해야 한다. 반대 방향으로.

검색 트리 부분은 완료되었으며, AVL 트리는 이제 막 시작되었습니다.
AVL 트리가 삽입되면 어떻게 해야 하나요?
삽입된 노드의 위치에 따라 다음과 같은 상황으로 나눌 수 있습니다.
여기에 이미지 설명을 삽입하세요.
새로 추가된 노드의 밸런스 팩터는 무엇입니까?
노드의 균형 요소에 먼저 영향을 미치는 요소는 무엇입니까? 왼쪽 및 오른쪽 하위 트리의 높이입니다.
새 노드의 균형 요소는 걱정하지 마세요. 0이어야 합니다. 왼쪽 트리와 오른쪽 트리의 높이에는 영향을 주지 않기 때문입니다.

나무가 영향을 받았는지 여부를 어떻게 측정합니까? 예를 들어, 우리가 처리해야 할 문제가 있나요?
균형 요소를 측정하고 업데이트하는 방법. 새 노드는 0이므로 말할 필요도 없습니다.
여기에 이미지 설명을 삽입하세요.
회전은 우리가 이야기하는 가장 중요한 개념입니다. 이것은 비교적 복잡한 일이므로 나중에 설명하겠습니다.

새 노드를 추가하면 어떤 노드가 영향을 받나요?
이는 조상, 즉 루트에서 루트까지의 경로에 있는 노드에만 영향을 미칩니다.
여기에 이미지 설명을 삽입하세요.

보다 정확하게는 노드를 삽입하면 일부 상위 노드에 직접적인 영향을 미칩니다.
정확히 누구에게 영향을 미칠지는 말하기 어렵습니다.

그래서 우리는 경로를 업데이트해야 합니다.
따라서 우리는 부모를 추가하기 전에 부모 추가를 처리하는 것이 어려운 이유를 이해

하지만 부모 추가를 처리하는 것이 더 쉽습니다.

이제 균형 요소가 어떻게 업데이트되는지 분석해 보겠습니다.
새 노드가 아버지 노드의 왼쪽에 있으면 어떻게 될까요? 새 노드가 아버지 노드의 오른쪽에 있으면 어떻게 될까요?
아버지의 균형 요인은 어떻습니까?

새 노드를 추가하면 하위 트리의 높이가 변경됩니다. 하위 트리 높이의 변화는 아버지의 균형 요소에 영향을 미칩니다.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.

반드시 모든 조상이 영향을 받는 것은 아닙니다.

계속해서 위쪽으로 업데이트를 진행하고 있으며, 삼거리 체인으로 진행하기가 수월할 것입니다.
여기에 이미지 설명을 삽입하세요.
상향 업데이트 여부는 업데이트가 완료된 후 아버지의 균형 요소가 어떻게 되는지에 따라 달라집니다.
이번에는 세 가지 상황으로 나누어진다.
업데이트 여부는 어떻게 결정되나요?
여기에 이미지 설명을 삽입하세요.

여기에 이미지 설명을 삽입하세요.

두 번째 경우:
여기에 이미지 설명을 삽입하세요.

여기에 이미지 설명을 삽입하세요.
당신은 더 이상 균형도 잡혀 있지 않습니다. 먼저 문제를 해결해야 합니다.

회전은 어떻습니까? 먼저 결론을 말씀드리자면
회전 후에는 괜찮습니다.그 본질은 높이를 줄인 다음 이전 높이로 돌아가서
다시 균형 잡힌 트리가 되는 것입니다.

회전이 4개 더 있는데 꽤 복잡합니다.

세 번째 상황:
0이 되면 계속 업데이트 하시겠습니까?
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
0은 계속해서 업데이트할 필요가 없습니다.0은 나를 치료하는 것과 같습니다.
상대 균형에서 절대 균형으로 바뀌는 것입니다. 더 이상 재생할 필요가 없습니다.

이제 AVL 트리의 대규모 프레임워크가 완성되었으며
때로는 루트 노드까지 지속적으로 업데이트해야 하는 경우도 있습니다. 다음 상황과 같습니다.
여기에 이미지 설명을 삽입하세요.
이제 노드를 삽입해야 합니다.
여기에 이미지 설명을 삽입하세요.

다음으로 균형 요소를 업데이트하는 코드를 작성합니다.
여기에 이미지 설명을 삽입하세요.

else를 직접 사용하는 것이 권장되지 않는 이유는 무엇입니까?
나무를 삽입하기 전에 나무에 문제가 생겼을 가능성이 있습니다.
예를 들어 일부 노드의 밸런스 팩터는 삽입 전 2, -2 이상이었는데, 이런 상황에 섞어서 사용하는 것은 매우 번거로운 일이 될 것입니다.

그래서 우리는 경고를 하고 제때에 문제를 찾을 수 있도록 도와줍니다.
여기에 이미지 설명을 삽입하세요.

위는 AVL 트리의 큰 프레임워크입니다.

지역적 불균형이 있을 때 어떻게 해야 합니까? 먼저 간단한 상황부터 이야기해보자

회전시켜 보세요
여기에 이미지 설명을 삽입하세요.

단일 회전이라는 가장 간단한 경우가 있습니다. 또한 다양한 상황으로 확장하여 천천히 처리해야 합니다.
그러나 어느 모양이든 회전하여 균형을 맞추고 높이를 낮출 수 있습니다.

회전에는 두 가지 주요 목적이 있습니다.
1. 이 하위 트리를 불균형에서 균형으로 변경합니다
. 2. 이 하위 트리의 높이를 줄입니다.

참고로 AVL 트리는 높이를 직접 제어할 수 있는데, 예를 들어 노드를 삽입해
왼쪽 트리와 오른쪽 트리의 높이 차이에 문제가 있는지 확인하고, 문제가 있으면 처리한다.
하지만 이 실시간 방식은 제어하기가 그리 쉽지 않습니다. 여기서는 균형 요소를 소개합니다.

싱글 스핀

다음으로 4개의 회전을 명확하게 설명해야 하는데, 이 4개의 회전은 상당히 복잡하므로
조금씩 설명하겠습니다.

왼쪽 회전 없음

위에서 이미 언급한 가장 간단한 경우를 먼저 살펴보겠습니다. 이 상황은 단순해 보이지만
실제로는 매우 복잡합니다.

노드를 삽입한 후 트리의 오른쪽이 높으므로 왼쪽으로 회전시킵니다.
이렇게 생겼는데 실제로는 어떤 모습인지 분석해 보겠습니다.
여기에 이미지 설명을 삽입하세요.

지금 상황에서는 오른쪽이 높은데 이를 왼쪽 일측회전이라고 합니다.
회전된 트리는 반드시 전체 트리일 필요는 없으며 부분적인 하위 트리일 수도 있습니다.
여기에 이미지 설명을 삽입하세요.

h가 0일 때
여기에 이미지 설명을 삽입하세요.

h가 1인 경우
h가 1인 경우는 매우 간단하며 노드가 하나뿐입니다.
여기에 이미지 설명을 삽입하세요.

h가 2일 때
여기에 이미지 설명을 삽입하세요.

h가 2인 경우가 많습니다.
여기에 이미지 설명을 삽입하세요.
결론부터 먼저 얘기하고, 나중에 자세히 설명하고,
여기에 이미지 설명을 삽입하세요.
무작위로 하나 뽑아서 살펴보도록 하겠습니다.
여기에 이미지 설명을 삽입하세요.

보세요, 균형 인자 30과 60을 충족합니다.
물론, a와 b는 y와 z일 수도 있습니다.

나무의 높이가 변하는 한 반드시 회전이 발생합니다
여기에 이미지 설명을 삽입하세요.

구체적인 도표는 끝이 없고 아래로 내려갈수록 조합도 많아집니다. 여기서는 h==2;의 예만 제시합니다.

c의 높이가 변하는 한 회전이 발생합니다.
c. 노드를 삽입하면 30bf가 2로 변경됩니다.

직접 시연해보셔도 됩니다

c가 y와 z일 수 있는 이유는 무엇입니까?
c가 y와 z이면 30bf가 2가 되는 데 영향을 주지 않습니다. 왜일까요?
c가 y처럼 보인다고 가정
여기에 이미지 설명을 삽입하세요.
하고 아래쪽부터 삽입하면
여기에 이미지 설명을 삽입하세요.
이렇다면 y 자체에 문제가 생기고 자체적으로 회전해서 30에 도달하지 못하게
됩니다
여기에 이미지 설명을 삽입하세요.
. 더 이상 진행되지 않습니다. 업데이트가 계속됩니다.

위의 내용 을 보세요.

여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.

먼저 30bf가 2로 바뀌는 상황을 분석해 보겠습니다.

h 상황을 끝내는 것은 불가능합니다. 그러면 AVL 트리를 어떻게 진행해야 할까요?
방금 언급한 상황 중 하나이며 처리 규칙은 동일합니다.
즉, 트리 c의 높이가 0에서 1, 1에서 2, 2에서 3으로 변경되면
30bf가 2로 변경됩니다
여기에 이미지 설명을 삽입하세요.
. 회전에 대한 간단한 요약은 두 단계로 보입니다. 간단하지만 잠깐만 기다려 주세요. 다음에는 문제를 처리해야겠습니다.
여기에 이미지 설명을 삽입하세요.

왜 이렇게 변한 걸까요?
여기에 이미지 설명을 삽입하세요.
검색 트리의 규칙을 어떻게 유지합니까?
b가 왜 30의 오른쪽이 될 수 있나요?
이 트리 b의 노드는 30보다 크고 60보다 작으므로 30의 오른쪽이 되어도 전혀 문제가 없습니다.
전체적으로 30트리가 60트리보다 작은 것을 볼 수 있습니다.

회전은 스크램블링이 아니라 검색 트리를 유지하는 규칙입니다.

위에서 회전의 목적, 회전의 원리, 회전의 과정에 대해 이야기했는데, 인터뷰에서는 AVL 나무를 손으로 찢으라는 것이 아니라 과정
에 대해 이야기하라고 할 수도 있습니다.
이렇듯 명확하게 분석해야 합니다.

우리는 왜 이렇게 회전하는 걸까요?
h의 상황이 셀 수 없이 많은 것처럼, 회전의 상황도 셀 수 없이 많기 때문에 조합도 셀 수 없이 많지만,
h의 상황이 아무리 많아도 회전 방법은 동일합니다.

오른쪽 단일회전

이 왼쪽 회전에 대해 이야기하기 전에 왼쪽 회전을 그릴 수 있습니다. 삽입한 위치에 회전이 발생합니다.
구체적인 그림을 한두 개 그려서 보십시오.

에 새 요소를 추가하면 회전이 발생하는데
여기에 이미지 설명을 삽입하세요.
왜 오른손 회전이라고 하나요?
왼쪽이 높고 오른쪽으로 회전합니다.

구체적인 그림을 그려서 간략하게 살펴볼 수 있습니다.
위와 완전 비슷해요

왼쪽 단일 회전 코드

그런 다음 몇 가지 코드를 작성합니다.
먼저 왼손회전 사용법을 살펴볼까요?
데이터를 삽입하고 업데이트한 다음 위쪽으로 업데이트합니다.
여기에 이미지 설명을 삽입하세요.
이제 왼쪽 단일 회전을 수행하고 싶습니다. 어떻게 회전합니까? 이제 이 기능을 구현해 보겠습니다. 이 기능은 여전히 ​​상대적으로 복잡합니까?

우선
여기에 이미지 설명을 삽입하세요.
왼쪽 한 번의 회전으로 부모에게 매개변수를 전달해야 하는데, 간단해 보이지만 실제로는 다루기가 쉽지 않습니다.

먼저 이동하려는 노드를 표시해 보겠습니다.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.여기에 이미지 설명을 삽입하세요.
지금은 우리의 왼손 회전인 것 같은데, 이 왼손 회전에 문제가 있는 것은 아닌지 생각해 보세요.
여기서 가장 큰 문제는 무엇입니까?
첫째, 부모를 유지하지 않았습니다. (부모를 유지하지 않으면 확실히 작동하지 않습니다.)

b의 괄호는 여전히 60을 가리키고 있는데 이는 엉망이므로
여기에 이미지 설명을 삽입하세요.
코드를 변경해야 합니다.
여기에 이미지 설명을 삽입하세요.

계속해서 위의 코드를 살펴보니 문제가 있는 걸까요?
이 회전은 단순해 보이지만 숨겨진 위험이 숨어 있으므로 조심하지 않으면 실수를 하게 됩니다.

subRL은 비어 있을 수 있습니다.
h==0, subRL은 비어 있습니다.
아래를 보세요. 상위, subR은 비어 있지 않지만 subRL은 비어 있고
여기에 이미지 설명을 삽입하세요.
subRL은 비어 있으면 위 코드가 충돌합니다.

변경하세요. 비어 있지 않으면 변경하고, 비어 있으면 이동하지 않습니다.

여기에는 큰 질문이 남아 있습니다.
회전하는 트리가 반드시 전체 트리일 필요는 없습니다
. 회전하는 트리가 로컬 하위 트리일 수 있기 때문입니다.
여기에 이미지 설명을 삽입하세요.

따라서 이러한 이유를 바탕으로 판단을 내려야 합니다. 위에 다른 노드가 있을 수 있습니다
여기에 이미지 설명을 삽입하세요.
. 30이 루트라면 새로운 루트는 60입니다.

30이 루트가 아닌 경우 이 노드를 업데이트해야 합니다. 이 노드는 여전히 30을 가리키고 있고, 트리는 이렇게 됩니다.
트리에 뭔가 문제가 있는 것입니다.

여기에 이미지 설명을 삽입하세요.

이 경우 상위 항목을 먼저 저장합니다. 그렇지 않으면 나중에 찾을 수 없습니다.
이를 두 가지 방법으로 변경합니다.
1. ppnode는 비어 있습니다. 이는 아버지가 없는 이진 트리에 루트 노드만 있기 때문에 루트임을 의미합니다.
여기서는 subR의 팬렛이 30을 가리킨다는 점에 유의해야 합니다. 회전해도 여전히 30을 가리킵니다. 여기서 업데이트해야 합니다.
여기에 이미지 설명을 삽입하세요.
마지막으로 균형 요소를 업데이트합니다.
여기에 이미지 설명을 삽입하세요.

왼쪽과 오른쪽을 업데이트하는 것 외에도 부모도 업데이트해야 하기 때문에 세 개의 포크 체인에 대해 지불해야 하는 대가가 있다고 이전에 말했듯이 부모가
없으면 찾을 수 없기 때문에 편리하기도 합니다. ppnode, 이전 레이어를 따라갈 ppnode를 찾을 수 없습니다. 연결이 더 번거로울 것입니다.
그래서 우리가 하는 모든 일은 왼쪽과 오른쪽, 부모를 업데이트하는 것 외에는 한 가지 일을 하는 것뿐입니다.

부모의 균형 요소는 0이어야 합니다. 이유는 무엇입니까?
이 추상적인 그림은 균형 요소에 영향을 미치는 왼쪽 및 오른쪽 하위 트리의 높이이기 때문에 매우 명확해 보입니다.


void RotateL(Node* parent)
{
    
    
	Node* subR = parent->_right;
	subRL = subR->_left;

	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;

	Node* ppnode = parent->_parent;

	subR->_left = parent;
	parent->_parent = subR;

	if (ppnode == nullptr)
	{
    
    
		_root = subR;
		_root->_parent = nullptr;
	}
	else
	{
    
    
		if (ppnode->_left == parent)
		{
    
    
			ppnode->_left = subR;
		}
		else
		{
    
    
			ppnode->_right = subR;
		}

		subR->_parent = ppnode;
	}

	parent->_bf = subR->_bf = 0;
}

오른쪽 회전 코드

여기에 이미지 설명을 삽입하세요.

올바른 단일 회전에 대해 먼저 직접 작성할 수 있습니다. 회전은 AVL 트리에서 가장 중요한 것입니다.
나중에 빨간색과 검은색 트리에도 회전이 있습니다.

노드를 변경할 때마다 해당 상위 노드도 변경된다는 점에 유의하세요.

여기에 이미지 설명을 삽입하세요.

좋아요, 위와 동일합니다. 여기서 개선이 필요합니다. 부모가 반드시 원래 루트일 필요는 없습니다.
여기에 이미지 설명을 삽입하세요.

여기서는 다른 방식으로 비교합니다.
마지막으로 균형 요소를 업데이트하는 것을 잊지 마세요.
여기에 이미지 설명을 삽입하세요.

void RotateR(Node* parent)
{
    
    
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;

	Node* ppnode = parent->_parent;

	subL->_right = parent;
	parent->_parent = subL;

	if (parent == _root)
	{
    
    
		_root = subL;
		_root->_parent = nullptr;
	}
	else
	{
    
    
		if (ppnode->_left == parent)
		{
    
    
			ppnode->_left = subL;
		}
		else
		{
    
    
			ppnode->_right = subL;
		}
		subL->_parent = ppnode;
	}

	subL->_bf = parent->_bf = 0;
}

그러면 이 두 회전을 언제 호출합니까?
주의 깊게 살펴보면
여기에 이미지 설명을 삽입하세요.
나중에 고려할 회전이 두 개 더 있다는 것을 알 수 있습니다.

이 회전 조각은 여전히 ​​매우 복잡합니다. 이 조각을 자세히 살펴보겠습니다
. 가장 중요한 것은 코드가 아닙니다. 그림을 다시 그려야 합니다. 그림을 다시 그리는 것이 매우 중요합니다.

더블 스핀

이중 회전은 복잡해 보이지만 실제로는 그렇게 복잡하지 않습니다.
더블 스핀은 두 개의 싱글 스핀이지만 더블 스핀이 두통을 유발할 수 있는 상황도 있습니다.

이것은 이중 회전의 다이어그램입니다.
여기에 이미지 설명을 삽입하세요.

여전히 이전과 동일합니다. 이것은 추상적인 그림입니다. 구체적인 그림으로 분석해야 합니다. 먼저
첫 번째 이중 회전 상황을 분석해 보겠습니다.
실제로 단일 회전은 직선이라는 것을 알 수 있습니다. 측면은 단순히 높습니다.
여기에 이미지 설명을 삽입하세요.

이중 회전은 더 복잡합니다.
A, b, c, d가 모두 비어 있으므로 b와 c가 존재하지 않습니다. 60도 존재하지 않습니다. 60은
새로 추가됩니다
. 회전은 언제 시작됩니까?
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.

h가 2이면 매우 복잡해집니다.
여기에 이미지 설명을 삽입하세요.

a의 삽입은 단순히 왼쪽 상단에 있는 단일 회전입니다.
이제 지식 b와 c를 삽입합니다.

이번 회전의 마지막 지점은 어디인가?
b와 c의 위치가 변경되면 회전이 발생합니다.

이번 로테이션에는 매우 역겨운 점이 있는데, 위의 로테이션 하나로는 여기서 문제를 해결할 수 없습니다.
여기에 이미지 설명을 삽입하세요.
위의 단일 스핀을 누르면 작동하지 않습니다.
-2가 왼쪽이 더 높으니 오른쪽으로 회전하면 작동하지 않습니다.
여기에 이미지 설명을 삽입하세요.

형태적으로 보면 Double Spin은 폴리라인 형태로 전체 높이가 왼쪽에 있지만 축점 30도 오른쪽에 높습니다.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.

균형이 없습니다. 왼쪽의 높이는 1이고 오른쪽의 높이는 3입니다
. 단지 방향을 바꾸는 문제일 뿐이므로 한 번의 회전으로는 여기서 문제를 해결할 수 없습니다.

따라서 여기서 문제를 해결하려면 이중 스핀을 사용해야 합니다.
더블 스핀하는 방법?

여기에 이미지 설명을 삽입하세요.

이 부분은 균형 요소 업데이트와 관련이 있으므로 별도로 그려보겠습니다.
먼저 왼쪽 회전을 하고, 30도 위치까지 왼쪽 회전을 해보자.
여기에 이미지 설명을 삽입하세요.
회전시켜 보면 이 사진은 한쪽이 높다는 점만 빼면 매우 친숙해 보입니다.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
우리는 회전을 직접 작성할 필요가 없습니다.

좌우 이중 회전

왼쪽과 오른쪽으로 이중 회전하는 것은 매우 간단합니다. 이전 코드를 재사용하면 됩니다.
여기에 이미지 설명을 삽입하세요.

더블 스핀의 최종 결과로 보면 60을 위로 밀어 뿌리를 만들고, 60의 왼쪽과 오른쪽은 30과 90으로 나누어지고, b와 c는 30과 90의 왼쪽과 오른쪽으로 나누어진다.

하지만 더블 스핀에서 가장 복잡한 것은 밸런스 팩터의 조정인데,
이 세 가지 위치가 모두 0이 아니며, -1로 업데이트되지도 않습니다.

여기에는 b에 새로운 것을 추가하는 것과 c에 새로운 것을 추가하는 두 가지 상황이 있습니다.
여기에 이미지 설명을 삽입하세요.

이는 단일 회전과 유사하며 단일 회전에서는 회전이 완료된 후 두 균형 요소가 0이 되어야 합니다.

이제 질문이 생깁니다. b 또는 c에 삽입할지 여부를 어떻게 알 수 있습니까?
식별하는 방법? b에 60bf를 넣으면 -1이고, c에 60bf를 넣으면 1이다. 여기서 상황을 파악할 수 있을 것 같다.
하지만 또 다른 상황이 있는데, b와 c에 삽입하는 대신 h가 0이면 어떻게 되는지 살펴보세요.

60 자체가 새로운 추가인 경우 60의 균형 요소는 0입니다.
여기에 이미지 설명을 삽입하세요.
이 세 가지 경우의 회전은 동일하지만 균형 요소의 업데이트는 다릅니다.

균형 요소에 대해 어떻게 생각하시나요?
먼저 녹음해 두지 않으면 나중에 업데이트할 수 없습니다.
여기에 이미지 설명을 삽입하세요.
subLR의 균형 요소를 살펴보겠습니다.
회전 후 이러한 노드의 균형 요소가 0으로 변경되므로 subLR의 균형 요소를 미리 기록합니다.
여기에 이미지 설명을 삽입하세요.

bf==0의 마지막 유형은 작성할 필요가 없지만 단일 회전이 조정되거나 변경되지 않는 경우 단일 회전에 의존하지 않습니다.
여기에 이미지 설명을 삽입하세요.

그렇다면 좌우 이중회전이란 언제를 의미하는 걸까요?
왼쪽이 더 높고 오른쪽이 더 높습니다.
여기에 이미지 설명을 삽입하세요.

void RotateLR(Node* parent)
{
    
    
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;

	RotateL(parent->_left);
	RotateR(parent);

	if (bf == 1)
	{
    
    
		parent->_bf = 0;
		subLR->_bf = 0;
		subL->_bf = -1;
	}
	else if (bf == -1)
	{
    
    
		parent->_bf = 1;
		subLR->_bf = 0;
		subL->_bf = 0;
	}
	else if (bf == 0)
	{
    
    
		parent->_bf = 0;
		subLR->_bf = 0;
		subL->_bf = 0;
	}
	else
	{
    
    
		assert(false);
	}
}

오른쪽 왼쪽 쌍선

좌우 이중회전은 모두가 도면에 따라 조금씩 분석해야 하는데, 논리와 과정은 위와 똑같습니다.

먼저 큰 프레임워크를 작성해 보겠습니다. 여기서의 프로세스는 동일합니다. subRL의 균형 요소( 1, -1, 0일 수 있음
여기에 이미지 설명을 삽입하세요.
)를 살펴봐야 합니다. 여기서는 c에서 새로운 추가만 그립니다. b에서는 새로운 것일 수 있고 60은 새로 추가되었습니다. 모두 모두 그려야 합니다.

여기에 이미지 설명을 삽입하세요.

업그레이드 보류 중
여기에 이미지 설명을 삽입하세요.

우리는 왜 추상적인 그림을 그리는가?
수많은 상황을 표현하기 때문이죠.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
60 부근에 새로 추가하면 회전이 발생합니다. 즉, 전체적으로 60에서 트리 높이가 변경되면
90이 업데이트되고, 90이 30으로 업데이트되고, 30의 위치가 회전하게 됩니다.

h = 2는 더 복잡합니다.
여기에 이미지 설명을 삽입하세요.
이러한 상황은 끝이 없습니다.특정 상황을 분석할 수는 없습니다.특정 상황을 분석하면 망가지고
소극적인 상황에 빠지게 되므로 이러한 상황을 분석하고 요약해야 합니다.

여기서 더 자세히 볼 수 있도록 안내하고 싶은 것은 h에 관계없이0,h1, h==2이면 모두 같은 동작을 하고,
복회전 동작도 동일하며, 먼저 90도에서 오른쪽 한바퀴 돌리고, 그 다음 30도에서 왼쪽 한바퀴 돌리면 끝이다.

여기에 이미지 설명을 삽입하세요.

단일 회전의 경우 균형 계수를 0으로 설정하는 것은 적절하지 않습니다.

다음으로 균형 요소에 대한 자세한 분석을 수행해야 합니다.
여기에는 세 가지 상황이 있습니다.
여기에 이미지 설명을 삽입하세요.
60 자체가 새로 추가된 경우 회전한 후에는 문제가 없습니다.
그러나 b에 삽입하는 경우 c에 삽입하는 경우는 별도로 처리해야 합니다.

이 전체 과정에서 우리는 이 세 가지 상황을 어떻게 구별할지에 대한 상세한 분석을 수행해야 하며
, 이 세 가지 상황을 분석하면 회전을 제어하는 ​​방법과 균형 요소를 처리하는 방법을 알 수 있습니다.

여기서 핵심은 회전이 60
에 의해 발생해야 하기 때문에 균형 요소 60을 관찰할 수 있다는 것입니다. 이 위치는 루트 포인트이며 이 위치는 90 업데이트, 30 업데이트를 발생시키고
최종적으로 회전을 발생시킵니다.

여기에 이미지 설명을 삽입하세요.
여기서 잘 제어하려면 회전 후에도 변하지 않기 때문에 기록해야 합니다. 모든
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
회전에는 특징이 있습니다. 회전이 균형 잡힌 트리가 되면 높이가 감소합니다.
따라서 높이를 낮추어도 상위 레이어에는 영향을 미치지 않으므로 회전 후에 깨질 수 있습니다.

그래서 로컬 서브트리이고 상위 레이어에는 아무런 영향을 주지 않으므로 회전이 완료된 후에 끝난다.

void RotateRL(Node* parent)
{
    
    
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	RotateR(parent->_right);
	RotateL(parent);

	if (bf == 1)
	{
    
    
		subR->_bf = 0;
		parent->_bf = -1;
		subRL->_bf = 0;
	}
	else if (bf == -1)
	{
    
    
		subR->_bf = 1;
		parent->_bf = 0;
		subRL->_bf = 0;
	}
	else if (bf == 0)
	{
    
    
		subR->_bf = 0;
		parent->_bf = 0;
		subRL->_bf = 0;
	}
	else
	{
    
    
		assert(false);
	}
}

시험

다음으로 AVL 트리를 테스트하겠습니다.
여기서는 두 가지 상황을 준비했습니다. 첫 번째 그룹은 일반 상황이고 두 번째 그룹은 더블 스핀이 포함되어 있습니다.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.
그 다음에는 중간 순서로 이동하겠습니다.
여기에 이미지 설명을 삽입하세요.
여기에 이미지 설명을 삽입하세요.

업그레이드 보류 중
그냥 실행해 보았는데 처음에는 문제가 없는 것 같았습니다.
여기에 이미지 설명을 삽입하세요.

하지만 이건 아무것도 설명할 수 없잖아?
이는 검색 트리라는 것만 증명할 수 있으며 검색과 AVL 트리 사이에는 여전히 거리가 있습니다.

이제 이진 트리가 균형 트리인지 판단해야 합니다.
어떻게 판단할까요?
이 분야에서는 여전히 빠른 판단을 내릴 수 있습니다.
여기에 이미지 설명을 삽입하세요.
이전 노드를 준 후에는 무엇을 해야 합니까?
우리는 현재 트리가 균형 트리인지 확인하고 싶습니다.
균형 요소를 한 번만 확인하는 것만으로는 충분하지 않습니다. 균형 요소를 직접 업데이트하기 때문에 잘못 업데이트하면 어떻게 될까요?
그래서 여기서 우리는 정직하게 높이를 추구하고 있습니다.

먼저 높이를 구하는 하위 함수를 작성해 보겠습니다.
여기에 이미지 설명을 삽입하세요.

빈 나무라면 균형 잡힌 나무여야 합니다.
빈 나무가 아닌 경우 왼쪽 나무의 높이와 오른쪽 나무의 높이를 구한 후 높이 차이를 계산합니다.
여기에 이미지 설명을 삽입하세요.

그런데 현재 재생 중인 균형 트리를 찾는 방법이 조건을 충족하지 않습니다.
다음 상황을 살펴보세요.
여기에 이미지 설명을 삽입하세요.
이 트리는 이전 검사에 따르면 AVL 트리와 일치
하지만 여기서는 일치하지 않습니다. 한 번의 클릭만으로 충분합니다
.AVL 트리는 현재 트리가 필요하지 않으며 모든 트리가 규칙을 준수해야 합니다.

모든 하위 트리를 확인하는 방법은 무엇입니까?
재귀를 사용할 수 있습니다.
그런 다음 각 노드가 들어갈 때 하위 트리를 계속 검사하기 때문에 추가 검사를 위해 왼쪽 및 오른쪽 하위 트리를 반복합니다.
여기에 이미지 설명을 삽입하세요.

여기에 이미지 설명을 삽입하세요.

여기에 이미지 설명을 삽입하세요.
결과를 보면 현재 트리는 일관성이 있습니다.

현재 트리는 비교적 단순합니다. 다른 장면을 살펴보겠습니다.
테스트 후에도 일관됩니다.

한 가지 더 해야 할 일이 있는데, AVL 트리는 괜찮은데 밸런스 팩터 업데이트에 문제가 있을 가능성이 있나요?

균형 요소가 잘못 업데이트되면 현 단계에서는 문제가 없지만 나중에 문제가 발생합니다. 간단한 예를 들어보겠습니다.
여기에 이미지 설명을 삽입하세요.
실수로 루트의 균형 요소를 0으로 업데이트했는데, 이로 인해 다음 문제가 발생합니다.
이 단계에서는 해당 트리가 AVL 트리인지 확인하고 높이를 확인하면 문제가 발견되지 않습니다.
여기에 이미지 설명을 삽입하세요.
아래 노드를 추가한 후 균형 요소를 업데이트하면 회전해야 할 때 회전하지 않거나
회전하지 말아야 할 때 회전하게 됩니다.
여기에 이미지 설명을 삽입하세요.
따라서 밸런스 팩터가 잘못되면 현재는 문제가 발생하지 않지만 나중에 큰 문제가 발생하게 됩니다.

그래서 우리는 균형 요소를 다시 확인해야 합니다.
여기에 이미지 설명을 삽입하세요.
균형 요소가 잘못 업데이트되면 현재 트리에는 문제가 없지만 나중에 큰 문제가 발생합니다.

하지만 이렇게 작성하면 트리가 AVL 트리라는 의미일까요?
방금 위에서 쓴 내용은 아무 것도 설명하지 않습니다.

우리는 무작위 값의 마지막 물결을 다시 테스트할 예정입니다.
업그레이드 보류 중

AVL 트리 디버깅은 조정이 어렵고
여기에 이미지 설명을 삽입하세요.
삽입할 때마다 다시 확인해야 하며 실제로 트리를 지속적으로 확인해야 하기 때문에 비용이 상당히 크고, 재귀를 사용하여 트리를 확인하는 속도도 상당히 느리다. .

마지막으로 한번 확인해 보겠습니다.
여기에 이미지 설명을 삽입하세요.
여러번 테스트 해보셔도 되고,
문제가 없다면 트리에는 큰 문제가 없습니다.
이렇게 수만 개의 값을 무작위로 삽입하면 기본적으로 모든 시나리오를 다룰 수 있습니다.

AVL 트리에도 검색과 삭제 기능이 있는데 검색과 검색 트리는 동일하므로 여기서는 쓰지 않겠습니다.
삭제에 대해서는 이야기하지 않겠습니다. 우리 뒤에 있는 빨간색과 검은색 나무도 마찬가지입니다.

삭제의 개념은 기본적으로 삽입의 개념과 유사하기 때문에
여전히 세 가지 큰 단계가 있습니다.
먼저 삭제할 노드를 찾습니다.
둘째, 균형 요소를 업데이트합니다.
셋째, 회전이 필요한지 확인하십시오.
그러나 삭제 프로세스는 삽입보다 더 복잡합니다.

우리가 주의해야 할 또 다른 점은 AVL 트리와 레드-블랙 트리를 학습하는 목적은
작업할 때 레드-블랙 트리를 손으로 찢는 것이 아니라는 점입니다.

이렇게 말하면, 우리는 데이터 구조를 배울 때만 대부분의 데이터 구조를 이해할 수 있습니다.
아마도 미래에는 우리 스스로 연결 리스트를 작성하거나 심지어 레드-블랙 트리를 스스로 작성하게 될 것입니다.
그러나 우리 중 99%는 그렇지 못할 것입니다. 이 나무는 손으로 찢을 수 있기 때문에 손으로 찢을 수 있습니다. 어떤 세부 사항에서는 신뢰할 수 없을 수도 있습니다.
그들은 모두 도서관에 있고 매우 성숙한 사람들이 많이 있습니다.


이를 연구하는 의미는 균형을 이루는 방법과 효율성을 제어하는 ​​방법을 이해해야 한다는 것입니다 .

업그레이드 보류 중

AVL 트리 성능

AVL 트리의 성능은 마지막 한두 개의 레이어가 약간 누락된다는 점을 제외하면 전체 이진 트리의 성능과 매우 유사합니다.
더 엄밀히 말하면 그 모양은 완전한 이진 트리와 약간 비슷합니다. 단, 완전한 이진 트리에서는 마지막 노드가 왼쪽에서 오른쪽으로 연속되어야 한다는 점이 다릅니다.
그렇지 않지만 누락된 노드는 거의 무시할 수 있습니다.

따라서 그 높이는 logN이고, 추가, 삭제, 수정의 효율성은 logN에 도달할 수 있는데, 이 효율성은 이미 매우 높습니다.
10억 개의 노드가 있고 최대 30번만 값을 검색할 수 있기 때문입니다.

추천

출처blog.csdn.net/weixin_68359117/article/details/135240124