c# performance data-structures treap

c# - ¿Por qué la inserción en mi árbol es más rápida en la entrada ordenada que en la entrada aleatoria?



performance data-structures (8)

Agregué el cálculo de la desviación estándar y cambié la prueba para que se ejecute en la prioridad más alta (para reducir el ruido al máximo). Estos son los resultados:

Random Ordered 0,2835 (stddev 0,9946) 0,0891 (stddev 0,2372) 0,1230 (stddev 0,0086) 0,0780 (stddev 0,0031) 0,2498 (stddev 0,0662) 0,1694 (stddev 0,0145) 0,5136 (stddev 0,0441) 0,3550 (stddev 0,0658) 1,1704 (stddev 0,1072) 0,6632 (stddev 0,0856) 1,4672 (stddev 0,1090) 0,8343 (stddev 0,1047) 3,3330 (stddev 0,2041) 1,9272 (stddev 0,3456) 7,9822 (stddev 0,3906) 3,7871 (stddev 0,1459) 18,4300 (stddev 0,6112) 10,3233 (stddev 2,0247) 44,9500 (stddev 2,2935) 22,3870 (stddev 1,7157) 110,5275 (stddev 3,7129) 49,4085 (stddev 2,9595) 275,4345 (stddev 10,7154) 107,8442 (stddev 8,6200) 667,7310 (stddev 20,0729) 242,9779 (stddev 14,4033)

He ejecutado un perfilador de muestreo y aquí están los resultados (cantidad de veces que el programa estuvo en este método):

Method Random Ordered HeapifyRight() 1.95 5.33 get_IsEmpty() 3.16 5.49 Make() 3.28 4.92 Insert() 16.01 14.45 HeapifyLeft() 2.20 0.00

Conclusión: el azar tiene una distribución bastante razonable entre la rotación izquierda y derecha, mientras que el ordenado nunca gira a la izquierda.

Aquí está mi programa mejorado "benchmark":

static void Main(string[] args) { Thread.CurrentThread.Priority = ThreadPriority.Highest; Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime; List<String> rndTimes = new List<String>(); List<String> orderedTimes = new List<String>(); rndTimes.Add(TimeIt(50, RandomInsert)); rndTimes.Add(TimeIt(100, RandomInsert)); rndTimes.Add(TimeIt(200, RandomInsert)); rndTimes.Add(TimeIt(400, RandomInsert)); rndTimes.Add(TimeIt(800, RandomInsert)); rndTimes.Add(TimeIt(1000, RandomInsert)); rndTimes.Add(TimeIt(2000, RandomInsert)); rndTimes.Add(TimeIt(4000, RandomInsert)); rndTimes.Add(TimeIt(8000, RandomInsert)); rndTimes.Add(TimeIt(16000, RandomInsert)); rndTimes.Add(TimeIt(32000, RandomInsert)); rndTimes.Add(TimeIt(64000, RandomInsert)); rndTimes.Add(TimeIt(128000, RandomInsert)); orderedTimes.Add(TimeIt(50, OrderedInsert)); orderedTimes.Add(TimeIt(100, OrderedInsert)); orderedTimes.Add(TimeIt(200, OrderedInsert)); orderedTimes.Add(TimeIt(400, OrderedInsert)); orderedTimes.Add(TimeIt(800, OrderedInsert)); orderedTimes.Add(TimeIt(1000, OrderedInsert)); orderedTimes.Add(TimeIt(2000, OrderedInsert)); orderedTimes.Add(TimeIt(4000, OrderedInsert)); orderedTimes.Add(TimeIt(8000, OrderedInsert)); orderedTimes.Add(TimeIt(16000, OrderedInsert)); orderedTimes.Add(TimeIt(32000, OrderedInsert)); orderedTimes.Add(TimeIt(64000, OrderedInsert)); orderedTimes.Add(TimeIt(128000, OrderedInsert)); var result = string.Join("/n", (from s in rndTimes join s2 in orderedTimes on rndTimes.IndexOf(s) equals orderedTimes.IndexOf(s2) select String.Format("{0} /t/t {1}", s, s2)).ToArray()); Console.WriteLine(result); Console.WriteLine("Done"); Console.ReadLine(); } static double StandardDeviation(List<double> doubleList) { double average = doubleList.Average(); double sumOfDerivation = 0; foreach (double value in doubleList) { sumOfDerivation += (value) * (value); } double sumOfDerivationAverage = sumOfDerivation / doubleList.Count; return Math.Sqrt(sumOfDerivationAverage - (average * average)); } static String TimeIt(int insertCount, Action<int> f) { Console.WriteLine("TimeIt({0}, {1})", insertCount, f.Method.Name); List<double> times = new List<double>(); for (int i = 0; i < ITERATION_COUNT; i++) { Stopwatch sw = Stopwatch.StartNew(); f(insertCount); sw.Stop(); times.Add(sw.Elapsed.TotalMilliseconds); } return String.Format("{0:f4} (stddev {1:f4})", times.Average(), StandardDeviation(times)); }

