二叉树
二叉树(Binary tree)是每个节点最多只有两个分支(即不存在分支度大于2的节点)的树结构。通常分支被称作“左子树”或“右子树”。二叉树的分支具有左右次序,不能随意颠倒。
二叉树的存储方式是以节点和节点间以链的方式连接在一起的。可以看作是链表结构的升级版,因为链表是只有一个指针指向下一个节点,而二叉树是可以有两个向下的指针的。其实可以把链表理解为二叉树的一个极端情况(二叉树的每一层都只存在左子树或只存在右子树)。
二叉搜索树
定义
二叉搜索树(Binary Search Tree),也称为二叉查找树、有序二叉树(ordered binary tree)或 排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值。
- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值。
- 任意节点的左、右子树也分别为二叉查找树。
二叉排序树的查找时间复杂度在O(nlogn) 到O(n) 之间。
例如:
属性及方法
二叉树节点实例的属性及方法
因为是节点和指针组成的树结构,所以对该树的任何操作,本质就是对树中的节点和节点与节点间关系的操作,可以为节点定义以下属性和方法。
节点值(node.value)
左子节点(node.left)
右子节点(node.right)
父节点(node.parent)
树高(node.height)
左子树高(node.leftHeight)
右子树高(node.rightHeight)
设置左子节点(node.setLeft(node))
设置右子节点(node.setRight(node))
设置节点值(node.setValue(value))
删除子节点(node.removeChild(value))
替换子节点(node.replaceChild(nodeToReplace, replacementNode))
复制节点(node.copyNode(node))
节点有序遍历,以中序遍历为例(node.traverseInOrder())
搜索二叉树节点实例的特有实例方法
因为搜索二叉树是满足某些条件的二叉树,所以在继承上面二叉树的这些基础节点的属性和方法外,还依据自身的特点定义一些操作方法。
插入(node.insert(value))
删除(node.remove(value))
查找(node.find(value))
查找最小值(node.findMin())
操作
搜索二叉树主体思想就是小的节点在左,大的在右。以当前节点为依据,要操作的值比当前节点值大(小),就在当前节点的右(左)分支进行操作,如果右(左)节点也有分支,就进行递归操作直至获取到目标结果。
插入(insert)
-
判断当前节点是否有值,如果没有值,设置当前节点的值(
node.setValue(value))为传入的参数。 -
如果当前节点有值,让新增的值与当前节点的值进行比较:
如果新增值大于(小于)当前节点值,且当前节点没有右(左)子节点,则创建一个二叉搜索树节点实例作为当前节点的右(左)子节点。如果当前节点存在右(左)子节点,调用右(左)子节点的
insert方法,传入比较的参数,递归与下一分支树进行比较,最终确认新增节点位置。
例如:
向空二叉搜索树中依次插入2,1,3,7,4,6
第一步: 插入第一个节点2。
第二步: 需要新插入1。判断得新值1比2小,插入到节点2的左子节点位置。
第三步: 需要新插入3。判断得新值3比2大,插入到节点2的右子节点位置。
第四步: 需要新插入7。首先判断当前节点2比7小,所以再判断2的右子节点值和7的大小,3比7小,且3没有右子节点,就把7设置为3的右子节点。
第五步: 需要新插入4。首先判断当前节点2比4小,所以再判断2的右子节点值和4的大小,3比4小,然后再判断3的右子节点和4的大小,7大于4,且7没有左子节点,就把4设置为7的左子节点。
第六步: 需要新插入6。首先判断当前节点2比6小,所以再判断2的右子节点值和6的大小,3比6小,然后再判断3的右子节点和6的大小,7大于6,然后判断7的左子节点4与6大小,6大于4,且4没有右子节点,就把6设置为4的右子节点。
以上所有插入操作完成。
其实以上这个例子,完成插入后可以看出,这个树向右偏的很严重,对查找的性能是右影响下的,所以出现了平衡二叉树以优化二叉搜索树。
查找(find)
-
将当前节点值与入参查找值进行比较,如果相等,返回当前节点。
-
如果不相等,则比较当前节点的值与入参查找值的大小。
如果当前节点值大(小)于查找值,且当前节点存在右(左)子节点,则调用右(左)子节点的
find方法,传入查找值,进行新一轮查找,以此递归下去,如果找到目标节点就会返回,找不到返回null。
例如:
在以上树中,查找是否存在值为5的节点。
第一步: 判断当前节点值是否和5相等,20不等于5且大于5,所以把当前节点20的左子节点递归查找。
第二步: 判断当前节点值是否和5相等,这里相等,直接返回。
查找最小值(findMin)
- 判断当前节点有无左子节点,如果没有,返回当前节点。
- 如果当前节点存在左子节点,调用左子节点的
findMin方法。
删除(remove)
-
根据入参查找(
find)获取到待删除的节点。如果查不到抛出异常。 -
待删除的节点,通常有三种情况:
A. 待删除的节点是叶子节点,也就是说其没有左子节点也没有右子节点。
i:如果待删除的节点是根节点,也就是没有父节点(
parent=== null),直接抹去当前节点的值即可(node.setValue(undefined))ii:如果待删除的节点存在父节点,直接把其父节点指向该删除节点的指针移除掉即可(
nodeToRemove.parent.removeChild(nodeToRemove))。B. 待删除的节点只有一个子节点。
i:如果是待删除节点是根节点,把子节点的值、左节点和右节点复制给根节点即可(
nodeToRemove.parent.copyNode(childNode, nodeToRemove))。ii:如果待删除节点存在父节点,就把父节点指向待删除节点的指针指向待删除节点的子节点即可(
nodeToRemove.parent.replaceNode(nodeToRemove, childNode))。C. 待删除节点同时存在左子节点和右子节点。
i:如果待删除节点的右子节点只有右子节点(
nodeToRemove.right === nodeToRemove.right.findMin()),就把待删除节点的右子节点值替换删除节点的值,待删除节点的右子节点指针向其右节点的右节点即可。ii:如果待删除节点的右节点存在左子节点,递归找到待删除节点右子分支里最左边的值,也就右子分支里最小的值,替换为待删除节点的值。
删除叶子节点示例
删除该二叉搜索树中值为5的节点。
第一步: 找到值为5的节点,把指向该删除节点的其父节点的指针删除即可。
删除只有一个子节点的示例
例一:
删除二叉搜索树中值为20的节点。
第一步: 找到值为20的节点。
第二步: 把指向删除节点的其父节点的指针指向待删除节点的子节点即可。
例二:
删除二叉搜索树中值为10的节点。
第一步: 找到值为10的节点,这里是根节点。
第二步: 把根节点的值,左节点,右节点都替换为其子节点即可。
删除存在两个子节点的节点
示例一:
删除二叉搜索树中值为20的节点。
第一步: 找到值为20的节点。
第二步: 待删除的节点左右子分支都存在,找右子子分支里面最小的值的节点。
第三步: 用最小节点的值替换要删除的节点的值,然后删掉最小节点。
示例二:
删除二叉搜索树中值为25的节点。
第一步: 找到值为25的节点。
第二步: 待删除节点存在左右子分支,通过调用node.right.finMind()方法得知,右分支的最小节点就等于待删除节点的右节点。
第三步: 把右子节点替换待删除节点即可。
节点有序遍历(traverseInOrder)
搜索二叉树,是左小于中小于右,可以用中序遍历按顺序返回树的节点数组。
例如:
按顺序遍历输出该二叉搜索树。
第一步: 从根节点10开始查找,判断当前节点有无左子节点,这里有左子节点5,然后再递归判断5是否左子节点,没有就把5存入数组第一位。
第二步: 值为5的节点没有右子节点了,该节点已遍历完成,再向上一层对其父节点操作。
第三步: 值为10的节点存入数组第二位。
第四步: 值为10的节点存在右子节点,再将其右子节点按相同的遍历方法遍历,找到最小节点。
第五步: 把最小节点放入数组中。
第五步: 把最小节点父节点放入数组中。
第六步: 把右子节点放入数组中,有序遍历结束。