Forum: PC-Programmierung Algorithmus mit O(log n)


von Programmierer (Gast)


Lesenswert?

Hallo,

ich sitze gerade an einer Algorithmen Übung. Man hat 2 Listen, die beide 
sortiert sind, wobei jedes Element nur einmal vorkommt. Nun soll das 
k.-größte Element bestimmt werden und zwar in O(log n), mit n der 
größten Liste.
Erst einmal etwas grundlegendes. Gibt es einen Trick, aus der 
geforderten Komplexität, auf den Algorithmus zu schließen?

Also z.B. bei O(n) ist klar, dass eine Liste Linear durchsucht wird. Bei 
O(n*log(n)) denke ich sofort an "teile und herrsche"...Stichwort 
Quicksort.

Aber zu O(log(n)) fällt mir im Moment nichts ein.

Wie geht ihr vor, wenn ihr so eine Aufgabe bekommt?

Viele Grüße

von (prx) A. K. (prx)


Lesenswert?

Programmierer schrieb:

> Erst einmal etwas grundlegendes. Gibt es einen Trick, aus der
> geforderten Komplexität, auf den Algorithmus zu schließen?

Manchmal geht das. Man geht die Tabelle der in Frage kommenden 
Algorithmen durch und sucht nach einem mit O(log(n)). ;-)

> Also z.B. bei O(n) ist klar, dass eine Liste Linear durchsucht wird.

Denk dran, dass du 2 Listen hast, nicht eine.

Edit: Insbesondere solltest du dran denken, dass die Listen bereits 
sortiert sind.

von Klaus W. (mfgkw)


Lesenswert?

bei log n kann man schon mal an binäre Suche und ähnliches denken.

von Programmierer (Gast)


Lesenswert?

Ja eine binäre Suche habe ich implementiert. Es ist nur die Frage, wie 
ich diese jetzt nutzen kann, um das gesuchte Element in beiden Listen zu 
finden.

von Karl H. (kbuchegg)


Lesenswert?

Ich hab die Aufgabenstellung immer noch nicht verstanden.

Im Grunde geht es doch darum, von hinten beginnend in jeder Liste k 
Elemente abzuzählen und dann das k-te zu liefern. Denn da die Liste 
sortiert ist, muss das dann das k-größte sein.

Annahme: ich weiss, dass die 'Liste' n Elemente groß ist. Und ich 
organisiere die Liste als balanzierten binärer Baum. Dann sind die 
Knoten 0 bis n/2 im linken Teilbaum und die Knoten n/2+1 bis n im 
rechten Teilbaum. Ich muss mich also nur fragen ob mein k größer oder 
kleiner als n/2 ist. Wenn größer, dann kann das gesuchte Element nur im 
rechten Teilbaum sein. Wenn es kleiner/gleich ist, dann im linken. Und 
dort kann ich dasselbe Spiel wieder machen: Ich nehm die Hälfte vom 
Intervall und frage mich in welchen der beiden Teile mein gesuchtes 
Element liegen wird.

Im Grunde ist das binäres suchen, nur dass ich nicht nach einem 
Schlüssel suche, sondern nach der Platznummer.

Wenn man will, kann man das auch so auffassen, dass ich meinen Knoten 
einen zusätzlichen Member verpasse, der einfach die Durchnumerierung der 
Knoten ist und nach dem ich suche.

von (prx) A. K. (prx)


Lesenswert?

Karl Heinz Buchegger schrieb:

> Ich hab die Aufgabenstellung immer noch nicht verstanden.

Ich interpretiere es so, dass aus beiden Listen zusammen ein Ergebnis 
ermittelt werden soll. Nicht zwei.

> Im Grunde geht es doch darum, von hinten beginnend in jeder Liste k
> Elemente abzuzählen und dann das k-te zu liefern. Denn da die Liste
> sortiert ist, muss das dann das k-größte sein.

Das wäre dann aber O(k) und nicht als O(f(n)) ausdrückbar.

Über den Begriff "Liste" bin ich aber auch gestolpert. Müsste genauer 
spezifiziert sein, was darunter zu verstehen ist. Will hoffen, dass der 
Satz oben nicht die ursprüngliche Formulierung ist.

von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:
> Karl Heinz Buchegger schrieb:
>
>> Ich hab die Aufgabenstellung immer noch nicht verstanden.
>
> Ich interpretiere es so, dass aus beiden Listen zusammen ein Ergebnis
> ermittelt werden soll. Nicht zwei.

