热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java数据结构与算法:二叉树

原文链接:http:www.cnblogs.comskywang12345p3576452.html1.二叉查找树简介二叉查找树(BinarySearchTree

原文链接:http://www.cnblogs.com/skywang12345/p/3576452.html


1. 二叉查找树简介

二叉查找树(Binary Search Tree),又被称为二叉搜索树。

它是特殊的二叉树&#xff1a;对于二叉树&#xff0c;假设x为二叉树中的任意一个结点&#xff0c;x节点包含关键字key&#xff0c;节点x的key值记为key[x]。如果y是x的左子树中的一个结点&#xff0c;则key[y] <&#61; key[x]&#xff1b;如果y是x的右子树的一个结点&#xff0c;则key[y] >&#61; key[x]。那么&#xff0c;这棵树就是二叉查找树。如下图所示

二叉树

在二叉查找树中&#xff1a;


  • 若任意节点的左子树不空&#xff0c;则左子树上所有结点的值均小于它的根结点的值
  • 任意节点的右子树不空&#xff0c;则右子树上所有结点的值均大于它的根结点的值
  • 任意节点的左、右子树也分别为二叉查找树
  • 没有键值相等的节点&#xff08;no duplicate nodes&#xff09;

2. 二叉查找树的Java实现


2.1 二叉查找树节点的定义

public class BSTree<T extends Comparable<T>> {private BSTNode mRoot; // 根结点public class BSTNode<T extends Comparable<T>> {T key; // 关键字(键值)BSTNode left; // 左孩子BSTNode right; // 右孩子BSTNode parent; // 父结点public BSTNode(T key, BSTNode parent, BSTNode left, BSTNode right) {this.key &#61; key;this.parent &#61; parent;this.left &#61; left;this.right &#61; right;}}......
}

BSTree是二叉树&#xff0c;它保护了二叉树的根节点mRoot&#xff1b;mRoot是BSTNode类型&#xff0c;而BSTNode是二叉查找树的节点&#xff0c;它是BSTree的内部类。BSTNode包含二叉查找树的几个基本信息&#xff1a;


  • key – 它是关键字&#xff0c;是用来对二叉查找树的节点进行排序的。
  • left – 它指向当前节点的左孩子。
  • right – 它指向当前节点的右孩子。
  • parent – 它指向当前节点的父结点。

2.2 遍历

这里讲解前序遍历、中序遍历、后序遍历3种方式。


2.2.1 前序遍历

若二叉树非空&#xff0c;则执行以下操作&#xff1a;


  • 访问根结点&#xff1b;
  • 先序遍历左子树&#xff1b;
  • 先序遍历右子树。

前序遍历代码

private void preOrder(BSTNode tree) {if(tree !&#61; null) {System.out.print(tree.key&#43;" ");preOrder(tree.left);preOrder(tree.right);}
}public void preOrder() {preOrder(mRoot);
}

2.2.2 中序遍历

若二叉树非空&#xff0c;则执行以下操作&#xff1a;
(01) 中序遍历左子树&#xff1b;
(02) 访问根结点&#xff1b;
(03) 中序遍历右子树。

中序遍历代码

private void inOrder(BSTNode tree) {if(tree !&#61; null) {inOrder(tree.left);System.out.print(tree.key&#43;" ");inOrder(tree.right);}
}public void inOrder() {inOrder(mRoot);
}

2.3 后序遍历

若二叉树非空&#xff0c;则执行以下操作&#xff1a;
(01) 后序遍历左子树&#xff1b;
(02) 后序遍历右子树&#xff1b;
(03) 访问根结点。

后序遍历代码

private void postOrder(BSTNode tree) {if(tree !&#61; null){postOrder(tree.left);postOrder(tree.right);System.out.print(tree.key&#43;" ");}
}public void postOrder() {postOrder(mRoot);
}

看看下面这颗树的各种遍历方式&#xff1a;

二叉树

对于上面的二叉树而言&#xff0c;


  • 前序遍历结果&#xff1a; 3 1 2 5 4 6
  • 中序遍历结果&#xff1a; 1 2 3 4 5 6
  • 后序遍历结果&#xff1a; 2 1 4 6 5 3

2.4 层序遍历

所谓层序遍历(Levelorder Traversal)二叉树&#xff0c;是指从二叉树的第一层(根结点)开始&#xff0c;自上至下逐层遍历&#xff0c;在同一层中&#xff0c;则按从左到右的顺序对结点逐个访问。对于右图所示的二叉树&#xff0c;按层序遍历方式进行遍历所得到的结点序列为&#xff1a;A、B、C、D、E、F、G、H、I。

二叉树


3. 二叉树的存储结构


3.1 数组表示法

二叉树的数组表示就是采用一组连续存储空间存储二叉树结点中的数据元素&#xff0c;利用数组下标来反映数据元素之间的关系。

对具有n个结点的完全二叉树按从上到下、自左向右的顺序连续给结点编号0、1、2、…、n-1。按此结点编号将二叉树中各结点中的数据元素顺序地存放于一个一维数组中&#xff0c;首先将根结点中的数据元素储存在数组的0号位置&#xff1b;对于二叉树中任一个结点&#xff0c;如果它的数据元素存储在数组的第i个位置&#xff0c;就把它的左、右孩子结点中的数据元素分别存放在数组的第2*i&#43;1个位置和第2*i&#43;2个位置。这样就得到了二叉树的一种数组表示法。

采用这种方法表示一般的二叉树时&#xff0c;空间利用效率低是一个主要的问题。当被表示的二叉树结构很不完整时&#xff0c;在数组中就会出现很多空位置&#xff0c;因此空间浪费就变得非常大。

二叉树

用这种方法表示二叉树时&#xff0c;还有一个问题需要注意的是&#xff1a;必须处理结点不存在的情况。如果一个结点不存在&#xff0c;就必须在数组中相应位置设置一个特殊标志&#xff0c;指明在这个位置没有结点。

二叉树

二叉树的二叉链表表示&#xff0c;对于大多数的应用来说是适合的。但是&#xff0c;在二叉链表中要想找出一个结点的双亲是比较困难的&#xff0c;必须通过二叉树的遍历才能实现。如果在应用中需要方便地找到任何一个结点的双亲&#xff0c;可以在结点中增加一个Parent域来指向该结点的双亲&#xff0c;二叉树的这种表示方法称为三叉链表。

二叉树


3.2 链表表示法

在二叉树的链表表示中&#xff0c;树中的每一个元素用一个结点表示&#xff0c;结点一般包括三个域&#xff0c;其结构如图(a)所示。其中&#xff0c;Data域用于存放数据元素的信息&#xff1b;leftChild域用于存放指向其左孩子结点的指针&#xff1b;rightChild域用于存放指向其右孩子结点的指针。二叉树的这种链表表示称为二叉链表。

二叉树


4. 二叉树实现

中序遍历是有序的二叉树(不重复)

public class MyTree
{
private Node root; // 根节点private class Node{Node parrent; // 父节点Node left; // 左儿子Node right; // 右儿子Object data;public Node(Object data) {this.data &#61; data;}}/*** &#64;param data* 传递的数据* &#64;return 父节点的值*/private Node findParrent(Object data, Node currentNode) {// 从根节点找Node temp &#61; currentNode;Node parrent &#61; currentNode;// 循环找while (temp !&#61; null) {parrent &#61; temp;// 比较if (compare(data, temp.data)) {// data 大于 当前节点temp &#61; temp.right;} else {// data 小于 当前节点temp &#61; temp.left;}}return parrent;}public void update(Object oldData,Object newData){remove(oldData);add(newData);}/*** 添加数据* * &#64;param data* 要添加的数据*/public void add(Object data) {// 判断该数据是否存在if (contains(data))return;// 1.把数据放到节点中Node node &#61; new Node(data);// 2.把节点链接到二叉树中// 是否有根节点if (root &#61;&#61; null) {root &#61; node;// 保存到根节点中} else {// 找位置,找父节点,比较父节点的值&#xff0c;小左边 大右边Node parrent &#61; findParrent(data, root);// 设置新增节点的父节点node.parrent &#61; parrent;// 比较if (compare(data, parrent.data)) {// 自己比父节点大parrent.right &#61; node;} else {// 自己比父节点小parrent.left &#61; node;}}}/*** &#64;param data* &#64;return 是否包含该数据*/public boolean contains(Object data) {return null !&#61; find(data);}private Node find(Object data) {Node temp &#61; root;// 从根节点找while (temp !&#61; null) {// 判断数据if (temp.data.equals(data)&& temp.data.hashCode() &#61;&#61; data.hashCode()) {// 找到数据break;} else if (compare(data, temp.data)) {// true data > temp// 从右边找temp &#61; temp.right;} else {// false data // 从坐标边找temp &#61; temp.left;}}return temp;}public void remove(Object data) {// 1. 查找数据是否存在Node temp &#61; find(data);// 2. 存在&#xff1a;找到数据节点if (temp !&#61; null) {// 存在// 3. 删除节点// 1. 根节点if (temp &#61;&#61; root) {// 11 没有儿子if (temp.left &#61;&#61; null && temp.right &#61;&#61; null) {root &#61; null;} else if (temp.right &#61;&#61; null) {root &#61; root.left;root.parrent &#61; null;// 12 只有左儿子} else if (temp.left &#61;&#61; null) {// 13 只有右儿子root &#61; root.right;root.parrent &#61; null;} else {// 14 两个儿子都有// 保留左儿子Node left &#61; getLeft(temp);// left成为新的根节点root &#61; left;left.parrent &#61; null;}} else {// 2. 非根节点if (temp.left &#61;&#61; null && temp.right &#61;&#61; null) {// 21 没有儿子if (compare(temp.data, temp.parrent.data)) {//在父节点右边temp.parrent.right &#61; null;} else {//在父节点左边temp.parrent.left &#61; null;}} else if (temp.right &#61;&#61; null) {// 22 只有左儿子if (compare(temp.data, temp.parrent.data)) {//在父节点右边temp.parrent.right &#61; temp.left;temp.left.parrent &#61; temp.parrent;} else {//在父节点左边temp.parrent.left &#61; temp.left;temp.left.parrent &#61; temp.parrent;}} else if (temp.left &#61;&#61; null) {// 23 只有右儿子if (compare(temp.data, temp.parrent.data)) {//在父节点右边temp.parrent.right &#61; temp.right;temp.right.parrent &#61; temp.parrent;} else {//在父节点左边temp.parrent.left &#61; temp.right;temp.right.parrent &#61; temp.parrent;}} else {// 24 两个儿子都有Node left &#61; getLeft(temp);//上面还有父节点&#xff08;爷爷&#xff09;if (compare(left.data, temp.parrent.data)) {//比爷爷节点大temp.parrent.right &#61; left;left.parrent &#61; temp.parrent;} else {//比爷爷节点小temp.parrent.left &#61; left;left.parrent &#61; temp.parrent;}}}}}/*** &#64;param node* 要删除的节点* &#64;return 左儿子节点*/private Node getLeft(Node node) {// 保留左儿子Node left &#61; node.left;// 处理右节点Node rightNewParrent &#61; findParrent(node.right.data, left);rightNewParrent.right &#61; node.right;// 把删除节点的右节点放到删除节点的左儿子最右边node.right.parrent &#61; rightNewParrent;return left;}/*** &#64;param o1* 第一个值* &#64;param o2* 第二个值* &#64;return 如果o1 大于 o2 返回true 否则false*/public boolean compare(Object o1, Object o2) {boolean res &#61; false;// 判断o1 有没有实现比较器if (o1 instanceof Comparable) {Comparable c1 &#61; (Comparable) o1;Comparable c2 &#61; (Comparable) o2;if (c1.compareTo(c2) > 0) {res &#61; true;} else {// 默认值就是false}} else {// 传递的对象没有比较器res &#61; o1.toString().compareTo(o2.toString()) > 0 ? true : false;}return res;}// 递归打印public void print() {print(root);}public void print(Node node) {if (node &#61;&#61; null) {return;} else {// 遍历 中序print(node.left);System.out.println(node.data &#43; ",");print(node.right);}}}

public class TestTreeApp
{
public static void main(String[] args) {MyTree trees &#61; new MyTree();int[] datas &#61; {55,33,44,88,66,99};for (int d : datas) {trees.add(d);}trees.print();System.out.println();//测试删除trees.update(33,77);trees.print();}}

数据结构与算法


推荐阅读
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文详细探讨了KMP算法中next数组的构建及其应用,重点分析了未改良和改良后的next数组在字符串匹配中的作用。通过具体实例和代码实现,帮助读者更好地理解KMP算法的核心原理。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • Java 类成员初始化顺序与数组创建
    本文探讨了Java中类成员的初始化顺序、静态引入、可变参数以及finalize方法的应用。通过具体的代码示例,详细解释了这些概念及其在实际编程中的使用。 ... [详细]
  • 主要用了2个类来实现的,话不多说,直接看运行结果,然后在奉上源代码1.Index.javaimportjava.awt.Color;im ... [详细]
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • 本文详细介绍了Java中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 前言--页数多了以后需要指定到某一页(只做了功能,样式没有细调)html ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
author-avatar
百变精灵99
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有