Реализовать кучу с помощью двоичного дерева

Этот вопрос задан раньше в Stack Exchange, но он остался без ответа.

Ссылка на ранее заданный вопрос: Бинарная куча, реализованная через двоичную структуру дерева

Как реализовать кучу в двоичном дереве. Чтобы реализовать кучу, важно знать последний заполненный node и первый незанятый node. Это можно сделать при упорядочивании уровня дерева, но тогда сложность времени будет O (n), чтобы найти первый незанятый node. Итак, как реализовать кучу в двоичном дереве в O (logn)?

Спасибо Шекхар

Ответ 1

Чтобы реализовать кучу с двоичным деревом с временной сложностью O (log n), вам необходимо сохранить общее количество узлов в качестве переменной экземпляра.

Предположим, что у нас была куча 10 тотальных узлов.

Если бы мы добавили node...

Мы увеличиваем общее число узлов на единицу. Теперь мы имеем 11 общих узлов. Мы преобразуем новое общее число узлов (11) в его двоичное представление: 1011.

При двоичном представлении полных узлов (1011) мы избавимся от первой цифры. Впоследствии мы используем 011 для навигации по дереву до следующего местоположения, чтобы вставить node in. 0 означает, что нужно идти влево, а 1 означает идти вправо. Поэтому, с 011, мы бы пошли влево, идем направо, и идем направо..., что приводит нас к следующему месту для вставки.

Мы исследовали один node за уровень, делая временную сложность O (log n)

Ответ 2

Вы не будете реализовывать двоичное дерево кучи IN, потому что куча - это двоичное дерево. Куча поддерживает следующее свойство порядка - с учетом node V, его родитель больше или равно V. Также куча завершена двоичное дерево. У меня был курс ADS в uni, поэтому я дам вам мою реализацию кучи на Java позже в ответе. Просто перечислите основные методы, которые вы получаете:

  • size() O (1)
  • isEmpty() O (1)
  • insert() O (logn)
  • removeMin() O (logn)
  • min() O (1)

Вот мой Heap.java файл:

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

    private Object S[];
    private int last;
    private int capacity;

    public Heap() {
        S = new Object[11];
        last = 0;
        capacity = 7;
    }

    public Heap(int cap) {
        S = new Object[cap + 1];
        last = 0;
        capacity = cap;
    }

    public int size() {
        return last;
    }

    //
    // returns the number of elements in the heap
    //

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

    //
    // is the heap empty?
    //

    public E min() throws HeapException {
        if (isEmpty())
            throw new HeapException("The heap is empty.");
        else
            return (E) S[1];
    }

    //
    // returns element with smallest key, without removal
    //

    private int compare(Object x, Object y) {
        return ((E) x).compareTo((E) y);
    }

    public void insert(E e) throws HeapException {
        if (size() == capacity)
            throw new HeapException("Heap overflow.");
        else{
            last++;
            S[last] = e;
            upHeapBubble();
        }       
    }

    // inserts e into the heap
    // throws exception if heap overflow
    //

    public E removeMin() throws HeapException {
        if (isEmpty())
            throw new HeapException("Heap is empty.");
        else {
            E min = min();
            S[1] = S[last];
            last--;
            downHeapBubble();
            return min;
        }
    }

    //
    // removes and returns smallest element of the heap
    // throws exception is heap is empty
    //

    /**
     * downHeapBubble() method is used after the removeMin() method to reorder the elements
     * in order to preserve the Heap properties
     */
    private void downHeapBubble(){
        int index = 1;
        while (true){
            int child = index*2;
            if (child > size())
                break;
            if (child + 1 <= size()){
                //if there are two children -> take the smalles or
                //if they are equal take the left one
                child = findMin(child, child + 1);
            }
            if (compare(S[index],S[child]) <= 0 )
                break;
            swap(index,child);
            index = child;
        }
    }

    /**
     * upHeapBubble() method is used after the insert(E e) method to reorder the elements
     * in order to preserve the Heap properties 
     */
    private void upHeapBubble(){
        int index = size();
        while (index > 1){
            int parent = index / 2;
            if (compare(S[index], S[parent]) >= 0)
                //break if the parent is greater or equal to the current element
                break;
            swap(index,parent);
            index = parent;
        }       
    }

    /**
     * Swaps two integers i and j
     * @param i
     * @param j
     */
    private void swap(int i, int j) {
        Object temp = S[i];
        S[i] = S[j];
        S[j] = temp;
    }

    /**
     * the method is used in the downHeapBubble() method
     * @param leftChild
     * @param rightChild
     * @return min of left and right child, if they are equal return the left
     */
    private int findMin(int leftChild, int rightChild) {
        if (compare(S[leftChild], S[rightChild]) <= 0)
            return leftChild;
        else
            return rightChild;
    }

    public String toString() {
        String s = "[";
        for (int i = 1; i <= size(); i++) {
            s += S[i];
            if (i != last)
                s += ",";
        }
        return s + "]";
    }
    //
    // outputs the entries in S in the order S[1] to S[last]
    // in same style as used in ArrayQueue
    //

}

