相比于链表需要逐个遍历节点,二叉树在查询节点时会更加方便,二叉树是平衡树的话,那么有有两个分支,遍历数据时会更快,当然这是理想状态下,二叉树也可以写成链表状,就看如何构建了。对于二叉树的学习要知道一些基本概念,比如节点和根节点以及子节点和叶子节点(度为零的节点)的概念,以及二叉树和某个节点的深度和高度的概念,以及度的概念。常见的二叉树有排序树,红黑树,字典树,哈夫曼树,其中哈夫曼树是设计压缩软件的关键,这篇文章我们先看一下排序树以及最基本的三序遍历,即前、中、后三序遍历(递归/非递归),根据访问根节点root的顺序确定是前中还是后序遍历,层序遍历是根据层数进行逐个遍历。
首先创建Tree类来表示二叉树类,然后创建内部类TreeNode来表示二叉树的节点。同时两个类中写出对应的属性,在Tree类中写上添加节点的方法addNode,在这个方法中有一点值得一提,在做链表或者二叉树题目时,我们都通常把root节点转移至一个curr节点,这里可以认为root就是curr。这样避免最后找不到root,因为在方法中curr要不断迭代,curr可以改变,为了方便解决问题,但是不能使root发生改变。
附上此时的递归方法的前中后遍历方法和Tree类以及TreeNode类的代码
package com.ms.binarytree0724;
public class Tree {
public TreeNode root;//树的根节点
//接下来按照
public void addNode(TreeNode node) {
if (root == null) {
root = node;
} else {
TreeNode curr = root;//可以认为这就是链表中的一个节点,也只是一个节点。
while (true) {
if (node.data < curr.data) {
if (curr.left == null) {
curr.left = node;
break;
}
curr = curr.left;
}
if (node.data > curr.data) {
if (node.data > curr.data) {
if (curr.right == null) {
curr.right = node;
break;
}
curr = curr.right;
}
}
}
}
}
//递归前序遍历
public void printTreeq(TreeNode root) {
if (root != null) {
System.out.print(root.data+" ");
printTreeq(root.left);
printTreeq(root.right);
}
}
//递归中序遍历
public void printTreez(TreeNode root){
if (root!=null){
printTreez(root.left);
System.out.print(root.data+" ");
printTreez(root.right);
}
}
//递归后续遍历
public void printTreeh(TreeNode root){
if (root!=null){
printTreeh(root.left);
printTreeh(root.right);
System.out.print(root.data+" ");
}
}
public static void main(String[] args) {
int[] a = {7,6,8,9,3,4,2,1};
Tree tree = new Tree();
for (int i =0;i<a.length;i++){
TreeNode node = new TreeNode(a[i]);
tree.addNode(node);
}
tree.printTreeq(tree.root);
}
}
//递归中序遍历
//递归后续遍历
//二叉树的基本概念:左子树,右子树,父节点,子节点,树的深度(从上到下的所有节点数-1)
//(二叉)树的(每个)节点 类
class TreeNode {
public int data;
public TreeNode left;
public TreeNode right;
public TreeNode(int data){
this.data=data;
}
}
接着就开始写出非递归的前中后遍历方法。以及在实现层序遍历时我们使用队列Queue。值得一提的是,LinkedList实现了Queue,所以LinkedList可以当做Queue来使用。附上总体代码
package com.ms.binarytree0724;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class Tree {
public TreeNode root;//树的根节点
//接下来按照
public void addNode(TreeNode node) {
if (root == null) {
root = node;
} else {
TreeNode curr = root;//可以认为这就是链表中的一个节点,也只是一个节点。
while (true) {
if (node.data < curr.data) {
if (curr.left == null) {
curr.left = node;
break;
}
curr = curr.left;
}
if (node.data > curr.data) {
if (node.data > curr.data) {
if (curr.right == null) {
curr.right = node;
break;
}
curr = curr.right;
}
}
}
}
}
//递归前序遍历
public void printTreeq(TreeNode root) {
if (root != null) {
System.out.print(root.data+" ");
printTreeq(root.left);
printTreeq(root.right);
}
}
//递归中序遍历
public void printTreez(TreeNode root){
if (root!=null){
printTreez(root.left);
System.out.print(root.data+" ");
printTreez(root.right);
}
}
//递归后续遍历
public void printTreeh(TreeNode root){
if (root!=null){
printTreeh(root.left);
printTreeh(root.right);
System.out.print(root.data+" ");
}
}
public static void main(String[] args) {
int[] a = {7,6,8,9,3,4,2,1};
Tree tree = new Tree();
for (int i =0;i<a.length;i++){
TreeNode node = new TreeNode(a[i]);
tree.addNode(node);
}
//tree.printTreeq(tree.root);
}
//非递归前序遍历
public void preOrder(TreeNode root){
if(root==null) return;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);//先把root放到栈中,否则进不了循环,同时保证循环迭代中的名称代号相同,千言万语一句话,root只能读不能写
while(!stack.isEmpty()){
TreeNode node = stack.pop();
System.out.print(node.data+" ");//第一次循环时就输出了root节点的值
if(node.right!=null) stack.push(node.right); //if语句的简便写法
if(node.left!=null) stack.push(node.left);
}
}
//非递归中序遍历 这个方法的关键是我们要先找到最左节点,然后遍历完左节点后再输出root,然后再是右节点
public void midOrder(TreeNode root) {
if (root == null) return;
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = root;
while(!stack.isEmpty()||curr!=null){
while(curr!=null){
stack.push(curr);
curr=curr.left;
}TreeNode node =stack.pop();
if (node.right!=null) curr = node.right;
}
}
//非递归后续遍历 这个考虑把先序改一下,变成根 左 右 的存放顺序,然后统一放到一个新stack中再集体打印即可。
public void postOrder(TreeNode root){
if(root ==null) return;
Stack<TreeNode> stack = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
stack2.push(node);
if (node.left!=null) stack.push(node.left);
if (node.right!=null) stack.push(node.right);
}
while(!stack2.isEmpty()){
TreeNode node1 =stack2.pop();
System.out.println(node1.data);
}
}
//层序遍历
public void bfs(TreeNode root){
if (root==null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
System.out.println(node.data);
if (node.left!=null) queue.add(node.left);
if (node.right!=null) queue.add(node.right);
}
}
}
//二叉树的基本概念:左子树,右子树,父节点,子节点,树的深度(从上到下的所有节点数-1)
//(二叉)树的(每个)节点 类
class TreeNode {
public int data;
public TreeNode left;
public TreeNode right;
public TreeNode(int data){
this.data=data;
}
}
最后附上二叉树的一些基础知识
节点的度: 一个节点含有的子树的个数称为该节点的度,或者正常情况下可以说是该节点含有的子节点即可。如上图,T 节点的度为4
树的度: 一颗树中,最大的节点的度称为树的度。如上图,该树的度为4
叶子节点或终端节点: 度为0的节点称为叶子节点。T4、T11、T21、T22、T31、T32、T33为叶子节点
父节点: 若一个节点含有子节点,则这个节点称为其子节点的父节点。如上图,T 节点是 T4 节点的父节点
子节点: 一个节点含有的子树的根节点称为该节点的子节点。如上图,T4 节点是 T 节点的子节点
根节点: 一颗树中,没有双亲节点的节点称为根节点。如上图,T 节点为根节点
节点的层次: 从根开始定义,根为第1层,根的子节点为第二层,以此类推。如上图,该树有3层
节点的深度: 某节点层次是第几层,则它的深度是多少。如上图,T 节点深度为1,T1 节点深度为2
树的高度: 树中节点的最大层次。如上图,树的高度为3
非终端节点或分支节点: 度不为0的节点。如上图,T、T1、T2、T3 为分支节点
兄弟节点: 父亲节点相同的节点互称为兄弟节点。如上图,T1、T2、T3、T4 互称为兄弟节点
堂兄弟节点: 双亲在同一层次的节点互称为堂兄弟节点。如上图,T11、T21 互称为堂兄弟节点
节点的祖先: 从根节点到该节点所经过分支上的所有节点都称为该节点的祖先。如上图,T、T1 节点都为 T11 节点的祖先
子孙: 以某节点为根的子树中,任意节点都称为该节点的子孙。如上图,该树中除 T 节点其它节点都是 T 节点的子孙
森林: 由 m(m>=0)棵互不相交的树的集合称为森林。