JS는 이진 검색 트리[추가, 삭제, 수정 및 쿼리]를 구현합니다(자세한 설명).

요약

데이터 구조의 출현이 어떤 문제를 해결할 수 있기 때문이라면 우리의 전통적인 선형 목록에는 배열과 연결 목록이 있는데 전자는 수정 및 확인에 더 큰 이점이 있고 후자는 추가 및 삭제에 더 큰 이점이 있습니다.
그러나 우리는 종종 이러한 기능을 응용 프로그램에서 사용해야 하며 배열 또는 연결 목록을 선택하는 데 약간의 결정성이 있습니다.
이진 검색 트리는 빠른 배열 수정 및 쿼리, 연결 목록 추가 및 삭제가 빠른 데이터 구조입니다.

이진 탐색 트리는 이진 트리의 특징을 가지고 있으며 각 노드의 왼쪽 하위 트리의 모든 노드는 현재 노드의 값보다 작고 오른쪽 하위 트리의 모든 노드는 현재 노드의 값보다 큽니다. 마디.

데이터 조작 및 검색 작업에서 매우 효율적으로 만드는 것은 바로 이 기능입니다.

이 기사에서는 주로 JS를 사용하여 이진 검색 트리를 구현합니다.
준비 작업은 이진 트리 클래스와 노드 클래스를 정의하는 것입니다.

function BST () {
    
    
  this.root = null;
}

function Node (val, left, right) {
    
    
  this.val = val;
  this.left = left;
  this.right = right;
}

1. 증가

노드 추가 작업을 구현하려면 다음과 같은 몇 가지 상황에서 작업을 수행해야 합니다.

1. 이진 트리가 비어 있습니다.

이 경우 root === null, 새로운 노드의 값을 root에게 직접 줄 수 있습니다.

2. 이진 트리에는 루트 노드가 하나만 있습니다.

이 경우 우리는 매우 잘 판단할 수 있으며 노드 노드와 루트 노드 중 어느 값이 더 큰지 판단하기만 하면 됩니다.
node.val > root.val : root.right = 노드
node.val < root.val : root.left = 노드

3. 이진 트리의 노드 수가 2보다 큽니다.

이 상황은 더 복잡하고 노드 추가의 어려움이기도 합니다.
이 문제에 대해 이런 식으로 생각할 수 있습니다. 루트 노드를 현재 노드로 사용합니다 .

현재 비어 있는 노드 의 값 과 새 노드 의 값을 비교합니다
. node.val > empty.val : empty = empty.right
node.val < empty.val : empty = empty.left

현재 노드에 자식 노드가 없을 때까지 새 노드가 현재 노드의 왼쪽 또는 오른쪽에 있는지 확인합니다.

여기에 이미지 설명 삽입
그림과 같이 노드 2를 이진 트리에 삽입하려면 위의 단계입니다.

BST.prototype.add = function (val) {
    
    
  let node = new Node(val, null, null);
  //情况1
  if (this.root === null) {
    
    
    return this.root = node
  }
  let empty = this.root;
  //循环直到empty的左子节点和右子节点都为null
  while (empty.right != null || empty.left != null) {
    
    
    if (val > empty.val) {
    
    
    //情况2
      if (empty.right == null) {
    
    
        empty.right = node;
        return;
      } else {
    
    
        empty = empty.right;
        where = 'right'
      }
    } else {
    
    
    //情况2
      if (empty.left == null) {
    
    
        empty.left = node;
        return;
      } else {
    
    
        where = 'left'
      }
      empty = empty.left;
    }
  }
  //判断最后新节点为empty的左子节点还是右子节点
  if (val > empty.val) {
    
    
    empty.right = node;
  } else {
    
    
    empty.left = node
  }
}

2. 확인

둘째, 요소가 존재하는지 여부를 확인하는 방법에 대해 이야기해 보겠습니다. 먼저 요소를 삭제하지 않는 이유는 무엇입니까? 노드 삭제의 전제는 노드를 찾는 것이므로 먼저 검색 방법에 대해 이야기합시다.

여기에 이미지 설명 삽입
예를 들어 노드 12를 찾고자 하는 경우 생각해보면 실제로는 비교적 간단합니다.
우리는 루트노드와 먼저 비교하고 12 > 5를 찾은
다음 루트노드의 오른쪽 노드와 비교하고 12 > 8을 찾은
다음 8의 오른쪽 노드와 비교하고 12 === 12만 찾으면 됩니다.

이것은 12개의 노드가 있고 true를 반환할 수 있음을 의미합니다.
여기에서 각 단계에 대한 솔루션이 동일하므로 재귀적 사고를 사용하여 해결합니다 .

