Quelqu'un peut-il me donner des idées pour le problème suivant? P>
donné un tableau ar [] code> de longueur
n code> et quelques requêtes, chaque requête est de la forme
a, b, C code> em> strud>, recherchez le numéro avec le plus petit index
i code> tel que l'index réside dans la plage
[C, N] code> et telle que
a
(n-1) code> au total,
c code> va de
1 code> à
n - 1 code>. La complexité attendue de chaque requête doit être sur
o (journal n) code> em> strong> et un précompement sur la complexité au plus < code> O (n journal n) code> em> strong> convient. Intuitivement, l'arbre de segment est venu dans mon esprit, mais je ne pouvais pas penser à un moyen de la construire, ni quoi rester dans chaque noeud. P>
7 Réponses :
Avez-vous parcouru Ceci? Je suppose que cela vous aidera Comprenez la structure et pensez à un moyen de construire cela peut être ensemble de comparaisons. P>
Je sais ce qu'est un arbre de segment. Et j'ai déjà mis en œuvre des choses simples en l'utilisant. Pouvez-vous être plus précis s'il vous plaît? Pouvez-vous me dire ce que je devrais stocker dans chaque nœud de l'arbre de segment?
Peut-être que je manque quelque chose. Cela ne répond pas à vos exigences?
for (int i = c; i < n; i++) { if (a < ar[i] && ar[i] < b) return i; }
Ce serait O (n) cependant, o (log n) semble impliquer une recherche binaire, donc je pense que quelque chose manque de la question.
Oui, c'est ce que j'ai manqué. Je viens de sauter juste au-dessus de la partie du journal :)
@Chrischilvers o (journal n) peut également impliquer une sorte d'arborescence de segment. Il est i> b> quelque chose que je n'ai pas mentionné dans la question, je vais mettre à jour immédiatement.
@ Abody97, descendant d'un arbre binaire peut être considéré comme une recherche binaire.
Construisez une gamme d'index et triez-les. C'est-à-dire, étant donné le tableau [20, 0, 10, 15, 5] code>, vous créeriez un tableau initial
[0, 1, 2, 3, 4] code>. Maintenant, vous triez la gamme d'index pour refléter les éléments en cours de tri. L'index trié serait
[1, 4, 2, 3, 0] code>. Vous vous retrouvez avec deux tableaux:
original = [20, 0, 10, 15, 5]
tag = [1, 4, 2, 3, 0]
Donc, fondamentalement, vous comprimez les chiffres. Étant donné que chaque index reflète l'ordre de l'entier d'origine dans la matrice de tri, le segment tag [c] ... tag [n] code> n'est pas trié, alors comment allez-vous effectuer une recherche binaire sur ce? Peut-être que j'ai eu quelque chose de mal, s'il vous plaît expliquer si possible.
@ Abody97: Hmmm, tu as raison. Pourquoi cela a-t-il travaillé dans ma tête? Permettez-moi de me réfléchir.
La première tentative que j'ai faite construire un arbre des gammes.
Vous trouverez d'abord la racine d'un sous-arbre qui contenait toutes les solutions à la requête. C'est assez facile. Toutefois: ce sous-arbre contient des valeurs en dehors du jeu de solutions. P>
afin de trouver le nombre le plus bas, vous passeriez l'arborescence deux fois (de la racine vers le bas du côté gauche de l'arbre et de la racine de la racine de la racine. sur le côté droit). À chaque point de ces travers, vous vérifiez si vous avez trouvé une solution inférieure à votre solution précédente. P>
Une fois terminé, la solution la plus basse dans cet ensemble serait le plus bas. P >
Étant donné que la vérification des solutions de chaque nœud nécessitait une recherche binaire dans la liste des index dans ce sous-arbre, vous finiriez de courir à O (ln (n) ^ 2) qui est trop lent. P>
L'amélioration de cela vient du fait que cela serait facile si nous recherchions toujours le premier élément de la matrice. Ensuite, vous n'avez pas besoin de faire une recherche binaire, vous obtenez simplement le premier élément. P>
Pour arriver à cet endroit, vous finissez par construire N arbres. P>
static void testBoth(final int[] array, final ArraySearcher searcher, final int a, final int b, final int c) { System.out.println("ArraySearcherTest.testBoth(array, mSearcher, " + a + ", " + b + ", " + c + ");"); int expected = ArraySearcher.bruteForceSearch(array, a, b, c); Integer calcObj = searcher.search(a, b, c); int calcInt = -1; if (null != calcObj) { calcInt = calcObj.intValue(); } assertEquals(expected, calcInt); } @Test public void randomizedProblemTester() { for (int i = 0; i < 100; i++) { // build arrays from 5 to 20 elements long int[] array = new int[TestUtils.randomInt(5, 20)]; System.out.print("int[] array = {"); for (int j = 0; j < array.length; j++) { // with values from 0 to 100 array[j] = TestUtils.randomInt(0, 100); System.out.print(array[j] + ", "); } System.out.println("};"); ArraySearcher mSearcher = new ArraySearcher(array); for (int j = 0; j < array.length; j++) { int a = TestUtils.randomInt(0, 100); int b = TestUtils.randomInt(a, 100); int c = TestUtils.randomInt(0, 20); ArraySearcherTest.testBoth(array, mSearcher, a, b, c); } } }
Quelque chose ne va pas avec Findlowestindex comme il est écrit. Il ne renvoie que la valeur transversale ou null. Semble facilement réparable, change simplement ce retour final null à quelque chose de mieux je pense. Fonctionne sur vos exemples car ils renvoient tous le troisième paramètre transmis avec lequel vous avez commencé. En pensant encore aux considérations d'efficacité car je ne suis pas très familier avec les arbres de segment, mais semble prometteur de toute façon.
L'efficacité et la technique me ressemblent. Joli! Besoin d'un bit de test unitaire pour prouver que ce code est juste cependant, je ne l'ai pas vérifié à fond. Mieux vaut travailler sur l'explication que je dirais, c'est plus important que le code de toute façon.
Fist de tous, merci pour la réponse! J'ai traversé votre code, je ne pouvais pas obtenir chaque partie i> b>, j'apprécierais vraiment l'explication que j'attendais avec impatience. Merci encore! :)
Ech ... Vous avez raison (dans votre commentaire sur la modification). Cela ne le résout pas lorsque la solution couvre plusieurs arbres. J'espère que vous pourrez résoudre cela, je suis très intéressé de voir s'il y a une solution.
La nouvelle technique semble telle qu'elle devrait fonctionner aussi bien que O (log n) des requêtes, mais je ne suis pas convaincu que la configuration est O (n log n). Bien sûr, le tri est O (n log n), mais vous construisez ensuite N arbres, et je pense que la construction de chaque arbre prend O (n log n), donc je pense que votre temps de configuration fonctionne à O (n ^ 2 log n ).
@Thomas ahle HRM, oui vous êtes correct. Je ne suis pas sûr qu'il y ait une solution propre à ce problème. On dirait que Ye Olde Trade désactivé entre la mémoire, le temps de configuration et le temps d'exécution.
@ Mason: Consultez ma réponse, il construit les arbres de presque la voie normale, mais utilise un truc de style de programmation fonctionnel pour sauver les arbres intermédiaires sans perdre de mémoire.
Si je lis cela correctement, il y a des requêtes N-1 avec seulement C Modification,
Dans ce cas, pourquoi ne résolvez pas les requêtes en arrière?
Prenez d'abord la dernière requête car il implique le dernier élément de vérification de la matrice si l'élément tombe Between a et b si cela stocke le résultat dans un tableau ANS [N-1] = N-1 d'autre permet de mettre des ans [n-1] = -1, pour toute requête suivante j à partir de N-2 à 0 Tout cela peut être effectué dans O (n). P> P>
J'ai modifié la technique de Mason Bryant sur quelque chose qui fonctionne. Les problèmes étaient plus des bugs dans Findlowestindex et un bogue plus important à la recherche de l'arborescence (il peut renvoyer plusieurs résultats).
Malgré cela, cela ne résout pas le problème. La technique consiste à stocker notre réseau dans un arbre de segment typique, mais avec les informations de segment habituelles, nous stockons également, dans l'ordre, tous les index des éléments sous chaque nœud. Ce stockage supplémentaire ne prend qu'un laissez-moi savoir quelle étape vous voulez savoir si vous souhaitez clarifier. P > C # Code: P> O (N Journal N) Code> La configuration de l'heure est suffisamment facile, mais en utilisant cette technique, je ne peux obtenir que
O ((log n) ^ 2) code> temps de requête. Je me demande si vous avez un lien avec le problème initial au cas où il y a des éclaircissements plus nombreux? Ou je me demande si le problème est vraiment satisfait. Ou peut-être
o ((log n) ^ 2) code> est "à propos de"
o (journal n) code> comme demande de problème, c'est inférieur à
o (n) Quoi qu'il en soit. p>
O (n journal n) code> heure / espace Si vous l'ajoutez correctement (n éléments stockés à chacun des niveaux de log n), donc cela ne fait pas mal de notre temps de configuration. de quelque manière que ce soit qui compte. Ensuite, nous interrogeons l'arborescence pour trouver l'ensemble minimum de nœuds contenus par notre gamme de (A, B). Cette requête prend à peu près au même moment qu'une requête d'arborescence de segment typique (
O (log n) code>) et trouve au maximum environ 2 * log n segments correspondants. Comme nous interrogeons, nous trouvons l'indice le plus bas de chaque segment correspondant qui correspond à notre contrainte c. Nous pouvons utiliser une recherche binaire pour trouver cet index depuis que nous avons conservé les indices dans l'ordre, il faut donc
O (journal n) code> le pire des cas pour chaque nœud correspondant. Lorsque nous l'ajoutons de manière appropriée, le temps total est
o ((log n) ^ 2) code>. P>
void Test() {
DateTime start;
TimeSpan at = new TimeSpan(), bt = new TimeSpan();
Random rg = new Random();
for(int i = 0; i < 20; i++) {
// build arrays from 5 to 10000 elements long, random values between 0 and 100
// Break even time for queries is around 10000 elements in array for the values and queries used
int[] array = (from n in Enumerable.Range(1, rg.Next(5, 10000)) select rg.Next(0, 100)).ToArray<int>();
// Setup should be O(n log n) time/space
ArraySearcher Searcher = new ArraySearcher(array);
// Test each array a number of times equal to its length, with random values for 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;
// Search should be O(log n) time, but in reality is 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<SegmentNode> Nodes;
public ArraySearcher(int[] array) {
Nodes = array.Select((value, index) => new SegmentNode(value, value, new List<int> { index }, null, null)).ToList<SegmentNode>();
// Sorting will take us O(n log n)
Nodes.Sort();
// Creating a normal segment tree takes O(n log n)
// In addition, in this tree each node stores all the indices below it in order
// There are a total of n of these indices at each tree level, kept in order as we go at no extra time cost
// There are log n levels to the tree
// So O(n log n) extra time and space is spent making our lists, which doesn't hurt our big O notation compared to just sorting
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<int> Indices;
public SegmentNode Left, Right;
public SegmentNode(int Min, int Max, List<int> 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<SegmentNode> 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<int> NewIndices = new List<int>();
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) {
// This will find at most O(log n) matching segments, always less than 2 from each level of the tree
// Each matching segment is binary searched in at worst O(log n) time
// Total does indeed add up to O((log n)^2) if you do it right
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<int> 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;
// no exact match, so return the next highest.
return Indices[mid];
}
}
}
Je pense que je l'obtiens. Et je pense que o (log ^ 2 n) code> la complexité est bien (espérons-le). Mais il y a quelque chose que je ne peux pas obtenir, pourquoi stockez-vous une liste de tous les index? N'est-il pas suffisant de stocker le début et la fin de chaque segment? (Désolé si je sonne bête: D).
Le début et la fin de chaque segment vous permettent de savoir quand nous avons des valeurs qui correspondent à notre contrainte a
i> = c code>, et nous devons le faire dans
O (journal n) code> heure pour obtenir
O (log ^ 2 N) code> TIME TIME. Si nous cherchions l'arbre entier pour les éléments satisfaisant que cela prendrait
O (n) code> temps, trop lent. Ainsi, nous avons à la place chaque nœud conserve une liste ordonnée d'indices qu'il contient, afin que nous puissions de rechercher binaires de la liste dans
O (log n) code> heure. J'espère que cela aide. Et j'espère que ça va bien!
OK, je pensais que je devrais implémenter le Le code fonctionne par bâtiment Les arbres réels sont assez similaires aux intervalles. Les nœuds stockent la valeur minimale et maximale qu'elles s'étendent et le plus petit indice qu'ils contiennent. Ma mise en œuvre n'est toutefois pas auto-équilibrée, mais cela peut être ajouté en utilisant vos heuristiques préférées. P> O (nlogn) code> /
o (logn) code> solution que j'ai discuté avec user12861.
N code> arbres, un pour chaque
C code>. Chaque arborescence partage la plupart de son contenu avec les plus petits, avec un maximum de
logn code> de nouveaux nœuds. De cette façon, le coût total de la mémoire et du temps de prétraitement est limité à
O (nlogn) code>. P>
import sys
class Node:
pass
class Interval(Node):
def __init__(self,left,val,i,right):
self.i = i; self.val = val
self.left = left; self.right = right
self.mini = min(left.mini, i, right.mini)
self.min = min(left.min, val, right.min)
self.max = max(left.max, val, right.max)
def add(self,val,i):
# We do naive inserting here. In a real worst case logn solution,
# self-balancing would take place here.
if (val,i) < (self.val,self.i): return Interval(self.left.add(val,i), self.val, self.i, self.right)
if (self.val,self.i) < (val,i): return Interval(self.left, self.val, self.i, self.right.add(val,i))
return self
def query(self,a,b):
# If the entire interval is covered, return mini, this is what keeps us
# at max 2 opened nodes per level in the tree.
if a <= self.min and self.max <= b: return self.mini
# Otherwise compose an answer from the subtrees.
res = sys.maxint
if a <= self.val <= b: res = min(res, self.i)
if self.left.min <= b and self.left.max >= a: res = min(res, self.left.query(a,b))
if self.right.min <= b and self.right.max >= a: res = min(res, self.right.query(a,b))
return res
class Tip(Node):
def __init__(self):
# This is a typical null-pattern idiom. The self.values are set to
# the min/max identities to ensure to interference with the upwards
# value propagation.
self.mini = sys.maxint
self.min = sys.maxint
self.max = -sys.maxint
def add(self,val,i):
return Interval(Tip(), val, i, Tip())
def query(self,a,b):
return sys.maxint
# The input data array
data = [0, 3, 1, 2, 0, 4, 9, 6, 1, 7]
N = len(data)
# Build the array of trees. O(nlogn)
ar = [None]*N + [Tip()]
for i in range(N-1, -1, -1):
ar[i] = ar[i+1].add(data[i],i)
# The query function. O(logn)
def query(a,b,c):
return ar[c].query(a,b)
# Test
print query(2, 7, 4) # = 5
Cela ressemble à la solution pour moi. Très agréable! Malgré vos indications dans les commentaires, je ne pouvais pas comprendre comment construire ou interroger l'arbre moi-même, alors merci d'avoir fourni le code. Une note, le code que vous avez fourni est pour un <= ar [i] <= b, alors que la question ci-dessus a demandé un
Bien sûr, la pire performance sans équilibrage de soi est assez médiocre. O (n ^ 2) Configuration et o (n) requêtes. Je ne suis pas trop expérimenté avec des arbres, alors je ne sais pas comment faire l'équilibre de soi-même, mais j'espère que cela peut être fait si vous le dites.
L'équilibrage de l'auto-équilibrage prendrait autant de code que c'est au-dessus, alors je pensais que cela se concentrerait sur les choses intéressantes. Si vous souhaitez effectuer une version équilibrée, je vous recommande de regarder dans les TEAPS. Le point clé de tous les équilibrements est la rotation, qui, dans ce cas, ainsi que pour la plupart des données de données à base d'arbres, ne changez pas la sémantique.
Non, le tableau n'est pas trié :)
J'ai supprimé ma réponse jusqu'à ce que je pensais à la contrainte
C code> ...
@Olicharlesworth d'accord, prenez votre temps: D
Est-il destiné, que l'index
i = 0 code> ne sera jamais retourné (
0, au lieu de
0 <= C code> )? Est la plus grande complexité du temps ou de l'espace ou les deux?
Dans chaque nœud de votre arborescence de segment, vous conservez une liste ordonnée des numéros dans le segment. Ensuite, votre recherche binaire de
C code>.
@Thomasahle c'est la réponse que j'ai posté il y a 2 heures. Et comme je l'ai dit là-bas, cela semble finir par O (log n) ^ 2) pour des requêtes, pas tout à fait O (log n).
@ user12861 Maintenant que j'y pense, nous n'avons pas besoin d'un arbre de segment, car la plage
[c, n] code> va toujours jusqu'au droit. Par conséquent, nous n'avons besoin que de précalcomputer toutes les listes commandées, qui peuvent être effectuées dans l'utilisation d'une structure immuable à la liste des arbres / commandés dans
O (NLogn) CODE> Time and Memory.
@Thomasahle Vous avez raison, cela ressemble à une meilleure approche. Je vais essayer de coder cela et de voir si je peux le faire travailler.
@Thomasahle sur la réflexion ultérieure, cette technique ne fonctionnera pas. PRÉCOMPUNTING N Les listes commandées avec des longueurs allant de 1 à n prendront une heure (n ^ 2 log n), qui est supérieure à la méthode de l'arborescence de segment. Sauf si vous avez une technique intelligente, auquel cas je serais intéressé à la voir si vous avez le temps et l'inclination à le coder.
@ user12861: vous stockez les listes commandées comme arbres de recherche binaires. Tout d'abord, vous créez le bien le plus. Étant donné que vous utilisez une implémentation d'arborescence immuable, vous pouvez créer un nouvel arborescence avec une valeur ajoutée dans l'heure et la mémoire de connexion.
@Thomasahle Cela semble certainement intelligemment, mais je ne peux pas faire fonctionner moi-même (je ne peux pas comprendre comment construire l'arbre de la droite et le garder suffisamment équilibré si les valeurs de la matrice d'origine sont, disons: 80, 60, 76, 88). Si vous le pouvez, j'aimerais voir du code, ou une explication complète. J'aimerais pouvoir comprendre, car si cela fonctionnait, cela sonne comme si elle serait réellement O (journal n) pour les requêtes au lieu de O (log ^ 2 N).
@ user12861 Vous avez raison, je devrais mettre mon code sur lequel ma bouche est :) J'ai envoyé une réponse ci-dessous. Vérifiez-le et voyez ce que vous en pensez.