【数据结构】二叉树的学习

二叉树(BinaryTree)


为什么要选择二叉树?

二叉树的出现是一种折中的选择.因为本人是做Java开发的,所以下面的举例都以Java为开发语言.

在Java的集合框架中,有两个常见的List,一个是ArrayList,一个是LinkedList.

ArrayList是基于数组实现的List,在数组中遍历元素很快,因为只需要获取下标就可以拿到元素,但是在增删元素的时候效率不好.设想一下,一个数组的中间插入一个元素,那么这个元素后面的元素都需要往后面挪一位,加上数组的扩容,在频繁增减元素的时候这个性能不好.

LinkedList是基于链表实现的List,里面的每个元素都会保存着上一个元素(prevNode)和下一个元素(nextNode)的内存地址,所以在增删元素方面有着很便捷的操作,只需要将插入位置的上一个元素的nextNode和下一个元素的prevNode指向插入的newNode,那么这个新的元素就加到链表中.但是这种结构有一个问题就是遍历效率不好,需要一直查找下一个元素的内存地址来遍历.

以上的问题在集合的元素数量少的时候并没有太大的区别,但是随着数据量增大的时候,性能问题开始出现.

那么,有没有什么数据接口可以平衡插入和遍历的性能呢?答案就是二叉树.


什么是二叉树?

树结构中每个节点(Node)最多只有左子树(left subtree)和右子树(right subtree)两个子节点的结构.
二叉树由这样的n(其中:n >= 0)个节点组成.所以,有可能是空集.

示意图

结构示意

二叉树术语

  • 深度: 二叉树最长的一条支的节点数
  • 根节点: 不为空的二叉树的第一层的节点

二叉树的常见操作

二叉树的实现

树是由一个个节点组成,那么先实现一个节点

final class Node<T> {
    //左子节点
    private Node<T> leftNode;
    //右子节点
    private Node<T> rightNode;
    //节点的值
    private T value;

    private Node() {}

    private Node(Node<T> leftNode, Node<T> rightNode, T value) {
        this.leftNode = leftNode;
        this.rightNode = rightNode;
        this.value = value;
    }

    public Node<T> getLeftNode() {
        return leftNode;
    }

    public void setLeftNode(Node<T> leftNode) {
        this.leftNode = leftNode;
    }

    public Node<T> getRightNode() {
        return rightNode;
    }

    public void setRightNode(Node<T> rightNode) {
        this.rightNode = rightNode;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Node {leftNode=" + leftNode + ", rightNode=" + rightNode + ", value=" + value + "}";
    }

}

二叉树的遍历

树的遍历主要分成两种,深度优先和广度优先。深度优先又分为三种: 前序遍历, 中序遍历, 后序遍历

假设当前有一个二叉树如下:

              a
            /   \
           b     c
          / \     \
         d   e     f
深度优先
前序遍历

前序遍历: 先遍历根节点,然后是左子节点,最后右子节点,即根左右

/**
 * 前序遍历DLR
 */
public static void dlr(Node<String> node) {
    if (node == null)
        return;
    //先是根节点    
    System.out.println(node.getValue());
    //然后左节点    
    dlr(node.getLeft());
    //最后右节点    
    dlr(node.getRight());
}
中序遍历

中序遍历: 先遍历左子节点,然后是根节点,最后是右子节点,即左根右

/**
 * 中序遍历LDR
 */
public static void ldr(Node<String> node) {
    if (node == null)
        return;
    //先是左节点
    ldr(node.getLeft());
    //然后根节点    
    System.out.println(node.getValue());
    //最后右节点    
    ldr(node.getRight());
}
后序遍历

后序遍历: 先遍历左子节点,然后是右子节点,最后是根节点

/**
 * 后序遍历LRD
 */
public static void lrd(Node<String> node) {
    if (node == null)
        return;
    //先是左节点  
    lrd(node.getLeft());
    //然后右节点    
    lrd(node.getRight()); 
    //最后根节点       
    System.out.println(node.getValue());
}
广度优先

广度优先其实就是一层一层的遍历,这个需要通过队列来实现

public static void lay(Node<String> node) {
    //这边的6是因为我测试的时候已经树的节点数而给的容量,实际应该维护树的节点数然后用这个树来初始化队列
    Queue<Node<String>> queue = new ArrayBlockingQueue<Node<String>>(6);
    System.out.println(node.getValue());
    if (node.getLeft() != null)
        queue.add(node.getLeft());
    if (node.getRight() != null)
        queue.add(node.getRight());
    Node<String> _node;
    while ((_node = queue.poll()) != null) {
        System.out.println(_node.getValue());
        if (_node.getLeft() != null)
            queue.add(_node.getLeft());
        if (_node.getRight() != null)
            queue.add(_node.getRight());
    }
}

二叉树的增删

因为此处的二叉树没有涉及到平衡等性质,所以新增和删除没有什么特殊的性质,可以任意添加和删除,将左右子节点直接进行赋值。


后续学习

接下来学习平衡二叉树、红黑树、N叉树。红黑树在HashMap中有用到,N叉树是数据库索引的一种实现,都可以学习看看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值