Crea un BST bilanciato dall'elenco ordinato di numeri interi


15

Dato un elenco unico e ordinato di numeri interi, creare un albero di ricerca binaria bilanciato rappresentato come un array senza ricorrere alla ricorsione.

Per esempio:

func( [1,2,3,5,8,13,21] ) => [5,2,13,1,3,8,21]

Prima di iniziare, un suggerimento: possiamo semplificare un po 'questo problema in modo da non dover effettivamente pensare agli interi di input (o ad altri oggetti comparabili per quella materia!).

Se sappiamo che l'elenco di input è già ordinato, i suoi contenuti sono irrilevanti. Possiamo semplicemente pensarci in termini di indici nella matrice originale.

Una rappresentazione interna dell'array di input diventa quindi:

func( [0,1,2,3,4,5,6] ) => [3,1,5,0,2,4,6]

Ciò significa piuttosto che scrivere qualcosa che ha a che fare con oggetti comparabili, in realtà abbiamo solo bisogno di scrivere una funzione che mappa dall'intervallo [0, n) all'array risultante. Una volta che abbiamo il nuovo ordine, possiamo semplicemente applicare la mappatura ai valori nell'input per creare l'array di ritorno.

Le soluzioni valide devono:

  • Accettare un array a zero elementi e restituire un array vuoto.
  • Accetta una matrice intera di lunghezza n e restituisce una matrice intera
    • Di lunghezza compresa tra n e la successiva potenza massima di 2 meno 1. (ad es., Per le dimensioni di input 13, tornare tra 13 e 15).
    • Matrice che rappresenta un BST in cui il nodo principale è nella posizione 0 e l'altezza è uguale al registro (n) dove 0 rappresenta un nodo mancante (o un nullvalore simile se la tua lingua lo consente). I nodi vuoti, se presenti, devono esistere solo alla fine dell'albero (ad es. [2,1,0])

L'array intero di input ha le seguenti garanzie:

  • I valori sono numeri interi con segno a 32 bit maggiori di zero.
  • I valori sono unici.
  • I valori sono in ordine crescente dalla posizione zero.
  • I valori possono essere sparsi (cioè non adiacenti l'uno all'altro).

Vince il codice più conciso per numero di caratteri ASCII, ma sono anche interessato a vedere soluzioni eleganti per qualsiasi lingua particolare.

Casi test

Le uscite per array semplici contenenti 1a nper vari n. Come descritto sopra, i messaggi finali 0sono opzionali.

[]
[1]
[2,1,0]
[2,1,3]
[3,2,4,1,0,0,0]
[4,2,5,1,3,0,0]
[4,2,6,1,3,5,0]
[4,2,6,1,3,5,7]
[5,3,7,2,4,6,8,1,0,0,0,0,0,0,0]
[6,4,8,2,5,7,9,1,3,0,0,0,0,0,0]
[7,4,9,2,6,8,10,1,3,5,0,0,0,0,0]
[8,4,10,2,6,9,11,1,3,5,7,0,0,0,0]
[8,4,11,2,6,10,12,1,3,5,7,9,0,0,0]
[8,4,12,2,6,10,13,1,3,5,7,9,11,0,0]
[8,4,12,2,6,10,14,1,3,5,7,9,11,13,0]
[8,4,12,2,6,10,14,1,3,5,7,9,11,13,15]

Tutte le domande su questo sito, che si tratti di un puzzle di programmazione o di un codice golf, dovrebbero avere un criterio di vincita primario oggettivo, in modo che sia possibile decidere indiscutibilmente quale voce dovrebbe vincere.
Howard,

@Ciao Grazie. Aggiornato con criteri definitivi per il vincitore.
Jake Wharton,

1
Sarebbe molto utile avere alcuni casi di test che coprano i casi difficili, piuttosto che (come al momento) solo il più semplice.
Peter Taylor,