Dann hab ich die Aufgabenstellung allerdings wirklich nicht kapiert. 
Vielleicht wäre ein Beispiel angebracht?

>> Im Grunde geht es doch darum, von hinten beginnend in jeder Liste k
>> Elemente abzuzählen und dann das k-te zu liefern. Denn da die Liste
>> sortiert ist, muss das dann das k-größte sein.
>
> Das wäre dann aber O(k) und nicht als O(f(n)) ausdrückbar.

Nicht ganz. Denn wenn es sich tatsächlich im eine vorwärts verkettete 
Liste handelt, kannst du die Liste nicht von hinten aufarbeiten, sondern 
musst von vorne durchgehen. Und damit sind wir dann wieder beim linearen 
"Durchsuchen" von vorne: O(n).

von (prx) A. K. (prx)


Lesenswert?

Karl Heinz Buchegger schrieb:

> Dann hab ich die Aufgabenstellung allerdings wirklich nicht kapiert.
> Vielleicht wäre ein Beispiel angebracht?

Zunächst würde schon der Originaltext weiterhelfen. Denn vor jeder 
Lösung steht die Notwendigkeit, überhaupt erst einmal die Frage zu 
verstehen. Unzählige Aufgaben werden schon in diesem Stadium versemmelt.

von Programmierer (Gast)


Lesenswert?

Hier mal ein Beispiel für die Listen:

[1,4,7,9,13]

[2,3,6,8,24,44]

Kannst bitte mal an diesem Beispiel exemplarisch die Geschichte mit dem 
Baum erläutern?

von (prx) A. K. (prx)


Lesenswert?

Baum ist witzlos, denn der müsste bereits fertig vorliegen, andernfalls 
liegt man weit oberhalb von O(n) weil die Reorganisation mitgerechnet 
werden muss.

von Karl H. (kbuchegg)


Lesenswert?

Programmierer schrieb:
> Hier mal ein Beispiel für die Listen:
>
> [1,4,7,9,13]
>
> [2,3,6,8,24,44]
>
> Kannst bitte mal an diesem Beispiel exemplarisch die Geschichte mit dem
> Baum erläutern?

Wie ist jetzt die Sache mit dem k-größten Element zu verstehen?

Suchst du das k-größte Element, wenn man beide Listen zusammennimmt, 
oder das k-größte in der einen Liste und das k-größte in der anderen 
Liste? Oder wie ist das zu verstehen.

Konkret: Wenn das 3.-größte Element gesucht wird, was ist dann die 
korrekte Antwort in diesem Beispiel?
7, 8, 13?

von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:
> Karl Heinz Buchegger schrieb:
>
>> Dann hab ich die Aufgabenstellung allerdings wirklich nicht kapiert.
>> Vielleicht wäre ein Beispiel angebracht?
>
> Zunächst würde schon der Originaltext weiterhelfen. Denn vor jeder
> Lösung steht die Notwendigkeit, überhaupt erst einmal die Frage zu
> verstehen. Unzählige Aufgaben werden schon in diesem Stadium versemmelt.

Da hast du recht.
Aber entweder ich sitze jetzt wirklich auf der Leitung oder ich sehs 
einfach nicht. Für mich ist die Aufgabenstellung alles andere als klar.

von (prx) A. K. (prx)


Lesenswert?

Karl Heinz Buchegger schrieb:

> Für mich ist die Aufgabenstellung alles andere als klar.

Yep. Deshalb ist das ohne O-Ton bloss Kaffeesatzlesen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:
> Über den Begriff "Liste" bin ich aber auch gestolpert. Müsste genauer
> spezifiziert sein, was darunter zu verstehen ist. Will hoffen, dass der
> Satz oben nicht die ursprüngliche Formulierung ist.

Für eine einfach verkette Liste würd mir einfallen – da eine Liste etwas 
"Lineares" ist:

Eine Datenstruktur, bei der

• Es in Zeit O(1) möglich ist, den Nachfolger eines Elements zu finden
• Es in Zeit O(1) möglich ist, den Nachfolger aus der Struktur
  zu entfernen
• Es in Zeit O(1) möglich ist, einen Nachfolger hinter ein Element
  einzufügen
• Der Verwaltings-Overhead pro Element O(1) ist

Einfach anzunehemn, die Liste sei als balancierter Baum implementiert 
oder als Hash-Tabelle etc., find ich geschummelt... Kann aber mitunter 
dazu dienlich sein, den Aufgabensteller vorzuführen ;-)

von MaWin (Gast)


Lesenswert?

