最大堆与最小堆

最大堆和最小堆特性:

(1)都是完全二叉树,最大堆根节点元素大于所有子节点,最小堆根节点元素小于所有子节点;

(2)完全二叉树可以使用数组实现,根节点i(数组索引值),左子节点为 2*i+1,右子节点为 2*(i+1);节点i的父节点为 (i-1)>>1;(注意,计算父节点时,最好使用位操作,否则可能出错

(3)完全二叉树节点n个,高度为log2(n+1),插入和删除操作时间复杂度为O(log n)

最大堆和最小堆的应用:

(1)利用最大堆和最小堆实现堆排序,如按升序排序,采用最小堆,每次从堆顶删除的元素即为最小的元素;同理采用最大堆,可实现降序排序

(2)利用最大堆和最小堆实现Top K问题,

    1)计算数组最大的K个数,可使用最小堆,先构建K大小的最小堆,新元素与堆顶元素比较,如果大于堆顶元素,则删除堆顶,加入新元素;否则最小堆不变;

    2)计算数组最小的K个数,可使用最大堆,同理,如果新元素比堆顶小,则删除堆顶,加入新元素;否则最大堆不变。

关键思路:

1.构建堆

已最大堆为例,构建最大堆,从下往上执行判断,开始于最后一个非叶子节点根节点采用下浮操作与每个子树所有节点比较,找到适合的位置;每个子树都必须是最大堆
(1)起始为最后一个非叶子节点,位置为(endIdx - 1)>> 1,依次从下往上遍历所有非叶子节点
(2)构建子树为最大堆,子树根节点下浮操作,与所有节点比较,找到子树根节点在子树中适合的位置
(3)构建子树为最大堆时,注意更新原根节点的位置


2.插入操作:节点上浮法。

在尾部添加元素,依次往上找父节点(for(int i = (endidx-1) >> 1; i >= 0 ; i = (i-1) >> 1),注意此处如果用除2,会出现死循环)比较和替换。因为插入操作时,已是最大堆。


3.删除操作:最大堆最后一个结点的下沉。

删除根节点,将最后一个节点放在根节点位置,然后依次往下比较,找到合适的位置

代码实现

最大堆:构建、新增和删除,其中 属性 int[] n 存储最大堆。

package com.jdk.heap.test;

public class Heap {

    private int[] n;
    
    public Heap(int[] n) {
        this.n = n;
    }
    
    public void createMaxHeap() {
        int endidx = n.length - 1;
        
        //1. 从最后一个非叶子节点开始
        for(int i = (endidx-1)/2; i >= 0; i--) {
            
            //2.构建子树最大堆,子树根节点必须与所有节点进行比较
            int rootidx = i;//在子树中不断更新原root的位置
            int left = 2*i + 1;
            int right = 2*(i+1);
            //3.非叶子节点包含叶子情况存在3种情况:1.左右子节点都有;2.只有左节点;3.没有子节点
            //3.1 包含左右两个子节点的比较
            while(left <= endidx && right <= endidx) {
                //3.1.1根节点大于两个子节点时break,因为左右子树已是最大堆
                if(n[rootidx] > Math.max(n[left], n[right])) {
                    break;
                }
                //3.1.2 根节点小于左右子节点的最大者,与子节点最大者交换;更新根节点rootidx
                if(n[left] > n[right]) {
                    swap(n, left, rootidx);
                    rootidx = left;
                } else {
                    swap(n, right, rootidx);
                    rootidx = right;
                }
                //3.1.3 更新左右子节点
                left = 2*rootidx + 1;
                right = 2*(rootidx + 1);
            } 
            //3.2如果根节点没有左右子节点,存在2种情况:1.只有左节点,该节点为数组最后一位;2.没有子节点
            //故只需要判断第1种情况即可
            if(left == endidx) {
                if(n[rootidx] < n[left]) swap(n, left, rootidx);
            }
        }
        
    }
    
    private void swap(int[] n, int n1, int n2) {
        int tmp = n[n1];
        n[n1] = n[n2];
        n[n2] = tmp;
    }
    
    /**
     * 最大堆中插入元素
     * 节点上浮操作,将新节点放入尾部,然后依次往上遍历
     * @param n
     * @param k
     */
    public void insert(int k) {
        int[] copy = new int[n.length+1];
        for(int i = 0; i < n.length; i++) {
            copy[i] = n[i];
        }
        copy[n.length] = k;
        int endidx = copy.length-1;
        
        //因为是在原最大堆中添加元素,故只需要比较上下两级
        for(int i = (endidx-1) >> 1; i >= 0 ; i = (i-1) >> 1) {
            //过程与构建最大堆相同
            int left = 2*i + 1;
            int right = 2*(i+1);
            if(left <= endidx && right <= endidx) {
                if(copy[i] > Math.max(copy[left], copy[right])) {
                    continue;
                }
                
                if(copy[left] > copy[right]) {
                    swap(copy, left, i);
                } else {
                    swap(copy, right, i);
                }
            } else if(left == endidx ) {
                if(copy[i] < copy[left]) swap(copy, left, i);
            } 
        }
        n = copy;
    }
    
