0
点赞
收藏
分享

微信扫一扫

【平衡树与优先队列】

佃成成成成 2022-02-25 阅读 43

二叉树

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();
    }
}
举报

相关推荐

0 条评论