Schnellster Algorithmus, um das Produkt aller Teilmengen zu ermitteln

23

Bei gegebenen nZahlen in einem Array (Sie können nicht davon ausgehen, dass es sich um Ganzzahlen handelt) möchte ich das Produkt aller Teilmengen der Größe berechnen n-1.

Sie können dies tun, indem Sie alle Zahlen miteinander multiplizieren und dann nacheinander durch jede dividieren, solange keine der Zahlen Null ist. Wie schnell können Sie dies jedoch ohne Unterteilung tun?

Wenn Sie keine Division zulassen, wie viele arithmetische Operationen (z. B. Multiplikation und Addition) sind mindestens erforderlich, um das Produkt aller Teilmengen der Größe n-1 zu berechnen?

Natürlich können Sie es in (n-1)*nMultiplikationen tun .

Zur Verdeutlichung handelt es sich bei der Ausgabe um nverschiedene Produkte, und die einzigen Operationen, die außer Lesen und Schreiben in den Speicher zulässig sind, sind Multiplikation, Addition und Subtraktion.

Beispiel

Wenn die Eingabe drei Zahlen hat 2,3,5, dann ist die Ausgabe drei Zahlen 15 = 3*5, 10 = 2*5und 6 = 2*3.

Gewinnkriterium

Die Antworten sollten eine genaue Formel für die Anzahl der Rechenoperationen enthalten, für die der Code verwendet n. Um das Leben einfacher zu machen, werde ich mich einfach n = 1000an Ihre Formel anschließen, um deren Punktzahl zu beurteilen. Je niedriger desto besser.

Wenn es zu schwierig ist, eine genaue Formel für Ihren Code zu erstellen, können Sie sie einfach ausführen n = 1000und die arithmetischen Operationen im Code zählen. Eine genaue Formel wäre jedoch am besten.

Sie sollten Ihre Punktzahl für n=1000Ihre Antwort zum einfachen Vergleich hinzufügen .

Arthur
quelle
4
Dürfen wir das Multiplizieren mit 1 als frei zählen? Andernfalls würde ich eine benutzerdefinierte Multiplikationsfunktion definieren, die dies ausführt.
xnor
3
Würde es gegen die Regeln verstoßen, eine ganze Reihe von Multiplikationen parallel durchzuführen, indem man Zahlen zusammen mit ausreichend vielen 0-stelligen Spacern verkettet?
xnor
1
Zählen die Operationen wie +auf Indizes ? Wenn dies der Fall ist, zählt dann auch die Array-Indizierung? (da es sich immerhin um syntaktischen Zucker zur Zugabe und Dereferenzierung handelt).
Nore
2
@nore OK Ich gebe nach :) Zähle einfach arithmetische Operationen, die die Eingabe in irgendeiner Weise beinhalten.
Arthur
1
Klar kannst du es in (n-1)*nMultiplikationen machen. Du meinst (n-2)*n, richtig?
Luis Mendo

Antworten:

25

Python, 3 (n-2) Operationen, Score = 2994

l = list(map(float, input().split()))
n = len(l)

left = [0] * len(l)
right = [0] * len(l)
left[0] = l[0]
right[-1] = l[-1]
for i in range(1,len(l)-1):
  left[i] = l[i] * left[i - 1]
  right[-i-1] = l[-i-1] * right[-i]

result = [0] * len(l)
result[-1] = left[-2]
result[0] = right[1]
for i in range(1, len(l) - 1):
  result[i] = left[i - 1] * right[i+1]

print(result)

Die Arrays leftund rightenthalten die kumulierten Produkte des Arrays von links bzw. von rechts.

BEARBEITEN: Beweisen Sie, dass 3 (n-2) die optimale Anzahl von Operationen ist, die für n> = 2 benötigt werden, wenn wir nur die Multiplikation verwenden.

