数据结构与算法之美-14 |树和二叉树

一、树(Tree)

一.什么是树

树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

二.树的结构图

在这里插入图片描述
下图,A 节点就是 B 节点的父节点,B 节点是 A 节点的子节点。B、C、D 这三个节点的父节点是同一个节点,所以它们之间互称为兄弟节点。把没有父节点的节点叫做根节点,也就是图中的节点 E。我们把没有子节点的节点叫做叶子节点或者叶节点,比如图中的 G、H、I、J、K、L 都是叶子节点。
在这里插入图片描述
关于“树”,还有三个比较相似的概念:高度(Height)、深度(Depth)、层(Level)。
在这里插入图片描述

二、二叉树(Binary Tree)

二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只有左子节点,有的节点只有右子节点。
在这里插入图片描述
编号 2 的二叉树中,叶子节点全都在最底层,除了叶子节点之外,每个节点都有左右两个子节点,这种二叉树就叫做满二叉树
编号 3 的二叉树中,若设二叉树的深度为k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都连续集中在最左边,这种二叉树叫做完全二叉树,如果一棵二叉树是满二叉树, 则它必定是完全二叉树。

一.如何存储一棵二叉树

存储一棵二叉树,有两种方法,一种是基于指针或者引用的二叉链式存储法,一种是基于数组的顺序存储法。

1、基于链表的链式存储法

原文的图
在这里插入图片描述

借来的图
在这里插入图片描述
原文的图我觉得有歧义,但是画的比较形象,借来的图很清楚明确的表示出来了树的顺序。

2、基于数组的顺序存储法

在这里插入图片描述

3、为什么完全二叉树的子节点靠左而不靠右

如果某棵二叉树是一棵完全二叉树,那用数组存储无疑是最节省内存的一种方式。因为数组的存储方式并不需要像链式存储法那样,要存储额外的左右子节点的指针。

二.二叉树的遍历

1、前序遍历

前序遍历是指,对于树中的任意节点来说,先打印这个节点,然后再打印它的左子树,最后打印它的右子树。

2、中序遍历

中序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它本身,最后打印它的右子树。

3、后序遍历

后序遍历是指,对于树中的任意节点来说,先打印它的左子树,然后再打印它的右子树,最后打印这个节点本身。

三、二叉查找树(Binary Search Tree)

二叉查找树是二叉树中最常用的一种类型,也叫二叉搜索树。二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。

一.二叉查找树的常用操作

1、二叉查找树的查找操作

先取根节点,如果它等于我们要查找的数据,那就返回。如果要查找的数据比根节点的值小,那就在左子树中递归查找;如果要查找的数据比根节点的值大,那就在右子树中递归查找。


public class BinarySearchTree {
    
    
  private Node tree;

  public Node find(int data) {
    
    
    Node p = tree;
    while (p != null) {
    
    
      if (data < p.data) p = p.left;
      else if (data > p.data) p = p.right;
      else return p;
    }
    return null;
  }

  public static class Node {
    
    
    private int data;
    private Node left;
    private Node right;

    public Node(int data) {
    
    
      this.data = data;
    }
  }
}

2、二叉查找树的插入操作

从根节点开始,依次比较要插入的数据和节点的大小关系。如果要插入的数据比节点的数据大,并且节点的右子树为空,就将新数据直接插到右子节点的位置;如果不为空,就再递归遍历右子树,查找插入位置。同理,如果要插入的数据比节点数值小,并且节点的左子树为空,就将新数据插入到左子节点的位置;如果不为空,就再递归遍历左子树,查找插入位置。


public void insert(int data) {
    
    
  if (tree == null) {
    
    
    tree = new Node(data);
    return;
  }

  Node p = tree;
  while (p != null) {
    
    
    if (data > p.data) {
    
    
      if (p.right == null) {
    
    
        p.right = new Node(data);
        return;
      }
      p = p.right;
    } else {
    
     // data < p.data
      if (p.left == null) {
    
    
        p.left = new Node(data);
        return;
      }
      p = p.left;
    }
  }
}

3、二叉查找树的删除操作

删除操作比较复杂,分三种情况进行处理。

1.要删除的节点没有子节点

第一种最简单,如果要删除的节点没有子节点,我们只需要直接将父节点中,指向要删除节点的指针置为 null。

2.要删除的节点只有一个子节点

第二种也不复杂,如果要删除的节点只有一个子节点(只有左子节点或者右子节点),只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。

3.要删除的节点有两个子节点

如果要删除的节点有两个子节点,这就比较复杂了。首先要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上。然后再删除掉这个最小节点,因为最小节点肯定没有左子节点(如果有左子结点,那就不是最小节点了),所以,我们可以应用上面两条规则来删除这个最小节点。

用一句话来形容就是,如果该节点有左右子节点,需要去寻找右节点的最小子节点,替换要删除的节点。
在这里插入图片描述

4.代码


public void delete(int data) {
    
    
  Node p = tree; // p指向要删除的节点,初始化指向根节点
  Node pp = null; // pp记录的是p的父节点
  while (p != null && p.data != data) {
    
    
    pp = p;
    if (data > p.data) p = p.right;
    else p = p.left;
  }
  if (p == null) return; // 没有找到

  // 要删除的节点有两个子节点
  if (p.left != null && p.right != null) {
    
     // 查找右子树中最小节点
    Node minP = p.right;
    Node minPP = p; // minPP表示minP的父节点
    while (minP.left != null) {
    
    
      minPP = minP;
      minP = minP.left;
    }
    p.data = minP.data; // 将minP的数据替换到p中
    p = minP; // 下面就变成了删除minP了
    pp = minPP;
  }

  // 删除节点是叶子节点或者仅有一个子节点
  Node child; // p的子节点
  if (p.left != null) child = p.left;
  else if (p.right != null) child = p.right;
  else child = null;

  if (pp == null) tree = child; // 删除的是根节点
  else if (pp.left == p) pp.left = child;
  else pp.right = child;
}

4、代码判断一个二叉树是否平衡

二.支持重复数据的二叉查找树

二.叉查找树的时间复杂度分析

猜你喜欢

转载自blog.csdn.net/qq_38173650/article/details/114119710