Ahora siempre he escuchado que los árboles de búsqueda binarios son más rápidos de construir a partir de datos seleccionados al azar que los datos ordenados, simplemente porque los datos ordenados requieren un rebalanceo explícito para mantener la altura del árbol al mínimo.

Recientemente, implementé un treap inmutable, un tipo especial de árbol binario de búsqueda que utiliza la aleatorización para mantenerse relativamente equilibrado. En contraste con lo que esperaba, descubrí que puedo construir constantemente un tratamiento casi 2 veces más rápido y generalmente mejor equilibrado a partir de datos ordenados que datos no ordenados, y no tengo idea de por qué.

Aquí está mi implementación de treap:

Y aquí hay un programa de prueba:

using System; using System.Collections.Generic; using System.Linq; using System.Diagnostics; namespace ConsoleApplication1 { class Program { static Random rnd = new Random(); const int ITERATION_COUNT = 20; static void Main(string[] args) { List<double> rndTimes = new List<double>(); List<double> orderedTimes = new List<double>(); rndTimes.Add(TimeIt(50, RandomInsert)); rndTimes.Add(TimeIt(100, RandomInsert)); rndTimes.Add(TimeIt(200, RandomInsert)); rndTimes.Add(TimeIt(400, RandomInsert)); rndTimes.Add(TimeIt(800, RandomInsert)); rndTimes.Add(TimeIt(1000, RandomInsert)); rndTimes.Add(TimeIt(2000, RandomInsert)); rndTimes.Add(TimeIt(4000, RandomInsert)); rndTimes.Add(TimeIt(8000, RandomInsert)); rndTimes.Add(TimeIt(16000, RandomInsert)); rndTimes.Add(TimeIt(32000, RandomInsert)); rndTimes.Add(TimeIt(64000, RandomInsert)); rndTimes.Add(TimeIt(128000, RandomInsert)); string rndTimesAsString = string.Join("/n", rndTimes.Select(x => x.ToString()).ToArray()); orderedTimes.Add(TimeIt(50, OrderedInsert)); orderedTimes.Add(TimeIt(100, OrderedInsert)); orderedTimes.Add(TimeIt(200, OrderedInsert)); orderedTimes.Add(TimeIt(400, OrderedInsert)); orderedTimes.Add(TimeIt(800, OrderedInsert)); orderedTimes.Add(TimeIt(1000, OrderedInsert)); orderedTimes.Add(TimeIt(2000, OrderedInsert)); orderedTimes.Add(TimeIt(4000, OrderedInsert)); orderedTimes.Add(TimeIt(8000, OrderedInsert)); orderedTimes.Add(TimeIt(16000, OrderedInsert)); orderedTimes.Add(TimeIt(32000, OrderedInsert)); orderedTimes.Add(TimeIt(64000, OrderedInsert)); orderedTimes.Add(TimeIt(128000, OrderedInsert)); string orderedTimesAsString = string.Join("/n", orderedTimes.Select(x => x.ToString()).ToArray()); Console.WriteLine("Done"); } static double TimeIt(int insertCount, Action<int> f) { Console.WriteLine("TimeIt({0}, {1})", insertCount, f.Method.Name); List<double> times = new List<double>(); for (int i = 0; i < ITERATION_COUNT; i++) { Stopwatch sw = Stopwatch.StartNew(); f(insertCount); sw.Stop(); times.Add(sw.Elapsed.TotalMilliseconds); } return times.Average(); } static void RandomInsert(int insertCount) { Treap<double> tree = new Treap<double>((x, y) => x.CompareTo(y)); for (int i = 0; i < insertCount; i++) { tree = tree.Insert(rnd.NextDouble()); } } static void OrderedInsert(int insertCount) { Treap<double> tree = new Treap<double>((x, y) => x.CompareTo(y)); for(int i = 0; i < insertCount; i++) { tree = tree.Insert(i + rnd.NextDouble()); } } } }

Y aquí hay un gráfico que compara los tiempos de inserción aleatorios y ordenados en milisegundos:

Insertions Random Ordered RandomTime / OrderedTime 50 1.031665 0.261585 3.94 100 0.544345 1.377155 0.4 200 1.268320 0.734570 1.73 400 2.765555 1.639150 1.69 800 6.089700 3.558350 1.71 1000 7.855150 4.704190 1.67 2000 17.852000 12.554065 1.42 4000 40.157340 22.474445 1.79 8000 88.375430 48.364265 1.83 16000 197.524000 109.082200 1.81 32000 459.277050 238.154405 1.93 64000 1055.508875 512.020310 2.06 128000 2481.694230 1107.980425 2.24

No veo nada en el código que haga que la entrada ordenada sea asintóticamente más rápida que la entrada desordenada, por lo que no puedo explicar la diferencia.

¿Por qué es mucho más rápido crear un tratamiento a partir de una entrada ordenada que una entrada aleatoria?


Ejecuté su código, y creo que tiene que ver con el número de rotaciones. Durante la entrada ordenada, el número de rotaciones es óptimo, y el árbol nunca tendrá que girar hacia atrás.

Durante la entrada aleatoria, el árbol tendrá que realizar más rotaciones, ya que puede tener que girar hacia adelante y hacia atrás.

Para averiguarlo realmente, tendría que agregar contadores para los números de rotaciones a la izquierda y a la derecha para cada ejecución. Probablemente puedes hacer esto tú mismo.

ACTUALIZAR:

Puse puntos de interrupción en rotateleft y rotateright. Durante la entrada ordenada, nunca se usa el rotatorio. Durante la entrada aleatoria, ambos son afectados, y me parece que se usan con más frecuencia.

ACTUALIZACIÓN 2:

Agregué algunos resultados a la ejecución ordenada de 50 elementos (sustituyendo por claridad a los enteros), para obtener más información:

TimeIt(50, OrderedInsert) LastValue = 0, Top.Value = 0, Right.Count = 0, Left.Count = 0 RotateLeft @value=0 LastValue = 1, Top.Value = 1, Right.Count = 0, Left.Count = 1 LastValue = 2, Top.Value = 1, Right.Count = 1, Left.Count = 1 LastValue = 3, Top.Value = 1, Right.Count = 2, Left.Count = 1 RotateLeft @value=3 RotateLeft @value=2 RotateLeft @value=1 LastValue = 4, Top.Value = 4, Right.Count = 0, Left.Count = 4 LastValue = 5, Top.Value = 4, Right.Count = 1, Left.Count = 4 LastValue = 6, Top.Value = 4, Right.Count = 2, Left.Count = 4 RotateLeft @value=6 LastValue = 7, Top.Value = 4, Right.Count = 3, Left.Count = 4 LastValue = 8, Top.Value = 4, Right.Count = 4, Left.Count = 4 RotateLeft @value=8 RotateLeft @value=7 LastValue = 9, Top.Value = 4, Right.Count = 5, Left.Count = 4 LastValue = 10, Top.Value = 4, Right.Count = 6, Left.Count = 4 RotateLeft @value=10 RotateLeft @value=9 RotateLeft @value=5 RotateLeft @value=4 LastValue = 11, Top.Value = 11, Right.Count = 0, Left.Count = 11 LastValue = 12, Top.Value = 11, Right.Count = 1, Left.Count = 11 RotateLeft @value=12 LastValue = 13, Top.Value = 11, Right.Count = 2, Left.Count = 11 RotateLeft @value=13 LastValue = 14, Top.Value = 11, Right.Count = 3, Left.Count = 11 LastValue = 15, Top.Value = 11, Right.Count = 4, Left.Count = 11 RotateLeft @value=15 RotateLeft @value=14 LastValue = 16, Top.Value = 11, Right.Count = 5, Left.Count = 11 LastValue = 17, Top.Value = 11, Right.Count = 6, Left.Count = 11 RotateLeft @value=17 LastValue = 18, Top.Value = 11, Right.Count = 7, Left.Count = 11 LastValue = 19, Top.Value = 11, Right.Count = 8, Left.Count = 11 RotateLeft @value=19 LastValue = 20, Top.Value = 11, Right.Count = 9, Left.Count = 11 LastValue = 21, Top.Value = 11, Right.Count = 10, Left.Count = 11 RotateLeft @value=21 LastValue = 22, Top.Value = 11, Right.Count = 11, Left.Count = 11 RotateLeft @value=22 RotateLeft @value=20 RotateLeft @value=18 LastValue = 23, Top.Value = 11, Right.Count = 12, Left.Count = 11 LastValue = 24, Top.Value = 11, Right.Count = 13, Left.Count = 11 LastValue = 25, Top.Value = 11, Right.Count = 14, Left.Count = 11 RotateLeft @value=25 RotateLeft @value=24 LastValue = 26, Top.Value = 11, Right.Count = 15, Left.Count = 11 LastValue = 27, Top.Value = 11, Right.Count = 16, Left.Count = 11 RotateLeft @value=27 LastValue = 28, Top.Value = 11, Right.Count = 17, Left.Count = 11 RotateLeft @value=28 RotateLeft @value=26 RotateLeft @value=23 RotateLeft @value=16 RotateLeft @value=11 LastValue = 29, Top.Value = 29, Right.Count = 0, Left.Count = 29 LastValue = 30, Top.Value = 29, Right.Count = 1, Left.Count = 29 LastValue = 31, Top.Value = 29, Right.Count = 2, Left.Count = 29 LastValue = 32, Top.Value = 29, Right.Count = 3, Left.Count = 29 RotateLeft @value=32 RotateLeft @value=31 LastValue = 33, Top.Value = 29, Right.Count = 4, Left.Count = 29 RotateLeft @value=33 RotateLeft @value=30 LastValue = 34, Top.Value = 29, Right.Count = 5, Left.Count = 29 RotateLeft @value=34 LastValue = 35, Top.Value = 29, Right.Count = 6, Left.Count = 29 LastValue = 36, Top.Value = 29, Right.Count = 7, Left.Count = 29 LastValue = 37, Top.Value = 29, Right.Count = 8, Left.Count = 29 RotateLeft @value=37 LastValue = 38, Top.Value = 29, Right.Count = 9, Left.Count = 29 LastValue = 39, Top.Value = 29, Right.Count = 10, Left.Count = 29 RotateLeft @value=39 LastValue = 40, Top.Value = 29, Right.Count = 11, Left.Count = 29 RotateLeft @value=40 RotateLeft @value=38 RotateLeft @value=36 LastValue = 41, Top.Value = 29, Right.Count = 12, Left.Count = 29 LastValue = 42, Top.Value = 29, Right.Count = 13, Left.Count = 29 RotateLeft @value=42 LastValue = 43, Top.Value = 29, Right.Count = 14, Left.Count = 29 LastValue = 44, Top.Value = 29, Right.Count = 15, Left.Count = 29 RotateLeft @value=44 LastValue = 45, Top.Value = 29, Right.Count = 16, Left.Count = 29 LastValue = 46, Top.Value = 29, Right.Count = 17, Left.Count = 29 RotateLeft @value=46 RotateLeft @value=45 LastValue = 47, Top.Value = 29, Right.Count = 18, Left.Count = 29 LastValue = 48, Top.Value = 29, Right.Count = 19, Left.Count = 29 LastValue = 49, Top.Value = 29, Right.Count = 20, Left.Count = 29

Los artículos pedidos siempre se agregan al lado derecho del árbol, naturalmente. Cuando el lado derecho se vuelve más grande que el izquierdo, ocurre un giro rotatorio. Rotateright nunca pasa. Un nuevo nodo superior se selecciona aproximadamente cada vez que el árbol se duplica. La aleatoriedad del valor de prioridad lo mueve un poco, por lo que pasa a 0, 1, 4, 11, 29 en esta ejecución.

Una corrida aleatoria revela algo interesante:

TimeIt(50, RandomInsert) LastValue = 0,748661640914465, Top.Value = 0,748661640914465, Right.Count = 0, Left.Count = 0 LastValue = 0,669427539533669, Top.Value = 0,748661640914465, Right.Count = 0, Left.Count = 1 RotateRight @value=0,669427539533669 LastValue = 0,318363281115127, Top.Value = 0,748661640914465, Right.Count = 0, Left.Count = 2 RotateRight @value=0,669427539533669 LastValue = 0,33133987678743, Top.Value = 0,748661640914465, Right.Count = 0, Left.Count = 3 RotateLeft @value=0,748661640914465 LastValue = 0,955126694382693, Top.Value = 0,955126694382693, Right.Count = 0, Left.Count = 4 RotateRight @value=0,669427539533669 RotateLeft @value=0,33133987678743 RotateLeft @value=0,318363281115127 RotateRight @value=0,748661640914465 RotateRight @value=0,955126694382693 LastValue = 0,641024029180884, Top.Value = 0,641024029180884, Right.Count = 3, Left.Count = 2 LastValue = 0,20709771951991, Top.Value = 0,641024029180884, Right.Count = 3, Left.Count = 3 LastValue = 0,830862050331599, Top.Value = 0,641024029180884, Right.Count = 4, Left.Count = 3 RotateRight @value=0,20709771951991 RotateRight @value=0,318363281115127 LastValue = 0,203250563798123, Top.Value = 0,641024029180884, Right.Count = 4, Left.Count = 4 RotateLeft @value=0,669427539533669 RotateRight @value=0,748661640914465 RotateRight @value=0,955126694382693 LastValue = 0,701743399585478, Top.Value = 0,641024029180884, Right.Count = 5, Left.Count = 4 RotateLeft @value=0,669427539533669 RotateRight @value=0,701743399585478 RotateLeft @value=0,641024029180884 LastValue = 0,675667521858433, Top.Value = 0,675667521858433, Right.Count = 4, Left.Count = 6 RotateLeft @value=0,33133987678743 RotateLeft @value=0,318363281115127 RotateLeft @value=0,203250563798123 LastValue = 0,531275219531392, Top.Value = 0,675667521858433, Right.Count = 4, Left.Count = 7 RotateRight @value=0,748661640914465 RotateRight @value=0,955126694382693 RotateLeft @value=0,701743399585478 LastValue = 0,704049674190604, Top.Value = 0,675667521858433, Right.Count = 5, Left.Count = 7 RotateRight @value=0,203250563798123 RotateRight @value=0,531275219531392 RotateRight @value=0,641024029180884 RotateRight @value=0,675667521858433 LastValue = 0,161392807104342, Top.Value = 0,161392807104342, Right.Count = 13, Left.Count = 0 RotateRight @value=0,203250563798123 RotateRight @value=0,531275219531392 RotateRight @value=0,641024029180884 RotateRight @value=0,675667521858433 RotateLeft @value=0,161392807104342 LastValue = 0,167598206162266, Top.Value = 0,167598206162266, Right.Count = 13, Left.Count = 1 LastValue = 0,154996359793002, Top.Value = 0,167598206162266, Right.Count = 13, Left.Count = 2 RotateLeft @value=0,33133987678743 LastValue = 0,431767346538495, Top.Value = 0,167598206162266, Right.Count = 14, Left.Count = 2 RotateRight @value=0,203250563798123 RotateRight @value=0,531275219531392 RotateRight @value=0,641024029180884 RotateRight @value=0,675667521858433 RotateLeft @value=0,167598206162266 LastValue = 0,173774613614089, Top.Value = 0,173774613614089, Right.Count = 14, Left.Count = 3 RotateRight @value=0,830862050331599 LastValue = 0,76559642412029, Top.Value = 0,173774613614089, Right.Count = 15, Left.Count = 3 RotateRight @value=0,76559642412029 RotateLeft @value=0,748661640914465 RotateRight @value=0,955126694382693 RotateLeft @value=0,704049674190604 RotateLeft @value=0,675667521858433 LastValue = 0,75742144871383, Top.Value = 0,173774613614089, Right.Count = 16, Left.Count = 3 LastValue = 0,346844367844446, Top.Value = 0,173774613614089, Right.Count = 17, Left.Count = 3 RotateRight @value=0,830862050331599 LastValue = 0,787565814232251, Top.Value = 0,173774613614089, Right.Count = 18, Left.Count = 3 LastValue = 0,734950566540915, Top.Value = 0,173774613614089, Right.Count = 19, Left.Count = 3 RotateLeft @value=0,20709771951991 RotateRight @value=0,318363281115127 RotateLeft @value=0,203250563798123 RotateRight @value=0,531275219531392 RotateRight @value=0,641024029180884 RotateRight @value=0,675667521858433 RotateRight @value=0,75742144871383 RotateLeft @value=0,173774613614089 LastValue = 0,236504829598826, Top.Value = 0,236504829598826, Right.Count = 17, Left.Count = 6 RotateLeft @value=0,830862050331599 RotateLeft @value=0,787565814232251 RotateLeft @value=0,76559642412029 RotateRight @value=0,955126694382693 LastValue = 0,895606500048007, Top.Value = 0,236504829598826, Right.Count = 18, Left.Count = 6 LastValue = 0,599106418713511, Top.Value = 0,236504829598826, Right.Count = 19, Left.Count = 6 LastValue = 0,8182332901369, Top.Value = 0,236504829598826, Right.Count = 20, Left.Count = 6 RotateRight @value=0,734950566540915 LastValue = 0,704216948572647, Top.Value = 0,236504829598826, Right.Count = 21, Left.Count = 6 RotateLeft @value=0,346844367844446 RotateLeft @value=0,33133987678743 RotateRight @value=0,431767346538495 RotateLeft @value=0,318363281115127 RotateRight @value=0,531275219531392 RotateRight @value=0,641024029180884 RotateRight @value=0,675667521858433 RotateRight @value=0,75742144871383 LastValue = 0,379157059536854, Top.Value = 0,236504829598826, Right.Count = 22, Left.Count = 6 RotateLeft @value=0,431767346538495 LastValue = 0,46832062046431, Top.Value = 0,236504829598826, Right.Count = 23, Left.Count = 6 RotateRight @value=0,154996359793002 LastValue = 0,0999000217299443, Top.Value = 0,236504829598826, Right.Count = 23, Left.Count = 7 RotateLeft @value=0,20709771951991 LastValue = 0,229543754006524, Top.Value = 0,236504829598826, Right.Count = 23, Left.Count = 8 RotateRight @value=0,8182332901369 LastValue = 0,80358425984326, Top.Value = 0,236504829598826, Right.Count = 24, Left.Count = 8 RotateRight @value=0,318363281115127 LastValue = 0,259324726769386, Top.Value = 0,236504829598826, Right.Count = 25, Left.Count = 8 RotateRight @value=0,318363281115127 LastValue = 0,307835293145774, Top.Value = 0,236504829598826, Right.Count = 26, Left.Count = 8 RotateLeft @value=0,431767346538495 LastValue = 0,453910283024381, Top.Value = 0,236504829598826, Right.Count = 27, Left.Count = 8 RotateLeft @value=0,830862050331599 LastValue = 0,868997387527021, Top.Value = 0,236504829598826, Right.Count = 28, Left.Count = 8 RotateLeft @value=0,20709771951991 RotateRight @value=0,229543754006524 RotateLeft @value=0,203250563798123 LastValue = 0,218358597354199, Top.Value = 0,236504829598826, Right.Count = 28, Left.Count = 9 RotateRight @value=0,0999000217299443 RotateRight @value=0,161392807104342 LastValue = 0,0642934488431986, Top.Value = 0,236504829598826, Right.Count = 28, Left.Count = 10 RotateRight @value=0,154996359793002 RotateLeft @value=0,0999000217299443 LastValue = 0,148295871982489, Top.Value = 0,236504829598826, Right.Count = 28, Left.Count = 11 LastValue = 0,217621828065078, Top.Value = 0,236504829598826, Right.Count = 28, Left.Count = 12 RotateRight @value=0,599106418713511 LastValue = 0,553135806020878, Top.Value = 0,236504829598826, Right.Count = 29, Left.Count = 12 LastValue = 0,982277666210326, Top.Value = 0,236504829598826, Right.Count = 30, Left.Count = 12 RotateRight @value=0,8182332901369 LastValue = 0,803671114520948, Top.Value = 0,236504829598826, Right.Count = 31, Left.Count = 12 RotateRight @value=0,203250563798123 RotateRight @value=0,218358597354199 LastValue = 0,19310415405459, Top.Value = 0,236504829598826, Right.Count = 31, Left.Count = 13 LastValue = 0,0133136604043253, Top.Value = 0,236504829598826, Right.Count = 31, Left.Count = 14 RotateLeft @value=0,46832062046431 RotateRight @value=0,531275219531392 RotateRight @value=0,641024029180884 RotateRight @value=0,675667521858433 RotateRight @value=0,75742144871383 LastValue = 0,483394719419719, Top.Value = 0,236504829598826, Right.Count = 32, Left.Count = 14 RotateLeft @value=0,431767346538495 RotateRight @value=0,453910283024381 LastValue = 0,453370328738061, Top.Value = 0,236504829598826, Right.Count = 33, Left.Count = 14 LastValue = 0,762330518459124, Top.Value = 0,236504829598826, Right.Count = 34, Left.Count = 14 LastValue = 0,699010426969738, Top.Value = 0,236504829598826, Right.Count = 35, Left.Count = 14

