Related
I'm trying to make a binary search tree. If I start with an array my function makes a binary search tree out of that array (everything fine here). like for an array like let a = [2,4,5,3,9,7,3,8,5]; the tree looks like the picture. my problem is with the insert() function. If I start with an empty array and add a node to it, it works. However, when I add a second node, that second node won't be added to my tree, and my tree is shown as having only 1 node in it (the root node). Here the snippet:
const Node = (data, left = null, right = null) => {
return {data, left, right};
};
const Tree = array => {
const remDupsAndSort = array => {
const mergeSort = array => {
if(array.length <= 1) return array;
let leftArr = array.slice(0, array.length / 2);
let rightArr = array.slice(array.length / 2);
return merge(mergeSort(rightArr), mergeSort(leftArr))
};
const merge = (leftArr, rightArr) => {
let sorted = [];
while(leftArr.length && rightArr.length){
if(leftArr[0] < rightArr[0]){
sorted.push(leftArr.shift());
}else{
sorted.push(rightArr.shift());
}
};
return [...sorted, ...leftArr, ...rightArr]
};
return mergeSort([... new Set(array)])
};
array = remDupsAndSort(array);
const buildTree = (array, start, end) => {
if(start > end) return null;
let mid = Math.floor((start + end) / 2);
let node = Node(array[mid]);
node.left = buildTree(array, start, mid - 1);
node.right = buildTree(array, mid + 1, end);
return node;
};
const insert = value => {
if(!root) return root = Node(value);
current = root;
while(current){
if(value < current){
current = current.left
}else{
current = current.right
}
}
current = Node(value)
// if(!current){
// current = Node(value)
// // }else{
// if(value < current){
// current.left = insert(value, current.left)
// }else{
// current.right = insert(value, current.right)
// }
// }
return root
};
const prettyPrint = (node = root, prefix = '', isLeft = true) => {
if(node){
if (node.right !== null) {
prettyPrint(node.right, `${prefix}${isLeft ? '│ ' : ' '}`, false);
}
console.log(`${prefix}${isLeft ? '└── ' : '┌── '}${node.data}`);
if (node.left !== null) {
prettyPrint(node.left, `${prefix}${isLeft ? ' ' : '│ '}`, true);
}
}else{
console.log(node)
}
}
let root = buildTree(array, 0, array.length - 1);
return {root, prettyPrint, insert}
};
let a = [2,4,5,3,9,7,3,8,5];
let b = [];
let c = [1,2,4,5,6,7]
let f = Tree(a)
let d = Tree(b)
let e = Tree(c)
d.insert(4)
// d.insert(8) ---> doesn't work
// d.prettyPrint()
// f.insert(1) ---> doesn't work
f.prettyPrint()
// e.prettyPrint()
// console.log(d.root)
if I run d.prettyPrint() I'll get └── 4 just as expected. But if I run d.insert(8) after that 8 isn't added to the tree and the code returns └── 4 again. To make matters more confusing if I console.log(d.root) it returns null even though my prettyPrint function returns └── 4 as the root.
Clearly I expect the nodes be added to the tree. On one of my attempts I tried to write the code like this:
const insert = (value, current = root) => {
if(!current){
current = Node(value)
}else{
if(value < current){
current.left = insert(value, current.left)
}else{
current.right = insert(value, current.right)
}
}
return current
};
even though I assigned current = root the code returned null for d.insert(4)
There are these issues in your insert function:
current is implicitly defined as a global variable -- this wouldn't parse in strict mode. It should be declared as a local variable, using let
The value is compared with a node object instead of with the data of that node. So value < current should be changed to value < current.data
The assignment of the new node object to current -- after the loop -- will not mutate the tree. It merely assigns that object to that variable. Such assignment can never change the tree, just like current = current.right does not mutate the tree either. You need instead to assign the new node to either the left or right property of the parent of current. So this assignment should happen one step earlier.
Correction:
const insert = value => {
if(!root) return root = Node(value);
let current = root; // Define with `let`
while(current){
if(value < current.data){ // Compare with the data, not the node
// Mutate the parent node when inserting
if (!current.left) return current.left = Node(value);
current = current.left
}else{
if (!current.right) return current.right = Node(value);
current = current.right
}
}
};
A SkipList is a probabilistic data structure used, at least in part, for implementing an ordered key/value map. It is arranged in levels where higher levels skip nodes (the higher up, the more it skips), and the lowest level contains the nodes. As far as I have read, each level is implemented as a linked list, possibly a doubly linked list. And for some reason, skip lists are better for concurrency because in a multithreaded environment, they can be implemented without locks to enable fast/optimal performance as compared to say red/black trees or B+trees.
Here we have a "proper skip list" implemented in JavaScript, copied here for reference. The link has a nice suite of tests to show how it works.
const DEFAULT_STACK_UP_PROBABILITY = 0.25;
class ProperSkipList {
constructor(options) {
options = options || {};
this.stackUpProbability = options.stackUpProbability || DEFAULT_STACK_UP_PROBABILITY;
this.updateLength = options.updateLength !== false;
this.typePriorityMap = {
'undefined': 0,
'object': 1,
'number': 2,
'bigint': 2,
'string': 3
};
this.clear();
}
clear() {
let headNode = {
prev: null
};
let tailNode = {
next: null
};
this.head = {
isHead: true,
key: undefined,
value: undefined,
nodes: [headNode]
};
this.tail = {
isTail: true,
key: undefined,
value: undefined,
nodes: [tailNode]
};
headNode.next = tailNode;
tailNode.prev = headNode;
headNode.group = this.head;
tailNode.group = this.tail;
this.length = this.updateLength ? 0 : undefined;
}
upsert(key, value) {
let {matchingNode, prevNode, searchPath} = this._searchAndTrack(key);
if (matchingNode) {
let previousValue = matchingNode.group.value;
matchingNode.group.value = value;
return previousValue;
}
// Insert the entry.
let newNode = {
prev: prevNode,
next: prevNode.next
};
let newGroup = {
key,
value,
nodes: [newNode]
};
newNode.group = newGroup;
prevNode.next = newNode;
newNode.next.prev = newNode;
// Stack up the entry for fast search.
let layerIndex = 1;
while (Math.random() < this.stackUpProbability) {
let prevLayerNode = searchPath[layerIndex];
if (!prevLayerNode) {
let newHeadNode = {
prev: null,
group: this.head
};
let newTailNode = {
next: null,
group: this.tail
};
newHeadNode.next = newTailNode;
this.head.nodes.push(newHeadNode);
newTailNode.prev = newHeadNode;
this.tail.nodes.push(newTailNode);
prevLayerNode = newHeadNode;
}
let newNode = {
prev: prevLayerNode,
next: prevLayerNode.next,
group: newGroup
};
prevLayerNode.next = newNode;
newNode.next.prev = newNode;
newGroup.nodes.push(newNode);
layerIndex++;
}
if (this.updateLength) this.length++;
return undefined;
}
find(key) {
let {matchingNode} = this._search(key);
return matchingNode ? matchingNode.group.value : undefined;
}
has(key) {
return !!this.find(key);
}
_isAGreaterThanB(a, b) {
let typeA = typeof a;
let typeB = typeof b;
if (typeA === typeB) {
return a > b;
}
let typeAPriority = this.typePriorityMap[typeA];
let typeBPriority = this.typePriorityMap[typeB];
if (typeAPriority === typeBPriority) {
return a > b;
}
return typeAPriority > typeBPriority;
}
// The two search methods are similar but were separated for performance reasons.
_searchAndTrack(key) {
let layerCount = this.head.nodes.length;
let searchPath = new Array(layerCount);
let layerIndex = layerCount - 1;
let currentNode = this.head.nodes[layerIndex];
let prevNode = currentNode;
while (true) {
let currentNodeGroup = currentNode.group;
let currentKey = currentNodeGroup.key;
if (!currentNodeGroup.isTail) {
if (this._isAGreaterThanB(key, currentKey) || currentNodeGroup.isHead) {
prevNode = currentNode;
currentNode = currentNode.next;
continue;
}
if (key === currentKey) {
let matchingNode = currentNodeGroup.nodes[0];
searchPath[layerIndex] = matchingNode;
return {matchingNode, prevNode: matchingNode.prev, searchPath};
}
}
searchPath[layerIndex] = prevNode;
if (--layerIndex < 0) {
return {matchingNode: undefined, prevNode, searchPath};
}
currentNode = prevNode.group.nodes[layerIndex];
}
}
_search(key) {
let layerIndex = this.head.nodes.length - 1;
let currentNode = this.head.nodes[layerIndex];
let prevNode = currentNode;
while (true) {
let currentNodeGroup = currentNode.group;
let currentKey = currentNodeGroup.key;
if (!currentNodeGroup.isTail) {
if (this._isAGreaterThanB(key, currentKey) || currentNodeGroup.isHead) {
prevNode = currentNode;
currentNode = currentNode.next;
continue;
}
if (key === currentKey) {
let matchingNode = currentNodeGroup.nodes[0];
return {matchingNode, prevNode: matchingNode.prev};
}
}
if (--layerIndex < 0) {
return {matchingNode: undefined, prevNode};
}
currentNode = prevNode.group.nodes[layerIndex];
}
}
findEntriesFromMin() {
return this._createEntriesIteratorAsc(this.head.nodes[0].next);
}
findEntriesFromMax() {
return this._createEntriesIteratorDesc(this.tail.nodes[0].prev);
}
minEntry() {
let [key, value] = this.findEntriesFromMin().next().value;
return [key, value];
}
maxEntry() {
let [key, value] = this.findEntriesFromMax().next().value;
return [key, value];
}
minKey() {
return this.minEntry()[0];
}
maxKey() {
return this.maxEntry()[0];
}
minValue() {
return this.minEntry()[1];
}
maxValue() {
return this.maxEntry()[1];
}
_extractNode(matchingNode) {
let nodes = matchingNode.group.nodes;
for (let layerNode of nodes) {
let prevNode = layerNode.prev;
prevNode.next = layerNode.next;
prevNode.next.prev = prevNode;
}
if (this.updateLength) this.length--;
return matchingNode.group.value;
}
extract(key) {
let {matchingNode} = this._search(key);
if (matchingNode) {
return this._extractNode(matchingNode);
}
return undefined;
}
delete(key) {
return this.extract(key) !== undefined;
}
findEntries(fromKey) {
let {matchingNode, prevNode} = this._search(fromKey);
if (matchingNode) {
return {
matchingValue: matchingNode.group.value,
asc: this._createEntriesIteratorAsc(matchingNode),
desc: this._createEntriesIteratorDesc(matchingNode)
};
}
return {
matchingValue: undefined,
asc: this._createEntriesIteratorAsc(prevNode.next),
desc: this._createEntriesIteratorDesc(prevNode)
};
}
deleteRange(fromKey, toKey, deleteLeft, deleteRight) {
if (fromKey == null) {
fromKey = this.minKey();
deleteLeft = true;
}
if (toKey == null) {
toKey = this.maxKey();
deleteRight = true;
}
if (this._isAGreaterThanB(fromKey, toKey)) {
return;
}
let {prevNode: fromNode, searchPath: leftSearchPath, matchingNode: matchingLeftNode} = this._searchAndTrack(fromKey);
let {prevNode: toNode, searchPath: rightSearchPath, matchingNode: matchingRightNode} = this._searchAndTrack(toKey);
let leftNode = matchingLeftNode ? matchingLeftNode : fromNode;
let rightNode = matchingRightNode ? matchingRightNode : toNode.next;
if (leftNode === rightNode) {
if (deleteLeft) {
this._extractNode(leftNode);
}
return;
}
if (this.updateLength) {
let currentNode = leftNode;
while (currentNode && currentNode.next !== rightNode) {
this.length--;
currentNode = currentNode.next;
}
}
let leftGroupNodes = leftNode.group.nodes;
let rightGroupNodes = rightNode.group.nodes;
let layerCount = this.head.nodes.length;
for (let layerIndex = 0; layerIndex < layerCount; layerIndex++) {
let layerLeftNode = leftGroupNodes[layerIndex];
let layerRightNode = rightGroupNodes[layerIndex];
if (layerLeftNode && layerRightNode) {
layerLeftNode.next = layerRightNode;
layerRightNode.prev = layerLeftNode;
continue;
}
if (layerLeftNode) {
let layerRightmostNode = rightSearchPath[layerIndex];
if (!layerRightmostNode.group.isTail) {
layerRightmostNode = layerRightmostNode.next;
}
layerLeftNode.next = layerRightmostNode;
layerRightmostNode.prev = layerLeftNode;
continue;
}
if (layerRightNode) {
let layerLeftmostNode = leftSearchPath[layerIndex];
layerLeftmostNode.next = layerRightNode;
layerRightNode.prev = layerLeftmostNode;
continue;
}
// If neither left nor right nodes are present on the layer, connect based
// on search path to remove in-between entries.
let layerRightmostNode = rightSearchPath[layerIndex];
if (!layerRightmostNode.group.isTail) {
layerRightmostNode = layerRightmostNode.next;
}
let layerLeftmostNode = leftSearchPath[layerIndex];
layerLeftmostNode.next = layerRightmostNode;
layerRightmostNode.prev = layerLeftmostNode;
}
if (deleteLeft && matchingLeftNode) {
this._extractNode(matchingLeftNode);
}
if (deleteRight && matchingRightNode) {
this._extractNode(matchingRightNode);
}
}
_createEntriesIteratorAsc(currentNode) {
let i = 0;
return {
next: function () {
let currentGroup = currentNode.group;
if (currentGroup.isTail) {
return {
value: [currentNode.key, currentNode.value, i],
done: true
}
}
currentNode = currentNode.next;
return {
value: [currentGroup.key, currentGroup.value, i++],
done: currentGroup.isTail
};
},
[Symbol.iterator]: function () { return this; }
};
}
_createEntriesIteratorDesc(currentNode) {
let i = 0;
return {
next: function () {
let currentGroup = currentNode.group;
if (currentGroup.isHead) {
return {
value: [currentNode.key, currentNode.value, i],
done: true
}
}
currentNode = currentNode.prev;
return {
value: [currentGroup.key, currentGroup.value, i++],
done: currentGroup.isHead
};
},
[Symbol.iterator]: function () { return this; }
};
}
}
Where I am coming from is wondering how the theoretical concepts of a SkipList are applied in this implementation. But that is not my question, my question is very specific as described at the bottom. But take a look at a few things first. For example, the clear function is implemented like this, creating a fresh new SkipList:
clear() {
let headNode = {
prev: null
};
let tailNode = {
next: null
};
this.head = {
isHead: true,
key: undefined,
value: undefined,
nodes: [headNode]
};
this.tail = {
isTail: true,
key: undefined,
value: undefined,
nodes: [tailNode]
};
headNode.next = tailNode;
tailNode.prev = headNode;
headNode.group = this.head;
tailNode.group = this.tail;
this.length = this.updateLength ? 0 : undefined;
}
Notice the "group" as in headNode.group = this.head has this structure:
{
isTail: boolean,
isHead: boolean.
key: undefined,
value: undefined,
nodes: [node]
}
Or inserting a new node:
// Insert the entry.
let newNode = {
prev: prevNode,
next: prevNode.next
};
let newGroup = {
key,
value,
nodes: [newNode]
};
newNode.group = newGroup;
prevNode.next = newNode;
newNode.next.prev = newNode;
So a node has a prev/next/group set of properties, while a group has a key/value/nodes. Finally, notice that this.head.nodes[layerIndex] and such is being called in several places, which to me seems like it simply bypasses the linked-list nature of the SkipList and has an internal array that stores all the nodes anyway. Basically I am confused, I have been looking at this code for a day and it looks to me like each level node is storing an array of all the children nodes for that level, or something like that. I am confused with what is happening here.
Can one explain what the general model is for this SkipList implementation? That is:
The meaning of group and node objects, and why they have their structure they do.
What the nodes array is doing on a group.
For context, I am trying to learn how to implement a SkipList, after reading several papers on it and understanding the general idea. I want to put some constraints on it, so don't want to use any existing implementation. I want it to work in JavaScript and C, so it needs to be single-threaded in one context and multithreaded in the other, so I have a bit to learn. But knowing how to implement it in JavaScript, like what the above code is doing, is a good first step.
The summary of your question is:
The meaning of group and node objects, and why they have their structure they do.
What the nodes array is doing on a group.
I'll use an image taken from brilliant.org:
The linked lists appear as horizontal bands, while the arrays appear as vertical stacks.
A group corresponds to one distinct key in the set -- marked in yellow. The corresponding value is not depicted in the image, but is just payload and is not essential for understanding the data structure. A group has a nodes array -- displayed as a stack on top of the key. Each of these nodes belong to a different linked list in which they represent that same key. Those nodes have backreferences again to the group, so a node knows which key it represents, and which other linked lists have a node for this same key.
So the nodes array actually duplicates a key to different lists. One nodes array does not represent different keys in the collection -- just one. It is like an elevator to jump from one train (linked list) to another, with the same key.
The sizes of these arrays are randomly determined, but are intended to be small on average. This is driven by the stackUpProbability. It represents the probability that an array is extended with one more node (upwards in the image). The probability for having at least one node (for a key) is obviously 1, but the probability that the array will have at least one more node in it (and thus have a size of at least 2), is stackUpProbability. The probability that it will have size 3 is (stackUpProbability)²... etc.
This way, the bottom linked list (at layerIndex 1) will have all keys in the collection (one node per key, in sorted order), but any next layer will have fewer keys, and the top layer might only have a hand full. The lists with fewer nodes provide a way to travel fast along the collection in search of a value. This provides a proximity of where a value could be found. Via the "elevator" a search algorithm can step down to lower (slower) linked lists and continue the search there, until it reaches the lowest level where the key will be found (if present), or where it would have been (if not).
I have tried working on this for a good while and can't seem to find a way to terminate the loop. I am not sure if I am even on the right track. I am trying to do a breadth first (level order) and apply a callback on each node while doing the traversal.
Here is the constructor function and the method to do the breadth first search...
function BinarySearchTree(value) {
this.value = value;
this.right = null;
this.left = null;
}
BinarySearchTree.prototype.add = function(value) {
if (value < this.value) {
if (this.left) this.left.add(value);
else this.left = new BinarySearchTree(value);
}
if (value > this.value){
if (this.right) this.right.add(value);
else this.right = new BinarySearchTree(value);
}
};
BinarySearchTree.prototype.breadthFirst = function(callback) {
let queue = [];
queue.push(this.value);
while (queue.length) {
queue.pop();
callback(this.value);
if (this.left) queue.push(this.left);
if (this.right) queue.push(this.right);
}
};
Any ideas as to why I am getting an infinite loop? Any tips or help will be greatly appreciated!
UPDATED: Sample data...
var array = [];
var func = function(value){ array.push(value); };
binarySearchTree.add(2);
binarySearchTree.add(3);
binarySearchTree.add(7);
binarySearchTree.add(6);
console.log(binarySearchTree.breadthFirst(func)); -> should output [ 5, 2, 3, 7, 6 ]
I have tried this...
BinarySearchTree.prototype.breadthFirst = function(callback) {
const queue = [];
let queueLength = this.value.length;
if (queueLength) {
queueLength--;
callback(this.value);
if (this.left) {
queue.push(this.left);
this.left.breadthFirst(callback);
}
if (this.right) {
queue.push(this.right);
this.right.breadthFirst(callback);
}
};
};
and this...
BinarySearchTree.prototype.breadthFirst = function(callback) {
const queue = [];
let queueLength = this.value.length;
while (queueLength) {
queueLength--;
callback(this.value);
if (this.left) {
queue.push(this.left);
callback(this.left);
}
if (this.left) {
queue.push(this.left);
callback(this.left);
}
};
};
as well as other variations and I still get an empty array as output!
Since you are removing a value from the queue array by using pop method but then after callback function is called with this.value you are pushing to the queue array the condition of while statement is always true which is causing the infinite loop.
maybe you can have something like this;
BinarySearchTree.prototype.breadthFirst = function(callback) {
const queue = [];
let queueLength = this.value.length;
while (queueLength) {
queueLength--;
callback(this.value);
if (this.left) queue.push(this.left);
if (this.right) queue.push(this.right);
};
};
Apart from the issue that you fixed in the first update to your question, there are still some other issues:
The driver code with sample input lacks a statement that calls the constructor, which I assume is this one:
var binarySearchTree = new BinarySearchTree(5);
This binarySearchTree will be the this object when breadthFirst is executed. The value of this does not change during the loop, so the loop keeps pushing the same this.left and this.right nodes
Although pop() is called on the queue, its return value is ignored, yet that is what you need to process.
The queue is initialised with a node value, but the queue should have nodes, not node values. Otherwise you can never retrieve the node's children.
The final console.log in your driver code is not going to print anything useful, since the breadthFirst method is not designed to return anything. Instead it provides its results via the callback. So this console.log is useless.
As the callback in the driver code collects values in an array, you would want to print that array when the tree traversal has completed.
Correction
Here is your script with those issues resolved:
function BinarySearchTree(value) {
this.value = value;
this.right = null;
this.left = null;
}
BinarySearchTree.prototype.add = function(value) {
if (value < this.value) {
if (this.left) this.left.add(value);
else this.left = new BinarySearchTree(value);
}
if (value > this.value){
if (this.right) this.right.add(value);
else this.right = new BinarySearchTree(value);
}
};
BinarySearchTree.prototype.breadthFirst = function(callback) {
let queue = [];
queue.push(this); // Don't push the value, push the root node
while (queue.length) {
let node = queue.pop(); // Pop returns the node: capture it!
callback(node.value); // Work with that node, not with `this`
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
};
var array = [];
var func = function(value){ array.push(value); };
var binarySearchTree = new BinarySearchTree(5); // This was missing
binarySearchTree.add(2);
binarySearchTree.add(3);
binarySearchTree.add(7);
binarySearchTree.add(6);
binarySearchTree.breadthFirst(func); // Does not return anything: don't print
console.log(array); // Output what was collected: [ 5, 2, 3, 7, 6 ]
Modern version
Some aspects of your code could use some nicer programming patterns:
There is no way to define an empty tree with your code. It would be better to define a container that supports this state, meaning that you don't have to call its constructor with a first value, but can add all values using the add method.
Since ECMAScript 2015 we can use the class syntax
Instead of using a callback system, use a generator
Here is how the code would look with these ideas and some other updates:
class Node {
constructor(value) {
this.value = value;
this.right = this.left = null;
}
}
class BinarySearchTree {
constructor() {
this.root = null;
}
add(value) {
function recur(node, value) {
if (!node) return new Node(value);
if (value < node.value) {
node.left = recur(node.left, value);
} else if (value > node.value) {
node.right = recur(node.right, value);
}
return node;
}
this.root = recur(this.root, value);
}
*breadthFirst() {
const queue = [];
if (this.root) queue.push(this.root);
while (queue.length) {
const node = queue.pop();
yield node.value;
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
}
}
const binarySearchTree = new BinarySearchTree;
for (const value of [5, 2, 3, 7, 6]) binarySearchTree.add(value);
console.log(...binarySearchTree.breadthFirst());
I have the following Node constructor:
const Node = function(data){
this.data = data
this.next = null
this.previous = null
}
that is used inside of my LinkedList constructor:
const LinkedList = function(){
this.head = new Node('head')
}
and I can insert nodes with the following method:
LinkedList.prototype.insert = function(item,after){
const newNode = new Node(item)
const curr = after ? this.find(after) : this.head
newNode.next = curr.next
newNode.previous = curr
curr.next = newNode
}
with the find method being:
LinkedList.prototype.find = function(item){
let currentNode = this.head
while(currentNode && currentNode.data !== item){
currentNode = currentNode.next
}
return currentNode
}
And can view the items as an array with the following method:
LinkedList.prototype.toArray = function(){
const arr = []
let currItem = this.head.next
while(currItem){
arr.push(currItem.data)
currItem = currItem.next
}
return arr
}
My issue is now I am trying to implement a switch function on the LinkedList where I can pass in two values and switch their location in the list. Below is what I have and it seems to work for items that are not next to each other:
LinkedList.prototype.switch = function(a,b){
const aNode = this.find(a),
bNode = this.find(b)
if(!aNode || !bNode){
throw new Error('Both nodes were not inside of the list')
}
const aNext = aNode.next,
aPrevious = aNode.previous,
bNext = bNode.next,
bPrevious = bNode.previous
aNode.next = bNext
aNode.previous = bPrevious
aNode.previous.next = aNode
bNode.next = aNext
bNode.previous = aPrevious
bNode.previous.next = bNode
}
I am wondering what I am doing wrong here that is causing this to make my computer hit infinite recursion when I swap elements that are right next to each other. For instance, the below lines of code works:
const list = new LinkedList()
list.insert(1)
list.insert(2,1)
list.insert(3,2)
list.switch(1,3)
list.toArray() // [3,2,1]
However if I have the following code, it
const list = new LinkedList()
list.insert(1)
list.insert(2,1)
list.switch(1,2)
list.toArray() // crashes terminal
I know it is a stupid logical error in my switch method but I cannot for the life of me figure out what.
The problem that I see is in your insert function. If you have a linked list with two items and you call insert('New Node', null) your list looks like this:
You still need to set the previous pointer to the new node like this:
LinkedList.prototype.insert = function(item,after){
const newNode = new Node(item);
const curr = after ? this.find(after) : this.head;
newNode.next = curr.next;
curr.next.previous = newNode; <----- This is the extra line
newNode.previous = curr;
curr.next = newNode;
}
If bNode.previous is null, and if you assign as the following,
aNode.previous = bPrevious
aNode.previous.next = aNode
then you are trying to reach the next field of null, which causes to crash.
I just struggled through a simple interview question: Please reverse a singly linked list.
While I failed to provide a working answer in time to save the interview, I was able to come up with a solution afterwards.
Is my solution correct? How would you analyze this with Big-Oh? Are there more efficient ways to reverse a singly linked list?
// reverse a linked list
var reverseLinkedList = function(linkedlist) {
var node = linkedlist;
var previous = null;
while(node) {
// reverse pointer
node.next = previous;
// increment previous to current node
previous = node;
// increment node to next node
if (node.next){
node = node.next
} else {
node = null;
}
}
}
Note: In my search for similar posts, I did find one example in JavaScript. I was wondering if my code is possible (without a temp variable). Thank you.
There are a couple of problems with your code. This should make it clear.
// reverse a linked list
var reverseLinkedList = function(linkedlist) {
var node = linkedlist;
var previous = null;
while(node) {
// save next or you lose it!!!
var save = node.next;
// reverse pointer
node.next = previous;
// increment previous to current node
previous = node;
// increment node to next node or null at end of list
node = save;
}
return previous; // Change the list head !!!
}
linkedlist = reverseLinkedList(linkedlist);
You could solve this problem recursively in O(n) time as ckersch mentions. The thing is, that you need to know that recursion is memory intensive since functions accumulate in the calls stack until they hit the stop condition and start returning actual things.
The way I'd solve this problem is:
const reverse = (head) => {
if (!head || !head.next) {
return head;
}
let temp = reverse(head.next);
head.next.next = head;
head.next = undefined;
return temp;
}
When reverse() reaches the end of the list, it will grab the last node as the new head and reference each node backwards.
This would be O(n) in time, since you do a constant number of operations on each node. Conceptually, there isn't a more efficient way of doing things (in terms of big-O notation, there's some code optimization that could be done.)
The reason why you can't exceed O(n) is because, in order to do so, you would need to skip some nodes. Since you need to modify each node, this wouldn't be possible.
Efficiency then comes down to a constant factor. The fewer operations you can do per item in the list, the faster your code will execute.
I'd implement like this:
function reverseLinkedList(list, previous){
//We need to use the the current setting of
//list.next before we change it. We could save it in a temp variable,
//or, we could call reverseLinkedList recursively
if(list.next !== null){
reverseLinkedList(list.next, list);
}
//Everything after 'list' is now reversed, so we don't need list.next anymore.
//We passed previous in as an argument, so we can go ahead and set next to that.
list.next = previous;
}
reverseLinkedList(list, null);
Of course, this is recursive, so it would be inefficient in terms of space, but I like recursive code :)
This also doesn't return the reversed linked list, but we could fairly easily modify things to do so if that were important.
ES6 solution:
Just keep a track of the reversed list and keep adding that to tmp.
const reverseLinkedList = (head) => {
let reversed = null;
while(head) {
const tmp = head;
head = head.next;
tmp.next = reversed;
reversed = tmp;
}
return reversed;
};
console.log(JSON.stringify(reverseLinkedList({
data: 1,
next: {
data: 2,
next: {
data: 3,
next: {
data: 4,
next: {
data: 5,
next: {
data: 5,
next: {
data: 6
}
}
}
}
}
}
})));
Reversing the SinglyLinkedList:
Input: 1->2->3->4->5->NULL
Output: 5->4->3->2->1->NULL
To understand The Solution we have to keep track of previous head and next variables
for example in above input Head = 1 ; next = 2 we don't have previous so assume previous = null
loop the list till head is not null. reverse the connections(previous and next) of head.
Below is the code
var reverseList = function(head) {
let previous = null;
while(head !== null){
let next = head.next;
head.next = previous;
previous= head
head = next;
}
return previous;
};
//O(n) | O(1) wherre n is the number of nodes in the linked list
class Node{
constructor(val){
this.val = val;
this.next = null;
}
}
function reverseLinkedList(head) {
if(!head) return null;
let p1 = head;
let p2 = null;
while(p1){
let temp = p1.next;
p1.next = p2;
p2 = p1;
p1 = temp;
}
return p2;
}
const a = new Node(1);
a.next = new Node(2);
a.next.next = new Node(3)
console.log("Current Node",a);
console.log("Reversed List",reverseLinkedList(a))
class LinkedList {
constructor () {
this.head = this.tail = null
}
// add to the end of the list
append (value) {
if (!this.tail) {
this.head = this.tail = new Node(value)
} else {
let oldTail = this.head
this.head = new Node(value)
this.head.next = oldhead
}
}
reverseList() {
//your code here
let currentNode = this.head
this.head = null
while(currentNode) {
if (!this.head) {
this.head = new Node(currenthead.data)
} else {
let oldhead = this.head
this.head = new Node(currentNode.data)
this.head.next = oldhead
}
currentNode = currentNode.next
}
}
}
class Node {
constructor (value, next) {
this.data = value
this.next = next || null
}
}
const list = new LinkedList()
list.append(1)
list.append(2)
list.reverseList()
Since inserting data at the beginning of the linked list pushes other first nodes till the end, and since it's a O(1) process.
Then I created the following function reverse()
where it basically insert node elements in the beginning which basically will get a reversed list at the end.
Here's a demo down below:
class Node {
constructor(data, next = null) {
this.data = data;
this.next = next;
}
}
class LinkedList {
constructor() {
this.head = null;
this.size = 0;
}
insertFirst(data = null) {
// make new head point to the previous head
this.head = new Node(data, this.head);
this.size ++;
}
insertLast(data = null) { // insert last in the beginning will be the first in the linked list
const node = new Node(data);
// If empty, insert first
if (!this.head) this.insertFirst(data);
else {
let current = this.head;
// while next is not null, continue
while (current.next)
current = current.next;
// eventually next is null, we want to set next here to the node we want to add
current.next = node;
}
this.size ++;
}
// print linked list
print() {
let current = this.head;
let output = "";
while (current) { // while current is not null, eventually it will be null
output += current.data + " => ";
current = current.next; // current jumping to the next node
}
output += "| NULL"; // ending
console.log(output);
return output;
}
reverse() {
if (!this.head) return; // if no head, do nothing
let current = this.head;
const linkedList = new LinkedList(); // create a new linked list
// don't worry, it will be garbage collected once this function ends since it's not a global variable
while (current) {
linkedList.insertFirst(current.data); // insert first at the beginning will be the end of the linked list at the end
current = current.next;
}
// assign current head to the reversed linked list head
this.head = linkedList.head;
}
}
const linkedList = new LinkedList();
// fill data as 100 -> 200 -> 300 -> 400
linkedList.insertLast(100);
linkedList.insertLast(200);
linkedList.insertLast(300);
linkedList.insertLast(400);
// To view results
const bodyElement = document.getElementsByTagName("body")[0];
bodyElement.innerHTML = `<p>Original Linked List: <b>${linkedList.print()}</b></p>`; // 100 200 300 400
linkedList.reverse();
bodyElement.innerHTML += `<p>Reversed Linked List: <b>${linkedList.print()}</b></p>`; // 400 300 200 100
b {
color: green;
}
<body></body>
Overall, the whole process of this reverse() function is O(n).
Hopefully this sounds clear to you, and correct me if I'm wrong.
This is my recursive solution:
https://codesandbox.io/s/reverse-linked-list-tqh2tq?file=/src/index.js
let d = { me: "d" };
let c = { me: "c", next: d };
let b = { me: "b", next: c };
let a = { me: "a", next: b };
const reverseMe = (o) => {
let lastDude;
if (o.next.next) lastDude = reverseMe(o.next);
else lastDude = o.next;
o.next.next = o;
o.next = null;
return lastDude;
};
console.log("result", reverseMe(a));