HeapException.java:

public class HeapException extends RuntimeException {
    public HeapException(){};
    public HeapException(String msg){super(msg);}
}

Интересной частью, которая дает вам производительность O (logn), является метод downHeapBubble() и upHeapBubble(). Я вскоре добавлю им хорошее объяснение.

upHeapBubble() используется при вставке нового node в кучу. Поэтому, когда вы вставляете, вы вставляете в последнюю позицию, а затем вам нужно вызвать upHeapBubble() следующим образом:

last++;
S[last] = e;
upHeapBubble();

Затем последний элемент сравнивается с ним parent и если родительский объект больше - swap: это делается max logn times, где n - количество узлов. Итак, здесь идет производительность logn.

Для части удаления - вы можете удалить только min - самый высокий node. Поэтому, когда вы его удаляете, вам нужно поменять его последним node, но тогда вам нужно сохранить свойство кучи, и вы должны сделать downHeapBubble(). Если значение node больше, чем у него сводка с наименьшим и т.д., Пока у вас не останется ни одного ребенка или у вас не будет детей меньшего размера. Это можно сделать с максимальным временем входа в систему, поэтому здесь идет производительность logn. Вы можете объяснить, почему эту операцию можно выполнить за несколько минут, просмотрев изображения двоичного дерева здесь

Ответ 3

ВЫПОЛНЕНИЕ HEAP ИСПОЛЬЗОВАНИЕ ДЕРЕВА

Я отвечаю на свой вопрос, который принимает O (log n), но ограничение заключается в том, чтобы сохранить указатель на родителя. если мы не сохраняем указатель на родительский элемент, нам нужно приблизительно O (n). Я разместил этот вопрос, чтобы получить решение для O (log n)

Вот шаги для вычисления следующего незанятого листа (у нас есть указатель на родительский node):

x = last inserted node. We save this after every insertion.
y = tmp node
z = next unoccupied node (next insertion)
   if x is left child
      z = x -> parent -> rightchild (problem solved.. that was easy)
   else if x is right child
      go to x parent, until parent becomes left child. Let this node be y
      (subtree rooted at y sibling will contain the next unoccupied node)
      z = y -> parent -> right -> go left until null

Это O (log n), но требуется указатель на родительский.

O (n) решение будет довольно простым, просто упорядочьте дерево, и мы получим местоположение следующего незанятого node.

Мой вопрос: как найти следующий незанятый node в O (log n) без использования родительского указателя.

Спасибо.

Ответ 4

Предполагая, что вы хотите использовать двоичное дерево связанное, без указателей на родительские узлы, тогда единственное решение, о котором я могу думать, это поддерживать счетчик числа детей в каждом node.

availableLeaf(node) {
    if( node.left is Empty || node.right is Empty )
        return node ;
    else
       if( node.left.count < node.right.count )
           return availableLeaf(node.left)
       else
           return availableLeaf(node.right)
}

Эта стратегия также уравновешивает количество узлов по каждой стороне каждого поддерева, что является полезным (хотя и крайне незначительным).

Это O (log n). Для отслеживания счета при вставке требуется пройти весь путь до крыши, но это не меняет характер O (lon n) этой операции. Аналогичная вещь с удалением.

