我们有序数组查找快,可以通过二分查找快速的查找到数据,但是要在有序数组中插入一个数的话,就必须先找到插入数据项的位置,然后将所有插入位置后面的数据项后移以为,给新的数据腾出空间,同时删除数据也是一样的道理,可以知道这样的话是很浪费时间的,
同时另外的一种数据结构——链表,链表的插入和删除很快,我们只需要改变一些引用,但是查找数据就会很慢,因为我们无论查找到什么数据,都需要从链表的第一个数据开始,遍历找到所有的数据项位置,
今天说到的树也是一种数据结构,同时具备数组查找快和链表的插入与删除的优点
树
树是一种抽象数据类型,用来模拟具有树状结构性质的数据集合,它是由n(n>0)个有限节点通过连接他们的边组成的层次集合,但是我们今天的树看起来会像一颗倒挂的树,也就是说它的根在上面,而叶子在下面
1. 节点:就是上面的圆圈,节点一般代表的是实体,在java的面向对象中,节点一般代表的是对象
2. 边:边就是连接节点的线,便表示节点的关联关系,一遍是从一个节点到另一个节点,也就是说一个节点到下一个节点的唯一方法就是顺着这个边前进,在java中相当于引用
树有很多种,像上面的一个节点有多余的两个子节点的树,称为多路树,而每个节点最多只有两个节点的一种形式成为二叉树,也是本章博客的重点
树的常用语
1. 路劲:顺着节点的边从一个节点到另一个节点,所经过的节点的顺序排列称为 路径
2. 根: 树的顶端的节点称为根节点,一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他的任意一个节点都必须有且只有一条路径,A就是根节点
3. 父节点: 若一个节点含有子节点,则这个节点称为其子节点的父节点,上面说到的B就是D,E的父节点
4. 子节点:一个子节点含有子树的根节点称为该节点的子节点 D是B的子节点
5. 兄弟节点: 具有相同父节点的节点称为兄弟节点,上面说的就是 D,E为兄弟节点
6. 叶节点: 没有子节点的节点称为叶节点,上面图中的H就是叶节点
7. 子树: 每个节点都可以作为子树的根,它和它所有的子节点、子节点的子节点等都包含在子树中。
8. 节点的层次: 从根节点定义,根为第一层,根的子节点为第二层,以此类推
9. 深度: 对于任意节点n,n的深度为从根到n的唯一路径长,根的深度为0;
10. 高度: 对于任意节点n,n的高度为从n到一片树叶的最长路径长,所有树叶的高度为0;
二叉树
二叉树: 树的每个节点最多只有两个节点
二叉树若它的左子树不为空,那么左子树所有的节点均小于它的根值,若它的右子树不为空,则右子树上的所有节点均大于它的根节点,它的左,右子树也分为二叉排序树
二叉树的节点类
package com.example.study;
/**
* @author wangchunguang
* @version $Id:
* @date 2018/12/19 23:11
*/
public class Node {
private Object date;//节点数据
private Node leftChild;//左子节点的引用
private Node rightChild;//右子节点的引用
// 打印节点的数据
public void disPlay(){
System.out.println(date);
}
}
查找节点
查找节点我们都是必须从根部进行遍历
- 查找值比当前节点大,则搜索右子树
- 查找值比当前的值小,则搜索左子树
- 查找值等于当前值,则停止搜索
// 表示根节点
private Node root;
@Override
public Node find(int key) {
Node current = root;
// 先判断节点是否为空
while (current != null) {
if (current.data > key) {
//当前值比查找的值大 就搜索左子树
current = current.leftChild;
} else if (current.data < key) {
//当前值比查找值小 搜索右子树
current = current.rightChild;
} else {
return current;
}
}
return null;//遍历结束整个树 都没有找到 返回null
}
用变量current来保存当前查找的节点,参数key表示要查找的值,刚开始将根节点赋值到current,在循环中,将要查找的值和current保存的节点进行对比,如果key小于当前节点,则搜索当前节点的左子节点,如果大于就搜索右子节点,如果等于的话 就返回当前节点,如果不存在的话就直接返回null
树的效率: 查找节点的时间取决于这节点所在的层次,每一层最多有2n-1个节点,总共N层共有2n-1个节点,那么时间复杂度为O(logn),底数为2。
插入节点
要插入节点,必须要找到插入位置,与查找类似,由于二叉搜索树的特殊性,待插入节点也需要从根节点上面进行比较,小于则与根节点的左子树进行比较,反之则与右子树比较,直到左子树为空或者右子树为空,则插入到相应的位置,比较过程中要注意保存父节点的信息,以及带插入位置是父节点的左子树还是右子树,才能插入正确的位置
// 插入节点
@Override
public boolean insert(int dara) {
Node newNode = new Node(dara);
if (root == null) {
//当前树为空树,没有任何节点
root = newNode;
return true;
} else {
Node current = root;
Node parentNode = null;
while (current != null) {
parentNode = current;
if (current.data > dara) {
//当前值比插入值大,搜索左子节点
current = current.leftChild;
if (current == null) {
//左子节点为空,直接将新值插入到该节点
parentNode.leftChild = newNode;//插入节点
return true;
}
} else {
current = current.rightChild;
if (current == null) {
//右子节点为空,直接将新值插入到该节点
parentNode.rightChild = newNode;//插入节点
return true;
}
}
}
}
return false;
}
遍历树
遍历树是根据一种特定的顺序访问树的每一个节点,常用的有前序遍历,中序遍历和后续遍历,而二叉搜索树最常用的是中序遍历
- 中序遍历:左子树——》根节点——》右子树
B-D-C-E-A-L-F-N-Q-M
解释先中序遍历A的左子树(BCDE,B没有左子树,遍历根B,然后右子树CDF,先遍历左子树D,然后根节点C,右子数E),然后是主根节点A,最后是右子树(FLMLNLQ,左子数为L,根F,右子树MNQ,先左子树NQ,N无左子树,先根N,后Q,最后M)
// 中序遍历
public static void infixOrder(Node current) {
if (current != null) {
infixOrder(current.leftChild);
System.out.print(current.data + " ");
infixOrder(current.rightChild);
}
}
- 前序遍历: 根节点——》左子树——》右子树
A-B-D-C-E-F-G
解释:先访问根节点A,然后访问A的左子树(包括BD,把分支BD当成一个整体,在先序访问根节点B,然后访问B的左子数D),最后访问A的右子数(包括CEFG,把它们看成一个整体,先访问C,在访问C的左子树E,然后访问C的右子数FG,先访问根节点F,然后左子数G)
// 前序遍历
public