数据结构与算法—二叉排序(查找)树

简介: 前面介绍学习的大多是线性表相关的内容,把指针搞懂后其实也没有什么难度。规则相对是简单的。

前言



前面介绍学习的大多是线性表相关的内容,把指针搞懂后其实也没有什么难度。规则相对是简单的。


再数据结构中才是数据结构标志性产物,(线性表大多都现成api可以使用),因为树的难度相比线性表大一些并且树的拓展性很强,你所知道的树、二叉树二叉排序树AVL树线索二叉树红黑树、B数、线段树等等高级数据结构。然而二叉排序树是所有的基础,所以彻底搞懂二叉排序树也是非常重要的。




20190818004635190.png


参考王道数据结构


二叉树也是树的一种,而二叉排序树又是二叉树的一种。


  • 树是递归的,将树的任何一个节点以及节点下的节点都能组合成一个新的树。并且很多操作基于递归完成。
  • 根节点: 最上面的那个节点(root),根节点没有前驱节点,只有子节点(0个或多个都可以)
  • 层数: 一般认为根节点是第1层(有的也说第0层)。而树的高度就是层数最高(上图层数开始为1)节点的层数
  • 节点关系:父节点:就是链接该节点的上一层节点,孩子节点:和父节点对应,上下关系。而祖先节点是父节点的父节点(或者祖先)节点。兄弟节点:拥有同一个父节点的节点们!
  • 度: 节点的度就是节点拥有孩子节点的个数(是孩子不是子孙).而树的度(最大)节点的度。同时,如果度大于0就成为分支节点,度等于0就成为叶子节点(没有子孙)。


相关性质


  • 树的节点数=所有节点度数+1.
  • 度为m的树第i层最多有mi-1个节点。(i>=1)
  • 高度而h的m叉树最多(mh-1)/(m-1)个节点(等比数列求和)
  • n个节点的m叉树最小高度[logm(n(m-1)+1)]


二叉树



二叉树是一树的一种,但应用比较多,所以需要深入学习。二叉树的每个节点最多只有两个节点


二叉树与度为2的树的区别:


  • 一:度为2的的树必须有三个节点以上,二叉树可以为空。
  • 二:二叉树的度不一定为2:比如说斜树。
  • 三:二叉树有左右节点区分,而度为2的树没有左右节点的区分。


几种特殊二叉树:

  • 满二叉树。高度为n的满二叉树有2n-1个节点


20190818122648279.png


完全二叉树:上面一层全部满,最下一层从左到右顺序排列


20190818123246861.png


  • 二叉排序树:树按照一定规则插入排序(本文详解)。
  • 平衡二叉树:树上任意节点左子树和右子树深度差距不超过1.


二叉树性质:


相比树,二叉树的性质就是树的性质更加具体化。

  • 非空二叉树叶子节点数=度为2的节点树+1.本来一个节点如果度为1.那么一直延续就一个叶子,但如果出现一个度为2除了延续原来的一个节点,会多出一个节点需要维系。所以到最后会多出一个叶子
  • 非空第i层最多有2i-1个节点。
  • 高为h的树最多有2h-1个节点(等比求和)。
  • 完全二叉树若从左往右,从上到下编号如图:


20190818124810107.png


二叉排序(搜索)树



概念


前面铺垫那么多,咱们言归正传,详细实现一个二叉排序树。首先要了解二叉排序树的规则:

  • 从任意节点开始,节点左侧节点值总比节点右侧值要小。


例如。一个二叉排序树依次插入15,6,23,7,4,71,5,50会形成下图顺序


20190818130004540.png


构造


首先二叉排序树是由若干节点构成。


  • 对于node需要这些属性:left,right,和value。其中left和right是左右指针,而value是储存的数据,这里用int 类型。


node类构造为:


class node {//结点
  public int value;
  public node left;
  public node right;
  public node()
  {
  }
  public node(int value)
  {
    this.value=value;
    this.left=null;
    this.right=null;
  }
  public node(int value,node l,node r)
  {
    this.value=value;
    this.left=l;
    this.right=r;
  }     
}


既然节点构造好了,那么就需要节点等其他信息构造成树。有了链表构造经验,很容易得知一棵树最主要的还是root根节点

所以树的构造为:


public class BinarySortTree {
  node root;//根
  public BinarySortTree()
  {root=null;}
  public void makeEmpty()//变空
  {root=null;}
  public boolean isEmpty()//查看是否为空
  {return root==null;}
  //各种方法
}

20190818182950766.png


主要方法


  • 既然已经构造号一棵树,那么就需要实现主要的方法。因为二叉排序树中每个节点都能看作一棵树。所以我们创建方法的是时候加上节点参数(也就是函数对每一个节点都能有效)


findmax(),findmin()


findmin()找到最小节点:


  • 因为所有节点的最小都是往左插入,所以只需要找到最左侧的返回即可。


findmax()找到最大节点:


  • 因为所有节点大的都是往右面插入,所以只需要找到最右侧的返回即可。


代码使用递归函数


