web-dev-qa-db-fra.com

Pile avec find-min/find-max plus efficace que O (n)?

Je suis intéressé par la création d'une structure de données Java similaire à une pile prenant en charge les opérations suivantes aussi efficacement que possible:

  • Push, qui ajoute un nouvel élément au sommet de la pile,
  • Pop, qui supprime l'élément supérieur de la pile,
  • Find-Max, qui retourne (mais ne supprime pas) le plus grand élément de la pile, et
  • Find-Min, qui retourne (mais ne supprime pas) le plus petit élément de la pile, et

Quelle serait la mise en œuvre la plus rapide de cette structure de données? Comment puis-je l'écrire en Java?

48
Techkriti

C'est une question classique sur les structures de données. L’intuition qui se cache derrière le problème est la suivante: la seule façon de modifier le maximum et le minimum est de transférer une nouvelle valeur dans la pile ou d’en extraire une nouvelle valeur. Dans ces conditions, supposons qu’à chaque niveau de la pile, vous gardiez une trace des valeurs maximales et minimales inférieures ou égales à ce point dans la pile. Ensuite, lorsque vous placez un nouvel élément sur la pile, vous pouvez facilement (en O(1) temps) calculer la valeur maximale et minimale n'importe où dans la pile en comparant le nouvel élément que vous venez de placer dans l'élément en cours. maximum et minimum. De même, lorsque vous supprimez un élément, vous exposez celui-ci dans la pile un pas en-dessous du sommet, qui contient déjà les valeurs maximale et minimale dans le reste de la pile.

Visuellement, supposons que nous ayons une pile et ajoutons les valeurs 2, 7, 1, 8, 3 et 9, dans cet ordre. Nous commençons par pousser 2, et donc nous poussons 2 sur notre pile. Puisque 2 est maintenant la valeur la plus grande et la plus petite de la pile, nous enregistrons ceci:

 2  (max 2, min 2)

Maintenant, appuyons sur 7. Puisque 7 est supérieur à 2 (le courant maximum), nous nous retrouvons avec ceci:

 7  (max 7, min 2)
 2  (max 2, min 2)

Notez que pour le moment, nous pouvons lire le max et le min de la pile en regardant le haut de la pile et en voyant que 7 correspond au max et 2 au min. Si nous poussons maintenant 1, nous obtenons

 1  (max 7, min 1)
 7  (max 7, min 2)
 2  (max 2, min 2)

Ici, nous savons que 1 est le minimum, car nous pouvons comparer 1 à la valeur minimale en cache stockée au sommet de la pile (2). En tant qu’exercice, assurez-vous de comprendre pourquoi, après avoir ajouté 8, 3 et 9, nous obtenons ceci:

 9  (max 9, min 1)
 3  (max 8, min 1)
 8  (max 8, min 1)
 1  (max 7, min 1)
 7  (max 7, min 2)
 2  (max 2, min 2)

Maintenant, si nous voulons interroger les max et min, nous pouvons le faire dans O(1) en lisant simplement les max et min stockés au sommet de la pile (respectivement 9 et 1).

Maintenant, supposons que nous sortions de l’élément supérieur. Cela donne 9 et modifie la pile pour être

 3  (max 8, min 1)
 8  (max 8, min 1)
 1  (max 7, min 1)
 7  (max 7, min 2)
 2  (max 2, min 2)

Et maintenant, remarquez que le maximum de ces éléments est 8, exactement la bonne réponse! Si nous avons ensuite poussé 0, nous aurions ceci:

 0  (max 8, min 0)
 3  (max 8, min 1)
 8  (max 8, min 1)
 1  (max 7, min 1)
 7  (max 7, min 2)
 2  (max 2, min 2)

Et, comme vous pouvez le constater, les valeurs max et min sont calculées correctement.

Globalement, cela conduit à une implémentation de la pile qui a O(1) Push, pop, find-max et find-min, ce qui est aussi asymptotique que possible. Je laisserai la mise en œuvre comme un exercice. :-) Toutefois, vous pouvez envisager de mettre en oeuvre la pile en utilisant l’une des techniques d’implémentation standard, telles que l’utilisation de tableau dynamique ou liste chaînée d’objets, chacun d’eux. qui contient l'élément de pile, min et max. Vous pouvez le faire facilement en utilisant ArrayList ou LinkedList. Vous pouvez également utiliser la classe Java Stack fournie, bien que le code IIRC présente une surcharge en raison de la synchronisation qui pourrait être inutile pour cette application.

Fait intéressant, une fois que vous avez construit une pile avec ces propriétés, vous pouvez l’utiliser comme un bloc de construction pour construire ne file d’attente avec les mêmes propriétés et une garantie de temps. Vous pouvez également l'utiliser dans une construction plus complexe pour créer une file d'attente à deux extrémités avec ces propriétés.

J'espère que cela t'aides!

EDIT: Si vous êtes curieux, j'ai des implémentations C++ de n min-stack = et un des précédents min-files sur mon site personnel. Espérons que cela montre à quoi cela pourrait ressembler dans la pratique!

111
templatetypedef

Bien que la réponse ait raison, nous pouvons faire mieux. Si la pile contient beaucoup d'éléments, nous gaspillons beaucoup d'espace. Cependant, nous pouvons économiser cet espace inutile comme suit:

Au lieu d'enregistrer la valeur minimale (ou maximale) avec chaque élément, nous pouvons utiliser deux piles. Comme les modifications de la valeur minimale (ou maximale) ne seront pas si fréquentes, nous poussons la valeur minimale (ou maximale) dans sa pile respective uniquement lorsque la nouvelle valeur correspond à <= (ou >=) à la valeur minimale actuelle (ou maximale).

