二分查找法
我们先来介绍一个搜索方法,接下来的二叉搜索树的处理会用到这个方法。 注意:
- 二分查找法一定要用在有序的数列中。
目标:
在有序的数组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;
}
二分搜索树
数据结构
二分搜索树的数据结构是字典数据结构(查找表/表结构)
如下图:
二分搜索树
其实就是二叉树。看下图:
二分搜索树的性质:
每一个节点的左右孩子,左边小,右边大。
我们知道堆的数据结构就是一个完全二叉树,二分搜索树不一定是个完全二叉树。所以不能使用数组的数据结构,而是使用表结构更加合理。
插入一个节点
如果我们想往下面的二分搜索树中插入一个60,应该如何插入?
思路:
- 插入的数据首先应该与跟节点比较。
- 比根节点大,就会插入到这个节点的右边节点
- 如果有右边节点就会与右边节点继续进行比较。
- 如果没有就会插入。
- 如果在查找过程中,发现有相等的数据,直接覆盖。 下图:
- 61比41要大,放在41右边。
- 61比58还要大,然后放在58的右边。
- 发现58下面只有一个左边节点,直接插入到58的右子节点。
- 如果在查找过程中,发现有相等的数据,直接覆盖。
这样就完成了一个插入的操作。 思路我们清楚了之后,来看看我们的代码
// 以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 }
}
总结
我们现在懂得了二叉搜索树的插入和搜索操作。
接下来我们会继续研究二叉搜索树,关于他的前中后序遍历。