public node findmin(node t)//查找最小返回值是node,调用查看结果时需要.value
{
  if(t==null) {return null;}
  else if(t.left==null) {return t;}
  else return(findmin(t.left)); 
}
public node findmax(node t)//查找最大
{
  if(t==null) {return null;}
  else if(t.right==null) {return t;}
  else return(findmax(t.right));  
}

2019081818434559.png


isContains(int x)


这里的意思是查找二叉查找树中是否存在x。


  • 假设我们我们插入x,那么如果存在x我们一定会在查找插入路径的过程中遇到x。因为你可以如果已经存在的点,再它的前方会走一次和它相同的步骤。也就是说前面固定,我来1w次x,那么x都会到达这个位置。那么我们直接进行查找比较即可!


public boolean isContains(int x)//是否存在
{
  node current=root;
  if(root==null) {return false;}
  while(current.value!=x&&current!=null) 
  {
    if(x<current.value) {current=current.left;}
    if(x>current.value) {current=current.right;}
    if(current==null) {return false;}//在里面判断如果超直接返回
  }
  //如果在这个位置判断是否为空会导致current.value不存在报错
   if(current.value==x) {return true;}    
  return false;   
}


insert(int x)


插入的思想和前面isContains类似。找到自己的位置(空位置)插入。但是又不太一样。你可能会疑问为什么不直接找到最后一个空,然后将current赋值过去current=new node(x)。这样的化current就相当于指向一个new node(x)节点。和树就脱离关系,所以要提前判定是否为空,若为空将它的left或者right赋值即可。


public node insert(int x)// 插入 t是root的引用
{
  node current = root;
  if (root == null) {
    root = new node(x);
    return root;
  }
  while (current != null) {
    if (x < current.value) {
      if (current.left == null) {
        return current.left = new node(x);}
      else current = current.left;}
      else if (x > current.value) {
      if (current.right == null) {
        return current.right = new node(x);}
      else current = current.right;
    }
  }
  return current;//其中用不到
}


比如说上面结构插入51


20190818185941403.gif


delete(int x)


删除操作算是一个相对较难理解的操作了。

删除节点规则:


  • 先找到这个点。这个点用这个点的子树可以补上的点填充该点,然后在以这个点为头删除替代的子节点(调用递归)然后在添加到最后情况(只有一个分支,等等)。
  • 首先要找到移除的位置,然后移除的那个点分类讨论,如果有两个儿子,就选右边儿子的最左侧那个点替代,然后再子树删除替代的那个点。如果是一个节点,判断是左空还是右空,将这个点指向不空的那个。不空的那个就替代了这个节点。入股左右都是空,那么他自己变空null就删除了。


删除的节点没有子孙:


这种情况不需要考虑,直接删除即可。(途中红色点)。另节点=null即可。


20190819115353460.png


左节点为空、右节点为空:

  • 此种情况也很容易,直接将删除点的子节点放到被删除位置即可。


20190819003504637.png


左右节点均不空

  • 这种情况相对是复杂的。因为这涉及到一个策略问题


20190819120921321.png


  • 如果拿19或者71节点填补。虽然可以保证部分侧大于小于该节点,但是会引起合并的混乱.比如你若用71替代23节点。那么你需要考虑三个节点(19,50,75)之间如何处理,还要考虑他们是否满,是否有子女。这是个极其复杂的过程。
  • 首先,我们要分析我们要的这个点的属性:能够继承被删除点的所有属性。如果取左侧节点(例如17)那么首先能满足所有右侧节点都比他大(右侧比左侧大)。那么就要再这边选一个最大的点让左半枝都比它小。我们分析左支最大的点一定是子树最右侧
  • 如果这个节点是最底层我们很好考虑,可以直接替换值,然后将最底层的点删除即可。但是如果这个节点有左枝。我们该怎么办?
  • 这个分析起来也不难,用递归的思想啊。我们删除这个节点,用可以满足的节点替换了。会产生什么样的后果?


20190819124915450.png


  • 多出个用过的19节点,转化一下,在左子树中删除19的点!那么这个问题又转化为删除节点的问题,查找左子树中有没有能够替代19这个点的。

所以整个删除算法流程为:


20190819130135332.png


代码为


public node remove(int x, node t)// 删除节点
  {
    if (t == null) {
      return null;
    }
    if (x < t.value) {
      t.left = remove(x, t.left);
    } else if (x > t.value) {
      t.right = remove(x, t.right);
    } else if (t.left != null && t.right != null)// 左右节点均不空
    {
      t.value = findmin(t.right).value;// 找到右侧最小值替代
      t.right = remove(t.value, t.right);
    } else // 左右单空或者左右都空
    {
      if (t.left == null && t.right == null) {
        t = null;
      } else if (t.right != null) {
        t = t.right;
      } else if (t.left != null) {
        t = t.left;
      }
      return t;
    }
    return t;
  }


完整代码


二叉排序树完整代码为:


package 二叉树;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Stack;
public class BinarySortTree {
  class node {// 结点
    public int value;
    public node left;
    public node right;
    public node() {
    }
    public node(int value) {
      this.value = value;
      this.left = null;
      this.right = null;
    }
    public node(int value, node l, node r) {
      this.value = value;
      this.left = l;
      this.right = r;
    }
  }
  node root;// 根
  public BinarySortTree() {
    root = null;
  }
  public void makeEmpty()// 变空
  {
    root = null;
  }
  public boolean isEmpty()// 查看是否为空
  {
    return root == null;
  }
  public node findmin(node t)// 查找最小返回值是node,调用查看结果时需要.value
  {
    if (t == null) {
      return null;
    } else if (t.left == null) {
      return t;
    } else
      return (findmin(t.left));
  }
  public node findmax(node t)// 查找最大
  {
    if (t == null) {
      return null;
    } else if (t.right == null) {
      return t;
    } else
      return (findmax(t.right));
  }
  public boolean isContains(int x)// 是否存在
  {
    node current = root;
    if (root == null) {
      return false;
    }
    while (current.value != x && current != null) {
      if (x < current.value) {
        current = current.left;
      }
      if (x > current.value) {
        current = current.right;
      }
      if (current == null) {
        return false;
      } // 在里面判断如果超直接返回
    }
    // 如果在这个位置判断是否为空会导致current.value不存在报错
    if (current.value == x) {
      return true;
    }
    return false;
  }
  public node insert(int x)// 插入 t是root的引用
  {
    node current = root;
    if (root == null) {
      root = new node(x);
      return root;
    }
    while (current != null) {
      if (x < current.value) {
        if (current.left == null) {
          return current.left = new node(x);}
        else current = current.left;}
        else if (x > current.value) {
        if (current.right == null) {
          return current.right = new node(x);}
        else current = current.right;
      }
    }
    return current;//其中用不到
  }
  public node remove(int x, node t)// 删除节点
  {
    if (t == null) {
      return null;
    }
    if (x < t.value) {
      t.left = remove(x, t.left);
    } else if (x > t.value) {
      t.right = remove(x, t.right);
    } else if (t.left != null && t.right != null)// 左右节点均不空
    {
      t.value = findmin(t.right).value;// 找到右侧最小值替代
      t.right = remove(t.value, t.right);
    } else // 左右单空或者左右都空
    {
      if (t.left == null && t.right == null) {
        t = null;
      } else if (t.right != null) {
        t = t.right;
      } else if (t.left != null) {
        t = t.left;
      }
      return t;
    }
    return t;
  }
}


结语



  • 这里我们优先学习了树,二叉树,以及二叉搜素树的基本构造。对于二叉搜素树插入查找比较容易理解但是实现的时候要注意函数对参数的引用等等。需要认真考虑。
  • 而偏有难度的是二叉树的删除,利用一个递归的思想,要找到特殊情况和普通情况,递归一定程度也是问题的转化(转成自己相同问题,作用域减小)需要思考。
  • 下面还会介绍二叉搜素树的三序遍历(递归和非递归).和层序遍历。需要的朋友请持续关注。另外,笔者数据结构专栏欢迎查房。!


目录
相关文章
|
27天前
|
算法
数据结构之博弈树搜索(深度优先搜索)
本文介绍了使用深度优先搜索(DFS)算法在二叉树中执行遍历及构建链表的过程。首先定义了二叉树节点`TreeNode`和链表节点`ListNode`的结构体。通过递归函数`dfs`实现了二叉树的深度优先遍历,按预序(根、左、右)输出节点值。接着,通过`buildLinkedList`函数根据DFS遍历的顺序构建了一个单链表,展示了如何将树结构转换为线性结构。最后,讨论了此算法的优点,如实现简单和内存效率高,同时也指出了潜在的内存管理问题,并分析了算法的时间复杂度。
45 0
|
19天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
42 5
|
1月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
71 16
|
1月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
91 8
|
1月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
97 7
|
1月前
|
算法
树的遍历算法有哪些?
不同的遍历算法适用于不同的应用场景。深度优先搜索常用于搜索、路径查找等问题;广度优先搜索则在图的最短路径、层次相关的问题中较为常用;而二叉搜索树的遍历在数据排序、查找等方面有重要应用。
36 2
|
27天前
|
算法
数据结构之文件系统模拟(树数据结构)
本文介绍了文件系统模拟及其核心概念,包括树状数据结构、节点结构、文件系统类和相关操作。通过构建虚拟环境,模拟文件的创建、删除、移动、搜索等操作,展示了文件系统的基本功能和性能。代码示例演示了这些操作的具体实现,包括文件和目录的创建、移动和删除。文章还讨论了该算法的优势和局限性,如灵活性高但节点移除效率低等问题。
44 0
|
2月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
29 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
2月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
35 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
2月前
|
Java C++
【数据结构】探索红黑树的奥秘:自平衡原理图解及与二叉查找树的比较
本文深入解析红黑树的自平衡原理,介绍其五大原则,并通过图解和代码示例展示其内部机制。同时,对比红黑树与二叉查找树的性能差异,帮助读者更好地理解这两种数据结构的特点和应用场景。
37 0