> Konkret: Wenn das 3.-größte Element gesucht wird, was ist dann die
> korrekte Antwort in diesem Beispiel? 7, 8, 13?

Na 13.

Du solltest noch mal im Duden nachschlagen was drittgrösst heisst.

Und JA, natürlich ist das eine AUFGABE.

Weil es eben nicht spotteinfach ist, den passenden Algorithmus zu 
finden, sondern etwas von der Intelligenz des Befragten abhängt.

Natürlich müsste man in dem Beispiel auch das 4-grösste finden.

von Programmierer (Gast)


Lesenswert?

Also eine Aufgabenstellung gibt es nicht. Es wurde uns nur gesagt, wer 
sich dafür interessiert kann es mal versuchen.

Ich habe mir aber Notizen gemacht.

Demnach ist das Element gesucht, was an k.-Stelle stehen würde, wenn man 
beide Liste aneinander hängt und sortieren würde. Ich denke das sollte 
jetzt klar sein. Entschuldigung für die eineindeutige Formulierung 
meinerseits.

von sic (Gast)


Lesenswert?

Hallo,

Die Listen sind l und m -elementig.
Die Positon der Einordnung des k.ten Elementes ist in der Gesamtliste 
ist in der einen Liste p und in der anderen q.

Programmierer schrieb:
> [1,4,7,9,13]
>
> [2,3,6,8,24,44]

Die 13 stände in Liste 1 mit 5 Elementen an Position 5 und würde in 
Liste 2 mit 6 elementen an Position 5 eingefügt werden, welche dann 7 
Elemente hätte.
Man muss sich scheinbar darauf einigen, dass das Element einmal 
vorhanden, das andere Mal aber eingefügt wird, auch wenn die Listen 
doppelte Elemente enthalten sollten (+1).
k wäre dann Position_in_Liste1 (=5-5)+ Position_in_Liste2 (6-5)+1
k wäre also 0+1+1 also das dritte, denn die höchste Position ergibt 0
k= 0 ergibt 44, als Position in Liste 1 = 6, da es eingefügt wird und 
Position in Liste 2 = 6, wo es vorhanden ist.
k= 5-6 +6-6 +1 = 0.

Jetzt fehlt nur ein schöner Algorithmus, wie man jetzt binär sucht.
bei k = 2 sucht man
In Liste 1 von li= 5-k= 3 bis re = 5 und in Liste 2 von li= 6-k= 4 bis 
re = 6
Nehme ich die Mitte aus Liste 1 [(li+re) / 2 = 4] = 9 findet sich die 9 
in Liste 2, wenn sie eingefügt würde an an Position 5.( binäre Suche im 
Bereich 4..6 )
k =5-4 + 6-5 +1  = 3, also zu groß.
Also setzte ich links li in Liste 1 auf mitte+1 = 4+1= 5
Der Wert aus Liste 1 ist dann 13 und dieser würde in Liste 2 immer noch 
an Position 5 eingetragen.
k= 2 wäre gefunden.

Hätte man es noch nicht gefunden wäre Liste 1 am Ende, dann kann man 
sofort aus Liste 2 das (m-k).te Element nehmen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Man merget die beiden Listen zu einer einzigen sortierten Liste zusammen
und nimmt davon das k.-letzte Element. Natürlich wird nur so viel wie
nötig gemerget, also insgesamt k Elemente. Man braucht aus den gemerge-
ten Elemente auch nicht eine neue Liste bilden, da ja letztendlich nur
eins davon (das k-te) als Ergebnis benötigt wird.

Damit ist die Komplexität von n unabhängig, nämlich O(k). Ist k eine
vorgegebene Konstante, ist die Komplexität sogar O(1).

Dabei bin ich davon ausgegangen, dass der Zugriff auf alle Listenele-
mente in O(1) erfolgt. Sind die Listen nur einfach verkettet, braucht
allein der Zugriff auf das letzte Element schon O(n), weswegen dann
O(log n) nicht realisierbar ist. Bei einfach verkettet Listen wäre es
günstiger, wenn entweder die Listen absteigend sortiert sind oder als
Ergebnis nicht das k.-größte, sonders das k.-kleinste Element gesucht
ist.

