Ich habe die Technik von Mason Bryant modifiziert, um etwas zu finden, das funktioniert. Die Probleme waren mehr Fehler in FindLowestIndex und ein größerer Fehler bei der Suche im Baum (es können mehrere Ergebnisse zurückgegeben werden).
Aber auch nach dieser Arbeit löst es das eigentliche Problem nicht. Die Zeit O(n log n) zum Einrichten ist einfach genug, aber wenn ich diese Technik verwende, kann ich nur eine Abfragezeit O((log n)^2) erhalten. Ich frage mich, ob Sie einen Link zum ursprünglichen Problem haben, falls dort weitere Erläuterungen vorhanden sind? Oder frage ich mich, ob das Problem wirklich lösbar ist. Oder vielleicht ist O((log n)^2) "ungefähr" O(log n), wie vom Problem verlangt, es ist sowieso weniger als O(n).
Die Technik besteht darin, unser Array in einem typischen Segmentsbaum zu speichern, aber zusammen mit den üblichen Segmentinformationen speichern wir auch alle Indizes der Elemente unter jedem Knoten in der Reihenfolge. Dieser zusätzliche Speicher dauert nur eine zusätzliche Zeit O(n log n), wenn Sie es ordentlich hinzufügen (n Elemente auf jedem der log n Ebenen gespeichert), daher beeinträchtigt dies unsere Einrichtungszeit nicht auf eine Weise, die zählt. Dann fragen wir den Baum ab, um die minimal notwendige Knotenmenge zu finden, die von unserem Bereich (a, b) enthalten ist. Diese Abfrage dauert ungefähr die gleiche Zeit wie eine typische Segmentbaumabfrage (O(log n)) und findet höchstens etwa 2*log n übereinstimmende Segmente. Während wir abfragen, finden wir den niedrigsten Index in jedem übereinstimmenden Segment, der unserer Bedingung c entspricht. Wir können einen Binärsuchvorgang verwenden, um diesen Index zu finden, da wir die Indizes in der Reihenfolge behalten haben, daher dauert dies im schlimmsten Fall O(log n) für jeden übereinstimmenden Knoten. Wenn wir alles angemessen zusammenzählen, beträgt die Gesamtzeit O((log n)^2).
Lassen Sie mich wissen, welche Schritte Sie gerne erklärt hätten.
C#-Code:
void Test() {
DateTime start;
TimeSpan at = new TimeSpan(), bt = new TimeSpan();
Random rg = new Random();
for(int i = 0; i < 20; i++) {
// Arrays von 5 bis 10000 Elementen lang erstellen, Zufallswerte zwischen 0 und 100
// Der Break-even-Zeitpunkt für Abfragen liegt bei etwa 10000 Elementen im Array für die verwendeten Werte und Abfragen
int[] array = (from n in Enumerable.Range(1, rg.Next(5, 10000)) select rg.Next(0, 100)).ToArray();
// Einrichtung sollte O(n log n) Zeit/Platz benötigen
ArraySearcher Searcher = new ArraySearcher(array);
// Jedes Array eine Anzahl von Malen gleich seiner Länge testen, mit Zufallswerten für a, b, c
for(int j = 0; j < array.Length; j++) {
int a = rg.Next(-1, 101), b = rg.Next(a, 102), c = rg.Next(0, array.Length);
start = DateTime.Now;
int expected = BruteSearch(array, a, b, c);
at += DateTime.Now - start;
// Die Suche sollte O(log n) Zeit dauern, ist aber in Wirklichkeit O((log n)^2) :(
start = DateTime.Now;
int got = Searcher.QuickSearch(a, b, c);
bt += DateTime.Now - start;
System.Diagnostics.Debug.Assert(got == expected);
}
}
MessageBox.Show(at.ToString() + ", " + bt.ToString());
}
int BruteSearch(int[] array, int a, int b, int c) {
for(int i = c; i < array.Length; i++)
if(a < array[i] && array[i] < b)
return i;
return -1;
}
class ArraySearcher {
SegmentNode Root;
List Nodes;
public ArraySearcher(int[] array) {
Nodes = array.Select((value, index) => new SegmentNode(value, value, new List { index }, null, null)).ToList();
// Das Sortieren wird uns O(n log n) kosten
Nodes.Sort();
// Das Erstellen eines normalen Segmentsbaums dauert O(n log n)
// Darüber hinaus speichert in diesem Baum jeder Knoten alle Indizes unterhalb in der richtigen Reihenfolge
// Insgesamt gibt es n dieser Indizes auf jeder Baumebene, die in der Reihenfolge gehalten werden, ohne zusätzliche Zeitkosten
// Es gibt log n Ebenen im Baum
// Also werden beim Erstellen unserer Listen zusätzlich O(n log n) Zeit und Platz verwendet, was im Vergleich zum einfachen Sortieren die Big O-Notation nicht beeinträchtigt
this.Root = SegmentNode.Merge(Nodes, 0, Nodes.Count - 1);
}
public int QuickSearch(int a, int b, int c) {
return this.Root.FindLowestIndex(a, b, c);
}
class SegmentNode : IComparable {
public int Min, Max;
public List Indices;
public SegmentNode Left, Right;
public SegmentNode(int Min, int Max, List Indices, SegmentNode Left, SegmentNode Right) {
this.Min = Min;
this.Max = Max;
this.Indices = Indices;
this.Left = Left;
this.Right = Right;
}
int IComparable.CompareTo(object other) {
return this.Min - ((SegmentNode)other).Min;
}
public static SegmentNode Merge(List Nodes, int Start, int End) {
int Count = End - Start;
if(Start == End)
return Nodes[Start];
if(End - Start == 1)
return SegmentNode.Merge(Nodes[Start], Nodes[End]);
return SegmentNode.Merge(SegmentNode.Merge(Nodes, Start, Start + Count/2), SegmentNode.Merge(Nodes, Start + Count/2 + 1, End));
}
public static SegmentNode Merge(SegmentNode Left, SegmentNode Right) {
int LeftCounter = 0, RightCounter = 0;
List NewIndices = new List();
while(LeftCounter < Left.Indices.Count || RightCounter < Right.Indices.Count) {
if(LeftCounter < Left.Indices.Count && (RightCounter == Right.Indices.Count || Left.Indices[LeftCounter] < Right.Indices[RightCounter]))
NewIndices.Add(Left.Indices[LeftCounter++]);
else
NewIndices.Add(Right.Indices[RightCounter++]);
}
return new SegmentNode(Left.Min, Right.Max, NewIndices, Left, Right);
}
public int FindLowestIndex(int SeekMin, int SeekMax, int c) {
// Dies wird höchstens O(log n) übereinstimmende Segmente finden, immer weniger als 2 auf jeder Ebene des Baums
// Jedes übereinstimmende Segment wird im schlimmsten Fall in O(log n) durchsucht
// Insgesamt ergibt sich dies tatsächlich zu O((log n)^2), wenn Sie es richtig machen
if(SeekMin < this.Min && SeekMax > this.Max)
return FindLowestIndex(this.Indices, c);
if(SeekMax <= this.Min || SeekMin >= this.Max)
return -1;
int LeftMatch = this.Left.FindLowestIndex(SeekMin, SeekMax, c);
int RightMatch = this.Right.FindLowestIndex(SeekMin, SeekMax, c);
if(LeftMatch == -1)
return RightMatch;
if(RightMatch == -1)
return LeftMatch;
return LeftMatch < RightMatch ? LeftMatch : RightMatch;
}
int FindLowestIndex(List Indices, int c) {
int left = 0, right = Indices.Count - 1, mid = left + (right - left) / 2;
while(left <= right) {
if(Indices[mid] == c)
return c;
if(Indices[mid] > c)
right = mid - 1;
else
left = mid + 1;
mid = left + (right - left) / 2;
}
if(mid >= Indices.Count)
return -1;
// keine genaue Übereinstimmung, also den nächsthöheren Wert zurückgeben
return Indices[mid];
}
}
}