poniedziałek, 28 maja 2012

Zrozumieć LinkedList

Klasa LinkedList jest w .NET Base Class Library od bardzo dawna (od wersji 2.0) i bardzo rzadko jest używana. Zawsze była dla mnie pewną tajemnicą, więc postanowiłem zrobić parę testów na tej klasie. Ogólny sposób działania linked listy można poczytać na stronie wikipedii. Poniżej przedstawiam w punktach krótką charakterystykę:

1) LinkedList występuje tylko w postaci generycznej (LinkedList<T>).

2) LinkedList nie jest "listą" - to znaczy, że nie implementuje interfejsu IList. Interfejsy z których implementuje LinkedList to:
  • ICollection<T>, ICollection
  • IEnumerable<T>, IEnumerable
  • ISerializable, IDeserializationCallback

Dla uproszczenia słownictwa przyjmujemy, że LinkedList<T> i List<T> będziemy nazywać listą.

3) Metody AddFirst i AddLast określają kolejność zapisywanych obiektów. Możemy w prosty sposób dodać obiekt na końcu listy jak i na początku
NP:
var linkedList = new LinkedList<int>();  
//tworzy LinkedList

            linkedList.AddFirst(1);    
//   1 

            linkedList.AddFirst(2);    
//   2, 1

            linkedList.AddFirst(3);    
//   3, 2, 1


            linkedList.AddLast(4);     
//   3, 2, 1, 4 

            linkedList.AddLast(5);     
//   3, 2, 1, 4, 5

            linkedList.AddLast(6);     
//   3, 2, 1, 4, 5, 6




4) Możemy dodać element do wyznaczonego węzła
var linkedList = new LinkedList<int>();            
//tworzy LinkedList

            var objOne= linkedList.AddFirst(1);                
// 1

            var objTwo =   linkedList.AddAfter(objOne, 2);     
// 1, 2

            var objThree = linkedList.AddBefore(objTwo, 3);    
// 1, 3, 2

            var objFour =  linkedList.AddAfter(objThree, 4);   
// 1, 3, 4, 2

            var objFive =  linkedList.AddAfter(objFour, 5);    
// 1, 3, 4, 5, 2



5) LinkedList nie stosuje się we wszystkich zagadnieniach. Poniżej przedstawiam wyniki czasu działa różnych operacji na LinkedList i List. Wyniki są przedstawiane w milisekundach dla tysiąca, 10 tysięcy, 100 tysięcy oraz miliona elementów w listach. Na początku implementacja:
            var list = new List<int>();
            var linkedList = new LinkedList<int>();
            Stopwatch watchLinkedList = new Stopwatch();
            Stopwatch watchList = new Stopwatch();

5a) Dodawanie elementu na końcu listy.
            watchList.Start();
            for (int i = 0; i < counts; i++)
            {
                list.Add(i);
            }
            watchList.Stop();

            watchLinkedList.Start();
            for (int i = 0; i < counts; i++)
            {
                linkedList.AddLast(i);
            }
            watchLinkedList.Stop();


Wykres jest przedstawiony w skali liniowej. Poniżej tabela z wynikami:

Add Last
 LinkedListList
1,00000
10,00000
100,00091
1,000,00010613
10,000,0002770256

Bardzo dokładnie widać, że czas dodawania elementu na koniec listy dla LinkedList jest większy niż dla List.

5b) Dodawanie elementu na początku listy
            watchList.Start();
            for (int i = 0; i < counts; i++)
            {
                list.Insert(0,i);
            }
            watchList.Stop();

            watchLinkedList.Start();
            for (int i = 0; i < counts; i++)
            {
                linkedList.AddFirst(i);
            }
            watchLinkedList.Stop();


Add First
 LinkedListList
1,00000
10,000025
100,00082347
1,000,00097274540

Wykres jest przedstawiony w skali logarytmicznej. Tutaj dokładnie widać, że czas dla List jest dużo większy od LinkedList. Dla 100 tys. jest około 300 razy większy, a dla miliona elementów jest z 3 000 razy większy.

Sprawdźmy co się stanie, gdy porównamy dla LinkedList metodę AddFirst (dodawanie elementu na początek listy) z metodą AddLast (dodawanie elementu na koniec listy). Okazuje się, że czasy dla tych operacji są bardzo zbliżone.


Add FirstAdd Last
1,00000
10,00000
100,00089
1,000,00097106
10,000,00027752770


5c) Usuwanie podanego elementu z gotowej listy (za pomocą metody Remove(int))

Remove
 LinkedListList
1,00000
10,000024
100,00022370
1,000,00030283608

Czas dla List<T> przy operacji usunięcia elementów jest bardzo duży. Wiąże się to z koniecznością "przebudowania" listy (klasa List<T> jest zaimplementowania za pomocą Array).

5d) Sortujemy elementy. Do tego zastosujemy metodę rozszerzającą OrderBy

OrderBy
 LinkedListList
1,00004
10,00011
100,0001819
1,000,000212233
10,000,00033353092

Czas poświęcony na sortowanie listy dla obu klas jest bardzo podobny.

5e) Dodawanie elementu w sam środek listy
Kod dla List:
for (int i = 0 ; i < counts ; i++)
{
  int center = (int)
        Math.Floor( (double)list.Count / 2 );
  list.Insert( center, i );
}

Kod dla LinkedList:
bool addBefore = false;
LinkedListNode<int> centerNode = linkedList.AddFirst(0); ;
for (int i = 1; i < counts; i++)
{
if (addBefore)
{
  centerNode = linkedList.AddBefore( centerNode, i );
  addBefore = false;
}
else
{
  centerNode = linkedList.AddAfter( centerNode, i );
  addBefore = true;
}
}


Add Inner
 LinkedListList
1,00010
10,000015
100,00091467
1,000,000155148823
10,000,000273619992188

Wykres jest przedstawiony w skali logarytmicznej. Operacja na 10 milionach elementach w LinkedList jest podobnie czasochłonna co operacja na 100 tysiącach dla List. W tym eksperymencie ewidentnie widać, że lepiej jest zastosować LinkedList.

Poniżej jest porównanie 3 operacji. Dodawanie elementu na początek listy, w środek i na koniec list. Wyniki są prawie identyczne.
Add FirstAdd InnerAdd Last
1,000010
10,000000
100,000899
1,000,00097155106
10,000,000277527362770


5f) Przechodzenie po liście. Dla uproszenie sprawdzę wykonania operacji foreach oraz znajdę liczbę maksymalną.

ForeachMax
 LinkedListListLinkedLIstList
1,0000000
10,0000000
100,0001011
1,000,000951511
10,000,0009666165118

Wnioski: LinkedList<T> bardzo dobrze sprawuje się przy modyfikacji (dodawanie i usuwanie) obiektów w liście, natomiast List<T> dobrze sprawdza się przy dodawaniu elementu na końcu listy. Z racja, że LinkedList<T> nie ma indeksu to przechodzenie po liście jest kosztowniejsze niż dla List<T>.


Brak komentarzy:

Prześlij komentarz