두 갈래 트리 생성, 삽입, 삭제
22061 단어 프로그램 알고리즘 기반
두 갈래 트리 생성, 삽입, 삭제
두 갈래 나무가 뭐예요?
우리는 각 노드에 최대 두 개의 하위 노드가 있는 나무를 두 갈래 나무, 즉 영어의 Binary Tree라고 부른다.우리는 이 두 개의 노드를 각각 왼쪽 노드와 오른쪽 노드라고 부른다.일반적으로 두 갈래 나무의 노드는 다음과 같다.
다음 JavaScript 코드를 사용하여 두 갈래 트리의 노드를 나타낼 수 있습니다.
class BinaryTreeNode {
constructor(data, leftChild, rightChild) {
this.data = data;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
};
두 갈래 나무의 정의에 따라 우리는 다음과 같은 두 가지 특성을 쉽게 얻을 수 있다.
이 특성은 수학 귀납법으로 결론을 얻을 수 있다.
이 결론은 두 갈래 나무의 각 노드가 가장 많은 노드 수를 가설할 수 있다. 2, 그러면 높이가 h인 두 갈래 나무의 총 노드 포인트는 1+2+4...+2h-1이다. 이것은 간단한 등비수열이기 때문에 2h-1과 같다
두 갈래 나무의 이 두 가지 기초 특성을 유연하게 활용하면 두 갈래 나무와 관련된 알고리즘 문제를 비교적 편리하게 해결할 수 있다.
두 갈래 트리 만들기
두 갈래 나무를 만드는 데는 여러 가지 방식이 있다. 우리는 여기서 이러한 전제 가설을 한다. 하나의 수조를 정하고 층 순서에 따라 완전한 두 갈래 나무를 만드는 것이다.
완전 두 갈래 나무: 두 갈래 나무 중 마지막 층을 제외한 모든 층을 완전히 채우고, 마지막 층은 가능한 한 왼쪽 나무를 채운다
두 갈래 트리 알고리즘을 만드는 핵심은 노드와 하위 노드의 대응 관계를 어떻게 확정하는가이다.완전 두 갈래 나무의 정의에 따르면 마지막 층의 모든 노드를 제외하고 두 개의 하위 노드가 있다는 결론을 얻을 수 있다.
수조에서 인덱스가 i인 원소의 왼쪽 노드는 수조에서 인덱스가 (2*i)+1인 원소이고, 오른쪽 노드는 인덱스가 (2*i)+2인 원소이다
두 갈래 트리를 만드는 알고리즘은 다음과 같이 JavaScript로 표시됩니다.
function createCompleteBinaryTreeFromArray(dataArray) {
let treeNodeArray = dataArray.map(function(data){
return new BinaryTreeNode(data, null, null);
});
for (let i = 0; i < treeNodeArray.length; ++i) {
let node = treeNodeArray[i];
node.leftChild = treeNodeArray[i * 2 + 1];
node.rightChild = treeNodeArray[i * 2 + 2];
}
return treeNodeArray[0];
};
설명해야 할 것은 여기에서 먼저 Array의map 방법을 호출하여 데이터 수조를 이차수 노드 수조로 전환한 다음에 노드 수조에 노드와 하위 노드의 대응 관계를 설정하여 이차수를 만드는 목적을 달성한다. 이때 노드 수조의 첫 번째 요소는 이차수의 뿌리 노드이다.
두 갈래 트리 노드 삽입하기
기존의 두 갈래 나무에 새 노드를 삽입하는 관건은 삽입할 위치를 어떻게 찾느냐에 있다. 만약 삽입된 두 갈래 나무가 어떤 순서를 유지해야 한다면 추가 처리가 필요하다.여기서 우리는 간단한 상황을 고려한다. 데이터를 주고 층 순서에 따라 적당한 위치를 찾아 새 노드를 삽입하는 것이다.
이른바 층 순서란 넓이를 우선하는 방식에 따라 두 갈래 나무를 훑어보는 순서이다
넓이를 우선적으로 두 갈래 나무를 훑어보면 경험이 있는 프로그래머는 데이터 구조인 대기열을 떠올린다.대기열은 일종의 선입선출(FIFO) 데이터 구조로 우리는 순서대로 노드를 대기열에 넣고 방문할 때 노드를 대기열에서 제거하는 방식으로 광범위한 우선순위를 실현할 수 있다.방문할 때 첫 번째 왼쪽 노드나 오른쪽 노드가 비어 있는 노드만 찾으면 새 노드를 이 노드의 왼쪽 노드나 오른쪽 노드로 삽입할 수 있습니다.알고리즘에 대한 간단한 설명은 다음과 같습니다.
function insertInLevelOrder(root, data) {
let visitNodeQueue = [];
visitNodeQueue.push(root);
while(visitNodeQueue.length != 0)
{
let currentNode = visitNodeQueue.shift();
if (!currentNode.leftChild){
currentNode.leftChild = new BinaryTreeNode(data, null, null);
break;
}
else if (!currentNode.rightChild){
currentNode.rightChild = new BinaryTreeNode(data, null, null);
break;
} else {
visitNodeQueue.push(currentNode.leftChild);
visitNodeQueue.push(currentNode.rightChild);
}
}
return root;
}
두 갈래 트리 노드 삭제
두 갈래 나무의 노드를 삭제하려면 보통 두 가지 절차가 필요하다. 하나, 삭제할 노드를 찾아라.둘째, 노드를 삭제한 후 두 갈래 나무의 완전성을 확보해야 한다.첫 번째 단계는 이해하기 쉽다. 두 번째 단계는 삭제할 노드가 잎 노드가 아니라면 삭제한 후에 처리하지 않으면 두 갈래 나무 구조가 파괴되기 때문이다. 그래서 우리는 노드를 삭제한 후의 구조를 두 갈래 나무 구조라고 복원할 방법을 강구해야 한다.상대적으로 두 번째 절차의 실현은 좀 복잡하기 때문에 우리는 다른 방식으로 이 두 번째 절차를 회피하고 싶지만 노드를 삭제하는 조작을 실현할 수 있다.
우리는 만약 삭제된 노드가 잎 노드라면 별도의 처리를 하지 않아도 삭제된 두 갈래 나무인지 두 갈래 나무인지 보장할 수 있다는 것을 안다.그래서 우리는 삭제 조작을 잎 노드에 대한 삭제라고 바꿀 수 있다. 알고리즘은 문자로 다음과 같이 설명한다.
알고리즘 두 번째 단계에서 우리는 적당한 잎 노드가 마지막 층에서 가장 오른쪽에 있는 잎 노드라고 가정할 수 있다. 마찬가지로 우리는 넓이를 우선하는 방식으로 이 잎 노드를 찾을 수 있다.전체 삭제 알고리즘은 다음과 같이 Javascript로 설명됩니다.
function deleteTreeNode(root, data) {
if (root == null) {
return null;
}
if (root.leftChild == null && root.rightChild == null) {
if (root.data === data) {
return null;
}
return root;
}
let nodeToDeleted = null;
let nodeDeepest = null;
let visitNodeQueue = [root];
while (visitNodeQueue.length != 0) {
nodeDeepest = visitNodeQueue.shift();
if (nodeDeepest.data == data) {
nodeToDeleted = nodeDeepest;
}
if (nodeDeepest.leftChild) {
visitNodeQueue.push(nodeDeepest.leftChild);
}
if (nodeDeepest.rightChild) {
visitNodeQueue.push(nodeDeepest.rightChild);
}
}
if (nodeToDeleted) {
nodeToDeleted.data = nodeDeepest.data;
deleteDeepestTreeNode(root, nodeDeepest);
}
return root;
}
위 코드에서 우리는 두 갈래 트리를 넓게 우선적으로 훑어보고 삭제할 노드 (node To Deleted) 와 교체할 잎 노드 (node Deepest) 를 지정합니다.이 두 노드를 포지셔닝한 후에 해야 할 일은 노드를 삭제할 데이터를 이 잎 노드라고 하는 데이터, 즉 이 줄 코드로 바꾸는 것이다.
nodeToDeleted.data = nodeDeepest.data;
그 다음에 잎 노드, 즉 이 줄 코드를 삭제합니다.
deleteDeepestTreeNode(root, nodeDeepest);
deleteDeepestTreeNode 이 함수는 잎 노드를 삭제하는 데 사용됩니다. 이 함수는 다음과 같습니다.
function deleteDeepestTreeNode(root, treeNode) {
let visitNodeQueue = [];
visitNodeQueue.push(root);
while(visitNodeQueue.length != 0) {
let currentNode = visitNodeQueue.shift();
if (currentNode == treeNode) {
// only one node in this tree, and this node is to be deleted
return null;
}
if (currentNode.leftChild) {
if (currentNode.leftChild == treeNode) {
currentNode.leftChild = null;
return root;
} else {
visitNodeQueue.push(currentNode.leftChild);
}
}
if (currentNode.rightChild){
if (currentNode.rightChild == treeNode) {
currentNode.rightChild = null;
return root;
} else {
visitNodeQueue.push(currentNode.rightChild);
}
}
}
return root;
}
delete Deepest TreeNode의 핵심도 두 갈래 나무를 우선적으로 훑어보고 삭제할 잎 노드의 부모 노드를 찾습니다. 잎 노드가 이 아버지 노드의 왼쪽 노드나 오른쪽 노드라면 왼쪽 노드나 오른쪽 노드를 인용하면 됩니다.
총결산
이상의 알고리즘을 실현함으로써 우리는 트리와 같은 비선형 구조를 반복할 때 대기열로 넓이를 우선하는 반복을 자주 실현하고 교묘한 알고리즘을 실현했다.유사한 것은 창고를 통해 깊이를 우선적으로 트리나 그림을 훑어보는 것도 있으니 나중에 소개해 드리겠습니다.