BST.prototype.search = function (val) {
    
    
  const searchNode = function (node, val) {
    
    
  //两种情况下的递归终止条件,顺序不能错
    if (node === null) {
    
    
      return false
    }
    if (node.val === val) {
    
    
      return true
    }
    if (val > node.val) {
    
    
    //右子树递归
      return searchNode(node.right, val);
    } else {
    
    
    //左子树递归
      return searchNode(node.left, val);
    }
  }
  return searchNode(this.root, val)
}

3. 삭제

우리는 이미 노드를 찾는 방법을 알고 있습니다. 지금 우리가 하고 싶은 것은 이 노드를 삭제하는 것입니다. 이를 여러 상황으로 나누어 보겠습니다.

1. node.left === null && node.right === null

이 경우 삭제된 노드 노드에는 자식 노드가 없으므로 이 노드를 직접 삭제합니다 .

2. node.left === null || node.right === null

이 상황의 의미는 삭제된 노드 노드는 자식 노드가 하나만 있고 다른 자식 노드는 null이라는 것입니다.사실 이 상황도 매우 간단합니다.노드의 자식 노드를 노드 자체로 교체하기만 하면 됩니다 .

3. node.left != null && node.right != null

이 경우 좀 더 복잡한데, 삭제된 노드는 왼쪽 자식 노드와 오른쪽 자식 노드가 모두 존재하는데 이 경우 삭제 후에도 이진 탐색 트리의 특성이 변하지 않도록 해야 한다.

여기에 이미지 설명 삽입

예를 들어 노드 4를 삭제하면 이제 알고리즘을 고려하지 않고 상대적으로 저렴한 비용으로 이진 트리를 재구성하고 이진 트리가 여전히 이진 검색 트리의 속성을 갖도록 보장할 수 있습니다.

두 가지 상황을 볼 수 있는 것 같습니다.
여기에 이미지 설명 삽입
여기에 이미지 설명 삽입
이 두 상황의 차이점은 실제로 삭제할 노드, 가장 작은 왼쪽 하위 트리 또는 가장 큰 오른쪽 하위 트리가 있는 노드를 찾고 다음을 할당한다는 것 입니다. 이 노드의 값을 삭제한 다음 자체적으로 추방할 노드는 괜찮습니다.

따라서 가장 작은 왼쪽 하위 트리이든 가장 큰 오른쪽 하위 트리이든 방법을 선택할 수 있으므로 삭제 방법을 구현할 수 있습니다.

BST.prototype.remove = function (val) {
    
    

//找到要删除的节点
  const search = function (node, val) {
    
    
    if (val === node.left.val || val === node.right.val) {
    
    
      let parent = node.left.val === val ? node.left : node.right;
      let where = node.left.val === val ? 'left' : 'right';
      return [node, parent, where]
    }
    if (val > node.val) {
    
    
      return search(node.right, val)
    }
    if (val < node.val) {
    
    
      return search(node.left, val)
    }
  }
//保存要删除节点,要删除节点的父节点,已经删除的左右方向
  let [node, parent, where] = search(this.root, val)
  if (node) {
    
    
    if (parent.left == null && parent.right == null) {
    
    
      node[where] = null
    } else if (parent.left == null) {
    
    
      node[where] = parent.right;
    } else if (parent.right == null) {
    
    
      node[where] = parent.left;
    } else {
    
    
      let empty = parent
      //找到左子树中最小的节点
      while (empty.left.left != null) {
    
    
        empty = empty.left;
      }
      parent.val = empty.left.val;
      empty.left = null;
    }
  }
}

이를 달성하는 방법은 여러 가지가 있을 수 있습니다. 자신의 코드 습관을 살펴보세요 ^ - ^

4. 변화

위의 설명으로 수정하는 방법도 매우 간단하여 검색을 통해 현재 노드를 찾은 후 값을 교체하면 됩니다.
별로 할 말이 없습니다:

BST.prototype.replace = function (oldVal, newVal) {
    
    
  let node = this.root;
  const replaceNode = function (node) {
    
    
    if (node === null) {
    
    
      return false
    }
    if (node.val === oldVal) {
    
    
      return node.val = newVal;
    }
    if (node.val < oldVal) {
    
    
      replaceNode(node.right, oldVal);
    } else {
    
    
      replaceNode(node.left, oldVal)
    }
  }
  replaceNode(node);
}

여기에서 이진 검색 트리의 추가, 삭제, 수정 및 쿼리가 완료됩니다 ^ _ ^

추천

출처blog.csdn.net/weixin_46726346/article/details/120289424