Las rotaciones se producen no tanto porque el árbol está desequilibrado, sino debido a las prioridades, que se seleccionan al azar. Por ejemplo obtenemos 4 rotaciones en la 13ª inserción. Tenemos un árbol balanceado a 5/7 (que está bien), ¡pero llega a 13/0! Parece que el uso de prioridades aleatorias merece una mayor investigación. De todos modos, es fácil ver que las inserciones aleatorias causan muchas más rotaciones que las inserciones ordenadas.


Existen árboles de equilibrio automático para solucionar los problemas asociados con los datos distribuidos de forma no aleatoria. Por definición, intercambian un poco del mejor desempeño para mejorar enormemente el peor desempeño asociado con BST no balanceadas, específicamente la entrada clasificada.

Realmente estás pensando demasiado en este problema, porque una inserción más lenta de datos aleatorios frente a datos ordenados es una característica de cualquier árbol equilibrado. Pruébalo en una AVL y verás los mismos resultados.

Cameron tuvo la idea correcta, eliminando la verificación de prioridad para forzar el peor de los casos. Si haces eso e instrumentas tu árbol para que puedas ver cuántos rebalances están sucediendo para cada inserto, en realidad se vuelve muy obvio lo que está pasando. Cuando se insertan datos ordenados, el árbol siempre gira a la izquierda y el hijo derecho de la raíz siempre está vacío. La inserción siempre produce exactamente un reequilibrio porque el nodo de inserción no tiene hijos y no se produce recursión. Por otro lado, cuando lo ejecuta en los datos aleatorios, casi inmediatamente comienza a ver múltiples rebalances en cada inserción, tanto como 5 o 6 de ellos en el caso más pequeño (50 inserciones), porque sucede en subárboles como bien.