Другие операции являются обычными и сохраняют свои рабочие характеристики.

Вам нужны детали или вы предпочитаете самостоятельно их обрабатывать?

Если вы хотите использовать связанное двоичное дерево без какой-либо другой информации, кроме указателей слева и справа, я предлагаю вам инициировать награду за не менее 100 000 очков. Я не говорю, что это невозможно (потому что у меня нет математики, чтобы это доказать), но я говорю, что это не было найдено за несколько десятилетий (что я знаю).

Ответ 5

Моя реализация кучи

public class Heap <T extends Comparable<T>> {
    private T[] arr;
    private int size;

    public Heap(T[] baseArr) {
        this.arr = baseArr;
        size = arr.length - 1;
    }

    public void minHeapify(int i, int n) {
        int l = 2 * i + 1;
        int r = 2 * i + 2;

        int smallest = i;
        if (l <= n && arr[l].compareTo(arr[smallest]) < 0) {
            smallest = l;
        }
        if (r <= n && arr[r].compareTo(arr[smallest]) < 0) {
            smallest = r;
        }

        if (smallest != i) {
            T temp = arr[i];
            arr[i] = arr[smallest];
            arr[smallest] = temp;
            minHeapify(smallest, n);
        }
    }

    public void buildMinHeap() {
        for (int i = size / 2; i >= 0; i--) {
            minHeapify(i, size);
        }
    }

    public void heapSortAscending() {
        buildMinHeap();
        int n = size;
        for (int i = n; i >= 1; i--) {
            T temp = arr[0];
            arr[0] = arr[i];
            arr[i] = temp;
            n--;
            minHeapify(0, n);
        }
    }
}

Ответ 6

Двоичное дерево может быть представлено массивом:

import java.util.Arrays;

public class MyHeap {
    private Object[] heap;
    private int capacity;
    private int size;

    public MyHeap() {
        capacity = 8;
        heap = new Object[capacity];
        size = 0;
    }

    private void increaseCapacity() {
        capacity *= 2;
        heap = Arrays.copyOf(heap, capacity);
    }

    public int getSize() {
        return size;
    }

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

    public Object top() {
        return size > 0 ? heap[0] : null;
    }

    @SuppressWarnings("unchecked")
    public Object remove() {
        if (size == 0) {
            return null;
        }
        size--;
        Object res = heap[0];
        Object te = heap[size];
        int curr = 0, son = 1;
        while (son < size) {
            if (son + 1 < size
                    && ((Comparable<Object>) heap[son + 1])
                            .compareTo(heap[son]) < 0) {
                son++;
            }
            if (((Comparable<Object>) te).compareTo(heap[son]) <= 0) {
                break;
            }
            heap[curr] = heap[son];
            curr = son;
            son = 2 * curr + 1;
        }
        heap[curr] = te;
        return res;
    }

    @SuppressWarnings("unchecked")
    public void insert(Object e) {
        if (size == capacity) { // auto scaling
            increaseCapacity();
        }
        int curr = size;
        int parent;
        heap[size] = e;
        size++;
        while (curr > 0) {
            parent = (curr - 1) / 2;
            if (((Comparable<Object>) heap[parent]).compareTo(e) <= 0) {
                break;
            }
            heap[curr] = heap[parent];
            curr = parent;
        }
        heap[curr] = e;
    }
}

Использование:

    MyHeap heap = new MyHeap(); // it is a min heap
    heap.insert(18);
    heap.insert(26);
    heap.insert(35);
    System.out.println("size is " + heap.getSize() + ", top is " + heap.top());
    heap.insert(36);
    heap.insert(30);
    heap.insert(10);
    while(!heap.isEmpty()) {
        System.out.println(heap.remove());
    }

Ответ 7

import java.util.ArrayList;
import java.util.List;

/**
 * @author Harish R
 */
public class HeapPractise<T extends Comparable<T>> {

    private List<T> heapList;

    public List<T> getHeapList() {
        return heapList;
    }

    public void setHeapList(List<T> heapList) {
        this.heapList = heapList;
    }

    private int heapSize;

