Linked Lists in JavaScript
Take a look at the following image. This shows a "singly-linked list" (or just "linked list") which is basically just a list of items. You may ask why we can't just use an array for that. Well, we could, but you're here to learn about linked lists:
The following two diagrams both show linked lists (each representing the list: Richard, Diane, Henry, Sarah, Matthew, Jenny) in an array. The list on the left is ordered with each list member following the previous one in the array. The list on the right is rather more typical of what a list would look like after a few insertions and deletions. It's still the same list, but this time the elements are in a different order in the array. It's the links (shown by the arrows) that give the order of the nodes in the list, not the order in which they appear in the array. You'll notice that the last item in each list (Jenny in this case), has a link that points to the value -1, indicating "end of list". In this way, we may distinguish between a physical order (the order in which items are laid out in physical computer memory) and logical order (the order in which they appear in the list itself). In order to do anything with the linked list, we had first better add a few more nodes to it... Adding a node to the end of a linked listLet's suppose we want to add the name "Ian" to the end of the list. Firstly, we get an index of a slot where we can insert the name: var newnode = get_node("Ian"); The variable newnode now points to the array index where the new name is stored. The next property of this new node is automatically set to -1, indicating that it is last in the list. Now we have to persuade the node that is currently last in the list to point to the new node. This is done by starting at the beginning of the list, and looking at the next property of each element we come across. This will tell us the index in the array of the next node that we need to check. Eventually, the next property of a node will be -1, which tells us that that node is the last one in the list. var n = START; // Starts at first node in list var reached_end = false; while (reached_end == false) { var next_index = nodes[n].next; if (next_index == -1) // We've reached the end! reached_end = true; else n = next_index; // Moves to next one in list } When this loop terminates, n will contain the index in the array of the last node currently in the list. We know that the list starts off with only one node (the name Richard in nodes[0]), so the first time you run this function, the loop terminates immediately as the next property of nodes[0] is -1. As the list gets longer, the loop goes through more iterations before terminating. The only other thing to do is to set the next property of this node to point to the new name (index newnode): nodes[n].next = newnode; Searching a linked list for an itemWell this is fairly simple, particularly given the while loop that you met in the last section. All we have to do is go through the list again, this time checking the information part of each node. Suppose we need to check the list for the string search_string: var n = START; var reached_end = false; var found_it = false; // true if/when we find the name while (reached_end == false && found_it == false) { if (nodes[n].name == search_string) found_it = true; else { var next_index = nodes[n].next; if (next_index == -1) // We've reached the end! reached_end = true; else n = next_index; // Moves to next one in list } } if (found_it == true) alert("Found the name!"); You'll recognise the basic structure of this loop, but this time there is an additional stopping clause. The first thing that happens in the loop is that the contents of the node is compared to the search string. If they match, the variable found_it is set to true and the loop terminates. You will notice that the while loop also checks found_it as well as reached_end. If the search string is not found, the loop only terminates when reached_end becomes true, as before. We can tell that the search string was present after the loop simply by examining found_it. If true, then the variable n now holds the array index of the found node. That's why it was important to end the loop the moment that the search string was found. We can use n to display any more information about the node, or to do any editing. Adding a node in the middle of a linked listThis is a little harder as we need to juggle links between nodes carefully. Firstly, let's assume that we have been asked to insert a name before the node with a given name e.g. insert a node with the name "Pippa" before name "Sarah" in the list. We'll assume that the name to be inserted is given by new_name, the name before which it is to be inserted is called before_name, and that these are passed as parameters to a function insert_middle_node(). The routine is slightly different, depending on whether the "insert before" name is the first node of the list or not: function insert_middle_node (new_name, before_name) { if (nodes[START] == before_name) insert_node_at_start(new_name) else insert_node_in_middle(new_name, before_name) } if the starting node is the name before which to insert the new node, we call a special routine insert_node_at_start and pass it the parameter new_name. Otherwise, we call insert_node_in_middle, which handles insertions anywhere in the list apart from at the start or end. Inserting a node at the start is done as follows. We'll use the example of inserting the name "Pippa" before "Richard" (at the start of the list)
It is important that we do steps 2 and 3 above in the correct order. We must save the value pointed to by START before we overwrite it with the new value of the starting index. Here's the function that carries out the steps above. See if you can match the lines of the function with the steps above: function insert_node_at_start (new_name) { var new_node = get_node(new_name); nodes[new_node].next = START; START = new_node; } Inserting a node somewhere else in the list is a little harder. This is because we need to find the correct place for the insertion in the first place and then maintain some more pieces of information. We need a pointer (call it n1) that looks for the correct place in terms of the name before which we want to insert the new name, but also a pointer (call it n2) that points to the node before that one. In the example below, we will consider the example where the name "Pippa" is inserted before the name "Henry".
After the procedure we no longer need pointers n1 and n2. If you trace the arrows you will find that the node with the name "Pippa" lies in the list between Diane and Henry. However, you can see now why we needed n1. Suppose we had only used n2. When we had advanced it to the point where we had found the insert-before name, we couldn't have changed the next pointer of the node before that one in the list in order to maintain the chain structure. And here we have all that in the form of JavaScript. function insert_node_in_middle (new_name, before_name) { var new_node = get_node(new_name); var n1 = START; var n2 = nodes[START].next; while (nodes[n2].name != before_name && nodes[n2].next != -1) { n2 = nodes[n2].next; n1 = nodes[n1].next; } if (nodes[n2].name == before_name) { nodes[newnode].next = n2; nodes[n1].next = newnode; } else alert("Failed to find name: " + before_name); } Deleting a node from the listWe've done searching the list for an item, and adding a node to the list in various places. The only thing left is to delete a node from the list. Again the procedure will differ slightly depending on whereabouts in the list the node lies (start, middle or end). To delete a node from the start of the list:
Here's the computer code that does the same thing: function delete_start_node () { nodes[START].valid = false; START = nodes[START].next; // New start of list } This is the reason that we store the start of the list in variable START rather than assuming it is index 0 in the array. The following procedure is used to delete a node from the end of the list:
Here's the JavaScript that does the same thing: function delete_end_node () { if (START == -1) // If list already empty, abort! return; if (nodes[START].next == -1) // List contains one name only { START = -1; // List now empty return; } var n1 = START; var n2 = nodes[START].next; while (nodes[n2].next != -1) // As long as n2 isn't last node { n2 = nodes[n2].next; n1 = nodes[n1].next; } nodes[n2].valid = false; // Remove name from list nodes[n1].next = -1; // Indicates new end of list } The only other thing to consider is deleting a node from the middle of the list, defined as deleting the node before a given name. This is done in a similar manner to deleting the node at the end, except that instead of looking for the end of the list (nodes[n2].next == -1), we look for the "delete-before" name. However, in this case, we need to maintain three pointers: one for the node to be deleted (we'll call that n2), one for the node before that in the list (call it n1) to tie up the pointers correctly, and one for the next node in the list (call it n3) to detect the presence of the delete-before name.
And in JavaScript: function delete_middle_node (delete_before_name) { if (START == -1) return; if (nodes[START].name == delete_before_name || nodes[START].next == -1) return; var n1 = START; // Starting node var n2 = nodes[START].next; // 2nd node in list if (nodes[n2].name == delete_before_name) // 2nd name is delete-before name { delete_start_node(); // so delete first name (we already know how return; // to do this!) } var n3, finished = false; while (finished == false) { if (nodes[n2].next == -1) finished = true; else { n3 = nodes[n2].next; // 3rd node in list if (nodes[n3],name = delete_before_name) { // Found the name, so do the delete! finished = true; } } } } |