二叉树遍历
先序:A B D H I G C E F
中序:H D I B G A E C F
后序:H I D G B E F C A
层次遍历:A B C D G E F H I
树的高度:4
叶子节点:H I G E F(结点的左右子树都为空)
深度优先遍历:相当于前序遍历(后入先出,使用栈模拟)
广度优先遍历:相当于层次遍历(先入先出,使用队列模拟)
<?php
//书的结构
class Node {
public $value;
public $child_left;
public $child_right;
}
//遍历树的类
final class Ergodic {
//前序遍历:先访问根节点,再遍历左子树,最后遍历右子树;并且在遍历左右子树时,仍需先遍历根节点,然后访问左子树,最后遍历右子树
public static function preOrder($root)
{
if ($root != null) {
echo $root->value, ' ';
self::preOrder($root->child_left);
self::preOrder($root->child_right);
}
}
//中序遍历:先遍历左子树、然后访问根节点,最后遍历右子树;并且在遍历左右子树的时候。仍然是先遍历左子树,然后访问根节点,最后遍历右子树
public static function midOrder($root)
{
if ($root != null) {
self::midOrder($root->child_left);
echo $root->value, ' ';
self::midOrder($root->child_right);
}
}
//后序遍历:先遍历左子树,然后遍历右子树,最后访问根节点;同样,在遍历左右子树的时候同样要先遍历左子树,然后遍历右子树,最后访问根节点
public static function endOrder($root)
{
if ($root != null) {
self::endOrder($root->child_left);
self::endOrder($root->child_right);
echo $root->value, ' ';
}
}
//层次遍历:按层遍历,从上到下,从左到右
public static function levelOrder($root)
{
$data = [];
array_push($data, $root);
while (!empty($data)) {
$tmp = array_shift($data);
if ($tmp != null) {
//先输出根节点
echo $tmp->value, ' ';
//左子树入队
if ($tmp->child_left != null) {
array_push($data, $tmp->child_left);
}
//右子树入队
if ($tmp->child_right != null) {
array_push($data, $tmp->child_right);
}
}
}
}
//递归法求二叉树的高度:计算左右子树的高度,取最大的高度
public static function treeDepth($root)
{
if ($root == null) {
return 0;
}
$leftHigh = self::treeDepth($root->child_left);
$rightHigh = self::treeDepth($root->child_right);
return max($leftHigh, $rightHigh) + 1;
}
}
1、二叉查找树
二叉查找树,也称二叉搜索树,或二叉排序树。其定义也比较简单,要么是一颗空树,要么就是具有如下性质的二叉树:
(1) 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2) 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3) 任意节点的左、右子树也分别为二叉查找树;
(4) 没有键值相等的节点。
查找、插入、删除 https://blog.youkuaiyun.com/justinzengtm/article/details/80310860
查找:
- 从根结点开始查找,如果树为空,就返回NULL。
- 如果树不空,就让数据X和根结点的数据Data作比较。
- 如果X的值大于根结点的Data,就往右子树中进行搜索;如果X的值小于根结点的Data,就往左子树中进行搜索;如果X的值等于Data,就表示查找完成,返回该结点。
插入:
对于二叉搜索树的插入,插入结点后应该仍然要保持树是二叉搜索树,也就是说插入的结点应该仍要保持该结点的左子树比它小,右子树比它大。
- 空树,就首先生成根节点;
- 不是空树就按照查找的算法,找到父节点,然后作为叶子节点插入,如果值已经存在就插入失败。
删除:
- 要删除的结点是叶结点,也就是没有左右子树。这样的情况只要把该结点的父结点指向NULL即可。
- 删除的结点有一个子树(左子树或右子树),这时删除该结点的方法就是让该结点的父结点指向该结点的子树即可。
- 要删除的结点有左右两个子树,这个时候我们要采取的方法就是:从左子树里找一个最大值来代替,或者从右子树里找一个最小值来代替。
平衡二叉树
平衡二叉搜索树,又被称为AVL树,且具有以下性质:
- 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1;
- 并且左右两个子树都是一棵平衡二叉树。
由于普通的二叉查找树会容易失去”平衡“,极端情况下,二叉查找树会退化成线性的链表,导致插入和查找的复杂度下降到 O(n) ,所以,这也是平衡二叉树设计的初衷。
查找:
同二叉查找树
插入:
在构建一棵平衡二叉树的过程中,当有新的节点要插入时,检查是否因插入后而破坏了树的平衡,如果是,则需要做旋转去改变树的结构。
-
左旋就是将节点的右支往左拉,右子节点变成父节点,并把晋升之后多余的左子节点出让给降级节点的右子节点;
-
而右旋就是反过来,将节点的左支往右拉,左子节点变成了父节点,并把晋升之后多余的右子节点出让给降级节点的左子节点。
-
即左旋就是往左变换,右旋就是往右变换。不管是左旋还是右旋,旋转的目的都是将节点多的一支出让节点给另一个节点少的一支。
删除:
删除了节点之后要维系二叉树的平衡,但是删除二叉树节点总结起来就两个判断:①删除的是什么类型的节点?②删除了节点之后是否导致失衡?
节点的类型有三种:1.叶子节点;2.只有左子树或只有右子树;3.既有左子树又有右子树。
针对这三种节点类型,再引入判断②,所以处理思路分别是:
(1)当删除的节点是叶子节点,则将节点删除,然后从父节点开始,判断是否失衡,如果没有失衡,则再判断父节点的父节点是否失衡,直到根节点,此时到根节点还发现没有失衡,则说此时树是平衡的;如果中间过程发现失衡,则判断属于哪种类型的失衡(左左,左右,右左,右右),然后进行调整。
(2)删除的节点只有左子树或只有右子树,这种情况其实就比删除叶子节点的步骤多一步,就是将节点删除,然后把仅有一支的左子树或右子树替代原有结点的位置,后面的步骤就一样了,从父节点开始,判断是否失衡,如果没有失衡,则再判断父节点的父节点是否失衡,直到根节点,如果中间过程发现失衡,则根据失衡的类型进行调整。
(3)删除的节点既有左子树又有右子树,这种情况又比上面这种多一步,就是中序遍历,找到待删除节点的前驱或者后驱都行,然后与待删除节点互换位置,然后把待删除的节点删掉,后面的步骤也是一样,判断是否失衡,然后根据失衡类型进行调整。
转自:https://blog.youkuaiyun.com/qq_25940921/article/details/82183093
AVL树删除节点时有可能因为失衡,导致需要从删除节点的父节点开始,不断的回溯到根节点,如果这棵平衡二叉树很高的话,那中间就要判断很多个节点。所以后来也出现了综合性能比其更好的树—-红黑树。
红黑树
它主要有两个特征:
- 节点都有颜色;
- 在插入和删除的过程中,要遵循保持这些颜色的不同排列的规则。
主要规则如下:
1. 每个节点不是红色就是黑色的;
2. 根节点总是黑色的;
3. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
4. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
在红-黑树中插入的节点都是红色的,这不是偶然的,因为插入一个红色节点比插入一个黑色节点违背红-黑规则的可能性更小。原因是:插入黑色节点总会改变黑色高度(违背规则4),但是插入红色节点只有一半的机会会违背规则3。另外违背规则3比违背规则4要更容易修正。当插入一个新的节点时,可能会破坏这种平衡性。
红-黑树主要通过三种方式对平衡进行修正:改变节点颜色、左旋和右旋。
转自:https://blog.youkuaiyun.com/eson_15/article/details/51144079