关于树的一些日常操作

树的存储结构

  • 优点:提高了数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也 可以保证数据的插入,删除,修改的速度。
  • 缺点:顺序存储可能会浪费空间(在非完全二叉树的时候),但是读取某个指定的节点的时候效率比较高O(0)链式存储相对二叉树比较大的时候浪费空间较少,但是读取某个指定节点的时候效率偏低O(nlogn)
    在这里插入图片描述

树的相关定义:

  1. 节点:是数据结构中,用来描述“树”型结构的名词。
  2. 叶结点或终端结点:度为0的结点称为叶结点。
  3. 父节点:若一个结点含有子结点,则这个结点称为其子结点的父结点;
  4. 子节点:一个结点含有的子树的根结点称为该结点的子结点;
  5. 叶子节点 :没有子节点的节点
  6. 节点的权:节点值
  7. 路径:从 root 节点找到该节点的路线
  8. 层:从根开始定义起,根为第1层,根的子结点为第2层,以此类推;
  9. 子树:以某结点为根的子树中任一结点都称为该结点的子孙。
  10. 树的高度(最大层数):树中结点的最大层次
  11. 森林 :多颗子树构成森林
  12. 树的度:一棵树中,最大的结点的度称为树的度

种类

  1. 无序树:树中任意节点的子结点之间没有顺序关系,这种树称为无序树,也称为自由树;
  2. 有序树:树中任意节点的子结点之间有顺序关系,这种树称为有序树;
  3. 二叉树:每个节点最多含有两个子树的树称为二叉树;
  4. 满二叉树:叶节点除外的所有节点均含有两个子树的树被称为满二叉树
  5. 完全二叉树:有个节点的满二叉树称为完全二叉树
  6. 哈夫曼树(最优二叉树):带权路径最短的二叉树称为哈夫曼树或最优二叉树;

二叉树的概念

  1. 树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。

  2. 二叉树的子节点分为左节点和右节点

  3. 示意图:
    在这里插入图片描述

  4. 二叉树遍历的说明:使用前序,中序和后序对下面的二叉树进行遍历.

  5. 前序遍历: 先输出父节点,再遍历左子树和右子树

  6. 中序遍历: 先遍历左子树,再输出父节点,再遍历右子树

  7. 后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点

  8. 总结: 看输出父节点的顺序,就确定是前序,中序还是后序
    简单的二叉树三遍历:

package tree;

public class BinaryTreeDemo {
    
    

	public static void main(String[] args) {
    
    
		Tree tree = new Tree();
		Node root = new Node(1, "zs1");
		Node node1 = new Node(2, "zs2");
		Node node2 = new Node(3, "zs3");
		Node node3 = new Node(4, "zs4");
		Node node4 = new Node(5, "zs5");
		
		root.setLeft(node1);
		root.setRight(node2);
		node1.setLeft(node3);
		node2.setRight(node4);
		tree.setNode(root);
		System.out.println("前序遍历");
		tree.preOrder();
		
		System.out.println("中序遍历");
		tree.midOrder();

		System.out.println("后序遍历");
		tree.postOrder();
		
		
	}
}

	// 创建二叉树
	class Tree {
    
    
		private Node node;

		public void setNode(Node node) {
    
    
			this.node = node;
		}

		// 前序遍历
		public void preOrder() {
    
    
			if (this.node != null) {
    
    
				this.node.preOrder();
			} else {
    
    
				System.out.println("二叉树为空,无法遍历");
			}
		}

		// 中序遍历
		public void midOrder() {
    
    
			if (this.node != null) {
    
    
				this.node.midOrder();
			} else {
    
    
				System.out.println("二叉树为空,无法遍历");
			}
		}

		// 后序遍历
		public void postOrder() {
    
    
			if (this.node != null) {
    
    
				this.node.postOrder();
			} else {
    
    
				System.out.println("二叉树为空,无法遍历");
			}
		}

	}

	// 创建结点
	class Node {
    
    
		private int no;
		private String name;
		private Node left;
		private Node right;

		public Node(int no, String name) {
    
    
			this.no = no;
			this.name = name;
		}

		// 构造方法 set get
		public int getNo() {
    
    
			return no;
		}

		public void setNo(int no) {
    
    
			this.no = no;
		}

		public String getName() {
    
    
			return name;
		}

		public void setName(String name) {
    
    
			this.name = name;
		}

		public Node getLeft() {
    
    
			return left;
		}

		public void setLeft(Node left) {
    
    
			this.left = left;
		}

		public Node getRight() {
    
    
			return right;
		}

		public void setRight(Node right) {
    
    
			this.right = right;
		}

		// 构造toString 方法
		@Override
		public String toString() {
    
    
			return "Node [no=" + no + ", 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);
		}

	}