    /**
     * 删除,删除根节点
     * 然后将尾节点放在根节点,再进行下浮操作
     * @param n
     */
    public int delete() {
        int root = n[0];
        if(n.length == 1) return root;
        
        int[] copy = new int[n.length-1];
        for(int i = 0; i < n.length-1; i++) {
            copy[i] = n[i];
        }
        copy[0] = n[n.length-1];
        
        //下浮操作
        int rootidx = 0;
        int left = 2*rootidx+1;
        int right = 2*(rootidx+1);
        //最后一个非叶子节点包含2个子节点
        while(left <= copy.length-1 && right <= copy.length-1) {
            if(copy[rootidx] > Math.max(copy[left], copy[right])) {
                break;
            }
            
            if(copy[left] > copy[right]) {
                swap(copy, left, rootidx);
                rootidx = left;
            } else {
                swap(copy, right, rootidx);
                rootidx = right;
                
            }
            left = 2*rootidx +1;
            right = 2*(rootidx + 1);
        }
        
        //最后一个非叶子节点包含1个子节点,只能为左节点
        if(left == copy.length-1) {
            if(copy[rootidx] < copy[left]) swap(copy, left, rootidx);
        }
        n = copy;
        return root;
    }
    
    
    
    public int[] getN() {
        return n;
    }

    public void setN(int[] n) {
        this.n = n;
    }
    
}

  测试:

public static void main(String[] args) {
        
        int[] n = new int[] {79,66,43,83,30,87,38,55,91,72,49,9};
        Heap heap = new Heap(n);
        heap.createMaxHeap();
        n = heap.getN();
        for(int i : n) {
            System.out.print(heap.delete() + " ");
        }
    }

 输出(有时间,画图说明):

91 87 83 79 72 66 55 49 43 38 30 9 

  

最小堆

实现与最大堆类似,也将代码附上

package com.jdk.heap.test;

public class MiniHeap {

    public void createMiniHeap(int[] n) {
        int endIdx = n.length-1;
        
        for(int i = (endIdx-1) >> 1; i >= 0; i--) {
            int rootIdx = i;
            int left = 2*rootIdx + 1;
            int right = 2*(rootIdx + 1);
            
            while(left <= endIdx && right <= endIdx) {
                if(n[rootIdx] < Math.min(n[left], n[right])) {
                    break;
                }
                //与子节点的小者交换
                if(n[left] < n[right]) {
                    swap(n, left, rootIdx);
                    rootIdx = left;
                }else {
                    swap(n, right, rootIdx);
                    rootIdx = right;
                }
                left = 2*rootIdx +1;
                right = 2*(rootIdx+1);
            }
            if(left == endIdx) {
                if(n[left] < n[rootIdx]) swap(n, left, rootIdx);
            }
        }
    }
    
    private void swap(int[] n, int n1, int n2) {
        int tmp = n[n1];
        n[n1] = n[n2];
        n[n2] = tmp;
    }
    
    public int[] insert(int[] n, int k) {
        int[] copy = new int[n.length+1];
        for(int i = 0; i < n.length; i++) {
            copy[i] = n[i];
        }
        copy[n.length] = k;
        
        int endIdx = copy.length-1;
        
        for(int i = (endIdx-1)>>1; i >= 0; i = (i-1)>>1) {
            int left = 2*i+1;
            int right = 2*(i+1);
            if(left <= endIdx && right <= endIdx) {
                if(copy[i] < Math.min(copy[left], copy[right])) {
                    break;
                }
                if(copy[left] < copy[right]) {
                    swap(copy, left, i);
                }else {
                    swap(copy, right, i);
                }
            }
            if(left == endIdx) {
                if(copy[left] < copy[i]) swap(copy, left, i);
            }
        }
        return copy;
    }
    
    
    public int[] delete(int[] n) {
        int[] copy = new int[n.length-1];
        for(int i = 0; i < n.length-1; i++) {
            copy[i] = n[i];
        }
        copy[0] = n[n.length-1];
        
        int rootIdx = 0;
        int left = 2*rootIdx +1;
        int right = 2*(rootIdx + 1);
        int endIdx = copy.length-1;
        
        while(left <= endIdx && right <= endIdx) {
            if(copy[rootIdx] < Math.min(copy[left], copy[right])) {
                break;
            }
            
            if(copy[left] < copy[right]) {
                swap(copy, left, rootIdx);
                rootIdx = left;
            }else {
                swap(copy, right, rootIdx);
                rootIdx = right;
            }
            left = 2*rootIdx +1;
            right = 2*(rootIdx + 1);
        }
        if(left == endIdx) {
            if(copy[left] < copy[rootIdx]) swap(copy, left, rootIdx);
        }
        
        return copy;
    }
    
}

 测试:

public static void main(String[] args) {
        MiniHeap heap = new MiniHeap();
        int[] n = new int[] {79,66,43,83,30,87,38,55,91,72,49,9};
        heap.createMiniHeap(n);
        for(int i : n) {
            System.out.print(i + " ");
        }
    }

 输出:

9 30 38 55 49 43 79 83 91 72 66 87 

 

posted @ 2020-05-12 14:45  水木竹水  阅读(1188)  评论(0编辑  收藏  举报