Wir werden das durch Induktion tun; Mit dem obigen Algorithmus müssen wir nur beweisen, dass für n> = 2, 3 (n-2) eine Untergrenze für die Anzahl der erforderlichen Multiplikationen ist.

Für n = 2 benötigen wir mindestens 0 = 3 (2-2) Multiplikationen, das Ergebnis ist also trivial.

Sei n> 2, und angenommen, für n - 1 Elemente brauchen wir mindestens 3 (n - 3) Multiplikationen. Betrachten Sie eine Lösung für n Elemente mit k Multiplikationen. Jetzt entfernen wir das letzte dieser Elemente wie 1 und vereinfachen alle Multiplikationen direkt damit. Wir entfernen auch die Multiplikation, die zum Produkt aller anderen Elemente führt, da dieses nicht benötigt wird, da es niemals als Zwischenwert verwendet werden kann, um das Produkt von n-2 der anderen Elemente zu erhalten, da eine Division nicht zulässig ist. Dies ergibt 1 Multiplikationen und eine Lösung für n - 1 Elemente.

Nach Induktionshypothese haben wir l> = 3 (n-3).

Schauen wir uns nun an, wie viele Multiplikationen entfernt wurden. Einer von ihnen war derjenige, der zum Produkt aller Elemente mit Ausnahme des letzten führte. Darüber hinaus wurde das letzte Element direkt in mindestens zwei Multiplikationen verwendet: Wenn es nur in einer verwendet wurde, wurde es beim Multiplizieren mit einem Zwischenergebnis verwendet, das aus einem Produkt der anderen Elemente bestand; Nehmen wir an, dieses Zwischenergebnis enthielt ohne Verlust der Allgemeinheit das erste Element im Produkt. Dann gibt es keine Möglichkeit, das Produkt aller Elemente außer dem ersten zu erhalten, da jedes Produkt, das das letzte Element enthält, entweder das letzte Element oder das erste Element enthält.

Wir haben also k> = 1 + 3> = 3 (n-2), was den behaupteten Satz beweist.

nein
quelle
8
Dies erweist sich als sehr sauber in Haskell : f l = zipWith (*) (scanl (*) 1 l) (scanr (*) 1 $ tail l).
Xnor
Kommentare sind nicht für eine längere Diskussion gedacht. Diese Unterhaltung wurde in den Chat verschoben .
Dennis
12

Haskell , Score 2994

group :: Num a => [a] -> [[a]]
group (a:b:t) = [a,b] : group t
group [a] = [[a]]
group [] = []

(%) :: (Num a, Eq a) => a -> a -> a
a % 1 = a
1 % b = b
a % b = a * b

prod_one_or_two :: (Num a, Eq a) => [a] -> a
prod_one_or_two [a, b] = a % b
prod_one_or_two [x] = x

insert_new_value :: (Num a, Eq a) => ([a], a) -> [a]
insert_new_value ([a, b], c) = [c % b, c % a]
insert_new_value ([x], c) = [c]

products_but_one :: (Num a, Eq a) => [a] -> [a]
products_but_one [a] = [1]
products_but_one l = 
    do combination <- combinations ; insert_new_value combination
    where 
        pairs = group l
        subresults = products_but_one $ map prod_one_or_two pairs
        combinations = zip pairs subresults

Probieren Sie es online!

Sagen wir, wir bekommen die Liste [a,b,c,d,e,f,g,h]. Wir gruppieren es zuerst in Paare [[a,b],[c,d],[e,f],[g,h]]. Dann greifen wir auf die halbe Liste pairsihrer Produkte zurück, um sie zu bekommensubresults

[a*b, c*d, e*f, g*h] -> [(c*d)*(e*f)*(g*h), (a*b)*(e*f)*(g*h), (a*b)*(c*d)*(g*h), (a*b)*(c*d)*(e*f)]