查找指定节点

  • 根据所给id值查找对应的数
  • 思路分析:

分为三种遍历方式:

  1. 前序遍历
    1)先判断当前结点的id值是否为要查找的值
    2)相等就返回 不相等 就先判断左子节点是否为空,不为空则继续前序遍历
    3)左子节点如果判断没有要找的结点则继续向右子节点继续遍历查找
  2. 中序遍历
    1)判断当前结点的左子节点是否为空,不为空就继续遍历
    2)找到就返回,没有就和当前结点比较,是返回不是继续遍历
    3)右子节点是否为空,找到就返回,不是就置null
  3. 后序遍历
    1)判断当前结点的左子节点是否为空,不为空就继续遍历
    2)右子节点是否为空,找到就返回
    3)没有就和当前结点比较,是返回不是就置null
package tree;

public class BinaryTreeDemo1 {
    
    

	public static void main(String[] args) {
    
    
		Tree1 tree = new Tree1();
		Node1 root = new Node1(1, "zs1");
		Node1 node1 = new Node1(2, "zs2");
		Node1 node2 = new Node1(3, "zs3");
		Node1 node3 = new Node1(4, "zs4");
		Node1 node4 = new Node1(5, "zs5");

		root.setLeft(node1);
		root.setRight(node2);
		node1.setLeft(node3);
		node2.setRight(node4);
		tree.setNode(root);

		Node1 test = tree.preOrderSearch(1);
		if (test != null) {
    
    
			System.out.printf("找到了,信息为 no=%d name=%s\n", test.getNo(), test.getName());
		} else {
    
    
			System.out.println("信息不存在");
		}

		Node1 test1 = tree.midOrderSearch(1);
		if (test1 != null) {
    
    
			System.out.printf("找到了,信息为 no=%d name=%s\n", test1.getNo(), test1.getName());
		} else {
    
    
			System.out.println("信息不存在");
		}

		Node1 test2 = tree.postOrderSearch(1);
		if (test2 != null) {
    
    
			System.out.printf("找到了,信息为 no=%d name=%s\n", test2.getNo(), test2.getName());
		} else {
    
    
			System.out.println("信息不存在");
		}
	}

}

//创建二叉树
class Tree1 {
    
    
	private Node1 node;

	public void setNode(Node1 root) {
    
    
		this.node = root;
	}

	// 前序遍历查找
	public Node1 preOrderSearch(int id) {
    
    
		if (this.node != null) {
    
    
			return this.node.preOrderSearch(id);
		} else {
    
    
			return null;

		}
	}

	// 中序遍历查找
	public Node1 midOrderSearch(int id) {
    
    
		if (this.node != null) {
    
    
			return this.node.midOrderSearch(id);
		} else {
    
    
			return null;
		}
	}

	// 后序遍历查找
	public Node1 postOrderSearch(int id) {
    
    
		if (this.node != null) {
    
    
			return this.node.postOrderSearch(id);
		} else {
    
    
			return null;
		}
	}

}

// 创建结点
class Node1 {
    
    
	private int no;
	private String name;
	private Node1 left;
	private Node1 right;

	public Node1(int no, String name) {
    
    
		this.no = no;
		this.name = name;
	}

	// 构造方法 set get
	public int getNo() {
    
    
		return no;
	}

	public void setNo(int no) {
    
    
		this.no = no;
	}

	public String getName() {
    
    
		return name;
	}

	public void setName(String name) {
    
    
		this.name = name;
	}

	public Node1 getLeft() {
    
    
		return left;
	}

	public void setLeft(Node1 left) {
    
    
		this.left = left;
	}

	public Node1 getRight() {
    
    
		return right;
	}

	public void setRight(Node1 right) {
    
    
		this.right = right;
	}