C'è qualche motivo per escludere la ricorsione? Non che sto guardando una soluzione ricorsiva, ma sembra sia artificiale che inutile.
dmckee --- ex gattino moderatore

1
Qualcuno può spiegare come l'elenco rappresenta un BST?
justinpc,

Risposte:


4

Ruby , 143

s=ARGV.size;r,q=[],[[0,s]];s.times{b,e=q.shift;k=Math::log2(e-b).to_i-1;m=(e-b+2)>(3<<k)?b+(2<<k)-1:e-(1<<k);r<<ARGV[m];q<<[b,m]<<[m+1,e]};p r

È una versione (liberamente) compressa del seguente codice che fondamentalmente fa un BFS sull'albero.

def l(n)
    k = Math::log2(n).to_i-1
    if n+2 > (3<<k) then
        (2<<k)-1
    else
        n-(1<<k) 
    end
end

def bfs(tab)
  result = []
  queue = [[0,tab.size]]
  until queue.empty? do
    b,e = queue.shift
    m = b+l(e-b)
    result << tab[m]
    queue << [b,m] if b < m
    queue << [m+1,e] if m+1 < e
  end
  result
end

p bfs(ARGV)

Inoltre, poiché si tratta di BFS, non di DFS, un requisito di soluzione non ricorsiva non è significativo e mette alcune lingue in svantaggio.

Modifica: soluzione fissa, grazie a @PeterTaylor per il suo commento!


@PeterTaylor L'intenzione era di mettere 3 a sinistra di 4, ma non ci sono spazi vuoti, quindi è sbagliato. Grazie per la segnalazione!
dtldarek,

@PeterTaylor Risolto il pranzo, dovrebbe funzionare ora.
dtldarek,

4

Java , 252

Ok, ecco il mio tentativo. Ho giocato con le operazioni sui bit e ho trovato questo modo diretto per calcolare l'indice di un elemento nel BST dall'indice nell'array originale.

Versione compressa

public int[]b(int[]a){int i,n=1,t;long x,I,s=a.length,p=s;int[]r=new int[(int)s];while((p>>=1)>0)n++;p=2*s-(1l<<n)+1;for(i=0;i<s;i++){x=(i<p)?(i+1):(p+2*(i-p)+1);t=1;while((x&1<<(t-1))==0)t++;I=(1<<(n-t));I|=((I-1)<<t&x)>>t;r[(int)I-1]=a[i];}return r;}

La versione lunga segue di seguito.

public static int[] makeBst(int[] array) {
  long size = array.length;
  int[] bst = new int[array.length];

  int nbits = 0;
  for (int i=0; i<32; i++) 
    if ((size & 1<<i)!=0) nbits=i+1;

  long padding = 2*size - (1l<<nbits) + 1;

  for (int i=0; i<size; i++) {
    long index2n = (i<padding)?(i+1):(padding + 2*(i-padding) + 1);

    int tail=1;
    while ((index2n & 1<<(tail-1))==0) tail++;
    long bstIndex = (1<<(nbits-tail));
    bstIndex = bstIndex | ((bstIndex-1)<<tail & index2n)>>tail;

    bst[(int)(bstIndex-1)] = array[i];
  }
 return bst;
}

È necessario un conteggio dei personaggi e questo non è attualmente giocato a golf.
dmckee --- ex gattino moderatore

@dmckee Ho modificato il post per includere una versione compressa e un conteggio dei caratteri
mikail sheikh

Bello spettacolo. Scommetto che alcuni di quegli spazi non sono necessari. In c, int[] b(int[] a)è altrettanto espresso come int[]b(int[]a).
dmckee --- ex gattino moderatore

