数据结构之树(二分搜索树,堆和优先队列)

二分搜索树

二分搜索树的实现

在这里插入图片描述

  • 二分搜索树不一定是完全二叉树。
  • 左边都比根节点小,右边都比根节点大。
  • 如果二叉排序树是平衡的,其查找效率为O(log2n),近似于折半查找。如果二叉排序树完全不平衡,则其深度可达到n,查找效率为O(n),退化为顺序查找。

声明树的结构:

public class BST<E extends Comparable<E>> {         //必须具有可比较性
	private class Node {
	     public E e;
	     public Node left, right;
	
	     public Node(E e) {
	         this.e = e;
	         left = null;
	         right = null;
	     }
	 }
    private Node root;
    private int size;

    public BST(){
        root = null;
        size = 0;
    }

    public int size(){
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    // 向二分搜索树中添加新的元素e
    public void add(E e){
        root = add(root, e);
    }
	 //方法
	 ...
	 ...
	 ...
}


向以node为根的二分搜索树中插入元素e,递归算法。不用递归,可采用链表类型的方法。

public void add(E e){
    root = add(root, e);
}

private Node add(Node node, E e){
    if(node == null){
        size ++;
        return new Node(e);
    }

    if(e.compareTo(node.e) < 0)
        node.left = add(node.left, e);
    else if(e.compareTo(node.e) > 0)
        node.right = add(node.right, e);

    return node;
}

二分搜索树的遍历:

public void preOrder(){
    preOrder(root);
}

// 前序遍历以node为根的二分搜索树, 递归算法
private void preOrder(Node node){
    if(node == null)
        return;

    System.out.println(node.e);
    preOrder(node.left);
    preOrder(node.right);
}



// 二分搜索树的非递归前序遍历(栈)
public void preOrderNR(){ 

    if(root == null)
        return;

    Stack<Node> stack = new Stack<>();
    stack.push(root);
    while(!stack.isEmpty()){
        Node cur = stack.pop();
        System.out.println(cur.e);

        if(cur.right != null)
            stack.push(cur.right);
        if(cur.left != null)
            stack.push(cur.left);
    }
}

// 二分搜索树的层序遍历(队列)
public void levelOrder(){

    if(root == null)
        return;

    Queue<Node> q = new LinkedList<>();
    q.add(root);
    while(!q.isEmpty()){
        Node cur = q.remove();
        System.out.println(cur.e);

        if(cur.left != null)
            q.add(cur.left);
        if(cur.right != null)
            q.add(cur.right);
    }
}

寻找二分搜索树的最小元素及删除:

// 寻找二分搜索树的最小元素
public E minimum(){
    if(size == 0)
        throw new IllegalArgumentException("BST is empty");

    Node minNode = minimum(root);
    return minNode.e;
}

// 返回以node为根的二分搜索树的最小值所在的节点
private Node minimum(Node node){
    if( node.left == null )
        return node;

    return minimum(node.left);
}


// 从二分搜索树中删除最小值所在节点, 返回最小值
public E removeMin(){
    E ret = minimum();
    root = removeMin(root);
    return ret;
}

// 删除掉以node为根的二分搜索树中的最小节点
// 返回删除节点后新的二分搜索树的根
private Node removeMin(Node node){

    if(node.left == null){
        Node rightNode = node.right;
        node.right = null;
        size --;
        return rightNode;
    }

    node.left = removeMin(node.left);
    return node;
}

删除任意一个节点:

// 从二分搜索树中删除元素为e的节点
public void remove(E e){
    root = remove(root, e);
}

// 删除掉以node为根的二分搜索树中值为e的节点, 递归算法
// 返回删除节点后新的二分搜索树的根
private Node remove(Node node, E e){

    if( node == null )
        return null;

    if( e.compareTo(node.e) < 0 ){
        node.left = remove(node.left , e);
        return node;
    }
    else if(e.compareTo(node.e) > 0 ){
        node.right = remove(node.right, e);
        return node;
    }
    else{   // e.compareTo(node.e) == 0

        // 待删除节点左子树为空的情况
        if(node.left == null){
            Node rightNode = node.right;
            node.right = null;
            size --;
            return rightNode;
        }

        // 待删除节点右子树为空的情况
        if(node.right == null){
            Node leftNode = node.left;
            node.left = null;
            size --;
            return leftNode;
        }

        // 待删除节点左右子树均不为空的情况
        // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点
        // 用这个节点顶替待删除节点的位置
        Node successor = minimum(node.right);
        successor.right = removeMin(node.right);
        successor.left = node.left;

        node.left = node.right = null;

        return successor;
    }
}

leetcode上相关题目

144. 二叉树的前序遍历
比较简单,略

804. 唯一摩尔斯密码词
解法:
适合唯一性的数据结构有:

