一、数组存储、链式存储与数存储
1,数组存储
优点:通过下标方式访问元素,速度快。对于有序数组,还可以通过二分查找提高检索速度。
缺点:如果检索具体某个值,或者插入值(按一定顺序)会整体移动,效率低
2,链式存储
优点:在一定程度上对数组存储有优化(比如:插入一个数值节点,只需要将插入节点连接到链表中即可,删除效率也很好)。
缺点:在进行检索时,效率仍然较低(比如:检索某个值,需要从头节点开始遍历)
3,树存储
能提高数据存储,读取的效率,比如利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入、删除和修改的速度。
二、二叉树
源码:二叉树
1,数的常用术语
- 节点
- 根节点
- 父节点
- 子节点
- 叶子节点 (没有子节点的节点)
- 节点的权(节点值)
- 路径(从 root 节点找到该节点的路线)
- 层
- 子树
- 树的高度(最大层数)
- 森林 :多颗子树构成森林
2,二叉树的概念
- 树有很多种, 每个节点最多只能有两个子节点的一种形式称为二叉树,二叉树的子节点分为左节点和右节点
- 如果该二叉树的所有叶子节点都在最后一层, 并且结点总数= 2^n -1, n 为层数, 则我们称为满二叉树
- 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层, 而且最后一层的叶子节点在左边连续, 倒数第二层的叶子节点在右边连续, 我们称为完全二叉树
3,二叉树的遍历
a)代码思路
- 前序遍历: 先输出父节点, 再遍历左子树和右子树
- 中序遍历: 先遍历左子树, 再输出父节点,再遍历右子树
- 后序遍历: 先遍历左子树, 再遍历右子树,最后输出父节点
b)代码实现
//编号 private int no; //名称 private String name; //左子节点 private Node left; //右子节点 private Node right; /** * 前序遍历 : 根 -> 左 -> 右 */ public void preOrder(){ if (this == null) { return; } if (this != null) { System.out.print(this.no + " "); } if (this.left != null) { this.left.preOrder(); } if (this.right != null) { this.right.preOrder(); } } /** * 中序遍历 : 左 -> 根 -> 右 */ public void midOrder(){ if (this == null) { return; } if (this.left != null) { this.left.midOrder(); } if (this != null) { System.out.print(this.no + " "); } if (this.right != null) { this.right.midOrder(); } } /** * 后序遍历 : 左 -> 右 -> 根 */ public void postOrder(){ if (this == null) { return; } if (this.left != null) { this.left.postOrder(); } if (this.right != null) { this.right.postOrder(); } if (this != null) { System.out.print(this.no + " "); } }
4,二叉树的查找
a)代码思路
- 将二叉树的前、中、后序遍历改为查找即可
- 编码思路:
- 如果查找到目标节点,直接返回,结束递归
- 如果找不到,继续递归执行前(中、后)序查找
b)代码实现
//编号 private int no; //名称 private String name; //左子节点 private Node left; //右子节点 private Node right; /** * 后序查找 */ public Node postSearch(int no) { Node node = null; if (this.left != null) { node = this.left.postSearch(no); } if (node != null) { return node; } if (this.right != null) { node = this.right.postSearch(no); } if (node != null) { return node; } System.out.println("后续查找"); if (this.no == no) { return this; } return node; } /** * 中序查找 */ public Node midSearch(int no) { Node node = null; //先向左找 if (this.left != null ) { node = this.left.midSearch(no); } //找到就返回 if (node != null) { return node; } System.out.println("中序查找~"); //找当前节点,找到返回 if (this.no == no) { return this; } //向右找 if (this.right != null) { node = this.right.midSearch(no); } return node; } /** * 前序查找 */ public Node preSearch(int no){ System.out.println("前序查找~"); if (this.no == no) { return this; } Node node = null; if (this.left != null) { node = this.left.preSearch(no); } if (node != null) { return node; } if (this.right != null) { node = this.right.preSearch(no); } return node; }
5,二叉树的删除
a)删除要求
- 如果删除的节点是叶子节点, 则删除该节点
- 如果删除的节点是非叶子节点, 则删除该子树
b)代码思路
- 先判断根节点 root 是不是待删除的节点,如果是,则删除根节点,否则开始执行递归
- 判断当前节点(this)的左节点是否为待删除的节点,如果是,删除 this.left ,然后返回,结束递归
- 判断当前节点(this)的左节点是否为待删除的节点,如果是,删除 this.right,然后返回,结束递归
- 否则继续执行左递归,左递归执行完后,执行右递归
c)代码实现
/** * 删除节点 */ public void del(int no) { if (this.left != null) { if (this.left.no == no) { this.left = null; return; } else { this.left.del(no); } } if (this.right != null ) { if (this.right.no == no) { this.right = null; return; } else { this.right.del(no); } } }
/** * 删除节点 */ public void del(int no) { if (root == null) { System.out.println("当前二叉树为空!"); return; } if (root.getNo() == no) { root = null; return; } root.del(no); }
三、顺序存储二叉树
源码:顺序存储二叉树
1,顺序存储二叉树与数组的转换
-
基本说明:从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组。
-
要求:
- 二叉树的结点,要求以数组的方式来存放 arr : [1, 2, 3, 4, 5, 6, 7]
- 要求在遍历数组 arr时,仍然可以用前序遍历,中序遍历和后序遍历的方式完成结点的遍历
2,顺序存储二叉树的特点
- 顺序二叉树通常只考虑完全二叉树
- 顺序存储二叉树中第 n 个元素的左子节点对应的数组下标为 2 * n + 1
- 顺序存储二叉树中第 n 个元素的右子节点对应的数组下标为 2 * n + 2
- 顺序存储二叉树中第 n 个元素的父节点对应的数组下标为 (n-1) / 2
- n 的含义: 表示二叉树中的第几个元素(按0开始编号,如上图所示)
3,代码实现
/** * 前序 */ public void preOrder(int index) { if (arr == null || arr.length == 0) { System.out.println("当前数组为空!"); return; } System.out.println(arr[index]); if ((2 * index + 1) < arr.length) { preOrder(2 * index + 1); } if ((2 * index + 2) < arr.length) { preOrder(2 * index + 2); } }
四、线索化二叉树
源码:线索化二叉树
1,原理
通过考察各种二叉链表,无论儿叉树的形态怎样,空链域的个数总是多过非空链域的个数。准确的说,n各结点的二叉链表共同拥有2n个链域,非空链域为n-1个,但当中的空链域却有n+1个。例如以下图所看到的。
可以通过充分利用二叉链表中的空指针域,存放节点在某种遍历方式下的前驱和后继节点的指针。我们把这种指向前驱和后继的指针成为线索,加上线索的二叉链表成为线索链表,对应的二叉树就成为“线索二叉树(Threaded Binary Tree)” 。
2,构建线索化二叉树(中序)
a)思路
在原始二叉树结点基础上,添加字段isLeftThread和isRightThread,从而判定指针域对应的是子节点还是前驱或后续线索
//节点存储结构 static class Node { String data; //数据域 Node left; //左指针域 Node right; //右指针域 boolean isLeftThread = false; //左指针域类型 false:指向子节点、true:前驱或后继线索 boolean isRightThread = false; //右指针域类型 false:指向子节点、true:前驱或后继线索 }
将最终的二叉树构建为如下图所示的线索二叉树
b)代码实现
/** * 中序线索化二叉树 * * @param node 节点 */ public void inThreadOrder(Node node) { if (node == null) { return; } //处理左子树 inThreadOrder(node.left); //左指针为空,将左指针指向前驱节点 if (node.left == null) { node.left = preNode; node.isLeftThread = true; } //前一个节点的后继节点指向当前节点 if (preNode != null && preNode.right == null) { preNode.right = node; preNode.isRightThread = true; } preNode = node; //处理右子树 inThreadOrder(node.right); }
3,遍历线索化二叉树
a)思路
- 找到线索二叉树的最左子节点
- 从左往右遍历各节点while(node!=null){node=node.right...}
- 如果右指针是线索指针直接打印,
- 如果右指针是节点则查找该右子树的最左结点
b)代码实现
/** * 中序遍历线索二叉树,按照后继方式遍历(思路:找到最左子节点开始) * * @param node */ public void inThreadList(Node node) { //1、找中序遍历方式开始的节点 while (node != null && !node.isLeftThread) { node = node.left; } while (node != null) { System.out.print(node.data + ", "); //如果右指针是线索 if (node.isRightThread) { node = node.right; } else { //如果右指针不是线索,找到右子树开始的节点 node = node.right; while (node != null && !node.isLeftThread) { node = node.left; } } } }