二叉树

来源:互联网 发布:不要在淘宝请符 编辑:程序博客网 时间:2024/05/16 10:12

定义:

二叉树(Binary Tree):n(n>=0)个结点的有限集合,该集合或者为空集(空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。

满二叉树:一颗深度为K的二叉树,如果它包含了2k-1个节点,则该二叉树为满二叉树。

完全二叉树:一颗n个节点的二叉树,按满二叉树的编号方式对它进行编号,若树中所有节点和满二叉树1~n编号完全一致,则称该树为完全二叉树。

性质:

①二叉树第i层上的节点数目之多为2i-1(i>=1);

②任何一颗二叉树中,如果叶子节点的数量为n0,度为2的节点数量为n2,则n0=n2+1;

③深度为k的二叉树至多有2k-1个结点(k>=1);

④具有n个节点的完全二叉树的深度为log2n+1。

⑤如果对一颗有n个节点的完全二叉树的节点按层进行编号,对于任一节点i(1<=i<=n),有:

     1)若i=1,则节点i无双亲,是根节点。

     2)若2i>n,则节点i为叶子节点,无左孩子;若2i<=n,则节点i有左孩子,编号为2i;

     3)若2i+1>n,则节点i无右节点;若2i+1<=n,则节点i有右节点,编号为2i+1。

二叉树的存储:

1、顺序存储:使用一个数组来存储一颗二叉树,对于普通的二叉树来说会造成空间的浪费。除非该二叉树是完全二叉树,为例解决这个问题我们可以采用链表方式存储二叉树。

2、二叉链表存储:为一个树节点添加两个指向其左右孩子节点的引用,但是这种存储方式在访问一个节点的父节点时比较困难,必须采用遍历二叉树的方式来搜索其父节点。为了解决这个问题,我们可以采用三叉链表存储;

3、三叉链表存储:为一个树节点添加两个指向其左右孩子节点的引用和一个指向其父节点的引用。