Con la verificación de prioridad activada, no solo los rebalances suelen ser menos costosos debido a que se insertan más nodos en el subárbol izquierdo (de donde nunca salen porque no ocurren inserciones), sino que también es menos probable que ocurran. ¿Por qué? Debido a que en el treap, los nodos de alta prioridad flotan hacia la parte superior, y las rotaciones a la izquierda constantes (no acompañadas por las rotaciones de la derecha) comienzan a empujar todos los nodos de alta prioridad hacia el subárbol izquierdo también. El resultado es que los rebalances ocurren con menos frecuencia debido a la distribución desigual de la probabilidad.

Si instrumentas el código de rebalanceo, verás que esto es cierto; tanto para la entrada ordenada como para la aleatoria, terminas con números casi idénticos de rotaciones a la izquierda, pero la entrada aleatoria también proporciona la misma cantidad de rotaciones a la derecha, lo que da como resultado el doble de todas. Esto no debería ser sorprendente: la entrada gaussiana debería dar como resultado una distribución gaussiana de las rotaciones. También verá que solo hay alrededor de 60-70% como muchos rebalances de nivel superior para la entrada ordenada, lo que quizás sea sorprendente, y nuevamente, esto se debe a la entrada ordenada de la entrada con la distribución natural de las prioridades.

También puede verificar esto inspeccionando el árbol completo al final de un bucle de inserción. Con la entrada aleatoria, las prioridades tienden a disminuir de manera bastante lineal por nivel; con la entrada ordenada, las prioridades tienden a permanecer muy altas hasta que se llega a uno o dos niveles desde la base.

Espero haber hecho un trabajo decente explicando esto ... hazme saber si algo de eso es demasiado vago.


Sí, es el número de rotaciones que está causando el tiempo extra. Esto es lo que hice:

  • Elimine las líneas que comprueban la prioridad en HeapifyLeft y HeapifyRight para que las rotaciones siempre se realicen.
  • Se agregó una Console.WriteLine después de if en RotateLeft y RotateRight .
  • Se agregó una Console.WriteLine en la parte IsEmpty del método Insert para ver qué se estaba insertando.
  • Se corrió la prueba una vez con 5 valores cada uno.

Salida:

TimeIt(5, RandomInsert) Inserting 0.593302943554382 Inserting 0.348900582338171 RotateRight Inserting 0.75496212381635 RotateLeft RotateLeft Inserting 0.438848891499848 RotateRight RotateLeft RotateRight Inserting 0.357057290783644 RotateLeft RotateRight TimeIt(5, OrderedInsert) Inserting 0.150707998383189 Inserting 1.58281302712057 RotateLeft Inserting 2.23192588297274 RotateLeft Inserting 3.30518679009061 RotateLeft Inserting 4.32788012657682 RotateLeft

Resultado: 2 veces más rotaciones en datos aleatorios.


Sólo estás viendo una diferencia de aproximadamente 2x. A menos que hayas sintonizado las luces de día de este código, eso es básicamente en el ruido. La mayoría de los programas bien escritos, especialmente aquellos que involucran estructura de datos, pueden fácilmente tener más espacio para mejorar que eso. Aquí hay un ejemplo.