Wenn wir das erste Element nehmen (c*d)*(e*f)*(g*h)und multiplizieren sie durch bund ajeweils erhalten wir das Produkt aller aber aund alle , aber b. Tun wir dies für jedes Paar und rekursives Ergebnis, wobei dieses Paar fehlt, erhalten wir das endgültige Ergebnis. Der Fall mit ungerader Länge wird speziell behandelt, indem das ungerade Element ungepaart an den rekursiven Schritt übergeben wird und das Produkt der zurückgegebenen verbleibenden Elemente das Produkt ohne dieses Element ist.

Die Anzahl der Multiplikationen t(n)gilt n/2für das Paarungsprodukt, t(n/2)für den rekursiven Aufruf und nfür die Produkte mit einzelnen Elementen. Dies gibt t(n) = 1.5 * n + t(n/2)für ungerade n. Die Verwendung einer genaueren Anzahl für ungerade nund das Ignorieren des Multiplizierens mit 1für den Basisfall ergibt eine Punktzahl 2997für n=1000.

xnor
quelle
Das ist sehr nett.
Arthur
Ich denke, der Grund, warum die Punktzahl 2995 und nicht 2994 ist, wie in meiner Antwort, ist, dass es das Produkt aller Zahlen auch in der Nicht-Potenz von zwei Fällen berechnet, die später abgeschnitten wird. Möglicherweise könnte eine sorgfältige Behandlung products_but_one'dies verhindern, indem Sie etwas in der richtigen Länge zurücksenden.
Nore
@nore Ich habe festgestellt, dass ich eine zusätzliche Multiplikation in meiner Zählung hatte, weil ich vergessen habe, dass der Basisfall eine hat 1, die sich frei multiplizieren lässt. Ich denke, dass die Polsterung 1 die Dinge nicht beeinflusst hat, aber ich habe meinen Algorithmus aufgeräumt, um sie nicht zu verwenden.
xnor
Nimmt dieser Code an, dass die Eingabe Ganzzahlen ist?
@Lembik Tut es, aber nur in den optionalen Typanmerkungen. Ich werde sie alle ändern float.
Xnor
9

Haskell , Punktzahl 9974

partition :: [Float] -> ([Float], [Float])
partition = foldr (\a (l1,l2) -> (l2, a:l1)) ([],[])

(%) :: Float -> Float -> Float
a % 1 = a
1 % b = b
a % b = a*b

merge :: (Float, [Float]) -> (Float, [Float]) -> (Float, [Float])
merge (p1,r1) (p2, r2) = (p1%p2, map(%p1)r2 ++ map(%p2)r1)

missing_products' :: [Float] -> (Float, [Float])
missing_products' [a] = (a,[1])
missing_products' l = merge res1 res2
    where
        (l1, l2) = partition l
        res1 = missing_products' l1
        res2 = missing_products' l2

missing_products :: [Float] -> [Float]
missing_products = snd . missing_products'

Probieren Sie es online!

Eine Divide-and-Conquer-Strategie, die sehr an Merge erinnert. Indiziert nicht.

Die Funktion partitionteilt die Liste in möglichst gleiche Hälften, indem alternierende Elemente auf gegenüberliegenden Seiten der Partition platziert werden. Wir führen die Ergebnisse (p,r)für jede der Hälften rekursiv zusammen , wobei rdie Liste der fehlenden Produkte und pdas Gesamtprodukt angezeigt werden.

Für die Ausgabe der vollständigen Liste muss sich das fehlende Element in einer der Hälften befinden. Das Produkt, bei dem dieses Element fehlt, ist ein Produkt, bei dem die Hälfte fehlt, multipliziert mit dem vollständigen Produkt für die andere Hälfte. Wir multiplizieren also jedes fehlende Produkt mit dem vollständigen Produkt der anderen Hälfte und erstellen eine Liste der Ergebnisse map(*p1)r2 ++ map(*p2)r1). Dies erfordert nMultiplikationen, wobei ndie Länge ist. Wir müssen auch ein neues Vollprodukt p1*p2für die zukünftige Verwendung erstellen, das 1 weitere Multiplikation kostet.