Hai a.lengthnell'allocazione dell'array. Modificalo in s. Sbarazzarsi di spazio tra for (più volte. Ciascun ciclo for crea un int i=0e anche int t=0. Crea con n( int n=0,i,t;) e poi solo i=0nei loop e t=1all'interno. Dichiara l'interno long xe long Icon se appena inizializza nel ciclo ( long s=a.length,I,x;e x=../ I=..). Non dovresti avere bisogno di spazi attorno al binario AND &.
Jake Wharton,

Inoltre, I=I|..può essere scrittoI|=..
Jake Wharton il

3
def fn(input):
    import math
    n = len(input)
    if n == 0:
        return []
    h = int(math.floor(math.log(n, 2)))
    out = []
    last = (2**h) - 2**(h+1) + n

    def num_children(level, sibling, lr):
        if level == 0:
            return 0
        half = 2**(level-1)
        ll_base = sibling * 2**level + lr * (half)
        ll_children = max(0, min(last, ll_base + half - 1) - ll_base + 1)
        return 2**(level-1) - 1 + ll_children

    for level in range(h, -1, -1):
        for sibling in range(0, 2**(h-level)):
            if level == 0 and sibling > last:
                break
            if sibling == 0:
                last_sibling_val = num_children(level, sibling, 0)
            else:
                last_sibling_val += 2 + num_children(level, sibling - 1, 1) \
                    + num_children(level, sibling, 0)
            out.append(input[last_sibling_val])
    return out

2

Non sono sicuro se questo si adatta esattamente alle tue esigenze di nodi vuoti alla fine dell'albero e sicuramente non vincerà alcun premio per brevità, ma penso che sia corretto e che abbia casi di test :)

public class BstArray {
    public static final int[] EMPTY = new int[] { };
    public static final int[] L1 = new int[] { 1 };
    public static final int[] L2 = new int[] { 1, 2 };
    public static final int[] L3 = new int[] { 1, 2, 3 };
    public static final int[] L4 = new int[] { 1, 2, 3, 5 };
    public static final int[] L5 = new int[] { 1, 2, 3, 5, 8 };
    public static final int[] L6 = new int[] { 1, 2, 3, 5, 8, 13 };
    public static final int[] L7 = new int[] { 1, 2, 3, 5, 8, 13, 21 };
    public static final int[] L8 = new int[] { 1, 2, 3, 5, 8, 13, 21, 35 };
    public static final int[] L9 = new int[] { 1, 2, 3, 5, 8, 13, 21, 35, 56 };
    public static final int[] L10 = new int[] { 1, 2, 3, 5, 8, 13, 21, 35, 56, 91 };

    public static void main(String[] args) {
        for (int[] list : Arrays.asList(EMPTY, L1, L2, L3, L4, L5, L6, L7, L8, L9, L10)) {
            System.out.println(Arrays.toString(list) + " => " + Arrays.toString(bstListFromList(list)));
        }
    }

    private static int[] bstListFromList(int[] orig) {
        int[] bst = new int[nextHighestPowerOfTwo(orig.length + 1) - 1];

        if (orig.length == 0) {
            return bst;
        }

        LinkedList<int[]> queue = new LinkedList<int[]>();
        queue.push(orig);

        int counter = 0;
        while (!queue.isEmpty()) {
            int[] list = queue.pop();
            int len = list.length;

            if (len == 1) {
                bst[counter] = list[0];
            } else if (len == 2) {
                bst[counter] = list[1];
                queue.add(getSubArray(list, 0, 1));
                queue.add(new int[] { 0 });
            } else if (len == 3) {
                bst[counter] = list[1];
                queue.add(getSubArray(list, 0, 1));
                queue.add(getSubArray(list, 2, 1));
            } else {
                int divide = len / 2;
                bst[counter] = list[divide];
                queue.add(getSubArray(list, 0, divide));
                queue.add(getSubArray(list, divide + 1, len - (divide + 1)));
            }
            counter++;
        }

        return bst;
    }

    private static int nextHighestPowerOfTwo(int n) {
        n--;
        n |= n >> 1;
        n |= n >> 2;
        n |= n >> 4;
        n |= n >> 8;
        n |= n >> 16;
        n++;

        return n;
    }

    private static int[] getSubArray(int[] orig, int origStart, int length) {
        int[] list = new int[length];
        System.arraycopy(orig, origStart, list, 0, length);
        return list;
    }
}

2

Golfscript ( 99 89)

~]:b[]:^;{b}{{:|.,.2base,(2\?:&[-)&2/]{}$0=&(2/+:o[=]^\+:^;|o<.!{;}*|o)>.!{;}*}%:b}while^p

