【Java实现数据结构】二叉树、二叉堆与优先队列

目录

树的定义

二叉树

二叉树的构建

二叉树的遍历

二叉堆

二叉堆的构建

优先队列


树的定义

树(tree)是n(n>=0)个节点的有限集。当n=0时,称为空树。在任意一个非空树中,有如下特点。

  1. 有且仅有一个特定的称为根的节点

  2. 当n>1时其余节点可分为m(m>0)个互不相交的有限集,每一个集合本身又是一个树,并称为根的子树。

树的术语:根节点(root)、叶子节点(leaf)、父节点(parent)、孩子节点(child)、兄弟节点(sibling)

二叉树

一种树的特殊形式,每个节点最多有两个孩子节点。一个称为左孩子(left child),一个称为右孩子(right child)。

二叉树可以用两种物理储存结构来表达:

  1. 链式储存结构
    二叉树的每个节点包含三部分:

  • 储存数据的data变量

  • 指向左孩子的left指针

  • 指向右孩子的right指针

public class TreeNode {
 int data;
 TreeNode leftnode;
 TreeNode rightnode;
 public TreeNode(int data) {
  this.data=data;
 }
}
  1. 数组 假设一个父节点的下标时parent,那么他的左孩子节点下标就是 2parent+1;右孩子节点下标就是2parent+2。 反过来,假设一个左孩子的节点下标时leftChild,那么它的父节点下标就是(leftChild-1)/2。

二叉树的构建

方法参数传入数据类型为LinkedList。

/**
  * 递归方法 创建(Integer)二叉树
  * @param inputList
  * @return
  */
 public static TreeNode createBT(LinkedList<Integer> inputList) {
  TreeNode node=null;
  if(inputList==null||inputList.isEmpty()) {
   return null;
  }
  Integer data=inputList.removeFirst();
  if(data!=null) {
   node=new TreeNode(data);
   node.leftnode=createBT(inputList);
   node.rightnode=createBT(inputList);
  }
  return node;
 }

二叉树的遍历

从节点之间位置关系的角度来看,二叉树的遍历分为4种。

  1. 前序遍历

  2. 中序遍历

  3. 后序遍历

  4. 层序遍历

从更宏观的角度来看,二叉树的遍历归结为两大类。

  1. 深度优先遍历(前中后序遍历)

  2. 广度优先遍历(层序遍历)

  • 前序遍历 输出顺序:根节点、左子树、右子树。

/**
  * 递归方法 前序遍历
  * @param node
  */
 public static void preOrderTraveral(TreeNode node) {
  if(node==null) {
   return;
  }
  System.out.print(node.data+"  ");
  preOrderTraveral(node.leftnode);
  preOrderTraveral(node.rightnode);
 }

另外一种方法:

/**
  * 非递归 前序遍历 利用栈
  * @param node
  */
 public static void preOrderTraveralWithStack(TreeNode node) {
  Stack<TreeNode> stack=new Stack<TreeNode>();
  
  while(node!=null||!stack.isEmpty()) {
   //迭代访问节点的左孩子,并入栈
   while(node!=null) {
    System.out.print(node.data+"  ");
    stack.push(node);
    node=node.leftnode;
   }
   
   if(!stack.isEmpty()) {
    node=stack.pop();
    node=node.rightnode;
   }
  }
 }
  • 中序遍历 输出顺序:左子树、根节点、右子树。

/**
  * 递归方法 中序遍历
  * @param node
  */
 public static void inOrderTraveral(TreeNode node) {
  if(node==null) {
   return;
  }
  inOrderTraveral(node.leftnode);
  System.out.print(node.data+"  ");
  inOrderTraveral(node.rightnode);
 }

另一种方法:

/**
  * 非递归 中序遍历 利用栈
  * @param node
  */
 public static void inOrderTraveralWithStack(TreeNode node) {
  Stack<TreeNode> stack=new Stack<TreeNode>();
   
  while(node!=null||!stack.isEmpty()) {
   while(node!=null) {
    stack.push(node);
    node=node.leftnode;
   }
   if(!stack.isEmpty()) {
    node=stack.pop();
    System.out.print(node.data+"  ");
    node=node.rightnode;
   }
  }
 }
  • 后序遍历 输出顺序:左子树,右子树,根节点。

/**
  * 后序遍历
  * @param node
  */
 public static void postOrderTraveral(TreeNode node) {
  if(node==null) {
   return;
  }
  postOrderTraveral(node.leftnode);
  postOrderTraveral(node.rightnode);
  System.out.print(node.data+"  ");
 }

另一种方式: 后序遍历的难点在于:需要判断上次访问的节点是位于左子树,还是右子树。 若是位于左子树,则需跳过根节点,先进入右子树,再回头访问根节点; 若是位于右子树,则直接访问根节点。

/**
  * 非递归 后序遍历 利用栈
  * @param node
  */
 public static void postOrderTraveralWithStack(TreeNode node) {
  if(node==null)
   return;
  Stack<TreeNode> s = new Stack<TreeNode>();
  
  TreeNode curNode; //当前访问的结点
  TreeNode lastVisitNode; //上次访问的结点
  curNode = node;
  lastVisitNode = null;
  
  //把currentNode移到左子树的最下边
  while(curNode!=null){
   s.push(curNode);
   curNode = curNode.leftnode;
  }
  while(!s.empty()){
   curNode = s.pop();  //弹出栈顶元素
   //一个根节点被访问的前提是:无右子树或右子树已被访问过
   if(curNode.rightnode!=null&&curNode.rightnode!=lastVisitNode){
    //根节点再次入栈
    s.push(curNode);
    //进入右子树,且可肯定右子树一定不为空
    curNode = curNode.rightnode;
    while(curNode!=null){
     //再走到右子树的最左边
     s.push(curNode);
     curNode = curNode.leftnode;
    }
   }else{
    //访问
    System.out.print(curNode.data+"  ");
    //修改最近被访问的节点
    lastVisitNode = curNode;
   }
  } //while
 }
  • 层序遍历(广度优先遍历)