Hier ist eine Implementation des im ersten Absatz Geschriebenen in
Haskell (das in C zu programmieren, war ich gerade zu faul ;-)). Da die
Listen in Haskell einfach verkettet sind, wird die k.-kleinste Zahl der
beiden aufsteigend sortierten Listen gesucht. Dass die Komplexität
tatsächlich von der Listenlänge unabhängig ist, sieht man daran, dass
auch unendlich lange Listen in endlicher Zeit bearbeitet werden (letzte
beide Programmzeilen).
1
mergeLists [] ys = ys
2
mergeLists xs [] = xs
3
mergeLists (x:xs) (y:ys)
4
  | x < y      = x : mergeLists xs (y:ys)
5
  | otherwise  = y : mergeLists (x:xs) ys
6
7
findKLeast k xs ys = mergeLists xs ys !! (k-1)
8
9
main = do
10
  print $ findKLeast 3 [1,4,7,9,13] [2,3,6,8,24,44]
11
  print $ findKLeast 7 [1,5..] [2,4..]
12
  print $ findKLeast 1000000 [1,5..] [2,4..]

von (prx) A. K. (prx)


Lesenswert?

Wärs möglich, das Haskell zumindest rudimentär zu kommentieren? Wenn ich 
hier mit APL kontern wollte würde mir das vermutlich auch fast niemand 
widerlegen und mangels jedweder Schleife oder Rekursion wäre O(1) 
garantiert. ;-)

Ich habe freilich den vagen Verdacht, dass dein Beispiel weniger gut 
funktioniert, wenn die Listen nicht so schön interleaved sind, sondern 
das erste Element von Liste A grösser ist als das letzte Element von 
Liste B. Oder sowas in der Art. Was sich zwar mit unendlichen Listen 
schlecht machen lässt, wohl aber mit endlichen.

von sic (Gast)


Lesenswert?

Hallo,

k ist nur ein Bruchteil von n und damit O(k) = O(k/n * n ) = k/n* O(n) = 
O(n)
Auch wenn man bei k > n/2 eben nach (n-k) sucht, bleibt es sich gleich.

von Yalu X. (yalu) (Moderator)


Lesenswert?

A. K. schrieb:
> Wärs möglich, das Haskell zumindest rudimentär zu kommentieren?

Ich habe mal irgendwo gelesen, Haskell-Code sei selbsterklärend ;-)

Aber eigentlich ist das Verfahren doch naheliegend: Man betrachtet nicht
die kompletten Listen, sondern nur deren paar größte (bzw. kleinste)
Elemente, was ja kein Problem ist, weil die Listen schon sortiert sind.
Insgesamt werden nur k+1 Elemente in die Hand genommen, egal wie lang
die Listen sind.

> Ich habe freilich den vagen Verdacht, dass dein Beispiel weniger gut
> funktioniert, wenn die Listen nicht so schön interleaved sind, sondern
> das erste Element von Liste A grösser ist als das letzte Element von
> Liste B.

Auch dann werden nur k+1 Elemente angeschaut, nämlich k von der einen
Liste und 1 von der anderen.

von (prx) A. K. (prx)


Lesenswert?

Bringt aber nicht weiter weil O(k) komplexer sein kann als O(log n).

von Yalu X. (yalu) (Moderator)


Lesenswert?

A. K. schrieb:
> Bringt aber nicht weiter weil O(k) komplexer sein kann als O(log n).

Das macht aber nichts, denn es wurde nach einem Algorithmus gefragt,
dessen Rechenzeit bei wachsendem n nicht stärker als log n steigt.
Darüber, wie die Rechenzeit von k abhängt, gibt es keine expliziten
Anforderungen. Man könnte zwar die fehlende Anforderung so interpre-
tieren, dass die Rechenzeit unabhängig von k sein soll, aber das ist
dann wirklich Interpretationssache.