    public HeapPractise() {
        this.heapList = new ArrayList<>();
        this.heapSize = heapList.size();
    }

    public void insert(T item) {
        if (heapList.size() == 0) {
            heapList.add(item);
        } else {
            siftUp(item);
        }

    }

    private void siftUp(T item) {
        heapList.add(item);
        heapSize = heapList.size();
        int currentIndex = heapSize - 1;
        while (currentIndex > 0) {
            int parentIndex = (int) Math.floor((currentIndex - 1) / 2);
            T parentItem = heapList.get(parentIndex);
            if (parentItem != null) {
                if (item.compareTo(parentItem) > 0) {
                    heapList.set(parentIndex, item);
                    heapList.set(currentIndex, parentItem);
                    currentIndex = parentIndex;
                    continue;
                }
            }
            break;
        }
    }

    public T delete() {
        if (heapList.size() == 0) {
            return null;
        }
        if (heapList.size() == 1) {
            T item = heapList.get(0);
            heapList.remove(0);
            return item;
        }
        return siftDown();
    }

    private T siftDown() {
        T item = heapList.get(0);
        T lastItem = heapList.get(heapList.size() - 1);
        heapList.remove(heapList.size() - 1);
        heapList.set(0, lastItem);
        heapSize = heapList.size();
        int currentIndex = 0;
        while (currentIndex < heapSize) {
            int leftIndex = (2 * currentIndex) + 1;
            int rightIndex = (2 * currentIndex) + 2;
            T leftItem = null;
            T rightItem = null;
            int currentLargestItemIndex = -1;
            if (leftIndex <= heapSize - 1) {
                leftItem = heapList.get(leftIndex);
            }
            if (rightIndex <= heapSize - 1) {
                rightItem = heapList.get(rightIndex);
            }
            T currentLargestItem = null;
            if (leftItem != null && rightItem != null) {
                if (leftItem.compareTo(rightItem) >= 0) {
                    currentLargestItem = leftItem;
                    currentLargestItemIndex = leftIndex;
                } else {
                    currentLargestItem = rightItem;
                    currentLargestItemIndex = rightIndex;
                }
            } else if (leftItem != null && rightItem == null) {
                currentLargestItem = leftItem;
                currentLargestItemIndex = leftIndex;
            }
            if (currentLargestItem != null) {
                if (lastItem.compareTo(currentLargestItem) >= 0) {
                    break;
                } else {
                    heapList.set(currentLargestItemIndex, lastItem);
                    heapList.set(currentIndex, currentLargestItem);
                    currentIndex = currentLargestItemIndex;
                    continue;
                }
            } else {
                break;
            }
        }
        return item;

    }

    public static void main(String[] args) {
        HeapPractise<Integer> heap = new HeapPractise<>();

        for (int i = 0; i < 32; i++) {
            heap.insert(i);
        }
        System.out.println(heap.getHeapList());
        List<Node<Integer>> nodeArray = new ArrayList<>(heap.getHeapList()
                .size());
        for (int i = 0; i < heap.getHeapList().size(); i++) {
            Integer heapElement = heap.getHeapList().get(i);
            Node<Integer> node = new Node<Integer>(heapElement);
            nodeArray.add(node);
        }
        for (int i = 0; i < nodeArray.size(); i++) {
            int leftNodeIndex = (2 * i) + 1;
            int rightNodeIndex = (2 * i) + 2;
            Node<Integer> node = nodeArray.get(i);
            if (leftNodeIndex <= heap.getHeapList().size() - 1) {
                Node<Integer> leftNode = nodeArray.get(leftNodeIndex);
                node.left = leftNode;
            }
            if (rightNodeIndex <= heap.getHeapList().size() - 1) {
                Node<Integer> rightNode = nodeArray.get(rightNodeIndex);
                node.right = rightNode;
            }
        }
        BTreePrinter.printNode(nodeArray.get(0));
        System.out.println(heap.delete());
        nodeArray = new ArrayList<>(heap.getHeapList().size());
        for (int i = 0; i < heap.getHeapList().size(); i++) {
            Integer heapElement = heap.getHeapList().get(i);
            Node<Integer> node = new Node<Integer>(heapElement);
            nodeArray.add(node);
        }
        for (int i = 0; i < nodeArray.size(); i++) {
            int leftNodeIndex = (2 * i) + 1;
            int rightNodeIndex = (2 * i) + 2;
            Node<Integer> node = nodeArray.get(i);
            if (leftNodeIndex <= heap.getHeapList().size() - 1) {
                Node<Integer> leftNode = nodeArray.get(leftNodeIndex);
                node.left = leftNode;
            }
            if (rightNodeIndex <= heap.getHeapList().size() - 1) {
                Node<Integer> rightNode = nodeArray.get(rightNodeIndex);
                node.right = rightNode;
            }
        }
        BTreePrinter.printNode(nodeArray.get(0));
    }
}