/**
  *  层序遍历
  * @param root
  */
 public static void levelOrderTraveral(TreeNode root) {
  Queue<TreeNode> queue=new LinkedList<TreeNode>();
  queue.offer(root);
  while(!queue.isEmpty()) {
   TreeNode node=queue.poll();//返回第一个元素并从队列中删除 
   System.out.print(node.data+"  ");
   if(node.leftnode!=null) {
    queue.offer(node.leftnode);//添加元素
   }
   if(node.rightnode!=null) {
    queue.offer(node.rightnode);
   }
  }
 }

二叉堆

最大堆:最大堆的任一个父节点的值,都大于或等于它的左右孩子节点的值。
最小堆:最大堆的任一个父节点的值,都小于或等于它的左右孩子节点的值。
最大堆的堆顶是整个堆中的最大元素,最小堆的堆顶是整个堆中的最小元素

二叉堆的构建

构建二叉堆就是把一个无序的完全二叉树调整为二叉堆,本质就是让所有非叶子节点依次下沉。
堆的插入和删除都是对单一节点的上浮或下沉操作,时间复杂度为O(logn),堆的构建的时间复杂度为O(n)。

  • 代码实现 二叉堆虽然是一个完全二叉树,但它的储存方式并不是链式储存,而是顺序储存。二叉树的所有节点都储存在数组中。
    假设父节点的下标是parent,那么它的左孩子就是2×parent+1,右孩子是2×parent+2。

/**
  * 上浮操作
  * @param array 待调整的堆
  */
 public static void upAdjust(int[] array) {
  int childIndex=array.length-1;
  int parentIndex=(childIndex-1)>>1;
  int temp=array[childIndex];
  while(childIndex>0&&array[childIndex]<array[parentIndex]) {
   // 不需交换,单向赋值即可
   array[childIndex]=array[parentIndex];
   childIndex=parentIndex;
   parentIndex=(childIndex-1)>>1;
  }
  array[childIndex]=temp;
 }
 
// 小项堆
/**
  * 下沉操作
  * @param array
  * @param parentIndex
  */
 public static void downAdjust(int[] array,int parentIndex) {
  int childIndex=2*parentIndex+1;
  int temp=array[parentIndex];
  
  while(childIndex<array.length) {
   if(childIndex+1<array.length&&array[childIndex+1]<array[childIndex]) {
    childIndex++;
   }
   if(temp<=array[childIndex])break;
   array[parentIndex]=array[childIndex];
   parentIndex=childIndex;
   childIndex=childIndex*2+1;
  }
  array[parentIndex]=temp;
 }
 
 /**
  * 构建堆
  * @param array
  */
 public static void buildHeap(int[] array) {
  for(int i=(array.length-1)>>1;i>=0;i--) {
   downAdjust(array, i);
  }
 }

构建大项堆代码与以上类似

优先队列

优先队列不再遵循先入先出的原则,分两种情况:

  • 最大优先队列,无论入队顺序如何,都是当前最大元素优先出队

  • 最小优先队列,无论入队顺序如何,都是当前最小元素优先出队

优先队列的入队和出队操作的时间复杂度都是0(logn)

  • 代码实现

package com.BinaryTree;

import java.util.Arrays;

/**
 * 最大优先队列
 * @author 代
 */
public class PriorityQueue {

 
 public static void main(String[] args) throws Exception {
  PriorityQueue priorityQueue=new PriorityQueue();
  priorityQueue.enQueue(3);
  priorityQueue.enQueue(5);
  priorityQueue.enQueue(10);
  priorityQueue.enQueue(2);
  priorityQueue.enQueue(7);
  System.out.println(priorityQueue.deQueue());
  System.out.println(priorityQueue.deQueue());
  System.out.println(priorityQueue.deQueue());
  

 }

 private int[] array;
 private int size=0;
 
 /**
  * 上浮
  */
 private void upAdjust() {
  int childIndex=size-1;
  int parentIndex=(childIndex-1)>>1;
  int temp=array[childIndex];
  
  while(childIndex>0&&temp>array[parentIndex]) {
   array[childIndex]=array[parentIndex];
   childIndex=parentIndex;
   parentIndex=(childIndex-1)>>1;
  }
  array[childIndex]=temp;
 }
 
 /**
  * 下沉
  */
 private void downAdjust() {
  int parentIndex=0;
  int childIndex=1;
  int temp=array[parentIndex];
  
  while(childIndex<size) {
   if(childIndex+1<size&&array[childIndex+1]>array[childIndex]) {
    childIndex++;
   }
   if(temp>=array[childIndex])break;
   array[parentIndex]=array[childIndex];
   parentIndex=childIndex;
   childIndex=2*parentIndex+1;
  }
  array[parentIndex]=temp;
 }
 
 public PriorityQueue() {
  //初始队列
  array=new int[32];
 }
 
 /**
  * 入队,扩容
  * @param key
  */
 public void enQueue(int key) {
  if(size>=array.length) {
   resize();
  }
  array[size++]=key;
  upAdjust();
 }
 
 /**
  * 出队 并获取堆顶数据(int)
  * @return
  * @throws Exception
  */
 public int deQueue() throws Exception {
  if(size<=0) {
   throw new Exception("the queue is empty !");
  }
  int head=array[0];
  array[0]=array[--size];
  downAdjust();
  return head;
 }
 
 /**
  * 扩容
  */
 private void resize() {
  int newSize=this.size*2;
  this.array=Arrays.copyOf(this.array, newSize);
 }
 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值