von sic (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

und wenn k immer der Median ist, also bei n/2 steht??
Mein Verfahren macht für jeweilige Listenlänge l = n/2 und der Suche 
nach k.tem Element für k = l == Median
1
l          ~Zugriffe
2
10000       108
3
100000      163
4
1000000     230  (100 Durchläufe)
5
10000000    307  (10 Durchläufe)
Eine log(n) oder log(k)-Abhängigkeit sehe ich nicht auf Anhieb, ist aber 
auch spät.

von MaWin (Gast)


Lesenswert?

> Darüber, wie die Rechenzeit von k abhängt,
> gibt es keine expliziten Anforderungen.

Doch.

k ist eine zufällig gleichverteilte Zahl aus dem Bereich 1...n.

Damit ist alles was von k abhängt O(n).

von Murkser (Gast)


Lesenswert?

Bei solchen Aufgaben wird k immer als Konstante gesehen. Klar, daß k <= 
n gilt, aber wenn nach dem drittgrößten Element gesucht in O(log n) 
gesucht wird, soll damit ausgedrückt werden, daß hier nur die 
Abhängigkeit von n interessiert bei gegebenem, festem k.

Damit paßt schon der Vorschlag, die k größten Elemente aus der kleineren 
Liste in die größere Liste jeweils an die korrekte Stelle einzufügen. Da 
die Listen sortiert sind, kann dies in O(k*log n) erfolgen. Dann das 
k-größte Element aus dieser Liste zurückliefern. Dies kostet O(k). Da k 
nun als konstant anzusehen ist, ist das Gesamtergebnis O(k*log n) + O(k) 
= O(1 * log n) + O(1) = O(log n).

Wenn die Aufgabenstellung hieße, jeweils das z.B. das k=n-2 größte 
Element zu finden, wäre k nicht mehr konstant.

von (prx) A. K. (prx)


Lesenswert?

MaWin schrieb:

> k ist eine zufällig gleichverteilte Zahl aus dem Bereich 1...n.

Eigentlich 1..2n. Wobei es in dieser Frage nicht auf die statistische 
Verteilung ankommt (hier geht es nicht um Statistik), sondern einfach 
nur den Wertebereich.

> Damit ist alles was von k abhängt O(n).

Yep, da O(2n)=O(n).

von (prx) A. K. (prx)


Lesenswert?

Murkser schrieb:

> Klar, daß k <= n gilt

Wieso? Bei zwei gleich langen Listen gibt es 2n Werte und k kann auch 
den Wert 2n haben.

> aber wenn nach dem drittgrößten Element gesucht in O(log n)
> gesucht wird, soll damit ausgedrückt werden, daß hier nur die
> Abhängigkeit von n interessiert bei gegebenem, festem k.

Wenn es von der Anzahl der Gesamtelemente abhängen sollte, dann wäre das 
also von maximal 2n, was jedoch immer noch in die gleiche Komplexität 
wie O(log n) darstellt. Da ein variables k also nicht in der Komplexität 
auftaucht lässt sich aus der Aufgabenstellung nicht schliessen, dass nur 
die Abhängigkeit von n interessiert.

von (prx) A. K. (prx)


Lesenswert?

A. K. schrieb:

> auftaucht lässt sich aus der Aufgabenstellung nicht schliessen, dass nur
> die Abhängigkeit von n interessiert.

Korrektur: ... dass k konstant ist..

von sic (Gast)


Lesenswert?

Guten morgen,

k = konstant für beleibiges n>k ist doch zu nichts nutze.
Das wäre ja O(1/n) für n->unendlich

Alles schon da gewesen, shit.

http://discuss.joelonsoftware.com/default.asp?interview.11.641601.12
>kth element in two sorted arrays
>
> Given two sorted arrays, find the kth element from both of them using only 
>constant space. Time complexity O(logn^2)


http://www.leetcode.com/2011/01/find-k-th-smallest-element-in-union-of.html>;
> O(lg m + lg n)

Wenn n=m= Anzahl der Elemente in einer Liste dann ist, ergibt sich
O(lg m + lg n)= O(lg (m*n))= O(lg( n²))= O(2*lg(n)) = O(log(n))
Das ist ja passend.

von Murkser (Gast)


Lesenswert?

Versetzt Euch doch mal in die Rolle des Algorithmen und 
Komplexitaetstheorie-Profs, der so eine Aufgabe stellt. Da kommt es 
nicht darauf an, ob das zu was nutze ist. Es ist eine Uebungsaufgabe!

Und wenn er sagt, das Problem soll in O(log n) geloest werden, heisst 
das wie bei allen aehnlichen Beispielen, die in der Vorlesung so 
durchgekaut werden, k darf als konstant angesehen werden. Macht die 
Sache nicht komplizierter als noetig.

Wenn es um ein "echtes" Praxisproblem geht, haette der TE (und 
derjenige, der die Aufgabe gestellt hat), nicht vorgegeben, dass es in 
O(log n) zu loesen sei, sondern er haette gesagt, "mein XYZ Programm 
dauert jetzt 10min. Das ist mir zu lang und ich will, dass das in 10s 
laeuft".

von sic (Gast)


Lesenswert?

Hallo,

es ist doch schon längstens für beliebiges 0<k<n entsprechend den 
Vorgaben gelöst, was gibt es noch zu diskutieren?
Mit k = konstant macht eine O-Notation gar keinen Sinn, um einen 
Algorithmus zu bewerten, indem man einfach gegewn unendlich laufen 
lässt.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.