[Data structure] Java implementation of heap: various operations of the heap & heap sort

The heap is logically a complete binary tree, so data can be stored through an array, while the rest of the trees mostly use a chain structure for data storage

  • Heap classification:
    • Big top heap: The big top heap means that the parent node is the largest in any (child) tree
    • Small top heap: Small top heap means that no matter in any (child) tree, the parent node is the smallest
  • Two operations on the heap:
    • Floating: generally used to balance the heap after adding new elements to the heap
    • Sinking: Generally used to balance the pile after removing the top of the pile and changing the tail to the top of the pile
  • Heap sorting: using the characteristics of the large top heap and the small top heap, the top of the heap is constantly removed, and the elements removed are the highest value of the elements in the heap, and then the heap is balanced

The following article takes the large top heap as an example and uses Java to implement various operations on the heap.

1.MaxHeap

Large top pile: For any (sub) pile, the top of the pile is the largest

// 这里Comparable保证所有结点可比,是成堆基础
public class MaxHeap <E extends Comparable<E>> {
    
    
	// 完全二叉树,排列整齐(相当于一层一层放入),可以用数组存储
    private ArrayList<E> data;

    public MaxHeap(int capcity) {
    
    
        data = new ArrayList<>(capcity);
    }

    public MaxHeap() {
    
    
        data = new ArrayList<>();
    }

    // 堆是否为空
    public boolean isEmpty() {
    
    
        return data.isEmpty();
    }
    // 堆中元素个数
    public int size() {
    
    
        return this.data.size();
    }
	
	//......
}

2. Operation 1: Get parent/child node

parent()

// 返回idx位置元素的父节点
// 注:这里从0放起(parent = (i - 1)/2 ),若是从1放起(parent = i / 2)
private int parent(int idx) {
    
    
    if (idx == 0) {
    
    
        throw new IllegalArgumentException("index-0 doesn't have parent");
    }
    return (idx - 1) / 2;
}

leftChild() / rightChild()

// 返回idx位置元素的孩子节点
// 注:这里从0放起
private int leftChild(int idx) {
    
    
    // 若从1放起,leftChild = idx * 2
    return idx * 2 + 1;
}
private int rightChild(int idx) {
    
    
    // 若从1放起,leftChild = idx * 2
    return idx * 2 + 2;
}

2. Operation 2: Add elements

add()

  1. Put the element at the end of the pile, the last element of the array
  2. Joined to the end, float up
public void add(E e) {
    
    
    data.add(e);
    // 传入需要上浮的索引
    siftUp(data.size() - 1);
}

siftUp ()

  • Floating up: child node exchanges with parent node (smaller than itself)
  • Time: O (logn), getting parent is continuously dichotomy (/2)
private void siftUp(int k) {
    
    
    // 只要是父节点(data[parent]) 比 子节点(data[k])小,就进行交换
    while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
    
    
        // 1.交换数组中位置
        // 注:这里是
        Collections.swap(k, parent(k));
        // 2.更新子节点,进行下一轮上浮
        // 注:也可以data.set进行三步走
        k = parent(k);
    }
}

3. Operation 3: Take out the top of the heap (the largest element)

extractMax()

Remove top

  1. Get top element
  2. Delete top
    1. Exchange the top of the heap (0) with the tail (size-1), because a new top of the heap will be generated
    2. Delete tail
    3. Top sinking
public E extractMax() {
    
    
    // 1.获取堆顶元素
    E ret = findMax() ;
    
	// 2.1 将堆顶换到堆尾
    Collections.swap(0, data.size() - 1);
    // 2.2 删除堆尾
    data.remove(data.size() - 1);
	// 2.3 下沉堆顶
    siftDown(0):
    
    return ret;
}

findMax()

Get the largest element in the heap

public E findMax() {
    
    
    if (data.size() == 0) {
    
    
        throw new IllegalArgumentException("Can not findMax when heap is empty");
    }
    // 堆顶最大 = 数组第一个元素(0)
    return data.get(0);
}

siftDown()

  • Heap top sinking: exchange with nodes with larger values ​​(and greater than their own) left and right child nodes
  • Time: O(logn), getting Child is continuously dichotomy (/2)
private void siftDown(int k) {
    
    
    // 1.判断当前节点是否有子节点
    // 注:因为leftChild索引肯定比rightChild小,所以只要有leftChild就有子节点
    while (leftChild(k) < data.size()) {
    
    
        // 2.拿到leftChild与rightChild的大值
        int j = leftChild(k);
        if (j + 1 < data.size() && data.get(j + 1).compareTo(data.get(j)) > 0) {
    
    
            j = rightChild(k);
        }
		
        // 3.判断子节较大值是否大于自己(父节点)
        if (data.get(k).compareTo(data.get(j)) >= 0) {
    
    
            break;
        }
		
        // 4.若大于,交换数组中两节点位置
        Collections.swap(k, j);
        
        // 更新父节点,进行下一轮下沉
        k = j;
    }
}

