目录
树
树的定义
树(tree)是n(n>=0)个节点的有限集。当n=0时,称为空树。在任意一个非空树中,有如下特点。
-
有且仅有一个特定的称为根的节点
-
当n>1时其余节点可分为m(m>0)个互不相交的有限集,每一个集合本身又是一个树,并称为根的子树。
树的术语:根节点(root)、叶子节点(leaf)、父节点(parent)、孩子节点(child)、兄弟节点(sibling)
二叉树
一种树的特殊形式,每个节点最多有两个孩子节点。一个称为左孩子(left child),一个称为右孩子(right child)。
二叉树可以用两种物理储存结构来表达:
-
链式储存结构
二叉树的每个节点包含三部分:
-
储存数据的data变量
-
指向左孩子的left指针
-
指向右孩子的right指针
public class TreeNode {
int data;
TreeNode leftnode;
TreeNode rightnode;
public TreeNode(int data) {
this.data=data;
}
}
-
数组 假设一个父节点的下标时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种。
-
前序遍历
-
中序遍历
-
后序遍历
-
层序遍历
从更宏观的角度来看,二叉树的遍历归结为两大类。
-
深度优先遍历(前中后序遍历)
-
广度优先遍历(层序遍历)
-
前序遍历 输出顺序:根节点、左子树、右子树。
/**
* 递归方法 前序遍历
* @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);
}
}