  • 二分排序树:上面解法自己实现了二分树
  • TreeSet:底层是TreeMap
  • HashSet:底层是HashMap
  • 全局排序一次,去除掉相邻重复的

堆和优先队列

堆的实现

在这里插入图片描述

  • 堆是完全二叉树
  • 用数组存储二叉堆,从索引1开始,则左节点索引=2*父节点的索引,右节点索引=左节点索引+1。
  • 从索引0开始,则左节点索引=2*父节点的索引+1,右节点索引=左节点索引+1。

在这里插入图片描述

堆的结构:

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

    private Array<E> data;

    public MaxHeap(int capacity){
        data = new Array<>(capacity);
    }

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

    // 返回堆中的元素个数
    public int size(){
        return data.getSize();
    }

    // 返回一个布尔值, 表示堆中是否为空
    public boolean isEmpty(){
        return data.isEmpty();
    }
    
    // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
    private int parent(int index){
        if(index == 0)
            throw new IllegalArgumentException("index-0 doesn't have parent.");
        return (index - 1) / 2;
    }
    
    // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
    private int leftChild(int index){
        return index * 2 + 1;
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
    private int rightChild(int index){
        return index * 2 + 2;
    }
}

增加元素,尾部添加,采用siftUp()操作:

// 向堆中添加元素
public void add(E e){
    data.addLast(e);
    siftUp(data.getSize() - 1);
}

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

删除元素,顶部删除,尾部元素移到顶部,进行siftDown()操作:

// 取出堆中最大元素
public E extractMax(){

    E ret = findMax();

    data.swap(0, data.getSize() - 1);
    data.removeLast();
    siftDown(0);

    return ret;
}

private void siftDown(int k){

    while(leftChild(k) < data.getSize()){
        int j = leftChild(k); // 在此轮循环中,data[k]和data[j]交换位置
        if( j + 1 < data.getSize() &&
                data.get(j + 1).compareTo(data.get(j)) > 0 )
            j ++;
        // data[j] 是 leftChild 和 rightChild 中的最大值

        if(data.get(k).compareTo(data.get(j)) >= 0 )
            break;

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

heapify操作,使无序数组变成堆:

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

优先队列:

private interface Queue<E> {

    int getSize();
    boolean isEmpty();
    void enqueue(E e);
    E dequeue();
    E getFront();
}

private class PriorityQueue<E extends Comparable<E>> implements Queue<E> {

    private MaxHeap<E> maxHeap;

    public PriorityQueue(){
        maxHeap = new MaxHeap<>();
    }

    @Override
    public int getSize(){
        return maxHeap.size();
    }

    @Override
    public boolean isEmpty(){
        return maxHeap.isEmpty();
    }

    @Override
    public E getFront(){
        return maxHeap.findMax();
    }

    @Override
    public void enqueue(E e){
        maxHeap.add(e);
    }

    @Override
    public E dequeue(){
        return maxHeap.extractMax();
    }
}

leetcode上相关题目

堆的典型应用:求1000000万个元素前100个最大元素。
先排序则需要nlogn,使用100个空间的堆,则时间复杂度为nlogm,m为100。
347. 前 K 个高频元素

解法:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        TreeMap<Integer, Integer> map = new TreeMap<>();
        for(int num: nums){
            if(map.containsKey(num))
                map.put(num, map.get(num) + 1);
            else
                map.put(num, 1);
        }

        PriorityQueue<Integer> pq = new PriorityQueue<>(
                (a, b) -> map.get(a) - map.get(b)
            );
        for(int key: map.keySet()){
            if(pq.size() < k)
                pq.add(key);
            else if(map.get(key) > map.get(pq.peek())){
                pq.remove();
                pq.add(key);
            }
        }
        int[] num = new int[k];
        int i = 0;
        while(!pq.isEmpty()){
            num[i] = pq.remove();
            i++;
        }
                 
        return num;
    }
}
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页