Dies gibt die allgemeine Rekursion für die Anzahl der Operationen t(n)mit nselbst: t(n) = n + 1 + 2 * t(n/2). Die ungerade ist ähnlich, aber eine der Unterlisten ist 1größer. Wenn wir die Rekursion herausrechnen, erhalten wir n*(log_2(n) + 1)Multiplikationen, obwohl die gerade / ungerade Unterscheidung diesen exakten Wert beeinflusst. Die Werte von bis zu t(3)werden verbessert , indem nicht durch Multiplikation 1eine Variante durch die Definition (%)von (*)der , die Verknüpfungen _*1oder 1*_Fällen.

Dies ergibt 9975Multiplikationen für n=1000. Ich glaube, Haskells Faulheit bedeutet, dass das nicht verwendete Gesamtprodukt in der äußeren Schicht nicht berechnet wird 9974. wenn ich mich irre, könnte ich es explizit weglassen.

xnor
quelle
Du hast mich eine Minute früher mit dem Zeitstempel geschlagen.
Nore
Wenn es schwierig ist, die Formel genau zu berechnen, können Sie sie einfach ausführen n = 1000und die arithmetischen Operationen im Code zählen.
Arthur
Da unser Code im Grunde genommen derselbe ist, verstehe ich nicht, wie Sie zu Multiplikationen gekommen sind 9974und nicht (im Fall der Berechnung des Gesamtprodukts in der äußeren Schicht). Haben Sie ein in die Eingabe aufgenommen, mit der Sie es getestet haben? 9975n = 10001
Nore
@nore Du hast recht, ich war um eins weg. Ich habe Code geschrieben, um die Rekursion für die Anzahl der Multiplikationsfunktionsaufrufe durchzuführen. Direktes Zählen von Anrufen wäre zuverlässiger - weiß jemand, wie ich das in Haskell machen würde?
xnor
1
@xnor Sie verwenden können , traceaus Debug.Tracemit einer catch-all | trace "call!" False = undefinedWache, glaube ich. Aber das wird unsafePerformIOunter der Haube verwendet, also ist es keine wirkliche Verbesserung.
Soham Chowdhury
6

Haskell , Score 2994

group :: [a] -> Either [(a, a)] (a, [(a, a)])
group [] = Left []
group (a : l) = case group l of
  Left pairs -> Right (a, pairs)
  Right (b, pairs) -> Left ((a, b) : pairs)

products_but_one :: Num a => [a] -> [a]
products_but_one [_] = [1]
products_but_one [a, b] = [b, a]
products_but_one l = case group l of
  Left pairs ->
    let subresults =
          products_but_one [a * b | (a, b) <- pairs]
    in do ((a, b), c) <- zip pairs subresults; [c * b, c * a]
  Right (extra, pairs) ->
    let subresult : subresults =
          products_but_one (extra : [a * b | (a, b) <- pairs])
    in subresult : do ((a, b), c) <- zip pairs subresults; [c * b, c * a]

Probieren Sie es online!

Wie es funktioniert

Dies ist eine bereinigte Version von xnors Algorithmus , die den ungeraden Fall auf einfachere Weise behandelt (Bearbeiten: Es sieht so aus, als hätte xnor ihn auf die gleiche Weise bereinigt):

[a, b, c, d, e, f, g] ↦
[a, bc, de, fg] ↦
[(bc) (de) (fg), a (de) (fg), a (bc) ( fg), a (bc) (de)] durch Rekursion ↦
[(bc) (de) (fg), a (de) (fg) c, a (de) (fg) b, a (bc) (fg) e, a (bc) (fg) d, a (bc) (de) g, a (bc) (de) f]

