forked from trekhleb/javascript-algorithms
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added doubly linked list (trekhleb#92)
* Added doubly linked list * improved doubly linked list coverage
- Loading branch information
Showing
5 changed files
with
510 additions
and
0 deletions.
There are no files selected for viewing
211 changes: 211 additions & 0 deletions
211
src/data-structures/doubly-linked-list/DoublyLinkedList.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
import DoublyLinkedListNode from './DoublyLinkedListNode'; | ||
import Comparator from './../../utils/comparator/Comparator'; | ||
|
||
export default class DoublyLinkedList { | ||
/** | ||
* @param {Function} [comparatorFunction] | ||
*/ | ||
constructor(comparatorFunction) { | ||
/** @var DoublyLinkedListNode */ | ||
this.head = null; | ||
|
||
/** @var DoublyLinkedListNode */ | ||
this.tail = null; | ||
|
||
this.compare = new Comparator(comparatorFunction); | ||
} | ||
|
||
/** | ||
* @param {*} value | ||
* @return {DoublyLinkedList} | ||
*/ | ||
prepend(value) { | ||
// Make new node to be a head. | ||
const newNode = new DoublyLinkedListNode(value, this.head); | ||
|
||
// If there is head, then it won't be head anymore | ||
// Therefore, make its previous reference to be new node (new head) | ||
// Then mark the new node as head | ||
if (this.head) { | ||
this.head.previous = newNode; | ||
} | ||
this.head = newNode; | ||
|
||
// If there is no tail yet let's make new node a tail. | ||
if (!this.tail) { | ||
this.tail = newNode; | ||
} | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* @param {*} value | ||
* @return {DoublyLinkedList} | ||
*/ | ||
append(value) { | ||
const newNode = new DoublyLinkedListNode(value); | ||
|
||
// If there is no head yet let's make new node a head. | ||
if (!this.head) { | ||
this.head = newNode; | ||
this.tail = newNode; | ||
|
||
return this; | ||
} | ||
|
||
// Attach new node to the end of linked list. | ||
this.tail.next = newNode; | ||
|
||
// Attach current tail to the new node's previous reference | ||
newNode.previous = this.tail; | ||
|
||
// Set new node to be the tail of linked list. | ||
this.tail = newNode; | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* @param {*} value | ||
* @return {DoublyLinkedListNode} | ||
*/ | ||
delete(value) { | ||
if (!this.head) { | ||
return null; | ||
} | ||
|
||
let deletedNode = null; | ||
let currentNode = this.head; | ||
|
||
do { | ||
if (this.compare.equal(currentNode.value, value)) { | ||
deletedNode = currentNode; | ||
|
||
if (deletedNode === this.head) { | ||
// set head to second node, which will become new head | ||
this.head = deletedNode.next; | ||
|
||
// set new head's previous to null | ||
if (this.head) { | ||
this.head.previous = null; | ||
} | ||
|
||
// If all the nodes in list has same value that is passed as argument | ||
// then all nodes will get deleted, therefore tail needs to be updated | ||
if (deletedNode === this.tail) { | ||
this.tail = null; | ||
} | ||
} else if (deletedNode === this.tail) { | ||
// set tail to second last node, which will become new tail | ||
this.tail = deletedNode.previous; | ||
this.tail.next = null; | ||
} else { | ||
const previousNode = deletedNode.previous; | ||
const nextNode = deletedNode.next; | ||
previousNode.next = nextNode; | ||
nextNode.previous = previousNode; | ||
} | ||
} | ||
|
||
currentNode = currentNode.next; | ||
} while (currentNode); | ||
|
||
return deletedNode; | ||
} | ||
|
||
/** | ||
* @param {Object} findParams | ||
* @param {*} findParams.value | ||
* @param {function} [findParams.callback] | ||
* @return {DoublyLinkedListNode} | ||
*/ | ||
find({ value = undefined, callback = undefined }) { | ||
if (!this.head) { | ||
return null; | ||
} | ||
|
||
let currentNode = this.head; | ||
|
||
while (currentNode) { | ||
// If callback is specified then try to find node by callback. | ||
if (callback && callback(currentNode.value)) { | ||
return currentNode; | ||
} | ||
|
||
// If value is specified then try to compare by value.. | ||
if (value !== undefined && this.compare.equal(currentNode.value, value)) { | ||
return currentNode; | ||
} | ||
|
||
currentNode = currentNode.next; | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* @return {DoublyLinkedListNode} | ||
*/ | ||
deleteTail() { | ||
if (!this.tail) { | ||
return null; | ||
} else if (this.head === this.tail) { | ||
const deletedTail = this.tail; | ||
this.head = null; | ||
this.tail = null; | ||
|
||
return deletedTail; | ||
} | ||
|
||
const deletedTail = this.tail; | ||
this.tail = this.tail.previous; | ||
this.tail.next = null; | ||
|
||
return deletedTail; | ||
} | ||
|
||
/** | ||
* @return {DoublyLinkedListNode} | ||
*/ | ||
deleteHead() { | ||
if (!this.head) { | ||
return null; | ||
} | ||
|
||
const deletedHead = this.head; | ||
|
||
if (this.head.next) { | ||
this.head = this.head.next; | ||
this.head.previous = null; | ||
} else { | ||
this.head = null; | ||
this.tail = null; | ||
} | ||
|
||
return deletedHead; | ||
} | ||
|
||
/** | ||
* @return {DoublyLinkedListNode[]} | ||
*/ | ||
toArray() { | ||
const nodes = []; | ||
|
||
let currentNode = this.head; | ||
while (currentNode) { | ||
nodes.push(currentNode); | ||
currentNode = currentNode.next; | ||
} | ||
|
||
return nodes; | ||
} | ||
|
||
/** | ||
* @param {function} [callback] | ||
* @return {string} | ||
*/ | ||
toString(callback) { | ||
return this.toArray().map(node => node.toString(callback)).toString(); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
src/data-structures/doubly-linked-list/DoublyLinkedListNode.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export default class DoublyLinkedListNode { | ||
constructor(value, next = null, previous = null) { | ||
this.value = value; | ||
this.next = next; | ||
this.previous = previous; | ||
} | ||
|
||
toString(callback) { | ||
return callback ? callback(this.value) : `${this.value}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Doubly Linked List | ||
|
||
In computer science, a doubly linked list is a linked data structure that consists of a set of sequentially linked records called nodes. Each node contains two fields, called links, that are references to the previous and to the next node in the sequence of nodes. The beginning and ending nodes' previous and next links, respectively, point to some kind of terminator, typically a sentinel node or null, to facilitate traversal of the list. If there is only one sentinel node, then the list is circularly linked via the sentinel node. It can be conceptualized as two singly linked lists formed from the same data items, but in opposite sequential orders. | ||
|
||
![Doubly Linked List](https://upload.wikimedia.org/wikipedia/commons/5/5e/Doubly-linked-list.svg) | ||
|
||
## References | ||
|
||
- [Wikipedia](https://en.wikipedia.org/wiki/Doubly_linked_list) | ||
- [YouTube](https://www.youtube.com/watch?v=JdQeNxWCguQ) |
Oops, something went wrong.