Acabo de ejecutar su código y tomé algunas fotos de pila. Esto es lo que vi:

Inserto Aleatorio:

1 Insert:64 -> HeapifyLeft:81 -> RotateRight:150 1 Insert:64 -> Make:43 ->Treap:35 1 Insert:68 -> Make:43

Inserto ordenado:

1 Insert:61 1 OrderedInsert:224 1 Insert:68 -> Make:43 1 Insert:68 -> HeapifyRight:90 -> RotateLeft:107 1 Insert:68 1 Insert:68 -> Insert:55 -> IsEmpty.get:51

Este es un número bastante pequeño de muestras, pero sugiere que en el caso de una entrada aleatoria, Make (línea 43) consume una fracción de tiempo mayor. Ese es este código:

private Treap<T> Make(Treap<T> left, T value, Treap<T> right, int priority) { return new Treap<T>(Comparer, left, value, right, priority); }

Luego tomé 20 imágenes de pila del código de inserción aleatoria para tener una mejor idea de lo que estaba haciendo:

1 Insert:61 4 Insert:64 3 Insert:68 2 Insert:68 -> Make:43 1 Insert:64 -> Make:43 1 Insert:68 -> Insert:57 -> Make:48 -> Make:43 2 Insert:68 -> Insert:55 1 Insert:64 -> Insert:55 1 Insert:64 -> HeapifyLeft:81 -> RotateRight:150 1 Insert:64 -> Make:43 -> Treap:35 1 Insert:68 -> HeapifyRight:90 -> RotateLeft:107 -> IsEmpty.get:51 1 Insert:68 -> HeapifyRight:88 1 Insert:61 -> AnonymousMethod:214

Esto revela cierta información.
El 25% del tiempo se gasta en la línea Make: 43 o sus clientes.
El 15% del tiempo se gasta en esa línea, no en una rutina reconocida, en otras palabras, en la creación de un nuevo nodo.
El 90% del tiempo se invierte en líneas. Insertar: 64 y 68 (que llaman Make y heapify).
El 10% del tiempo se gasta en RotateLeft y Right.
El 15% del tiempo se gasta en Heapify o sus llamados.

También hice una buena cantidad de pasos individuales (en el nivel de origen), y llegué a la sospecha de que, dado que el árbol es inmutable, dedica mucho tiempo a crear nuevos nodos porque no quiere cambiar los viejos. Entonces los viejos son recolectados en la basura porque ya nadie se refiere a ellos.

Esto tiene que ser ineficiente.

Todavía no estoy respondiendo a tu pregunta de por qué insertar números ordenados es más rápido que los números generados al azar, pero realmente no me sorprende porque el árbol es inmutable.

No creo que pueda esperarse que ningún razonamiento de rendimiento sobre los algoritmos de árbol se transfiera fácilmente a árboles inmutables, porque el cambio más profundo en el árbol hace que se reconstruya en el camino de regreso, a un alto costo en la new y recolección de basura.


tiene razón. Sin embargo, hay un poquito más de eso. No estoy diciendo que sea el factor más importante en este caso, sin embargo, está ahí y es difícil hacer algo al respecto.

Para una entrada ordenada, las búsquedas probablemente toquen los nodos que están calientes en el caché. (Esto es cierto en general para árboles equilibrados como árboles AVL, árboles rojo-negro, árboles B, etc.)

Dado que las inserciones comienzan con una búsqueda, esto también tiene un efecto en el rendimiento de inserción / eliminación.

Nuevamente, no estoy afirmando que sea el factor más importante en todos y cada uno de los casos. Sin embargo, está allí y es probable que las entradas ordenadas sean siempre más rápidas que las aleatorias en estas estructuras de datos.


Aaronaught ha hecho un trabajo realmente decente explicando esto.

Para estos dos casos especiales, me resulta más fácil comprenderlo en términos de la longitud de la ruta de inserción.

Para la entrada aleatoria, su ruta de inserción baja a una de las hojas y la longitud de la ruta, por lo tanto, el número de rotaciones, está delimitada por la altura del árbol.

En el caso ordenado, usted camina por la espina derecha de la pata y el límite es la longitud de la espina, que es menor o igual que la altura.

Como los nodos giran a lo largo de la ruta de inserción y su ruta de inserción es la columna vertebral en este caso, estas rotaciones siempre acortarán la columna (lo que resultará en una ruta de inserción más corta en la siguiente inserción, ya que la ruta de inserción es solo la columna vertebral, etc.) )

Edición: para el caso aleatorio, la ruta de inserción es 1.75x más larga.