Voici l'implémentation dans Java:

public class StackWithMinMax extends Stack<Integer> {

    private Stack<Integer> minStack;
    private Stack<Integer> maxStack;

    public StackWithMinMax () {
        minStack = new Stack<Integer>();    
        maxStack = new Stack<Integer>();    
    }

    public void Push(int value){
        if (value <= min()) { // Note the '=' sign here
            minStack.Push(value);
        }

        if (value >= max()) {
            maxStack.Push(value);
        }

        super.Push(value);
    }

    public Integer pop() {
        int value = super.pop();

        if (value == min()) {
            minStack.pop();         
        }

        if (value == max()) {
            maxStack.pop();         
        }

        return value;
    }

    public int min() {
        if (minStack.isEmpty()) {
            return Integer.MAX_VALUE;
        } else {
            return minStack.peek();
        }
    }

    public int max() {
        if (maxStack.isEmpty()) {
            return Integer.MIN_VALUE;
        } else {
            return maxStack.peek();
        }
    }
}

Notez qu'en utilisant cette approche, nous aurions très peu d'éléments dans minStack & maxStack, économisant ainsi de l'espace. par exemple.

Stack : MinStack : MaxStack

7         7         7
4         4         7
5         1         8 (TOP)
6         1 (TOP)         
7
8                 
1                  
1                  
7
2
4
2 (TOP)     
30
Vasu

Peut-être trop tard pour répondre, mais juste pour l'enregistrement. Voici le code Java.

import Java.util.ArrayList;
import Java.util.List;

public class MinStack {
    List<Node> items;

    public void Push(int num) {
        if (items == null) {
            items = new ArrayList<Node>();
        }
        Node node = new Node(num);
        if (items.size() > 0) {
            node.min = Math.min(items.get(items.size() - 1).min, num);
            node.max = Math.max(items.get(items.size() - 1).max, num);

        } else {
            node.min = num;
            node.max = num;
        }
        items.add(node);
        printStack();
    }

    public Node pop() {
        Node popThis = null;
        if (items != null && items.size() > 0) {
            popThis = this.items.get(items.size() - 1);
            items.remove(items.size() - 1);         
        }
        printStack();
        return popThis;
    }

    public int getMin() {
        if (items != null && items.size() > 0) {
            int min = this.items.get(items.size() - 1).min;
            System.out.println("Minimum Element > " + min);
            return min;
        }
        return -1;
    }

    public int getMax() {
        if (items != null && items.size() > 0) {
            int max = this.items.get(items.size() - 1).max;
            System.out.println("Maximum Element > " + max);
            return max;
        }
        return -1;
    }

    public void printStack() {
        int i = 0;
        for (Node n : items) {
            System.out.print(n.data + " > ");
            if (i == items.size() - 1) {
                System.out.print(" | Min = " + n.min + " |");
                System.out.print(" | Max = " + n.max + " |");

            }
            i++;
        }
        System.out.println();
    }

    public static void main(String args[]) {
        MinStack stack = new MinStack();
        stack.Push(10);

        stack.Push(13);
        stack.Push(19);
        stack.Push(3);
        stack.Push(2);
        stack.Push(2);
        stack.printStack();
        stack.pop();
        //stack.getMin();
        stack.printStack();

    }
}

Classe de pile:

class Node {

        int data;
        int min;
        int max;

        public Node(int data) {
            super();
            this.data = data;
        }

        public Node() {
            super();
        }
    }
2
Sanjay Kumar

Utiliser Linkedlist:

public class MaxMinStack {
    MaxMinLLNode headMin = null;
    MaxMinLLNode headMax = null;
    MaxMinLLNode tailMin = null;
    MaxMinLLNode tailMax = null;

    public void Push(int data) {
        MaxMinLLNode node = new MaxMinLLNode(data, null);
        if (headMin == null) {
            headMin = node;
            tailMin = node;
        } else {
            if (data < headMin.data) {
                tailMin = headMin;
                headMin = node;
                node.nextNodeReference = tailMin;
            }
        }

        if (headMax == null) {
            headMax = node;
            tailMax = node;
        } else {
            if (data > headMax.data) {
                tailMax = headMax;
                headMax = node;
                node.nextNodeReference = tailMax;
            }
        }

    }

    public void pop() {
        System.out.println("Max Element:" + " " + String.valueOf(headMax.data));
        System.out.println("Min Element:" + " " + String.valueOf(headMin.data));
    }

    public void traverse() {
        MaxMinLLNode ptrMin = headMin;
        MaxMinLLNode ptrMax = headMax;
        System.out.println("Min");
        while (ptrMin != null) {
            System.out.println(ptrMin.data);
            ptrMin = ptrMin.nextNodeReference;
        }

        System.out.println("Max");
        while (ptrMax != null) {
            System.out.println(ptrMax.data);
            ptrMax = ptrMax.nextNodeReference;
        }

    }

    public static void main(String[] args) {
        MaxMinStack m = new MaxMinStack();
         m.Push(7);
         m.Push(4);
         m.Push(5);
         m.Push(6);
         m.Push(7);
         m.Push(8);
         m.Push(1);
         m.Push(1);
         m.Push(7);
         m.Push(2);
         m.Push(4);
         m.Push(2);
         m.traverse();
         m.pop();
    }

}

class MaxMinLLNode {
    int data;
    MaxMinLLNode nextNodeReference;

    MaxMinLLNode(int data, MaxMinLLNode node) {
        this.data = data;
        this.nextNodeReference = node;
    }
}
0
Nikita Gupta