Fondamentalmente una porta diretta della mia soluzione Python, funziona praticamente allo stesso modo.

Probabilmente può essere migliorato un po 'con più "golfismi", già migliorato di 10 caratteri con l'input di @ petertaylor :)


Penso che dovrebbe essere possibile in non più di 70, anche se non ho ancora del tutto completato la mia risposta GolfScript. Tuttavia, ci sono alcuni facili miglioramenti nei tuoi. !{;}{}ifpuò essere solo !{;}*perché !garantisce di restituire 0o 1. Puoi usare token non alfabetici per le variabili, quindi se usi ^invece di r, |invece di x, &invece di yte puoi eliminare tutto quello spazio bianco.
Peter Taylor,

@PeterTaylor Grazie, non sapevo delle variabili non alfanumeriche, ancora molto nuovo in golfscript :)
Joachim Isaksson

2

Java 192

Indicizza le mappe in input per indicizzare in output

int[]b(int[]o){int s=o.length,p=0,u=s,i=0,y,r[]=new int[s],c[]=new int[s];while((u>>=1)>0)p++;for(int x:o){y=p;u=i;while(u%2>0){y--;u/=2;}r[(1<<y)-1+c[y]++]=x;i+=i>2*s-(1<<p+1)?2:1;}return r;}

Versione lunga:

static int[] bfs(int[] o) {
  int rowCount = 32 - Integer.numberOfLeadingZeros(o.length); // log2
  int slotCount = (1<<rowCount) - 1; // pow(2,rowCount) - 1

  // number of empty slots at the end
  int emptySlots = slotCount - o.length;
  // where we start to be affected by these empty slots
  int startSkippingAbove = slotCount - 2 * emptySlots; // = 2 * o.length - slotCount

  int[] result = new int[o.length];
  int[] rowCounters = new int[rowCount]; // for each row, how many slots in that row are taken
  int i = 0; // index of where we would be if this was a complete tree (no trailing empty slots)
  for (int x : o) {
    // the row (depth) a slot is in is determined by the number of trailing 1s
    int rowIndex = rowCount - Integer.numberOfTrailingZeros(~i) - 1;
    int colIndex = rowCounters[rowIndex]++; // count where we are
    int rowStartIndex = (1 << rowIndex) - 1; // where this row starts in the result array

    result[rowStartIndex + colIndex] = x;

    i++;
    // next one has to jump into a slot that came available by not having slotCount values
    if (i > startSkippingAbove) i++;
  }

  return result;
}

2

Wolfram Mathematica 11, 175 byte