	// 构造toString 方法
	@Override
	public String toString() {
    
    
		return "Node [no=" + no + ", name=" + name + "]";
	}

	// 前序遍历
	public Node1 preOrderSearch(int id) {
    
    
		System.out.println("进入前序遍历");
		if (this.no == id) {
    
    
			return this;
		}

		Node1 node = null;
		if (this.left != null) {
    
    
			node = this.left.preOrderSearch(id);
		}
		if (node != null) {
    
    
			return node;
		}
		if (this.right != null) {
    
    
			node = this.right.preOrderSearch(id);
		}
		return node;
	}

	// 中序遍历
	public Node1 midOrderSearch(int id) {
    
    
		System.out.println("进入中序遍历");
		Node1 node = null;
		if (this.left != null) {
    
    
			node = this.left.midOrderSearch(id);
		}
		if (node != null) {
    
    
			return node;
		}
		if (this.no == id) {
    
    
			return this;
		}

		if (this.right != null) {
    
    
			this.right.midOrderSearch(id);
		}
		return node;
	}

	// 后序遍历
	public Node1 postOrderSearch(int id) {
    
    

		System.out.println("进入后序遍历");
		Node1 node = null;
		if (this.left != null) {
    
    
			node = this.left.postOrderSearch(id);
		}
		if (node != null) {
    
    
			return node;
		}
		if (this.no == id) {
    
    
			return this;
		}
		if (this.right != null) {
    
    
			this.right.postOrderSearch(id);
		}
		return node;

	}

}

二叉树删除节点操作

  • 需求分析

1.所删除的节点是叶子节点,则直接删除
2.所删除的节点是非叶子节点,则删除该子树

  • 思路分析:
    1.首先需要考虑此树是否为空树,如果只有一个节点,直接置空即可,否则需要继续考虑:
    2.判断左右子节点是否是需要删除的节点,如果是;只需对应左右子节点置空即可,否则需要按照对应的方向进行递归处理,直到遍历完所有的节点。
// 删除节点
	public void delNode(int no) {
    
    

		if (this.left != null && this.left.no == no) {
    
    
			this.left = null;
			return;
		}

		if (this.right != null && this.right.no == no) {
    
    
			this.right = null;
			return;
		}
		//向左递归处理
		if (this.left == null) {
    
    
			this.left.delNode(no);
		}
		// 向右递归处理
		if (this.right == null) {
    
    
			this.right.delNode(no);
		}

	}

顺序存储二叉树

  • 需求分析:要求可以按照数组的方式存放对应的数据例如:
    arr[] ={1,2,3,4,5};
  • 顺序存储数据的特点:
  1. 顺序二叉树通常只考虑完全二叉树
  2. 第 n 个元素的左子节点为 2 * n + 1
  3. 第 n 个元素的右子节点为 2 * n + 2
  4. 第 n 个元素的父节点为 (n-1) / 2
  5. n : 表示二叉树中的第几个元素(按 0 开始编号)
package tree;

public class ArrBinaryTreeDemo {
    
    

	public static void main(String[] args) {
    
    
		int[] arr = {
    
     1, 2, 3, 4, 5, 6, 7 };
		// 创建一个 ArrBinaryTree
		ArrBinaryTree ar = new ArrBinaryTree(arr);
		System.out.println("前序遍历");
		ar.preOrder(0); // 1,2,4,5,3,6,7
		System.out.println();
		System.out.println("中序遍历");
		ar.midOrder(0);
		System.out.println();
		System.out.println("后序遍历");
		ar.postOrder(0);
	}

}

// 实现顺序存储二叉树遍历

class ArrBinaryTree {
    
    
	private int[] arr;

//构造器传入数组
	public ArrBinaryTree(int[] arr) {
    
    
		this.arr = arr;
	}

	// 顺序存储:前序遍历

	public void preOrder(int i) {
    
    
		// 如果数组为空,或者 arr.length = 0
		if (arr == null || arr.length == 0) {
    
    
			System.out.println("数组为空");
		}
		// 输出当前这个元素
		System.out.print(" " + arr[i]);

		// 向左递归遍历
		if ((2 * i + 1) < arr.length) {
    
    
			preOrder(2 * i + 1);
		}
		// 向右递归遍历
		if ((2 * i + 2) < arr.length) {
    
    
			preOrder(2 * i + 2);
		}
	}

