数据结构之树学习笔记

数据结构之树学习笔记-java

目录

  • 数据结构之树学习笔记-java
  • 一级目录
    • 二级目录
      • 三级目录
    • 什么是树
    • 树的实现
    • 什么是二叉树及其实现
    • 二叉树的遍历-深度优先搜索
    • 广度优先搜索(BFS)
    • 二叉查找树
      • 基础方法实现-插入
      • 基础方法实现-搜索
      • 基础方法实现-删除
    • AVL树-平衡二叉查找树
      • 平衡方法-单旋转
      • 平衡方法-双旋转
      • 什么时候单旋转,什么时候使用双旋转平衡
    • 二叉查找树-平均时间复杂度分析

一级目录

二级目录

三级目录

什么是树

参考博文

树(tree)是被称为结点(node)的实体的集合。结点通过边(edge)连接。每个结点都包含值或数据(value/date),并且每结节点可能有也可能没有子结点。

树的首结点叫根结点(即root结点)。如果这个根结点和其他结点所连接,那么根结点是父结点(parent node,与根结点连接的是子结点(child node)。

所有的结点都通过边(edge)连接。它是树中很重要得一个概念,因为它负责管理节点之间的关系。

叶子结点(leaves)是树末端,它们没有子结点。

树的高度(height)和深度(depth)

  • 树的高度是到叶子结点(树末端)的长度
  • 结点的深度是它到根结点的长度

树的实现

class TreeNode{
    object element;
    TreeNode firstChild;
    TreeNode nextchild;
}

并不是将所有的子节点都指向父节点,因为每个节点的子节点可能很多而且未知,这样很难实现而且浪费空间。

我们采用上面的方式,父节点只记录第一个子节点。再子节点之间做链表来记录

树的结构用到的很少,我们常用到的是二叉树

什么是二叉树及其实现

在计算机科学领域,二叉树是一种树形数据结构,它的每个节点最多有两个孩子,被叫作左孩子和右孩” —  Wikipedia

class BinaryTree {
    public BinaryTree left; //左节点
    public BinaryTree right; //右节点
    public String data;  //树的内容
    }

二叉树的遍历-深度优先搜索

简称DFS(Depth-First Search)

DFS又分为前序、中序、后序

前序:

  1. 输出节点的值
  2. 进入其左结点并输出。当且仅当它拥有左结点。
  3. 进入右结点并输出之。当且仅当它拥有右结点
/**
     * 前序遍历
     *
     * @param node
     */
    public static void preOrder(BinaryTree node) {
        if (node != null) {

            System.out.println(node.data);

            if (node.left != null) {
                node.left.preOrder(node.left);
            }

            if (node.right != null) {
                node.right.preOrder(node.right);
            }
        }
    }

中序:

  1. 进入左结点并输出之。当且仅当它有左结点。
  2. 输出根结点的值。
  3. 进入结节点并输出之。当且仅当它有结节点。

代码实现:

/**
     * 中序遍历
     *
     * @param node
     */
    public static void inOrder(BinaryTree node) {
        if (node != null) {
            if (node.left != null) {
                node.left.inOrder(node.left);
            }

            System.out.println(node.data);

            if (node.right != null) {
                node.right.inOrder(node.right);
            }
        }
    }

后序:

  1. 进入左结点输出,
  2. 进入右结点输出
  3. 输出根结点
/**
     * 后序遍历
     *
     * @param node
     */
    public static void postOrder(BinaryTree node) {
        if (node != null) {
            if (node.left != null) {
                node.left.postOrder(node.left);
            }

            if (node.right != null) {
                node.right.postOrder(node.right);
            }

            System.out.println(node.data);
        }
    }

广度优先搜索(BFS)

  1. 首先用add方法将根结点添加到队列中。
  2. 当队列不为空时迭代。
  3. 获取队列中的第一个结点,然后输出其值
  4. 将左节点和右结点添加到队列
  5. 在队列的帮助下我们将每一个结点值一层层输出
/**
     * 广度排序
     *
     * @param node
     */
    public static void bfsOrder(BinaryTree node) {
        if (node != null) {
            Queue queue = new ArrayDeque();
            queue.add(node);

            while (!queue.isEmpty()) {
                BinaryTree current_node = queue.poll();

                System.out.println(current_node.data);

                if (current_node.left != null) {
                    queue.add(current_node.left);
                }
                if (current_node.right != null) {
                    queue.add(current_node.right);
                }
            }
        }
    }

二叉查找树

二叉查找树有时候被称为二叉有序树或二叉排序树,二叉搜索树的值存储在有序的顺序中,因此,查找表和其他的操作可以使用折半查找原理。——Wikipedia

二叉查找树中一个节点的值大于其左子树所有节点,大于右子树所有节点

基础方法实现-插入

  1. 新结点值大于当前节点还是小于当前结点?
  2. 如果新节点的值大于当前结点,则转到右结点。如果当前节点没有右结点,则在那里插入新结点,否则返回步骤1。
  3. 如果新节点的值小于当前结点,则转到左结点。如果当前节点没有左结点,则在那里插入新结点,否则返回步骤1。
  4. 这里我们没有处理特殊情况。当新节点的值等于结点的当前值时,使用规则3。考虑在子结点的左侧插入相等的值。

代码实现:

/**
     * 插入树
     *
     * @param node
     * @param value
     */
    public void insertNode(BinaryTree node, Integer value) {
        if (node != null) {
            if (value <= Integer.valueOf(node.data) && node.left != null) {
                node.left.insertNode(node.left, value);
            } else if (value <= Integer.valueOf(node.data)) {
                node.left = new BinaryTree(String.valueOf(value));
            } else if (value > Integer.valueOf(node.data) && node.right != null) {
                node.right.insertNode(node.right, value);
            } else {
                node.right = new BinaryTree(String.valueOf(value));
            }
        }
    }

看起来很简单。

该算法的强大之处是其递归部分,即第9行和第13行。这两行代码均调用 insertNode 方法,并分别为其左结点和右结点使用它。第11行和第15行则在子结点处插入新结点。

基础方法实现-搜索

  1. 我们以根结点作为当前节点开始。给定值小于当前结点值吗?如果是,那么我将在左子树上查找它。
  2. 给定值大于当前结点值吗?如果是,那么我们将在右子树上查找它。
  3. 如果规则 #1 和 #2 均为假,我们可以比较当前节点值和给定值是否相等,一般也可以直接返回true。

代码实现:

 public boolean findNode(BinaryTree node, Integer value) {
        if (node != null) {
            if (value < Integer.valueOf(node.data) && node.left != null) {
                return node.left.findNode(node.left, value);
            }
            if (value > Integer.valueOf(node.data) && node.right != null) {
                return node.right.findNode(node.right, value);
            }
            return value == Integer.valueOf(node.data);
            //returen true;
        }
        return false;
    }

基础方法实现-删除

  1. 判断有几个子节点。
  2. 若没有子节点,直接删除。
  3. 若仅有一个子节点,使节点的父节点指向子节点。
  4. 若有两个孩子的节点,则需要从该节点的右子节点开始,找到具有最小值的节点。我们将把具有最小值的这个节点置于被删除的节点的位置。

代码实现:

    /**
     * 删除节点
     * @param node
     * @param value
     * @param parent
     * @return
     */
    public boolean removeNode(BinaryTree node, Integer value, BinaryTree parent) {
        if (node != null) {
            if (value < Integer.valueOf(node.data) && node.left != null) {
                return node.left.removeNode(node.left, value, node);
            } else if (value < Integer.valueOf(node.data)) {
                return false;
            } else if (value > Integer.valueOf(node.data) && node.right != null) {
                return node.right.removeNode(node.right, value, node);
            } else if (value > Integer.valueOf(node.data)) {
                return false;
            } else {
                if (node.left == null && node.right == null && node == parent.left) {
                    parent.left = null;
                    node.clearNode(node);
                } else if (node.left == null && node.right == null && node == parent.right) {
                    parent.right = null;
                    node.clearNode(node);
                } else if (node.left != null && node.right == null && node == parent.left) {
                    parent.left = node.left;
                    node.clearNode(node);
                } else if (node.left != null && node.right == null && node == parent.right) {
                    parent.right = node.left;
                    node.clearNode(node);
                } else if (node.right != null && node.left == null && node == parent.left) {
                    parent.left = node.right;
                    node.clearNode(node);
                } else if (node.right != null && node.left == null && node == parent.right) {
                    parent.right = node.right;
                    node.clearNode(node);
                } else {
                    node.data=String.valueOf(node.right.findMinValue(node.right));
                    node.right.removeNode(node.right,Integer.valueOf(node.right.data),node);
                }
                return true;
            }
        }
        return false;
    }

AVL树-平衡二叉查找树

定义:每个节点的左子树和右子树的高度最多差一的二叉查找树。

当不满足定义时,需要使用平衡方法使其满足,也就是平衡一棵树。我们做的只是一次插入或者删除的平衡。

代码实现

    private static class AvlNode{
        AvlNode(AnyType theElement)
        {this(theElement,null,null);}
        AvlNode(AnyType theElement,AvlNode lt,AvlNode rt)
        {element =theElement;left = lt;right=rt;height=0;}
        AnyType element;
        AvlNode left;
        AvlNode right;
        int height;
    }
    private  int height(AvlNode t){
        return t=null?-1:t.height;
    }

平衡方法-单旋转

单旋转本质就是把老子节点作为新根节点上,然后把老跟节点作为新跟节点的子节点。

单旋转又分为左旋转和右旋转,这是根据孙子节点和根节点的大小区分的,如果孙子节点比根节点小,那么不平衡的点一定是在跟节点的左节点。这时候把左子节点作为跟节点,把老跟节点作为他的右节点,把老左节点的右子树作为新右节点的左子树,这样似乎就是以左子节点为轴,将根节点顺时针左旋转,右旋转也是相同的道理。

上面这段话是对《数据结构与算法分析》(java)第四章第四小节的理解,书中有图。这里嫌麻烦就不画了。

左旋转代码实现

    private AvlNode rotateLeft(AvlNode root){
        AvlNode newRoot=root.left;
        root.left=newRoot.right;
        newRoot.right=root;
        root.height=Math.max(height(root.left),height(root.right))+1;
        newRoot.height=Math.max(height(newRoot.left),root.height)+1;
        return newRoot;
    }

右旋同理

平衡方法-双旋转

双旋转其实就是做两次单旋转。有时候一次单旋转不能解决问题,需要使用两次,使用情况下面说。

双旋转又分为左右旋转和右左旋转,就是根据两次单旋转的方向。

右左旋转代码实现

private AvlNode doubleRotateRightleft(AvlNode root){
    root.left=rotateRight(root.left);
    return rotateLeft(root);
}

什么时候单旋转,什么时候使用双旋转平衡

如果根节点的左子树的高度比右子树的高度大于1且根节点左节点的左节点大于右节点的高度

使用左旋转

或者根节点的右子树的高度比左子树的高度大于1且根节点右节点的右节点大于左节点的高度时,

使用右旋转。

如果根节点的左子树的高度比右子树的高度大于1且根节点左节点的左节点小于右节点的高度

使用右左旋转

或者根节点的右子树的高度比左子树的高度大于1且根节点右节点的右节点小于左节点的高度时,

使用左右旋转。

具体原因可见《数据结构与算法分析》(java)第四章第四小节

二叉查找树-平均时间复杂度分析

由上面的搜索实现可知。搜索的时间复杂度为O(d),d为数据节点深度。

经过一系列计算d的平均值=logN,n为数据整数。

具体计算可见《数据结构与算法分析》(java)第四章第三小节

你可能感兴趣的:(数据结构之树学习笔记)