[a, b, c, d, e, f, g, h]
[ab, cd, ef, gh]
[(cd) (ef) (gh), (ab) (ef) (gh), ( ab) (cd) (gh), (ab) (cd) (ef)] durch Rekursion ↦
[(cd) (ef) (gh) b, (cd) (ef) (gh) a, (ab) (ef ) (gh) d, (ab) (ef) (gh) c, (ab) (cd) (gh) f, (ab) (cd) (gh) e, (ab) (cd) (ef) h, (ab) (cd) (ef) g].

Anders Kaseorg
quelle
"Bei n Zahlen in einem Array (Sie können nicht annehmen, dass es sich um Ganzzahlen handelt)" Wir können nicht annehmen, dass es sich um Ganzzahlen handelt
5

O (n log n) Operationen, Punktzahl = 9974

Arbeitet mit einem binären Baum.

Python

l = list(map(int, input().split()))
n = len(l)

p = [0] * n + l
for i in range(n - 1, 1, -1):
  p[i] = p[i + i] * p[i + i+1]

def mul(x, y):
  if y == None:
    return x
  return x * y

r = [None] * n + [[None]] * n
for i in range(n - 1, 0, -1):
  r[i] = [mul(p[i + i + 1], x) for x in r[i + i]] + [mul(p[i + i], x) for x in r[i + i + 1]]

u = r[1]
j = 1
while j <= n:
  j += j
print(u[n+n-j:] + u[:n+n-j])

Dies erfordert auch Operationen zum Hinzufügen von Listen und einige Berechnungen für Zahlen, die nicht die Eingabewerte sind. nicht sicher, ob das zählt. Die mulFunktion dient zum Speichern von n Operationen für den Basisfall, um deren Verschwendung durch Multiplikation mit 1 zu vermeiden. In jedem Fall handelt es sich um O (n log n) -Operationen. Die genaue Formel ist, wenn nur arithmetische Operationen an Eingangszahlen zu zählen, mit j = floor(log_2(n)): j * (2^(j + 1) - n) + (j + 1) * (2 * n - 2^(j + 1)) - 2.

Vielen Dank an @xnor für das Speichern eines Vorgangs mit der Idee, das äußere Produkt nicht zu berechnen!

Der letzte Teil ist die Ausgabe der Produkte in der Reihenfolge des fehlenden Begriffs.