g[l_]:=(x[a_]:=Floor@Min[i-#/2,#]&@(i=Length[a]+1;2^Ceiling@Log2[i]/2);Join@@Table[Cases[l//.{{}->{},b__List:>(n[Take[b,#-1],b[[#]],Drop[b,#]]&@x[b])},_Integer,{m}],{m,x[l]}])

La funzione g[l]accetta come input a List(ad es. l={1,2,3,4,...}) E restituisce a Listnella forma desiderata. Funziona come segue:

  • x[a_]:=Floor@Min[i-#/2,#]&@(i=Length[a]+1;2^Ceiling@Log2[i]/2) prende un elenco e trova la radice del BST associato.
    • i=Length[a]+1 scorciatoia per la lunghezza dell'elenco
    • 2^Ceiling@Log2[i]/2 limite superiore sul valore della radice
    • Min[i-#/2,#]&@(...)Minimo dei due argomenti in cui #rappresenta ciò che è dentro(...)
  • l//.{...} Applicare ripetutamente le regole di sostituzione che seguono l
  • {}->{} Niente da fare (questo è il caso limite per evitare un ciclo infinito)
  • b__List:>(n[Take[b,#-1],b[[#]],Drop[b,#]]&@x[b])Dividi un Listin{{lesser}, root, {greater}}
  • Cases[...,_Integer,{m}] Prendi tutti gli interi a livello (profondità) m
  • Table[...,{m,1,x[l]}]Per tutti mfino a x[l](che in realtà è più del necessario).

Può essere testato eseguendo

Table[g[Range[a]], {a, 0, 15}]//MatrixForm

Questa implementazione non include gli zero finali.


1

Python ( 175 171)

Abbastanza condensato, ancora abbastanza leggibile;

def f(a):
 b=[a]
 while b:
  c,t=((s,2**(len(bin(len(s)))-3))for s in b if s),[]
  for x,y in c:
   o=min(len(x)-y+1,y/2)+(y-1)/2
   yield x[o]
   t+=[x[:o],x[o+1:]]
  b=t

Restituisce il risultato, in modo da poterlo scorrere su di esso o (per scopi di visualizzazione) stamparlo come un elenco;

>>> for i in range(1,17): print i-1,list(f(range(1,i)))
 0 []
 1 [1]
 2 [2, 1]
 3 [2, 1, 3]
 4 [3, 2, 4, 1]
 5 [4, 2, 5, 1, 3]
 6 [4, 2, 6, 1, 3, 5]
 7 [4, 2, 6, 1, 3, 5, 7]
 8 [5, 3, 7, 2, 4, 6, 8, 1]
 9 [6, 4, 8, 2, 5, 7, 9, 1, 3]
10 [7, 4, 9, 2, 6, 8, 10, 1, 3, 5]
11 [8, 4, 10, 2, 6, 9, 11, 1, 3, 5, 7]
12 [8, 4, 11, 2, 6, 10, 12, 1, 3, 5, 7, 9]
13 [8, 4, 12, 2, 6, 10, 13, 1, 3, 5, 7, 9, 11]
14 [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13]
15 [8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15]

@dtldarek Il suo commento sembra essere stato rimosso, ma questo sembra passare i casi di test ora.
Joachim Isaksson,

Ho eliminato il mio commento per evitare che le persone si astengano dal votare la risposta di @ dtldarek a causa di un commento che dice che era difettoso.
Peter Taylor,

@PeterTaylor Bene, grazie per la tua considerazione ;-)
dtldarek,

1

Giava

Questa è una soluzione di calcolo diretto. Penso che funzioni, ma ha un effetto collaterale pragmaticamente innocuo. L'array che produce potrebbe essere danneggiato ma non in alcun modo influirebbe sulle ricerche. Invece di produrre 0 (null) nodi, produrrà nodi non raggiungibili, ovvero i nodi saranno già stati trovati in precedenza nell'albero durante la ricerca. Funziona mappando l'array di indici di una potenza normale di array di alberi di ricerca binari di 2 dimensioni su un array di alberi di ricerca binari di dimensioni irregolari. Almeno, penso che funzioni.

import java.util.Arrays;

public class SortedArrayToBalanceBinarySearchTreeArray
{
    public static void main(String... args)
    {
        System.out.println(Arrays.toString(binarySearchTree(19)));
    }

    public static int[] binarySearchTree(int m)
    {
        int n = powerOf2Ceiling(m + 1);
        int[] array = new int[n - 1];

        for (int k = 1, index = 1; k < n; k *= 2)
        {
            for (int i = 0; i < k; ++i)
            {
                array[index - 1] = (int) (.5 + ((float) (m)) / (n - 1)
                        * (n / (2 * k) * (1 + 2 * index) - n));
                ++index;
            }
        }

        return array;
    }

    public static int powerOf2Ceiling(int n)
    {
        n--;
        n |= n >> 1;
        n |= n >> 2;
        n |= n >> 4;
        n |= n >> 8;
        n |= n >> 16;
        n++;

        return n;
    }

}

Ecco una versione più ridotta (solo la funzione e i nomi abbinati). Ha ancora lo spazio bianco, ma non sono preoccupato per la vittoria. Anche questa versione prende effettivamente un array. L'altro ha appena preso un int per indice più alto nella matrice.

public static int[] b(int m[])
{
    int n = m.length;
    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    n++;

    int[] a = new int[n - 1];

    for (int k = 1, j = 1, i; k < n; k *= 2)
    {
        for (i = 0; i < k; ++i)
        {
            a[j - 1] = m[(int) (.5 + ((float) m.length) / (n - 1)
                    * (n / (2 * k) * (1 + 2 * j) - n)) - 1];
            ++j;
        }
    }

    return a;
}

Dato che si tratta di code-golf , abbrevia i tuoi metodi / nomi / ecc. Il più breve possibile; rimuovere tutti gli spazi bianchi (e metodi / materiali non necessari) e inserire il conteggio dei caratteri. Altrimenti, stai andando alla grande.
Justin il

@Jake Wharton. Mi piacerebbe davvero vedere la tua soluzione di mappatura diretta. Non sono sicuro al 100% che il mio funzioni per array molto grandi perché si basa su una mappatura matematica continua i cui valori vengono arrotondati. Sembra certamente funzionare, ma non sono sicuro di come dimostrarlo.
metaphyze

1

GolfScript ( 79 77 70 caratteri)

Dato che l'esempio nella domanda usa una funzione, l'ho resa una funzione. Rimuovendo il {}:f;per lasciare un'espressione che prende input nello stack e lascia il BST nello stack si risparmiano 5 caratteri.

{[.;][{{.!!{[.,.)[1]*{(\(@++}@(*1=/()\@~]}*}%.{0=}%\{1>~}%.}do][]*}:f;

Demo online (nota: l'app potrebbe richiedere un po 'di riscaldamento: è scaduto due volte prima di funzionare in 3 secondi).

Con spazi bianchi per mostrare la struttura:

{
    # Input is an array: wrap it in an array for the working set
    [.;]
    [{
        # Stack: emitted-values working-set
        # where the working-set is essentially an array of subtrees
        # For each subtree in working-set...
        {
            # ...if it's not the empty array...
            .!!{
                # ...gather into an array...
                [
                    # Get the size of the subtree
                    .,
                    # OEIS A006165, offset by 1
                    .)[1]*{(\(@++}@(*1=
                    # Split into [left-subtree-plus-root right-subtree]
                    /
                    # Rearrange to root left-subtree right-subtree
                    # where left-subtree might be [] and right-subtree might not exist at all
                    ()\@~
                ]
            }*
        }%
        # Extract the leading element of each processed subtree: these will join the emitted-values
        .{0=}%
        # Create a new working-set of the 1, or 2 subtrees of each processed subtree
        \{1>~}%
        # Loop while the working-set is non-empty
        .
    }do]
    # Stack: [[emitted values at level 0][emitted values at level 1]...]
    # Flatten by joining with the empty array
    []*
}:f;

1

J , 52 byte

t=:/:(#/:@{.(+:,>:@+:@i.@>:@#)^:(<.@(2&^.)@>:@#`1:))

La funzione prende un elenco ordinato e ritorna in un ordine ad albero binario

notare che gli alberi hanno una forma identica ma il livello inferiore è ridotto

  • `1: inizia con 1
  • <.@(2&^.)@>:@# iterare per piano di log2 (lunghezza + 1)
  • +: , >:@+:@i.@>:@# loop: aggiunge il doppio dell'ultimo vettore con numeri dispari 1,3 .. 2 * lunghezza + 1
  • # /:@{. prendi solo il numero richiesto di oggetti e ottieni i loro indici di ordinamento
  • /: applica quegli indici di ordinamento al dato input

TIO


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.