Teraz przejdziemy po elementach listy wiązanej za pomocą stosu i pętli zamiast rekurencji, poznając patent, który bardzo się przyda w przyszłości do grafów.
Ok, z poprzedniego przykładu:
class Node{
constructor(val){
this.val = val;
this.next = null;
}
}
let one = new Node(1);
let two = new Node(2);
let three = new Node(3);
let four = new Node(4);
one.next = two;
two.next = three;
three.next = four;
Wtedy w ten sposób sobie wyświetlaliśmy elementy listy:
function printLinkedList(root){
function _handle(root){
if(root === null)
return;
console.log(root.val);
_handle(root.next);
}
_handle(root);
}
printLinkedList(one);
//1 2 3 4
Bardzo elegancko. Wersja ze stosem zamiast rekurencji aż tak elegancka nie jest, ale ma tę zaletę, że do drzewek i grafów również się nadaje, tam gdzie rekurencja zawodzi, bo jest więcej niż jeden kierunek, w którym można iść i zamiast wywołań rekurencyjnych next trzeba każdy kierunek (lewo, prawo, ten czy tamten graf) wrzucić na stos.
Ok, ale spróbujmy w naszym przykładzie stos wykorzystać:
function stackPrintLinkedList(root){
let nodeStack = [];
let inStack = [];
nodeStack.push(root);
while(nodeStack.length > 0){
let currentNode = nodeStack.pop();
inStack.push(currentNode.val);
if(currentNode.next !== null)
nodeStack.push(currentNode.next);
}
let outStack = [];
while(inStack.length > 0){
outStack.push(inStack.pop());
}
while(outStack.length > 0){
console.log(outStack.pop());
}
}
stackPrintLinkedList(one);
//1 2 3 4
Tutaj jest bardzo podstępny mechanizm użyty:
- stos nodeStack jest warunkiem pętli while
- zrzucamy z tego stosu i wrzucamy do inStack value
- jeżeli zrzucony element ma next, to wrzucamy go na stos będący warunkiem pętli (rekurencja w pętli, tak!)
I całe piękno tego rozwiązania, w przypadku LL brzydkiego, polega na tym, że node może mieć zamiast next left i right, N S W E (kierunki geograficzne) albo nieograniczoną ilość innych grafów.
I rekurencja tego NIE obsłuży. „Stosowa rekurencja” już tak.
Ok, ale jak to działa:
- Wrzuć root na stos będący warunkiem
- Zrzuć ze stosu i na inStack wrzuć wartość
- Sprawdź, czy next, jeśli tak, wrzuć na stos będący warunkiem
- Po pierwszej pętli zrzucaj z inStack 1,2,3,4 (4 pierwsze do zrzutu) i wrzucaj na outStack 4,3,2,1 (1 pierwsze do zrzutu)
- Zrzucaj z outStack i loguj 1, potem 2, 3, 4…
Cała filozofia i w przypadku linked list nie jest to nam potrzebne, rekurencja lepsza, natomiast drzewa i grafy (zwłaszcza grafy), tam się tego rodzaju stosy przydadzą, warto zatem znać to podejście.