=> Heap sort

Heap sorting principle: In short, it is to use the characteristics of the large top pile and the small top pile to continuously take out the top of the pile. The elements taken out are the maximum value of the elements in the pile, and then the pile is balanced. Put a sample code below:

public class Test {
    
    

    public static void main(String[] args) {
    
    
        int n = 10000000;

        MaxHeap<Integer> heap = new MaxHeap<>();
        Random random = new Random();
        // 一百万个随机数
        // 注:这里用的random类!!!
        for (int i = 0; i < n; i++)
            heap.add(random.nextInt(Integer.MAX_VALUE));

        // 不断从最大堆中取出堆顶  --> 从大到小
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
    
    
            arr[i] = heap.extractMax();
        }
		
        // 验证取出的元素是否按照从大到小排列
        for (int i = 0; i < n - 1; i++)
            if (arr[i] < arr[i + 1])
                throw  new IllegalArgumentException("Error");

        System.out.println("Test MaxHeap Success!");

    }
}

4. Operation 4: Take out the top of the heap and add another element

  • Idea 1: Take out the top of the heap (extractMax), and then add an element (add) ====> 2 * O (logn)
  • Idea 2: Modify the top of the pile directly, and then sink ====> O(logn)

replace()

public E replace(E e) {
    
    
        // 1.获取堆顶元素
        E ret = findMax();
    	// 2.修改堆顶,即数组0位置
        data.set(0, e);
        // 3.下沉
        siftDown(0);
    
        return ret;
}

5. Operation 5: Array stacking

heapify()

  • Idea 1: Add the known arrays to the heap one by one ====> Time = O(n*logn)
  • Idea 2: Start sinking from the last non-leaf node ====> Time = O(n)
public MaxHeap(E[] arr) {
    
    
    // 注:这里数组不能直接作为ArrayList参数,要先包装成List
    data = new ArrayList<>(Arrays.asList(arr));
    // 确定倒数第一个非叶子结点:最后一个叶子(length - 1)的父节点 ((i - 1)/ 2)
    for (int i = parent(arr.length - 1); i >= 0; i--) {
    
    
        // 逐个下沉
        siftDown(i);
    }
}

Complete code

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

public class MaxHeap <E extends Comparable<E>> {
    
    

    private ArrayList<E> data;

    public MaxHeap(int capcity) {
    
    
        data = new ArrayList<>(capcity);
    }

    public MaxHeap() {
    
    
        data = new ArrayList<>();
    }

    public MaxHeap(E[] arr) {
    
    
        data = new ArrayList<>(Arrays.asList(arr));
        for (int i = parent(arr.length - 1); i >= 0; i--) {
    
    
            siftUp(i);
        }
    }

    // 堆是否为空
    public boolean isEmpty() {
    
    
        return data.isEmpty();
    }
    // 堆中元素个数
    public int size() {
    
    
        return this.data.size();
    }

    // 返回idx位置元素的父节点
    // 注:这里从0放起(parent = (i - 1)/2 ),若是从1放起(parent = i / 2)
    private int parent(int idx) {
    
    
        if (idx == 0) {
    
    
            throw new IllegalArgumentException("index-0 doesn't have parent");
        }
        return (idx - 1) / 2;
    }

    // 返回idx位置元素的孩子节点
    // 注:这里从0放起
    private int leftChild(int idx) {
    
    
        // 若从1放起,leftChild = idx * 2
        return idx * 2 + 1;
    }
    private int rightChild(int idx) {
    
    
        // 若从1放起,leftChild = idx * 2
        return idx * 2 + 2;
    }

    // 添加元素
    public void add(E e) {
    
    
        data.add(e);
        siftUp(data.size() - 1);
    }

    private void siftUp(int k) {
    
    
        while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
    
    
            Collections.swap(data, k, parent(k));
            k = parent(k);
        }
    }

    // 获取堆中最大元素
    public E findMax() {
    
    
        if (data.size() == 0) {
    
    
            throw new IllegalArgumentException("Can not findMax when heap is empty");
        }
        // 堆顶最大,
        return data.get(0);
    }

    public E extractMax() {
    
    
        E ret = findMax() ;

        Collections.swap(data, 0, data.size() - 1);
        data.remove(data.size() - 1);

        siftDown(0);
        return ret;
    }

    private void siftDown(int 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) {
    
    
                break;
            }

            Collections.swap(data, k, j);
            k = j;
        }
    }

    public E replace(E e) {
    
    
        E ret = findMax();
        data.set(0, e);
        siftDown(0);
        return ret;
    }
}

Finally, students who are interested in the application of heap in Java have a look at the source code of PriorityQueue, which is implemented through a small top heap, here is a portal [Java collection source code] PriorityQueue source code analysis .

Guess you like

Origin blog.csdn.net/weixin_43935927/article/details/108859896