package tree;public class BinaryTree{public static class TreeNode<E>{E data;  TreeNode<E> parent;TreeNode<E> leftNode;TreeNode<E> rightNode;public TreeNode() {}public TreeNode(E data) {this.data=data;}public TreeNode(E data,TreeNode<E> leftNode,TreeNode<E> rightNode){this.data=data;this.leftNode=leftNode;this.rightNode=rightNode;}}private TreeNode root;public <E> void setRoot(TreeNode<E> root) {this.root = root;}// 返回根节点public <E> TreeNode<E> getRoot(){if(root==null || isEmpty()){throw new RuntimeException("root is null");}else {return root;}}public BinaryTree() {}public <E> BinaryTree(TreeNode<E> root) {this.root=root;}//返回父节点public <E> TreeNode<E> getParent(TreeNode<E> node){if(node==null){throw new RuntimeException("the node is null");}else {return node.parent;}}//为指定节点添加子节点public <E> void addNode(TreeNode<E> parent,E data, boolean isLeft){if(parent==null){throw new RuntimeException("parent is null,so can't add child to this parent");}else if (isLeft && parent.leftNode!=null) {throw new RuntimeException("the parent already has haven a leftchild");}else if (!isLeft && parent.rightNode!=null) {throw new RuntimeException("the parent already has haven a rightchild");}   TreeNode<E> child=new TreeNode<E>(data); if (isLeft) { parent.leftNode=child;}else {parent.rightNode=child;} child.parent=parent;}public <E> void addNode(TreeNode<E> parent,TreeNode<E> child, boolean isLeft){if(parent==null){throw new RuntimeException("parent is null,so can't add child to this parent");}else if (isLeft && parent.leftNode!=null) {throw new RuntimeException("the parent already has haven a leftchild");}else if (!isLeft && parent.rightNode!=null) {throw new RuntimeException("the parent already has haven a rightchild");} if (isLeft) { parent.leftNode=child;}else {parent.rightNode=child;} child.parent=parent;}// 判断二叉树是否为空public boolean isEmpty(){return root==null;}//返回指定节点的左子节点public <E> TreeNode<E> getLeftChild(TreeNode<E> parent){if(parent==null){throw new RuntimeException("parent is null");}else {return parent.leftNode==null? null : parent.leftNode;}}//返回指定节点的右子节点public <E> TreeNode<E> getRightChild(TreeNode<E> parent){if(parent==null){throw new RuntimeException("parent is null");}else {return parent.rightNode==null? null:parent.rightNode;}}//返回二叉树的深度public int deep(){return deep(root);}//使用递归,如果节点为空则深度为0,如果节点既没有左孩子,也没有右孩子,则深度为1,否则为其左右孩子树中最大的深度+1private <E> int deep(TreeNode<E> root) {if(root==null){return 0;}else if (root.leftNode ==null && root.rightNode==null) {return 1;}else {int leftDeep=deep(root.leftNode);int rightDeep=deep(root.rightNode);int max= leftDeep>rightDeep? leftDeep:rightDeep;return max+1;}}}

遍历二叉树

如果采用顺序结构来保存二叉树,程序遍历二叉树将非常简单,直接遍历底层数组即可。如果采用链表来保存二叉树则有一下两种遍历方式:
深度优先遍历
这种遍历方式采用递归调用的方式(利用栈的原理),递-入栈,归-出栈。
广度优先遍历
这种遍历方式将逐层访问每层的节点,先访问根(第一层)节点,然后访问第二层的节点……一次类推。因此广度优先遍历又称为按层遍历。

深度优先遍历

深度优先遍历又分为先序遍历、中序遍历、后序遍历

图解深度优先遍历:

图解深度优先遍历
先序遍历:
①访问根节点
②递归遍历左子树
③递归遍历右子树

public  List<TreeNode> preIterator(){return preIterator(root);}private List<TreeNode> preIterator(TreeNode root) {List<TreeNode> list=new ArrayList<TreeNode>();list.add(root);if(root.leftNode!=null){list.addAll(preIterator(root.leftNode));}if (root.rightNode!=null){list.addAll(preIterator(root.rightNode));}return list;}

中序遍历
①递归遍历左子树
②访问根节点
③递归遍历右子树

public List<TreeNode> midIterator(){return midIterator(root);}private  List<TreeNode> midIterator(TreeNode root) {List<TreeNode> list=new ArrayList<TreeNode>();if(root==null){return null;}if(root.leftNode!=null){list.addAll(midIterator(root.leftNode));}list.add(root);if(root.rightNode!=null){list.addAll(midIterator(root.rightNode));}return list;}

后序遍历
①递归遍历左子树
②递归遍历右子树
③访问根节点
public List<TreeNode> postIterator(){return postIterator(root);}private List<TreeNode> postIterator(TreeNode root) {if(root==null){return null;}List<TreeNode> list=new ArrayList<TreeNode>();if(root.leftNode!=null){list.addAll(postIterator(root.leftNode));}if(root.rightNode!=null){list.addAll(postIterator(root.rightNode));}list.add(root);return list;}

广度优先遍历

为了实现广度优先遍历,可以借助于具有“FIFO”特征的队列来实现:
①建立一个队列(先进先出),把书的根节点压入队列;
②从队列中弹出一个节点(第一次弹出的节点就是根节点),然后把该节点的左,右节点压入队列,如果没有子节点,则说明已经到达叶子节点。
③用循环重复执行②,直到队列为空时,说明所有的叶子节点(深度最深的层)都已经经过了队列,也就完成了遍历。
public List<TreeNode> breadthFirst(){Queue<TreeNode> queue=new ArrayDeque<TreeNode>();List<TreeNode> list=new ArrayList<TreeNode>();if(root!=null){//将树根加入队列queue.offer(root);}while(!queue.isEmpty()){//将队列的“队尾”的元素添加到list中list.add(queue.peek());//从队列中弹出一个节点,并获取TreeNode node=queue.poll();//如果该节点有左孩子,就把左孩子压入队列if(node.leftNode!=null){queue.offer(node.leftNode);}//如果该节点有右孩子,就把右孩子压入队列if(node.rightNode!=null){queue.offer(node.rightNode);}}return list;}


0 0
原创粉丝点击