树
概念
树是一种重要的非线性数据结构,直观地看,它是数据元素(在树中称为结点)按分支关系组织起来的结构,很象自然界中的树那样。
为什么要使用树结构?
在说为什么之前,我们先来看一下数组和链表的结构有哪些优缺点
数组的优缺点
- 优点:通过下标方式访问元素,速度非常的快。对于有序数组,还可以使用二分查找、斐波那契查找等算法来提高性能
- 缺点:如果我们需要按顺序插入一个元素时,数组的缺点就很明显了,他会整体移动,效率非常低
有小伙伴可能就会说可以使用集合的形式来存储数据,我们来ArrayList来举例
//空参构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//ArrayList也是一个Object数组的形式
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//当我们调用add方法时,他会调用grow方法来扩容数组,其底层原理其实是两个数组的copy
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
链表的优缺点
链表的优缺点跟数组正好相反
- 优点:在对元素的删除、插入等操作时,只需要将对应的节点指向改变,效率很高
- 缺点:在查找等操作时,需要从头开始遍历链表,效率非常低
树存储方式分析
能提高数据存储、读取的效率,比如使用二叉排序树,既可以保证数据的检索速度,同时也可以保证数据的插入、删除、修改的速度
树的常用术语
可以查看这位博主的文章,写的非常通俗易懂 点击跳转
二叉树
概念
二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个节点最多只能有两棵子树,且有左右之分。
二叉树的遍历
二叉树的遍历分为前序遍历、中序遍历以及后序遍历,通过以下这张图来了解一下三种遍历
图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fgB5JOdB-1649677865671)(/Users/yellowstar/Library/Application Support/typora-user-images/image-20220411152456244.png)]
代码实现
public class BinaryTreeDemo {
public static void main(String[] args) {
//创建一棵树
BinaryTree binaryTree = new BinaryTree();
HeroNode root = new HeroNode(1, "宋江");
HeroNode node1 = new HeroNode(2, "吴用");
HeroNode node2 = new HeroNode(3, "卢俊");
HeroNode node3 = new HeroNode(4, "林冲");
//暂时使用手动的方式创建树,之后会学习递归的方式
root.setLeft(node1);
root.setRight(node2);
node2.setRight(node3);
binaryTree.setRoot(root);
System.out.println("前序遍历");
binaryTree.preOrder(); // 1,2,3,4
System.out.println("中序遍历");
binaryTree.midOrder(); // 2,1,3,4
System.out.println("后序遍历");
binaryTree.postOrder(); // 2,4,3,1
}
}
//二叉树
class BinaryTree{
private HeroNode root;
public BinaryTree() {
}
public BinaryTree(HeroNode root) {
this.root = root;
}
public HeroNode getRoot() {
return root;
}
public void setRoot(HeroNode root) {
this.root = root;
}
//前序遍历
public void preOrder(){
if (this.root != null){
this.root.preOrder();
}else {
System.out.println("二叉树为空,无法遍历");
}
}
//中序遍历
public void midOrder(){
if (this.root != null){
this.root.midOrder();
}else {
System.out.println("二叉树为空,无法遍历");
}
}
//后序遍历
public void postOrder(){
if (this.root != null){
this.root.postOrder();
}else {
System.out.println("二叉树为空,无法遍历");
}
}
}
//创建节点
class HeroNode{
private int id;
private String name;
//左子节点,默认为空
private HeroNode left;
//右子节点,默认为空
private HeroNode right;
public HeroNode(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroNode getLeft() {
return left;
}
public void setLeft(HeroNode left) {
this.left = left;
}
public HeroNode getRight() {
return right;
}
public void setRight(HeroNode right) {
this.right = right;
}
@Override
public String toString() {
return "HeroNode{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
//前序遍历
public void preOrder(){
System.out.println(this);
if (this.left != null){
this.left.preOrder();
}
if (this.right != null){
this.right.preOrder();
}
}
//中序遍历
public void midOrder(){
if (this.left != null){
this.left.midOrder();
}
System.out.println(this);
if (this.right != null){
this.right.midOrder();
}
}
//后序遍历
public void postOrder(){
if (this.left != null){
this.left.postOrder();
}
if (this.right != null){
this.right.postOrder();
}
System.out.println(this);
}
}
二叉树的查找
二叉树的查找也分为前序查找、中序查找以及后序查找
图解
代码实现
在HeroNode添加查找方法
//前序查找
public HeroNode preOrderSearch(int no){
System.out.println("前序遍历次数");
//判断当前节点id是否为相同,如果相同,直接返回
if (this.id == no){
return this;
}
//如果左子节点不为空,继续递归前序查找
HeroNode resNode = null;
if (this.left != null){
resNode = this.left.preOrderSearch(no);
}
//判断左子节点有没有找到,如果找到,直接返回
if (resNode != null){
return resNode;
}
//如果右子节点不为空,继续递归前序查找
if (this.right != null){
resNode = this.right.preOrderSearch(no);
}
return resNode;
}
//中序查找
public HeroNode midOrderSearch(int no){
//如果左子节点不为空,继续递归前序查找
HeroNode resNode = null;
if (this.left != null){
resNode = this.left.midOrderSearch(no);
}
//判断左子节点有没有找到,如果找到,直接返回
if (resNode != null){
return resNode;
}
System.out.println("中序查找次数");
//判断当前节点id是否为相同,如果相同,直接返回
if (this.id == no){
return this;
}
//如果右子节点不为空,继续递归前序查找
if (this.right != null){
resNode = this.right.midOrderSearch(no);
}
return resNode;
}
//后序查找
public HeroNode postOrderSearch(int no){
//如果左子节点不为空,继续递归前序查找
HeroNode resNode = null;
if (this.left != null){
resNode = this.left.postOrderSearch(no);
}
//判断左子节点有没有找到,如果找到,直接返回
if (resNode != null){
return resNode;
}
//如果右子节点不为空,继续递归前序查找
if (this.right != null){
resNode = this.right.postOrderSearch(no);
}
//判断右子节点有没有找到,如果找到,直接返回
if (resNode != null){
return resNode;
}
System.out.println("后序查找次数");
//判断当前节点id是否为相同,如果相同,直接返回
if (this.id == no){
return this;
}
return resNode;
}
在二叉树中添加调用方法
//前序查找
public HeroNode preOrderSearch(int no){
if (this.root != null){
return this.root.preOrderSearch(no);
}else {
return null;
}
}
//中序查找
public HeroNode midOrderSearch(int no){
if (this.root != null){
return this.root.midOrderSearch(no);
}else {
return null;
}
}
//后序查找
public HeroNode postOrderSearch(int no){
if (this.root != null){
return this.root.postOrderSearch(no);
}else {
return null;
}
}
测试
public static void main(String[] args) {
//创建一棵树
BinaryTree binaryTree = new BinaryTree();
HeroNode root = new HeroNode(1, "宋江");
HeroNode node1 = new HeroNode(2, "吴用");
HeroNode node2 = new HeroNode(3, "卢俊");
HeroNode node3 = new HeroNode(4, "林冲");
HeroNode node4 = new HeroNode(5, "关胜");
//暂时使用手动的方式创建树,之后会学习递归的方式
root.setLeft(node1);
root.setRight(node2);
node2.setRight(node3);
node2.setLeft(node4);
binaryTree.setRoot(root);
System.out.println("前序查找~~~");
HeroNode resNode1 = binaryTree.preOrderSearch(5);
if (resNode1 != null){
System.out.println("找到英雄:" + resNode1);
}else {
System.out.println("不存在该英雄");
}
System.out.println("中序查找~~~");
HeroNode resNode2 = binaryTree.midOrderSearch(5);
if (resNode2 != null){
System.out.println("找到英雄:" + resNode2);
}else {
System.out.println("不存在该英雄");
}
System.out.println("后序查找~~~");
HeroNode resNode3 = binaryTree.postOrderSearch(5);
if (resNode3 != null){
System.out.println("找到英雄:" + resNode3);
}else {
System.out.println("不存在该英雄");
}
}
二叉树的删除
图解
代码实现
在HeroNode中添加以下代码
/** 删除节点
* 规定:如果当前节点是叶子节点,直接删除
* 如果当前节点是子节点,删除该节点的子树
*/
public void delHero(int no){
//如果当前节点的左子节点不为空,并且满足删除条件,则将this.left置空,返回(结束递归)
if (this.left != null && this.left.id == no){
this.left = null;
return;
}
//如果当前节点的右子节点不为空,并且满足删除条件,则将this.right置空,返回(结束递归)
if (this.right != null && this.right.id == no){
this.right = null;
return;
}
//如果1,2两步都没有删除节点,那么需要向左子树递归删除
if (this.left != null){
this.left.delHero(no);
}
//如果3步也没有删除节点,那么需要向右子树递归删除
if (this.right != null){
this.right.delHero(no);
}
}
对于root的操作,需要放在二叉树中进行
//删除
public void delHero(int no){
//判断root节点是否为空,如果为空则不需要删除,判断root节点是否有子节点,如果没有子节点,并且满足条件,则将root置空
if (this.root != null){
if (this.root.getLeft() != null || this.root.getRight() != null){
this.root.delHero(no);
}else {
if (this.root.getId() == no){
root = null;
}
}
}else {
System.out.println("二叉树为空");
}
}
测试
public class BinaryTreeDemo {
public static void main(String[] args) {
//创建一棵树
BinaryTree binaryTree = new BinaryTree();
HeroNode root = new HeroNode(1, "宋江");
HeroNode node1 = new HeroNode(2, "吴用");
HeroNode node2 = new HeroNode(3, "卢俊");
HeroNode node3 = new HeroNode(4, "林冲");
HeroNode node4 = new HeroNode(5, "关胜");
//暂时使用手动的方式创建树,之后会学习递归的方式
root.setLeft(node1);
root.setRight(node2);
node2.setRight(node3);
node2.setLeft(node4);
binaryTree.setRoot(root);
System.out.println("删除前");
binaryTree.preOrder();
binaryTree.delHero(3);
System.out.println("删除后");
binaryTree.preOrder();
}
}
顺序存储二叉树
概念
从数据存储的角度来看,数组存储方式和树的存储方式可以相互转换,即数组可以转化为树,树也可以转化成数组
代码实现
我们还是来实现顺序存储二叉树的前序遍历、中序遍历以及后序遍历
public class ArrayBinaryTreeDemo {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7};
ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);
arrayBinaryTree.postOrder();
}
}
class ArrayBinaryTree{
public int[] arr;
public ArrayBinaryTree(int[] arr) {
this.arr = arr;
}
public void preOrder(){
preOrder(0);
}
/**
* 前序遍历 遍历结果因为
* @param index 表示数组的下标
*/
public void preOrder(int index){
if (arr == null || arr.length == 0){
System.out.println("数组为空");
return;
}
//输出当前节点
System.out.println(arr[index]);
//递归向左遍历
if (arr.length > (2 * index) + 1){
preOrder((2 * index) + 1);
}
//递归向右遍历
if (arr.length > (2 * index) + 2){
preOrder((2 * index) + 2);
}
}
public void midOrder(){
midOrder(0);
}
public void midOrder(int index){
if (arr == null || arr.length == 0){
System.out.println("数组为空");
return;
}
//递归向左遍历
if (arr.length > (2 * index) + 1){
midOrder((2 * index) + 1);
}
//输出当前节点
System.out.println(arr[index]);
//递归向右遍历
if (arr.length > (2 * index) + 2){
midOrder((2 * index) + 2);
}
}
public void postOrder(){
postOrder(0);
}
public void postOrder(int index){
if (arr == null || arr.length == 0){
System.out.println("数组为空");
return;
}
//递归向左遍历
if (arr.length > (2 * index) + 1){
postOrder((2 * index) + 1);
}
//递归向右遍历
if (arr.length > (2 * index) + 2){
postOrder((2 * index) + 2);
}
//输出当前节点
System.out.println(arr[index]);
}
}