nein
quelle
Wenn es schwierig ist, die Formel genau zu berechnen, können Sie sie einfach ausführen n = 1000und die arithmetischen Operationen im Code zählen.
Arthur
Ich habe 10975 Operationen gezählt ...?
HyperNeutrino
p[i] = p[i + i] * p[i + i+1]wird nicht gezählt
HyperNeutrino
Hier geht es um n log2 n + nOperationen (das ist O (nlogn) btw
HyperNeutrino
@HyperNeutrino Die Operationen in p[i] = p[i + i] * p[i + i + 1]sollten durch die Multiplikationsoptimierung gespeichert werden. Ich hätte aber vielleicht einen zu viel gezählt.
Nore
3

O ((n-2) * n) = O (n 2 ): Triviale Lösung

Dies ist nur die einfache Lösung, mit der jede Teilmenge multipliziert wird:

Python

def product(array): # Requires len(array) - 1 multiplication operations
    if not array: return 1
    result = array[0]
    for value in array[1:]:
        result *= value
    return result

def getSubsetProducts(array):
    products = []
    for index in range(len(array)): # calls product len(array) times, each time calling on an array of size len(array) - 1, which means len(array) - 2 multiplication operations called len(array) times
        products.append(product(array[:index] + array[index + 1:]))
    return products

Beachten Sie, dass dazu auch nOperationen zum Hinzufügen von Listen erforderlich sind . nicht sicher, ob das zählt. Wenn das nicht erlaubt ist, dann product(array[:index] + array[index + 1:])kann zu ersetzt werden product(array[:index]) * product(array[index + 1:]), wodurch sich die Formel zu ändert O((n-1)*n).

HyperNeutrino
quelle
Sie können Ihre eigene Punktzahl zur Antwort hinzufügen. 998 * 1000 in diesem Fall.
Arthur
Benötigen Sie Ihre productFunktionsoperationen nicht O(n)? eines für jedes Element im Array (obwohl dies leicht geändert werden kann O(n-1))
Roman Gräf
@ RomanGräf Stimmt. Ich werde es in O (n-1) ändern, aber danke, dass Sie darauf hingewiesen haben.
HyperNeutrino
Dies wurde in Atomic-Code-Golf geändert ...
Erik der Outgolfer
@EriktheOutgolfer Was bringt mir das jetzt? Widersprechen sich das Etikett und die technischen Daten nicht jetzt, es sei denn, ich bin wirklich dumm?
HyperNeutrino
3

Python, 7540

Eine dreigliedrige Fusionsstrategie. Ich denke, ich kann es noch besser machen, mit einer noch größeren Verschmelzung. Es ist O (n log n).

BEARBEITEN: Ein Fehler wurde behoben.

count = 0
def prod(a, b):
    if a == 1: return b
    if b == 1: return a
    global count
    count += 1
    return a * b

def tri_merge(subs1, subs2, subs3):
    total1, missing1 = subs1
    total2, missing2 = subs2
    total3, missing3 = subs3

    prod12 = prod(total1, total2)
    prod13 = prod(total1, total3)
    prod23 = prod(total2, total3)

    new_missing1 = [prod(m1, prod23) for m1 in missing1]
    new_missing2 = [prod(m2, prod13) for m2 in missing2]
    new_missing3 = [prod(m3, prod12) for m3 in missing3]

    return prod(prod12, total3), new_missing1 + new_missing2 + new_missing3

def tri_partition(nums):
    split_size = len(nums) // 3
    a = nums[:split_size]
    second_split_length = split_size + (len(nums) % 3 == 2)
    b = nums[split_size:split_size + second_split_length]
    c = nums[split_size + second_split_length:]
    return a, b, c

def missing_products(nums):
    if len(nums) == 1: return nums[0], [1]
    if len(nums) == 0: return 1, []
    subs = [missing_products(part) for part in tri_partition(nums)]
    return tri_merge(*subs)

def verify(nums, res):
    actual_product = 1
    for num in nums:
        actual_product *= num
    actual_missing = [actual_product // num for num in nums]
    return actual_missing == res[1] and actual_product == res[0]

nums = range(2, int(input()) + 2)
res = missing_products(nums)

print("Verified?", verify(nums, res))
if max(res[1]) <= 10**10: print(res[1])

print(len(nums), count)

Die relevante Funktion ist missing_products, die das Gesamtprodukt und alle mit einem fehlenden Element gibt.

isaacg
quelle
Hast du die Multiplikationen mitgezählt tri_merge? Auch Sie können das 2 * split_size + ...in tri_partitionmit ersetzen split_size + split_size + ....
Roman Gräf
@ RomanGräf Ich habe es nach deinem Vorschlag umstrukturiert.
Isaacg
1

dc, score 2994

#!/usr/bin/dc -f

# How it works:
# The required products are
#
#   (b × c × d × e × ... × x × y × z)
# (a) × (c × d × e × ... × x × y × z)
# (a × b) × (d × e × ... × x × y × z)
# ...
# (a × b × c × d × e × ... × x) × (z)
# (a × b × c × d × e × ... × x × y)
#
# We calculate each parenthesised term by
# multiplying the one above (on the left) or below
# (on the right), for 2(n-2) calculations, followed
# by the n-2 non-parenthesised multiplications
# giving a total of 3(n-2) operations.

# Read input from stdin
?

# We will store input values into stack 'a' and
# accumulated product into stack 'b'.  Initialise
# stack b with the last value read.
sb

# Turnaround function at limit of recursion: print
# accumulated 'b' value (containing b..z above).
[Lbn[ ]nq]sG

# Recursive function - on the way in, we stack up
# 'a' values and multiply up the 'b' values.  On
# the way out, we multiply up the 'a' values and
# multiply each by the corresponding 'b' value.
[dSalb*Sb
z1=G
lFx
dLb*n[ ]n
La*]dsFx

# Do the last a*b multiplication
dLb*n[ ]n

# And we have one final 'a' value that doesn't have a
# corresponding 'b':
La*n

Ich gehe davon aus, dass der ganzzahlige Vergleich z1= (der die Rekursion beendet, wenn wir den letzten Wert erreichen) frei ist. Dies ist äquivalent zu Gleichen foreachin anderen Sprachen.

Vorführungen

for i in '2 3 5' '2 3 5 7' '0 2 3 5' '0 0 1 2 3 4'
do printf '%s => ' "$i"; ./127147.dc <<<"$i"; echo
done
2 3 5 => 15 10 6
2 3 5 7 => 105 70 42 30
0 2 3 5 => 30 0 0 0
0 0 1 2 3 4 => 0 0 0 0 0 0

Eine Demo mit großen und kleinen Eingaben:

./127147.dc <<<'.0000000000000000000542101086242752217003726400434970855712890625 1 18446744073709551616'
18446744073709551616 1.0000000000000000000000000000000000000000000000000000000000000000 .0000000000000000000542101086242752217003726400434970855712890625
Toby Speight
quelle
1

C ++, Score: 5990, O ([2NlogN] / 3)

Diese Implementierung verwendet eine Binärbaum-Nachschlagetabelle. Meine erste Implementierung war O (NlogN), aber eine Last-Minute-Optimierung, die das Produkt aller Array-Elemente abzüglich eines Paares nachschlägt, + 2 Multiplikationen sparten den Tag. Ich denke, das könnte noch ein bisschen weiter optimiert werden, vielleicht noch 16% ...

Ich habe einige Fehlerbehebungsspuren hinterlassen, nur weil es einfacher ist, sie zu löschen, als sie neu zu schreiben :)

[Bearbeiten] Die tatsächliche Komplexität wird bei O ([2NlogN] / 3) für 100 gemessen. Sie ist bei kleinen Mengen tatsächlich etwas schlechter als O (NlogN), tendiert jedoch zu O ([NlogN] / 2), wenn das Array wächst sehr großes O (0,57.NlogN) für eine Menge von 1 Million Elementen.

#include "stdafx.h"
#include <vector>
#include <iostream>
#include <random>
#include <cstdlib>

using DataType = long double;

using DataVector = std::vector<DataType>;

struct ProductTree
{
    std::vector<DataVector> tree_;
    size_t ops_{ 0 };

    ProductTree(const DataVector& v) : ProductTree(v.begin(), v.end()) {}
    ProductTree(DataVector::const_iterator first, DataVector::const_iterator last)
    {
        Build(first, last);
    }

    void Build(DataVector::const_iterator first, DataVector::const_iterator last)
    {
        tree_.emplace_back(DataVector(first, last));

        auto size = std::distance(first, last);
        for (auto n = size; n >= 2; n >>= 1)
        {
            first = tree_.back().begin();
            last = tree_.back().end();

            DataVector v;
            v.reserve(n);
            while (first != last) // steps in pairs
            {
                auto x = *(first++);
                if (first != last)
                {
                    ++ops_;
                    x *= *(first++); // could optimize this out,small gain
                }
                v.push_back(x);
            }
            tree_.emplace_back(v);
        }
    }

    // O(NlogN) implementation... 
    DataVector Prod()
    {
        DataVector result(tree_[0].size());
        for (size_t i = 0; i < tree_[0].size(); ++i)
        {
            auto depth = tree_.size() - 1;
            auto k = i >> depth;
            result[i] = ProductAtDepth(i, depth);
        }
        return result;
    }

    DataType ProductAtDepth(size_t index, size_t depth) 
    {
        if (depth == 0)
        {
            return ((index ^ 1) < tree_[depth].size())
                ? tree_[depth][index ^ 1]
                : 1;
        }
        auto k = (index >> depth) ^ 1;

        if ((k < tree_[depth].size()))
        {
            ++ops_;
            return tree_[depth][k] * ProductAtDepth(index, depth - 1);
        }
        return ProductAtDepth(index, depth - 1);
    }    

    // O([3NlogN]/2) implementation... 
    DataVector Prod2()
    {
        DataVector result(tree_[0].size());
        for (size_t i = 0; i < tree_[0].size(); ++i)    // steps in pairs
        {
            auto depth = tree_.size() - 1;
            auto k = i >> depth;
            auto x = ProductAtDepth2(i, depth);
            if (i + 1 < tree_[0].size())
            {
                ops_ += 2;
                result[i + 1] = tree_[0][i] * x;
                result[i] = tree_[0][i + 1] * x;
                ++i;
            }
            else
            {
                result[i] = x;
            }
        }
        return result;
    }

    DataType ProductAtDepth2(size_t index, size_t depth)
    {
        if (depth == 1)
        {
            index = (index >> 1) ^ 1;
            return (index < tree_[depth].size())
                ? tree_[depth][index]
                : 1;
        }
        auto k = (index >> depth) ^ 1;

        if ((k < tree_[depth].size()))
        {
            ++ops_;
            return tree_[depth][k] * ProductAtDepth2(index, depth - 1);
        }
        return ProductAtDepth2(index, depth - 1);
    }

};


int main()
{
    //srand(time());

    DataVector data;
    for (int i = 0; i < 1000; ++i)
    {
        auto x = rand() & 0x3;          // avoiding overflow and zero vaolues for testing
        data.push_back((x) ? x : 1);
    }

    //for (int i = 0; i < 6; ++i)
    //{
    //  data.push_back(i + 1);
    //}

    //std::cout << "data:[";
    //for (auto val : data)
    //{
    //  std::cout << val << ",";
    //}
    //std::cout << "]\n";

    ProductTree pt(data);
    DataVector result = pt.Prod2();

    //std::cout << "result:[";
    //for (auto val : result)
    //{
    //  std::cout << val << ",";
    //}
    //std::cout << "]\n";
    std::cout << "N = " << data.size() << " Operations :" << pt.ops_ << '\n';

    pt.ops_ = 0;
    result = pt.Prod();

    //std::cout << "result:[";
    //for (auto val : result)
    //{
    //  std::cout << val << ",";
    //}
    //std::cout << "]\n";

    std::cout << "N = " << data.size() << " Operations :" << pt.ops_ << '\n';

    return 0;
}

Der Vollständigkeit halber füge ich den Algorithmus von @ nore hinzu. Es ist wirklich schön und am schnellsten.

class ProductFlat
{
private:
    size_t ops_{ 0 };

    void InitTables(const DataVector& v, DataVector& left, DataVector& right)
    {
        if (v.size() < 2)
        {
            return;
        }

        left.resize(v.size() - 1);
        right.resize(v.size() - 1);

        auto l = left.begin();
        auto r = right.rbegin();
        auto ol = v.begin();
        auto or = v.rbegin();

        *l = *ol++;
        *r = *or++;
        if (ol == v.end())
        {
            return;
        }

        while (ol + 1 != v.end())
        {
            ops_ += 2;
            *l = *l++ * *ol++;
            *r = *r++ * *or++;
        }
    }

public:
    DataVector Prod(const DataVector& v)
    {
        if (v.size() < 2)
        {
            return v;
        }

        DataVector result, left, right;
        InitTables(v, left, right);

        auto l = left.begin();
        auto r = right.begin();
        result.push_back(*r++);
        while (r != right.end())
        {
            ++ops_;
            result.push_back(*l++ * *r++);
        }
        result.push_back(*l++);
        return result;
    }

    auto Ops() const
    {
        return ops_;
    }
};
Michaël Roy
quelle