二叉树
- AVL平衡树
- 最大堆
- 优先队列
AVL平衡树
起名原因:G.M.Adelson-Velsky和E.M.Landis
它是一种最早的自平衡二分搜索树结构;满二叉树一定是平衡树,高度最低;而完全二叉树也是平衡二叉树,叶子结点深度相差不为1;
AVL平衡树的定义
对于任意一个节点,左子树和右子树的高度差不能超过1;如图是平衡树;
平衡树的调整
既然是平衡树,当我们进行添加删除等操作后,该树有可能就不是平衡树了,所以我们需要将它调整为平衡树;那问题就来了,怎末调整,什么时候调整呢;首先标记节点的高度,再计算平衡因子,也就是该节点的左右子树的高度差;在我们对树进行添加删除元素时需要维护该树,从该操作节点向上回溯维护平衡性。
既然需要调整就会出现不同的情况,如下:
LL旋转
对于第一种情况添加元素在该节点左孩子的左孩子,如图所示进行操作;
就相当于将当前节点y向右旋转下来作为其左孩子x的右孩子,但是当前节点y的左孩子为子树可能存在右孩子,所以我们需要先将其右孩子保存再赋给当前节点y作为左孩子;主要是因为其值存在上图的大小关系,也就是不能改变二分搜索树的性质;具体就是x.right = y;y.left = T3;
RR旋转
插入的元素在不平衡节点的右孩子的右孩子,我们只需将其向左旋转,让不平衡节点y分支作为其右孩子的左孩子,还是一样其右孩子x的左孩子可能有元素,所以需要保存起来,让不平衡节点称为其右孩子的左孩子,然后将保存的值作为y的右孩子;也就是x.left = y;y.right=T3;
LR旋转
插入的元素在不平衡节点的左孩子的右孩子,先对x进行左旋转RR,再对y进行右旋转LL。
RL旋转
插入的元素再不平衡的节点的右孩子的左孩子,它的操作和LR旋转相反,先对x进行右旋转LL,再对y进行左旋转RR即可;
平衡树代码实现
1.平衡树的节点类及属性定义
节点类定义和TreeMap的节点类定义无非是多一个高度属性;而属性定义是一致的。
package tree;
import ifce.List;
import ifce.Map;
import ifce.Set;
import lianbiao.LinkedList;
import linestract.ArrayList;
public class AVLTreeMap<K extends Comparable<K>,V> implements Map<K,V> {
private class Node{
public K key;
public V value;
public int height;
public Node left;
public Node right;
public Node(K key,V value){
this.key = key;
this.value = value;
left = null;
right = null;
height = 1;
}
}
private Node root;
private int size;
public AVLTreeMap(){
root = null;
size = 0;
}
2.辅助函数
2.1根据所给节点和key查找相应的节点
//以node为根的子树中,查找key所在的结点;
private Node getNode(Node node,K key){
if(node == null){
return null;
}
if(key.compareTo(node.key)<0){
return getNode(node.left,key);
}else if(key.compareTo(node.key)>0){
return getNode(node.right,key);
}else{
return node;
}
}
2.2获取某个节点高度
根据所给节点判断是否为null,为null返回0,否则返回其高度即可。
//获取某个结点的高度;
private int getHeight(Node node){
if(node == null){
return 0;
}
return node.height;
}
2.3计算某个节点的平衡因子
若是传入节点为null,返回0,否则返回其左右孩子的高度差即可。
//计算某个结点的平衡因子;(左右孩子高度差);
private int getBalanceFactor(Node node){
if(node == null){
return 0;
}
return getHeight(node.left) - getHeight(node.right);
}
2.4验证是否为二分搜索树,以及是否为平衡树
验证是否为二分搜索树,我们是将该树进行中序遍历存储到list集合中,然后遍历集合,只要后面的值比前面的值大(所有)就是二分搜索树。
验证是否为平衡树,则是自己再实现一个从根结点遍历是否为平衡树,如果传入的节点为null,则是平衡树;如果不为空,则需要获取该节点的平衡因子,判断平衡因子的绝对值是否超过1,超过则不是,否则就是平衡的。但是需要从根到子孙,所以根符合并不代表整个树是平衡树,还有它的所分支是平衡的,左右分支的每个节点都是平衡的才是平衡树;
//验证是否为二分搜索树;
public boolean isBST(){
ArrayList<K> list = new ArrayList<>();
inOrderKeys(root,list);
for(int i=1;i<list.size();i++){
if(list.get(i-1).compareTo(list.get(i))>0){
return false;
}
}
return true;
}
private void inOrderKeys(Node node,ArrayList<K> list) {
if(node == null){
return;
}
inOrderKeys(node.left,list);
list.add(node.key);
inOrderKeys(node.right,list);
}
//验证是否为平衡树;
public boolean isBalanced(){
return isBalanced(root);
}
private boolean isBalanced(Node node) {
if(node == null){
return true;
}
//从根到子孙,所以根符合并不代表整个树是平衡树,还有它的所分支是平衡的,左右分支的每个节点都是平衡的才是平衡树;
int balancedFactor = getBalanceFactor(node);
if(Math.abs(balancedFactor)>1){
return false;
}
return isBalanced(node.left) && isBalanced(node.right);
}
2.5左右旋转实现
实现图分析在上方,只是旋转后需要更新动过节点的高度,然后将新节点向上返回;
public Node leftRotate(Node y){
Node x = y.right;
Node T3 = x.left;
x.left = y;
y.right = T3;
y.height = Math.max(getHeight(y.left),getHeight(y.right))+1;
x.height = Math.max(getHeight(x.left),getHeight(x.right))+1;
return x;
}
private Node rightRotate(Node y){
Node x = y.left;
Node T3 = x.right;
x.right = y;
y.left = T3;
y.height = Math.max(getHeight(y.left),getHeight(y.right))+1;
x.height = Math.max(getHeight(x.left),getHeight(x.right))+1;
return x;
}
3.添加元素
需要在实现一个返回Node节点的添加方法,递归结束条件是遍历的节点为空时,添加元素,元素个数加1;并返回新建节点;如果不空则需要根据key向不同方向递归;如果key与遍历的值相等,则更新值即可。然后既然添加了一个元素,那我们就需要更新高度,然后再向上回溯,判断该树是否为平衡树,若是某个节点不平衡则需要进行相应的旋转操作即可。
@Override
public void put(K key, V value) {
root = put(root,key,value);
}
private Node put(Node node, K key, V value) {
if(node == null){
size++;
return new Node(key,value);
}
if(key.compareTo(node.key)<0){
node.left = put(node.left,key,value);
}else if(key.compareTo(node.key)>0){
node.right = put(node.right,key,value);
}else {
node.value = value;
}
//当前结点的高度需要更新;
node.height = Math.max(getHeight(node.left),getHeight(node.right))+1;
//判断当前结点是否平衡;
int balanceFactor = getBalanceFactor(node);
//不平衡有四种情况;
//>1左侧不平衡,node.left>=0左侧的左侧不平衡;
if(balanceFactor>1 && getBalanceFactor(node.left)>=0){
return rightRotate(node);
}
//>1左侧不平衡,node.left<0左侧的右侧不平衡;
if(balanceFactor>1 && getBalanceFactor(node.left)<0){
node.left = leftRotate(node.left);
return rightRotate(node);
}
//<-1右侧不平衡,node.right>=0右侧的左侧不平衡;
if(balanceFactor<-1 && getBalanceFactor(node.right)>=0){
node.right = rightRotate(node.right);
return leftRotate(node);
}
//<-1右侧不平衡,node.right<0右侧的右侧不平衡;
if(balanceFactor<-1 && getBalanceFactor(node.right)<0){
return leftRotate(node);
}
return node;
}
4.删除元素
和TreeMap中的删除元素几乎一样,但是区别在于我们在向下递归时并不讲该节点返回,而是先定义一个节点,用来保存要返回的节点;当遍历的值和key相等则说明找到了删除元素,然后根据相应的情况做删除操作,这里我们是将不同情况并列处理,因为我们不在直接返回节点,二十保存在先前定义的节点;在这里我们需要判断我们删除的元素是否有孩子节点,若是没有删除后向上返回的节点为null,则不需要更新高度,因为对于null节点它是没有高度的,会报错的;不空则和添加元素一样需要更新高度,然后判断每个节点是否平衡,不平衡则进行相应的旋转操作即可。
@Override
public V remove(K key) {
Node delNode = getNode(root,key);
if(delNode != null){
root = remove(root,key);
return delNode.value;
}
return null;
}
private Node remove(Node node, K key) {
if(node == null){
return null;
}
Node retNode = null;
if(key.compareTo(node.key)<0){
node.left = remove(node.left,key);
retNode = node;
}else if(key.compareTo(node.key)>0){
node.right = remove(node.right,key);
retNode = node;
}else {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
retNode = rightNode;
} else if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size--;
retNode = leftNode;
} else {
Node successor = mininum(node.right);
successor.right = remove(node.right,successor.key);
successor.left = node.left;
node.left = node.right = null;
retNode = successor;
}
}
//删除的结点没有左右子树,则返回null,不需要更新高度;
if(retNode == null){
return retNode;
}
//更新高度;
retNode.height = Math.max(getHeight(retNode.left),getHeight(retNode.right))+1;
//判断是否平衡;
int balanceFactor = getBalanceFactor(retNode);
//>1左侧不平衡,node.left>=0左侧的左侧不平衡;
if(balanceFactor>1 && getBalanceFactor(retNode.left)>=0){
return rightRotate(retNode);
}
//>1左侧不平衡,node.left<0左侧的右侧不平衡;
if(balanceFactor>1 && getBalanceFactor(retNode.left)<0){
retNode.left = leftRotate(retNode.left);
return rightRotate(retNode);
}
//<-1右侧不平衡,node.right>=0右侧的左侧不平衡;
if(balanceFactor<-1 && getBalanceFactor(retNode.right)>=0){
retNode.right = rightRotate(retNode.right);
return leftRotate(retNode);
}
//<-1右侧不平衡,node.right<0右侧的右侧不平衡;
if(balanceFactor<-1 && getBalanceFactor(retNode.right)<0){
return leftRotate(retNode);
}
return retNode;
}
private Node mininum(Node node) {
if(node.left == null){
return node;
}
return mininum(node.left);
}
5.判断包含,是否为空,元素个数,通过key获取value,修改value等方法实现
判断包含只需通过辅助函数找节点,判断找到的节点是否为null即可;
获取值也是一样,判断找到的节点是否为null,不为null,则返回对应值,否则返回null;
修改值则是找到的节点为null,则表明不存在,我们向外抛出异常即可。存在则更新值即可;
元素个数返回size属性即可;而判空则是root为null,并且元素个数为0双重保险。
@Override
public boolean contains(K key) {
return getNode(root,key) != null;
}
@Override
public V get(K key) {
Node node = getNode(root,key);
if(node != null){
return node.value;
}
return null;
}
@Override
public void set(K key, V value) {
Node node = getNode(root,key);
if(node == null){
throw new IllegalArgumentException("key-value is not exist");
}
node.value = value;
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0 && root == null;
}
6.返回所有key,所有value,以及所有键值对entry数组的方法
和TreeMap实现一摸一样,通过中序遍历将其添加到相应的集合中。
@Override
public Set<K> keySet() {
TreeSet<K> set = new TreeSet<>();
inOrderKeySet(root,set);
return set;
}
private void inOrderKeySet(Node node, TreeSet<K> set) {
if(node == null){
return;
}
inOrderKeySet(node.left,set);
set.add(node.key);
inOrderKeySet(node.right,set);
}
@Override
public List<V> values() {
LinkedList<V> list = new LinkedList<>();
inOrderValues(root,list);
return list;
}
private void inOrderValues(Node node, LinkedList<V> list) {
if(node == null){
return;
}
inOrderValues(node.left,list);
list.add(node.value);
inOrderValues(node.right,list);
}
@Override
public Set<Entry<K, V>> entrySet() {
TreeSet<Entry<K,V>> entries = new TreeSet<>();
inOrderEntrys(root,entries);
return entries;
}
private void inOrderEntrys(Node node, TreeSet<Entry<K,V>> entries) {
if(node == null){
return;
}
inOrderEntrys(node.left,entries);
entries.add(new BSTEntry<>(node.key,node.value));
inOrderEntrys(node.right,entries);
}
7.entry子类实现
private class BSTEntry<K extends Comparable<K>,V> implements Entry<K,V>{
private K key;
private V value;
public BSTEntry(K key,V value){
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public String toString() {
return key + ":"+value;
}
@Override
public int compareTo(Entry<K, V> o) {
return this.getKey().compareTo(o.getKey());
}
}
最大堆
所谓最大堆就是要求根结点的值是这棵树的最大值,二叉堆是一颗完全二叉树(区别于满二叉树),堆中的某个节点的值总是不大于其父节点的值,通常这种堆称为最大堆(最小堆相反),下层的某个元素不一定小于上层的某个元素。
属性定义及构造器定义
使用的是ArrayList存储数据;所以在构造函数中需要创建一个list集合。
package tree;
import linestract.ArrayList;
import java.util.Iterator;
public class MaxHeap<E extends Comparable<E>> implements Iterable<E> {
private ArrayList<E> data;
public MaxHeap(){
data = new ArrayList<>();
}
获取父亲和左右孩子节点的下标
根据上图公式即可获得;
//获取父节点;
public int parent(int index){
if(index ==0){
throw new IllegalArgumentException("no parent");
}
return (index-1)/2;
}
//获取左孩子的位置
private int leftChild(int index){
return 2*index+1;
}
//获取右孩子位置;
private int rightChild(int index){
return 2*index+2;
}
元素的上浮和下沉操作
所谓的上浮就是我们添加元素时,发现添加的元素比它的父元素大,我们则需要将其与父节点进行交换;而下沉则是删除最大值后,从父节点向下沉,判断其是否比孩子的值大,只要是因为我们删除最大值,因为存储使用的时list,所以根据角标将最后一个角标的值和角标0进行交换,然后再删除最后一个角标的元素。
上浮就是只要传入角标大于0且该角标对应节点的值小于它的爸爸节点的值,就进行一次交换,然后让角标更新到它爸爸的角标继续判断是否上浮。
下沉则需要判断孩子问题,因为是完全二叉树,则如果没有左孩子,那也就没有右孩子,不下沉;如果有左孩子,右孩子不一定存在,判断是否存在;如果右孩子存在,则拿左右孩子的最大值和k对应值比较,k对应值大不下沉;
//元素上浮操作;
private void sitUp(int k){
while (k>0 && data.get(k).compareTo(data.get(parent(k)))>0) {
data.swap(k, parent(k));
k = parent(k);
}
}
//元素下沉操作;
private void sitDown(int k){
//如果没有左孩子,那也就没有右孩子,不下沉;
//如果有左孩子,右孩子不一定存在,判断是否存在;
//如果右孩子存在,则拿左右孩子的最大值和k对应值比较,k大不下沉;
while (leftChild(k)<data.size()){
int j = leftChild(k);
if(j+1<data.size() && data.get(j+1).compareTo(data.get(j))>0){
j = rightChild(k);
}
if(data.get(k).compareTo(data.get(j))<0){
data.swap(k,j);
k = j;
}else{
break;
}
}
}
添加元素
调用list添加方法即可,然后进行上浮操作。
public void add(E e){
data.add(e);
sitUp(data.size()-1);
}
查看最大,最小值
查看最大值,只要list集合不空就返回角标0的值,否则抛出异常。
查看最小值,则是遍历整个集合;
public E findMax(){
if(data.isEmpty()){
throw new IllegalArgumentException("maxHeap is null");
}
return data.get(0);
}
public E findMin(){
if(data.isEmpty()){
throw new IllegalArgumentException("maxHeap is null");
}
E min = data.get(0);
for(int i=0;i<data.size();i++){
if(min.compareTo(data.get(i))>0){
min = data.get(i);
}
}
return min;
}
删除最大值
只要集合不空,先保存最大值,然后交换角标0和最后一个角标的值,然后删除最后一个角标的元素,返回保存的最大值。
public E extractMax(){
if(data.isEmpty()){
throw new IllegalArgumentException("maxHeap is null");
}
E max = findMax();
data.swap(0,data.size()-1);
data.remove(data.size()-1);
sitDown(0);
return max;
}
替换最大值
将找到的最大值保存,调用集合的修改方法将其修改并进行下沉操作,返回原先的最大值。
//替换最大值,并返回原先的最大值。
public E replace(E e){
E res = findMax();
data.set(0,e);
sitDown(0);
return res;
}
其他方法
基本都是使用list集合的相应方法。
public int size(){
return data.size();
}
public boolean isEmpty(){
return data.isEmpty();
}
public void clear(){
data.clear();
}
@Override
public Iterator<E> iterator() {
return data.iterator();
}
@Override
public String toString() {
return data.toString();
}
优先队列
我们的优先队列是基于上面的最大堆实现的;
我们将最大堆类作为属性,进行了相应的队列操作,方法实现很简单。
出队一个元素就是最大堆删除最大元素,进队元素调用其添加方法即可,获取队头元素就是最大堆的查找最大元素,其他方法就是调用相应功能的方法即可。
package tree;
import ifce.Queue;
import java.util.Iterator;
//基于最大堆实现的优先队列;
public class PrioritiyQueue<E extends Comparable<E>> implements Queue<E> {
private MaxHeap<E> heap;
public PrioritiyQueue(){
heap = new MaxHeap<>();
}
@Override
public void offer(E element) {
heap.add(element);
}
@Override
public E poll() {
return heap.extractMax();
}
@Override
public E element() {
return heap.findMax();
}
@Override
public boolean isEmpty() {
return heap.isEmpty();
}
@Override
public void clear() {
heap.clear();
}
@Override
public int size() {
return heap.size();
}
@Override
public String toString() {
return heap.toString();
}
@Override
public Iterator<E> iterator() {
return heap.iterator();
}
}