二叉搜索树(一,插入与搜索)

157 阅读4分钟

二分查找法

我们先来介绍一个搜索方法,接下来的二叉搜索树的处理会用到这个方法。 注意:

  • 二分查找法一定要用在有序的数列中。

目标:

在有序的数组arr中,查找 target 如果找到 target,返回相应的索引 idnex 思路:

  • 在一个数组中,[left...right]查找target。
  • 二分查找其实很简单,就是找到中间的索引值,然后判断target比中间的值大还是小。
  • 如果等于中间的值,返回 index 即可。
  • 如果小于中间的值,接下来应该在[left...index-1]中查找,正好抛弃掉index。
  • 如果大于中间的值,在[index+1...right]中查找,也是正好抛弃掉index。
  • 判断都不满足,直接返回-1。
function binarySrerch(arr, int, target) {
    // 在 arr [lett ... right] 之中查找target    
    let left = 0    
    let right = n - 1    
    while (left <= right) {        
    // 取中间的index,        
    // let index = (left + right) / 2       
    // 上面这步是存在BUG的,因为数太大会造成溢出问题        
        let index = left + (right - left) / 2   
        if (arr[index] === target) { 
            return index;        
        }        
        if (target < arr[index]) {           
            // 在arr[left ... index-1]中查找target 
            right = index - 1       
        } else {            
            // 在arr[index+1 ... right]中查找target            
            left = index + 1        
        }    
    }    
    return -1;
}

二分搜索树

数据结构

二分搜索树的数据结构是字典数据结构(查找表/表结构) 如下图: image.png

二分搜索树

其实就是二叉树。看下图: image.png 二分搜索树的性质:

每一个节点的左右孩子,左边小,右边大。

我们知道堆的数据结构就是一个完全二叉树,二分搜索树不一定是个完全二叉树。所以不能使用数组的数据结构,而是使用表结构更加合理。

插入一个节点

如果我们想往下面的二分搜索树中插入一个60,应该如何插入?

image.png 思路:

  • 插入的数据首先应该与跟节点比较。
  • 比根节点大,就会插入到这个节点的右边节点
  • 如果有右边节点就会与右边节点继续进行比较。
  • 如果没有就会插入。
  • 如果在查找过程中,发现有相等的数据,直接覆盖。 下图:
  • 61比41要大,放在41右边。
  • 61比58还要大,然后放在58的右边。
  • 发现58下面只有一个左边节点,直接插入到58的右子节点。
  • 如果在查找过程中,发现有相等的数据,直接覆盖。 image.png 这样就完成了一个插入的操作。 思路我们清楚了之后,来看看我们的代码
// 以node为根节点的二叉搜索树,插入节点(key,value)
// 返回插入新节点后的二叉搜索树
class Node {
    key
    value
    left
    right
    root = null
    count = 0
    constructor(key, value) {
        this.key = key
        this.value = value
        this.left = this.right = null
    }
    size() {
        return this.count;
    }
    isEmpty() {
        return this.count === 0;
    }
    insert(key, value) {
        this.root = this._insert(this.root, key, value)
    }
    contain(key) {
        return this._contain(this.root, key)
    }
    // 以 node 为根的二叉搜索树,插入节点 (key, value)
    // 返回插入新节点后的二叉搜索树的根
    _insert(node, key, value) {
        if (node === null) {
            this.count++
            return new Node(key, value)
        }
        if (value === node.key) {
            node.key = value
        } else if (value < node.key) {
            node.left = this._insert(node.left, key, value)
        } else {
            node.right = this._insert(node.right, key, value)
        }
    }
    // 以node为根的二叉搜索树中是否包含键值为key的节点
    // 搜索的思路是和插入一样的
    _contain(node, key) {
        if (node === null) {
            return false
        }
        if (key in node) {
            return true
        } else {
            return this._contain(node.left, key) || this._contain(node.right, key)
        }
    }
}
// 数据结构是这样的
/*
    可能需要 插入/查找 到某个key对应的位置
*/
let node = {
    a: 1,
    left: { b: 2, left: null, right: null },
    right: { c: 3, left: null, right: null }
}

上面的可能看不懂,请看下面的代码。

  • 下面的代码只是用来举个例子
function _contain(node, key) {
    if (node === null) {
        return false
    }
    if (key in node) {
        return true
    } else {
        return this._contain(node.left, key) || this._contain(node.right, key)
    }
}
let node = {
    a: 1,
    left: { b: 2, left: null, right: null },
    right: { c: 3, left: null, right: null }
}
console.log(_contain(node, "b")) // true
console.log(_contain(node, "d")) // false

我们知道传入node查找是否存在一个key应该怎么做,那传入一个value是否能够找到对应的值?

其实传入value进行搜素,就和插入是一个道理。你都能找到value插入的位置,难道还不能找到对应的value值么?

我们来看代码(包含之前的插入等代码,为了不需要翻上面找):

// 以node为根节点的二叉搜索树,插入节点(key,value)
// 返回插入新节点后的二叉搜索树
class Node {
    key
    value
    left
    right
    root = null
    count = 0
    constructor(key, value) {
        this.key = key
        this.value = value
        this.left = this.right = null
    }
    size() {
        return this.count;
    }
    isEmpty() {
        return this.count === 0;
    }
    insert(key, value) {
        this.root = this._insert(this.root, key, value)
    }
    contain(key) {
        return this._contain(this.root, key)
    }
    search(value) {
        return this._search(this.root, value)
    }
    // 以 node 为根的二叉搜索树,插入节点 (key, value)
    // 返回插入新节点后的二叉搜索树的根
    _insert(node, key, value) {
        if (node === null) {
            this.count++
            return new Node(key, value)
        }
        if (value === node.key) {
            node.key = value
        } else if (value < node.key) {
            node.left = this._insert(node.left, key, value)
        } else {
            node.right = this._insert(node.right, key, value)
        }
    }
    // 以node为根的二叉搜索树中是否包含键值为key的节点
    // 搜索的思路是和插入一样的
    _contain(node, key) {
        if (node === null) {
            return false
        }
        if (key in node) {
            return true
        } else {
            return this._contain(node.left, key) || this._contain(node.right, key)
        }
    }
    // 在以node为根的二叉搜索树中查找是否存在value值
    _search(node, value) {
        if (node === null) {
            return null
        }
        if (value === node.key) {
            return node.key
        } else if (value < node.key) {
            return this._search(node.left, value)
        } else {
            return this._search(node.right, value)
        }
    }
}
// 数据结构是这样的
/*
    可能需要 插入/查找 到某个key对应的位置
*/
let node = {
    a: 1,
    left: { b: 2, left: null, right: null },
    right: { c: 3, left: null, right: null }
}

总结

我们现在懂得了二叉搜索树的插入和搜索操作。

接下来我们会继续研究二叉搜索树,关于他的前中后序遍历。