五.二叉树
一种数据结构:定义,构建,操作
我们就希望一种数据结构能同时具备数组查找快的优点以及链表插入和删除快的优点,于是 树 诞生了。
一.基本概念:
分类:普通二叉树,满二叉树,完全二叉树。二叉查找树(平衡二叉查找树(AVL树),红黑树)。二叉堆。优先队列
平衡二叉树和平衡二叉查找树得区分一下
1.定义:按定义可分为:空树和非空树
2.术语:根节点,子节点,叶节点,兄弟节点。节点的深度,高度,层数。树的高度
层次,高度,树的深度
树的高度:是二叉树中节点的最大层次数。层数=高度,深度=高度-1
3.性质:已知层数(高度)算节点个数,已知节点个数算层数(高度):总共有l层,则共有n=2l-1个节点;总共有n个节点,则共有l=log2n+1层
(1)总共 i 层的二叉树,最多有2i-1个节点(高度为h的二叉树中,最多有2h-1个节点)
(2)二叉树第i层最多有2i-1个节点
(3)一棵具有n个节点的完全二叉树,高度为h=log2n+1 (log2n向下取整)
也就是说:有n个节点的满二叉树或者完全二叉树,其高度或者层数位h=log2n+1
(4)父节点序号为p,则左子节点:l=2*p+1,右子节点r=2*p+2。子节点序号为i,父节点:(i-1)/2
4.二叉树一般用于实现二叉查找树和二叉堆。所以一般用二叉查找树(BST)或二叉堆
二叉查找树是二叉树中最常用的一种类型。顾名思义,二叉查找树是为了实现快速查找而生的。不过,它不仅仅支持快速查找一个数据,还支持快速插入、删除一个数据。
5.操作:查找节点,插入节点,删除节点。遍历
4.效率
遍历可能不如其他操作快,但是在大型数据库中,遍历是很少使用的操作,它更常用于程序中的辅助算法来解析算术或其它表达式。
二.二叉树的遍历
分类:深度优先遍历:前序,中序,后续。广度优先遍历:层序遍历
1.递归遍历:前序,中序,后序
1 //前序遍历 2 public static void preOrder(TreeNode root) { 3 if(root==null) 4 return; 5 System.out.println(root.data); 6 preOrder(root.leftChild); 7 preOrder(root.rightChild); 8 } 9 10 //中序遍历 11 public static void inOrder(TreeNode root) { 12 if(root==null) 13 return; 14 inOrder(root.leftChild); 15 System.out.println(root.data); 16 inOrder(root.rightChild); 17 } 18 19 //后序遍历 20 public static void postOrder(TreeNode root) { 21 if(root==null) 22 return; 23 postOrder(root.leftChild); 24 postOrder(root.rightChild); 25 System.out.println(root.data); 26 }
2.非递归遍历
1 //前序 2 public static void preOrderTraversalWithStack(TreeNode root) { 3 Stack<TreeNode> stack=new Stack<>(); 4 TreeNode current=root; 5 while(current!=null || !stack.isEmpty()) { 6 while(current!=null) { 7 System.out.println(current.data); 8 stack.push(current); 9 current=current.leftChild; 10 } 11 12 if(!stack.isEmpty()) { 13 current=stack.pop(); 14 current=current.rightChild; 15 } 16 } 17 } 18 19 //中序 20 public static void inOrder(TreeNode root) { 21 Stack<TreeNode> stack=new Stack<>(); 22 TreeNode current=root; 23 while(current!=null || !stack.isEmpty()) { // ||,不是&& 24 while(current!=null) { 25 stack.push(current); 26 current=current.leftChild; 27 } 28 29 if(!stack.isEmpty()) { 30 current=stack.pop(); 31 System.out.println(current.data); 32 current=current.rightChild; 33 } 34 } 35 } 36 37 //后序 38 public static void postOrder(TreeNode root){ 39 Stack<TreeNode> stack = new Stack<TreeNode>(); 40 TreeNode current = root; 41 TreeNode lastVisit = root; //记录最后一个访问的节点,用于检验是不是根节点的右节点 42 while(current != null || !stack.empty()){ 43 while(current != null){ 44 stack.push(current); 45 current=current.leftChild; 46 } 47 48 current = stack.peek(); 49 //两种情况:1.右子节点为空,直接访问 2.右子节点不为空,但已经访问过了,直接访问 50 if(current.rightChild == null || current.rightChild== lastVisit){ //current.rightChild== lastVisit 51 System.out.println(current.data); //此时说明:右子树已经访问完毕 52 stack.pop(); 53 lastVisit=current; 54 current = null; 55 }else { 56 current=current.rightChild; 57 } 58 } 59 }
3.层序遍历
1 public static void levelOrder(TreeNode root) { 2 LinkedList<TreeNode> queue=new LinkedList<>(); 3 queue.addLast(root); 4 while(!queue.isEmpty()) { 5 TreeNode node=queue.removeFirst(); 6 System.out.println(node.data); 7 if(node.leftChild!=null) 8 queue.addLast(node.leftChild); 9 if(node.rightChild!=null) 10 queue.addLast(node.rightChild); 11 } 12 }
三.二叉查找树(BST)
1.定义
2.BST的删除操作比价复杂
3.操作:查找,插入简单,删除比较复杂
(1)查插
1 //查找 2 public Node search(int data) { 3 Node current=root; 4 while(current != null) { 5 if(data==current.data) 6 return current; 7 else if(data<current.data) 8 current=current.left; 9 else 10 current=current.right; 11 } 12 return null; 13 } 14 15 //插入 16 public boolean insert(int data) { 17 Node node=new Node(data); 18 if(root==null) { //要区分:空树 和 非空树 19 root=node; 20 return true; 21 } 22 23 Node current=root; 24 while(current != null) { 25 if(data==current.data) 26 return false; 27 else if(data<current.data) { 28 if(current.left==null) { 29 current.left=node; 30 return true; //插完直接返回 31 } 32 current=current.left; 33 } 34 else { 35 if(current.right==null) { 36 current.right=node; 37 return true; 38 } 39 current=current.right; 40 } 41 } 42 return true; //恒返回 true 43 }
(2)删除
3.AVL树:平衡二叉查找树
(1)AVL树是特殊的平衡二叉树,也是特殊的二叉查找树。它是平衡二叉树和二叉查找树的结合
(1)定义:左右子树的高度差不超过1
平衡二叉查找树又称AVL树,它或者是一棵空树,或者是具有下列性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1
AVL树是最先发明的自平衡二叉查找树算法。AVL树算法的发明是为了平衡二叉查找树
4.红黑树
(1)红黑树是平衡二叉树的一种。平衡二叉树追求完全平衡,条件比价苛刻。红黑树放弃了追求完全平衡,追求大致平衡
红黑树是一种不严格的平衡二叉树
三.二叉堆
(1)定义:二叉堆本质是一个完全二叉树。分为:最大堆,最小堆
(2)用途:实现优先级队列和堆排序
(2)操作:1.插入节点 2.删除节点 3.构建二叉堆
这几种操作都依赖于二叉堆的自我调整:“上浮”调整,“下沉”调整
掌握:“上浮”调整,“下沉”调整,构建堆
(3)二叉堆基于数组实现:父节点索引parent,则左子节点:2*parent+1,右子节点:2*parent+2
(4)“上浮”,“下沉”代码
1 package com.midiyu.tree; 2 3 import java.util.Arrays; 4 5 public class BinaryHeap { 6 //“上浮”调整 7 public static void upAdjust(int[] array) { 8 int childIndex=array.length-1; 9 int parentIndex=(childIndex-1)/2; 10 int temp=array[childIndex]; 11 12 while(childIndex>0 && temp<array[parentIndex]) { 13 array[childIndex]=array[parentIndex]; 14 childIndex=parentIndex; 15 parentIndex=(parentIndex-1)/2; 16 } 17 array[childIndex]=temp; 18 } 19 20 //“下沉”调整 21 public static void downAdjust(int[] array,int parentIndex) { 22 int childIndex=2*parentIndex+1; 23 int temp=array[parentIndex]; 24 while(childIndex<array.length) { 25 if(childIndex+1<array.length && array[childIndex+1]<array[childIndex]) 26 childIndex++; 27 if(temp<=array[childIndex]) 28 break; 29 array[parentIndex]=array[childIndex]; 30 parentIndex=childIndex; 31 childIndex=2*childIndex+1; 32 } 33 array[parentIndex]=temp; 34 } 35 36 //构建堆 37 public static void buildHeap(int[] array) { 38 for(int i=(array.length-2)/2;i>=0;i--) //最后一个非叶子节点的索引:(最后一个叶子节点索引-1)/2 39 downAdjust(array, i); 40 } 41 42 public static void main(String[] args) { 43 int[] array = new int[] {1,3,2,6,5,7,8,9,10,0}; 44 upAdjust(array); 45 System.out.println(Arrays.toString(array)); 46 47 array = new int[] {7,1,3,10,5,2,8,9,6}; 48 buildHeap(array); 49 System.out.println(Arrays.toString(array)); 50 } 51 52 }
四.优先级队列
二叉堆是实现堆排序和优先级队列的基础
1.还是在队尾入队,队头出队。但先进不一定先出,而是根据优先级出队
最小优先级队列:当前最小元素优先出队
最大优先级队列:当前最大元素优先出队
2.实现:用最大堆来实现最大优先级队列。每一次入队操作就是堆的插入操作,每一次出队操作就是删除堆顶节点
入队:“上浮”,出队:“下沉”
3.实现代码
1 package com.midiyu.tree; 2 3 public class PriorithQueue { 4 int[] array; 5 int size; 6 7 public PriorithQueue() { 8 this.array=new int[32]; 9 } 10 11 //入队 12 public void enQueue(int key) { 13 // if(size>=array.length) 14 // resize(); 15 array[size++]=key; 16 upAdjust(); 17 } 18 19 //出队 20 public int deQueue() throws Exception { 21 if(size<=0) 22 throw new Exception("队已经空了"); 23 int head=array[0]; 24 array[0]=array[--size]; 25 downAdjust(); 26 return head; 27 } 28 29 //“上浮”调整 30 public void upAdjust() { 31 int childIndex=size-1; 32 int parentIndex=(childIndex-1)/2; 33 int temp=array[childIndex]; 34 while(childIndex>0 && array[childIndex]>array[parentIndex]) { 35 array[childIndex]=array[parentIndex]; 36 childIndex=parentIndex; 37 parentIndex=(parentIndex-1)/2; 38 } 39 array[childIndex]=temp; 40 } 41 42 //“下沉”调整 43 public void downAdjust() { 44 int parentIndex=0; 45 int childIndex=0; 46 int temp=array[parentIndex]; 47 while(childIndex<size) { 48 if(childIndex+1<size && array[childIndex+1]>array[childIndex]) 49 childIndex++; 50 if(temp>=array[childIndex]) 51 break; 52 array[parentIndex]=array[childIndex]; 53 parentIndex=childIndex; 54 childIndex=2*childIndex+1; 55 } 56 array[parentIndex]=temp; 57 } 58 59 60 61 public static void main(String[] args) throws Exception { 62 PriorithQueue priorithQueue=new PriorithQueue(); 63 priorithQueue.enQueue(3); 64 priorithQueue.enQueue(5); 65 priorithQueue.enQueue(10); 66 priorithQueue.enQueue(2); 67 priorithQueue.enQueue(7); 68 System.out.println("出队元素:"+priorithQueue.deQueue()); 69 System.out.println("出队元素:"+priorithQueue.deQueue()); 70 } 71 72 }