一、基本概念
1.树存储的优点
(1)数组存储:
优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低
(2)链式存储:
优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可, 删除效率也很好)。
缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)
(3)树存储:
能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
2.树的特点
树的常用术语(结合示意图理解):
1)
节点:指树中的每个存储数据的单元
2)
根节点:没有父节点的单元
3)
父节点:如果当前节点有连接到下一个节点的连线,那么该节点就是父节点
4)
子节点:如果当前节点上有被一条连线连接,那么该节点就是子节点
5)
叶子节点 :
没有子节点的节点
6)
节点的权:
节点值
7)
路径:
从
root
节点找到该节点的路线
8)
层:由父子节点构成的一横行
9)
子树:任意父节点与子节点都可以称为子树
10)
树的高度:
最大层数
11)
森林
:
多颗子树构成森林
3.二叉树
1)
树有很多种,每个节点
最多只能有两个子节点
的一种形式称为二叉树。
2)
二叉树的子节点分为左节点和右节点。
3)如果该二叉树的
所有叶子节点都在最后一层,并且结点总数= 2^n -1 , n 为层数,则我们称为满二叉树。
4)
如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树。
二、二叉树的实现及遍历
1.实现二叉树
(1)节点类
二叉树的节点的特点: 1.有两个节点,分别为左子节点和右子节点 2.用来判断大小的id 3.存储数据的Object,这里为了方便用String代替
public class Node {
public Node l_node;
public Node r_node;
private int id;
private String message;
public Node(int id, String message) {
this.id = id;
this.message = message;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return " id = " + id + " message = " + message;
}
}
(2)二叉树实现
思路分析: 1.创建一个指针head,初始化为Null,让指针始终指向根节点 2.创建其中的各种方法
public class BinaryTree {
private Node head;
}
(3)二叉树的添加方法
思路分析: 1.如果当前树为空,那就将加入的节点设为根节点,让head指针指向它 2.如果不为空,判断id值,id值比根节点大,加入右子节点,比根节点小,加入左子节点
public void add(Node node){
if(isEmpty()){
head = node;
return;
}
Node temp = head;
while (true){
if(node.getId() == temp.getId()){
System.out.println("当前id值已存在");
return;
}else if(node.getId() > temp.getId()){
if(temp.r_node == null){
temp.r_node = node;
break;
}
temp = temp.r_node;
}else if(node.getId() < temp.getId()){
if(temp.l_node == null){
temp.l_node = node;
break;
}
temp = temp.l_node;
}
}
}
(4)二叉树的删除方法
思路分析: 1.传入需要删除的id值,通过与上相同的方式找到该节点 2.如果该节点是叶子节点,那么直接删除 3.如果该节点是父节点,那么 (1)找到当前节点位置 (2)判断左右节点是否为空 (3)如果有一个为空,那就直接让另一个节点代替原来节点的位置 (4)如果都不为空,那优先右节点 ①优先右节点之后,就要考虑左节点的位置问题 ②根据树的特性,左节点中任一的数据始终比右节点中最小值要小 ③通过临时变量找到右节点中最小值的位置,将最小值的l_node指向之前的左节点 4.如果该节点是头节点,那么 (1)如果当前左右节点为空,那么让head指向null (2)如果当前左右节点有一个为空,那就让head指向另一个 (3)如果两个都不为空,那么参考3.(4)
public void deleteNode(int id){
if(isEmpty()){
System.out.println("树为空");
return;
}
if(id == head.getId()){
if(head.l_node == null && head.r_node == null){
head = null;
}else if(head.l_node == null && head.r_node != null){
head = head.r_node;
}else if(head.r_node == null && head.l_node != null){
head = head.l_node;
}else{
Node temp = head.r_node;
while (true){
if(temp.l_node == null){
break;
}
temp = temp.l_node;//一直向左走,找到最小值
}
//找到之后,让temp节点的左节点指向头节点的左节点
temp.l_node = head.l_node;
//头节点指向头节点的右节点
head = head.r_node;
}
return;
}
Node temp = head;
//temp始终指向要找到的节点的父节点
while (true){
//temp的左节点是否为要找到的节点
if(temp.l_node.getId() == id){//如果相等,那么找到了该节点
//情况一:该节点是叶子节点
if(temp.l_node.r_node == null && temp.l_node.l_node == null){
temp.l_node = null;
}else if(temp.l_node.r_node == null){//情况二:该节点不是叶子节点且右节点为空
temp.l_node = temp.l_node.l_node;
}else if(temp.l_node.r_node != null){//情况三:右节点不为空
Node temp1 = temp.l_node.r_node;
while (true){
if(temp1.l_node == null){
break;
}
temp1 = temp1.l_node;//一直向左走,找到最小值
}
temp1.l_node = temp.l_node.l_node;
temp.l_node = temp.l_node.r_node;
}
break;
//temp的右节点是否为要找到的节点
}else if(temp.r_node.getId() == id){
if(temp.r_node.r_node == null && temp.r_node.l_node == null){
temp.r_node = null;
}else if(temp.r_node.r_node == null){//情况二:该节点不是叶子节点且右节点为空
temp.l_node = temp.l_node.l_node;
}else if(temp.r_node.r_node != null){//情况三:右节点不为空
Node temp1 = temp.r_node.r_node;
while (true){
if(temp1.l_node == null){
break;
}
temp1 = temp1.l_node;//一直向左走,找到最小值
}
temp1.l_node = temp.r_node.l_node;
temp.r_node = temp.r_node.r_node;
}
break;
}
if(temp.r_node == null || temp.l_node == null){
System.out.println("未找到删除点");
return;
}
//temp向下走条件
if(temp.getId() > id){
temp = temp.l_node;
}else {
temp = temp.r_node;
}
}
}
(5)二叉树的修改方法
思路分析: 1.传入需要修改的Node对象,通过二叉树查找对应id值 2.使用temp指针,如果id大于temp.getId,那么向右查找,小于向左查找 3.如果id == temp.getId,说明找到了
public void update(Node node){
if(isEmpty()){
System.out.println("树为空");
return;
}
Node temp = head;
while (true){
if(temp.getId() == node.getId()){
temp.setMessage(node.getMessage());
break;
}
if(temp.getId() > node.getId() && temp.l_node != null){
temp = temp.l_node;
}else if(temp.getId() < node.getId() && temp.r_node != null){
temp = temp.r_node;
}else {
System.out.println("未找到该元素");
return;
}
}
}
(6)二叉树的查找方法
思路分析: 根据id查找,与修改方法的查找方式相同,只需要最后返回Node对象即可
public Node findNode(int id){
if(isEmpty()){
System.out.println("树为空");
return new Node(0,"");
}
Node temp = head;
while (true){
if(temp.getId() == id){
return temp;
}
if(temp.getId() > id && temp.l_node != null){
temp = temp.l_node;
}else if(temp.getId() < id && temp.r_node != null){
temp = temp.r_node;
}else {
System.out.print("未找到该数据");
return null;
}
}
}
2.三种遍历方式
因为每一种遍历方法都需要采用递归来完成,而且初始的传参是固定的,所以写一个list方法来选择任一种遍历方式
public void list(int i){
if(isEmpty()){
System.out.println("栈为空");
return;
}
//判断进行哪种遍历
/*
1为前序遍历
2为中序遍历
3为后序遍历
*/
Node temp = head;
switch (i){
case 1:
preorderList(temp);
break;
case 2:
break;
case 3:
break;
}
}
(1)前序遍历
思路分析: 1.前序遍历是指先对根节点进行遍历,再对左子节点,再对右子节点进行遍历(根,左,右) 2.如果当前节点不为空,那么输出该节点,判断它的左子节点是否为空 如果左子节点不为空,递归调用,把左子节点看为根节点,进行输出 如果左子节点为空,那么判断右子节点是否为空,如果右子节点不为空,输出并把右子节点当做根节点递归
private boolean preorderList(Node temp){
if(temp == null){//如果当前节点为空,返回false
return false;
}else {
System.out.println(temp);
}
if(preorderList(temp.l_node)){//左边不为空,继续递归,左边为空,向下执行
}else if(preorderList(temp.r_node)){//右边不为空,继续递归,右边为空,向下执行
}
return false;//如果temp的左右都为空,返回false
}
(2)中序遍历
思路分析: 1.与前序遍历的方法相似,中序遍历的顺序为左中右 2.如果当前节点为空,返回false,否则就判断左节点是否为空,左节点为空,就打印当前节点,判断右节点是否为空,右节点为空,返回false
public boolean midList(Node temp){
if(temp == null){
return false;
}
if(midList(temp.l_node)){
}
System.out.println(temp);
if(midList(temp.r_node)){
}
return false;
}
(3)后序遍历
思路分析: 1.后序遍历的顺序为左右中 2.与前两种思路相同
private boolean backList(Node temp){
if(temp == null){
return false;
}
if(backList(temp.l_node)){
}
if(backList(temp.r_node)){
}
System.out.println(temp);
return false;
}
三、二叉树的顺序存储
思路分析: 1.顺序化二叉树是以直接遍历将二叉树中的节点数据按每一行从左到右存入到一个数组中,遍历数组 2.前序遍历该数组
前序遍历
思路分析:与之前遍历二叉树的方式相同 1.传入数组开始遍历的索引 2.如果数组为空或者越界,那么返回,否则打印当前索引对应的值 3.向下进行递归,这次查找左右节点方式不同,根据数组的特性,它当前节点的左右节点可以表示为 左节点:2n+1 右节点:2n+2 父节点:(n-1)/2
public class ArrayBinaryTree {
private int[] arr;
public ArrayBinaryTree(int[] arr) {
this.arr = arr;
}
public void preList(int index){
if(arr == null || 0 == arr.length){
return;
}
System.out.println(arr[index]);
if((index * 2) + 1 < arr.length ){
preList((index * 2) + 1);
}
if((index * 2) + 2 < arr.length ){
preList((index * 2) + 2);
}
}
}