public class Node<T extends Comparable<?>> {
    Node<T> left, right;
    T data;

    public Node(T data) {
        this.data = data;
    }
}

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class BTreePrinter {

    public static <T extends Comparable<?>> void printNode(Node<T> root) {
        int maxLevel = BTreePrinter.maxLevel(root);

        printNodeInternal(Collections.singletonList(root), 1, maxLevel);
    }

    private static <T extends Comparable<?>> void printNodeInternal(
            List<Node<T>> nodes, int level, int maxLevel) {
        if (nodes.isEmpty() || BTreePrinter.isAllElementsNull(nodes))
            return;

        int floor = maxLevel - level;
        int endgeLines = (int) Math.pow(2, (Math.max(floor - 1, 0)));
        int firstSpaces = (int) Math.pow(2, (floor)) - 1;
        int betweenSpaces = (int) Math.pow(2, (floor + 1)) - 1;

        BTreePrinter.printWhitespaces(firstSpaces);

        List<Node<T>> newNodes = new ArrayList<Node<T>>();
        for (Node<T> node : nodes) {
            if (node != null) {
                String nodeData = String.valueOf(node.data);
                if (nodeData != null) {
                    if (nodeData.length() == 1) {
                        nodeData = "0" + nodeData;
                    }
                }
                System.out.print(nodeData);
                newNodes.add(node.left);
                newNodes.add(node.right);
            } else {
                newNodes.add(null);
                newNodes.add(null);
                System.out.print("  ");
            }

            BTreePrinter.printWhitespaces(betweenSpaces);
        }
        System.out.println("");

        for (int i = 1; i <= endgeLines; i++) {
            for (int j = 0; j < nodes.size(); j++) {
                BTreePrinter.printWhitespaces(firstSpaces - i);
                if (nodes.get(j) == null) {
                    BTreePrinter.printWhitespaces(endgeLines + endgeLines + i
                            + 1);
                    continue;
                }

                if (nodes.get(j).left != null)
                    System.out.print("//");
                else
                    BTreePrinter.printWhitespaces(1);

                BTreePrinter.printWhitespaces(i + i - 1);

                if (nodes.get(j).right != null)
                    System.out.print("\\\\");
                else
                    BTreePrinter.printWhitespaces(1);

                BTreePrinter.printWhitespaces(endgeLines + endgeLines - i);
            }

            System.out.println("");
        }

        printNodeInternal(newNodes, level + 1, maxLevel);
    }

    private static void printWhitespaces(int count) {
        for (int i = 0; i < 2 * count; i++)
            System.out.print(" ");
    }

    private static <T extends Comparable<?>> int maxLevel(Node<T> node) {
        if (node == null)
            return 0;

        return Math.max(BTreePrinter.maxLevel(node.left),
                BTreePrinter.maxLevel(node.right)) + 1;
    }

    private static <T> boolean isAllElementsNull(List<T> list) {
        for (Object object : list) {
            if (object != null)
                return false;
        }

        return true;
    }

}

Обратите внимание, что BTreePrinter - это код, который я получил где-то в Stackoverflow, и я модифицировал его для использования с 2-значными числами. Он будет разбит, если мы перейдем к 3-значным числам, и это просто для простого понимания того, как структура кучи looks. Исправить для трехзначных чисел - сохранить все как кратное 3. Также за счет кредитов Sesh Venugopal за замечательный учебник по структуре Youtube on Heap.