	// 顺序存储:中序遍历

	public void midOrder(int i) {
    
    
		// 如果数组为空,或者 arr.length = 0
		if (arr == null || arr.length == 0) {
    
    
			System.out.println("数组为空");
		}
		// 向左递归遍历
		if ((2 * i + 1) < arr.length) {
    
    
			preOrder(2 * i + 1);
		}
		// 输出当前这个元素
		System.out.print(" " + arr[i]);

		// 向右递归遍历
		if ((2 * i + 2) < arr.length) {
    
    
			preOrder(2 * i + 2);
		}
	}

	// 顺序存储:后序遍历

	public void postOrder(int i) {
    
    
		// 如果数组为空,或者 arr.length = 0
		if (arr == null || arr.length == 0) {
    
    
			System.out.println("数组为空");
		}
		// 向左递归遍历
		if ((2 * i + 1) < arr.length) {
    
    
			preOrder(2 * i + 1);
		}

		// 向右递归遍历
		if ((2 * i + 2) < arr.length) {
    
    
			preOrder(2 * i + 2);
		}
		// 输出当前这个元素
		System.out.print(" " + arr[i]);
	}

}

线索化二叉树

  • 目的:为了使节点的左右指针得到重充分的利用
  • 根据线索性质的不同,大致分为:前序线索二叉树、中序线索二叉树和后序线索二叉树三种
  • 一个结点的前一个结点,称为前驱结点
  • 一个结点的后一个结点,称为后继结点
  • n 个结点的二叉链表中含有 n+1个空指针域
    【公式 2n-(n-1)=n+1】
//定义ThreadedBinaryTree 实现了线索化功能的二叉树
class ThreadedBinaryTree {
    
    
	private Node root;

	// 为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
	// 在递归进行线索化时,pre 总是保留前一个结点
	private Node pre = null;

	public void setRoot (Node root) {
    
    
		this.root = root;
	}

	
	// 遍历线索化二叉树的方法
	public void threadedList() {
    
    
		// 定义一个变量,存储当前遍历的结点,从root开始
		HeroNode node = root;
		while (node != null) {
    
    
			// 循环的找到leftIndex == 1的结点,第一个找到就是8结点
			// 后面随着遍历而变化,因为当leftIndex==1时,说明该结点是按照线索化
			// 处理后的有效结点
			while (node.getLeftIndex() == 0) {
    
    
				node = node.getLeft();
			}

			// 打印当前这个结点
			System.out.println(node);
			// 如果当前结点的右指针指向的是后继结点,就一直输出
			while (node.getRightIndex() == 1) {
    
    
				// 获取到当前结点的后继结点
				node = node.getRight();
				System.out.println(node);
			}
			// 替换这个遍历的结点
			node = node.getRight();

		}
	}

因为对二叉树进行线索化操作过后,各个结点指向有变化,新加了前驱和后继结点指向,因此递归的方式不能再继续使用了,需要新的遍历方式:

线型遍历线索二叉树(中序遍历)

	public void threadedNode(Node node) {
    
    

		// 如果node==null, 不能线索化
		if (node == null) {
    
    
			return;
		}
		
			//1.线索化左子树
			threadedNode(node.getLeft());
			// 2.线索化当前结点
			// 处理当前结点的前驱结点
			
			if (node.getLeft() == null) {
    
    
				// 让当前结点的左指针指向前驱结点
				node.setLeft(cur);
				// 修改当前结点的左指针的类型,指向前驱结点
				node.setLeftIndex(1);
			}
			// 处理当前结点的后继结点
			if (cur != null && cur.getRight() == null) {
    
    
				// 让前驱结点的右指针指向当前结点
				cur.setRight(node);
				// 修改前驱结点的右指针类型
				cur.setRightIndex(1);
				
			}
			//每处理一个结点后,重新定义前驱结点,设置当前结点为下一个结点的前驱结点
			cur = node;

			// 3.线索化右子树
			
			threadedNodes(node.getRight());
	}

猜你喜欢

转载自blog.csdn.net/weixin_44763595/article/details/107582522
今日推荐