From 96ae82e7547f276b544d1fc28fb78df66865d28f Mon Sep 17 00:00:00 2001 From: Adam Ross <14985050+R055A@users.noreply.github.com> Date: Sat, 20 Jul 2024 03:06:34 +1200 Subject: [PATCH 1/4] Add circular singly linked list --- .../CircularLinkedList.js | 276 ++++++++++++++++++ .../CircularLinkedListNode.js | 10 + 2 files changed, 286 insertions(+) create mode 100644 src/data-structures/circular-linked-list/CircularLinkedList.js create mode 100644 src/data-structures/circular-linked-list/CircularLinkedListNode.js diff --git a/src/data-structures/circular-linked-list/CircularLinkedList.js b/src/data-structures/circular-linked-list/CircularLinkedList.js new file mode 100644 index 000000000..44781dfbb --- /dev/null +++ b/src/data-structures/circular-linked-list/CircularLinkedList.js @@ -0,0 +1,276 @@ +import CircularLinkedListNode from './CircularLinkedListNode'; +import Comparator from '../../utils/comparator/Comparator'; + +/** + * Circular linked list. Based on linked list, but circular. + */ +export default class CircularLinkedList { + /** + * @param {Function} [comparatorFunction] + */ + constructor(comparatorFunction) { + /** @var CircularLinkedListNode */ + this.head = null; + + /** @var CircularLinkedListNode */ + this.tail = null; + + this.compare = new Comparator(comparatorFunction); + } + + /** + * @param {*} value + * @return {CircularLinkedList} + */ + prepend(value) { + // Make new node to be a head. + const newNode = new CircularLinkedListNode(value, this.head); + + if (!this.head) { + newNode.next = newNode; + this.tail = newNode; + } else { + this.tail.next = newNode; + } + this.head = newNode; + + return this; + } + + /** + * @param {*} value + * @return {CircularLinkedList} + */ + append(value) { + const newNode = new CircularLinkedListNode(value); + + // If no head, make new node a head, else attach to the tail of circular linked list. + if (!this.head) { + newNode.next = newNode; + this.head = newNode; + } else { + newNode.next = this.head; + this.tail.next = newNode; + } + this.tail = newNode; + + return this; + } + + /** + * @param {*} value + * @param {number} rawIndex + * @return {CircularLinkedList} + */ + insert(value, rawIndex) { + const index = rawIndex < 0 ? 0 : rawIndex; + const newNode = new CircularLinkedListNode(value); + + if (!this.head) { + newNode.next = newNode; + this.head = newNode; + this.tail = newNode; + return this; + } + + if (index === 0) { + this.prepend(value); + return this; + } + + let currentNode = this.head; + let count = 0; + while (count < index - 1 && currentNode.next !== this.head) { + currentNode = currentNode.next; + count += 1; + } + + newNode.next = currentNode.next; + currentNode.next = newNode; + if (currentNode === this.tail) { + this.tail = newNode; + } + + return this; + } + + /** + * @param {*} value + * @return {CircularLinkedListNode} + */ + delete(value) { + if (!this.head) { + return null; + } + + let deletedNode = null; + let currentNode = this.head; + + while (currentNode && this.compare.equal(currentNode.value, value)) { + deletedNode = currentNode; + this.head = this.head.next; + this.tail.next = this.head; + + if (currentNode === this.tail) { + this.head = null; + this.tail = null; + return deletedNode; + } + currentNode = this.head; + } + + let previousNode = this.tail; + + do { + if (this.compare.equal(currentNode.value, value)) { + deletedNode = currentNode; + previousNode.next = currentNode.next; + + if (currentNode === this.tail) { + this.tail = previousNode; + } + } else { + previousNode = currentNode; + } + currentNode = currentNode.next; + } while (currentNode !== this.head); + + return deletedNode; + } + + /** + * @return {CircularLinkedListNode} + */ + deleteHead() { + if (!this.head) { + return null; + } + + const deletedHead = this.head; + + if (this.head === this.tail) { + this.head = null; + this.tail = null; + } else { + this.head = this.head.next; + this.tail.next = this.head; + } + + return deletedHead; + } + + /** + * @return {CircularLinkedListNode} + */ + deleteTail() { + if (!this.tail) { + return null; + } + + const deletedTail = this.tail; + + if (this.head === this.tail) { + this.head = null; + this.tail = null; + } else { + let currentNode = this.head; + while (currentNode.next !== this.tail) { + currentNode = currentNode.next; + } + currentNode.next = this.head; + this.tail = currentNode; + } + + return deletedTail; + } + + /** + * @param {Object} findParams + * @param {*} findParams.value + * @param {function} [findParams.callback] + * @return {CircularLinkedListNode} + */ + find({ value = undefined, callback = undefined }) { + if (!this.head) { + return null; + } + + let currentNode = this.head; + + do { + // 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; + } while (currentNode !== this.head); + + return null; + } + + /** + * @param {*[]} values - Array of values that need to be converted to a circular linked list. + * @return {CircularLinkedList} + */ + fromArray(values) { + values.forEach((value) => this.append(value)); + return this; + } + + /** + * @return {CircularLinkedListNode[]} + */ + toArray() { + const nodes = []; + if (!this.head) return nodes; + + let currentNode = this.head; + do { + nodes.push(currentNode); + currentNode = currentNode.next; + } while (currentNode !== this.head); + + return nodes; + } + + /** + * @param {function} [callback] + * @return {string} + */ + toString(callback) { + return this.toArray().map((node) => node.toString(callback)).toString(); + } + + /** + * Reverse a circular linked list. + * @returns {CircularLinkedList} + */ + reverse() { + if (!this.head || this.head === this.tail) { + return this; + } + + let currentNode = this.head; + let prevNode = this.tail; + let nextNode; + + do { + nextNode = currentNode.next; + currentNode.next = prevNode; + prevNode = currentNode; + currentNode = nextNode; + } while (currentNode !== this.head); + + this.tail = this.head; + this.head = prevNode; + this.tail.next = this.head; + + return this; + } +} diff --git a/src/data-structures/circular-linked-list/CircularLinkedListNode.js b/src/data-structures/circular-linked-list/CircularLinkedListNode.js new file mode 100644 index 000000000..ea65d91e2 --- /dev/null +++ b/src/data-structures/circular-linked-list/CircularLinkedListNode.js @@ -0,0 +1,10 @@ +export default class CircularLinkedListNode { + constructor(value, next = null) { + this.value = value; + this.next = next; + } + + toString(callback) { + return callback ? callback(this.value) : `${this.value}`; + } +} From e5f201b45c45b91738abac7fc1a92add8c2bf6e2 Mon Sep 17 00:00:00 2001 From: Adam Ross <14985050+R055A@users.noreply.github.com> Date: Sat, 20 Jul 2024 03:08:14 +1200 Subject: [PATCH 2/4] Add test suite for circular linked list 18 test cases 99% coverage --- .../__test__/CircularLinkedList.test.js | 368 ++++++++++++++++++ .../__test__/CircularLinkedListNode.test.js | 46 +++ 2 files changed, 414 insertions(+) create mode 100644 src/data-structures/circular-linked-list/__test__/CircularLinkedList.test.js create mode 100644 src/data-structures/circular-linked-list/__test__/CircularLinkedListNode.test.js diff --git a/src/data-structures/circular-linked-list/__test__/CircularLinkedList.test.js b/src/data-structures/circular-linked-list/__test__/CircularLinkedList.test.js new file mode 100644 index 000000000..a5fd68c40 --- /dev/null +++ b/src/data-structures/circular-linked-list/__test__/CircularLinkedList.test.js @@ -0,0 +1,368 @@ +import CircularLinkedList from '../CircularLinkedList'; + +/** + * Circular linked list test. Based on linked list test, but circular. + */ +describe('CircularLinkedList', () => { + it('should create empty circular linked list', () => { + const circularLinkedList = new CircularLinkedList(); + expect(circularLinkedList.toString()).toBe(''); + }); + + it('should append node to circular linked list', () => { + const circularLinkedList = new CircularLinkedList(); + + expect(circularLinkedList.head).toBeNull(); + expect(circularLinkedList.tail).toBeNull(); + + circularLinkedList.append(1); + expect(circularLinkedList.head.toString()).toBe('1'); + expect(circularLinkedList.tail.toString()).toBe('1'); + + circularLinkedList.append(2); + expect(circularLinkedList.head.value).toBe(1); + expect(circularLinkedList.tail.value).toBe(2); + expect(circularLinkedList.tail.next).toBe(circularLinkedList.head); + expect(circularLinkedList.toString()).toBe('1,2'); + }); + + it('should prepend node to circular linked list', () => { + const circularLinkedList = new CircularLinkedList(); + + circularLinkedList.prepend(1); + expect(circularLinkedList.head.toString()).toBe('1'); + expect(circularLinkedList.tail.toString()).toBe('1'); + + circularLinkedList.append(2); + expect(circularLinkedList.head.value).toBe(1); + expect(circularLinkedList.tail.value).toBe(2); + expect(circularLinkedList.tail.next).toBe(circularLinkedList.head); + expect(circularLinkedList.toString()).toBe('1,2'); + + circularLinkedList.prepend(3); + expect(circularLinkedList.head.value).toBe(3); + expect(circularLinkedList.tail.value).toBe(2); + expect(circularLinkedList.tail.next).toBe(circularLinkedList.head); + expect(circularLinkedList.toString()).toBe('3,1,2'); + }); + + it('should insert node to circular linked list', () => { + const circularLinkedList = new CircularLinkedList(); + + circularLinkedList.insert(4, 3); + expect(circularLinkedList.head.toString()).toBe('4'); + expect(circularLinkedList.tail.toString()).toBe('4'); + + circularLinkedList.insert(3, 2); + circularLinkedList.insert(2, 1); + circularLinkedList.insert(1, -7); + circularLinkedList.insert(10, 9); + + expect(circularLinkedList.toString()).toBe('1,4,2,3,10'); + }); + + it('should create circular linked list from array', () => { + const circularLinkedList = new CircularLinkedList(); + circularLinkedList.fromArray([1, 1, 2, 3, 3, 3, 4, 5]); + + expect(circularLinkedList.toString()).toBe('1,1,2,3,3,3,4,5'); + }); + + it('should delete node by value from circular linked list', () => { + const circularLinkedList = new CircularLinkedList(); + + expect(circularLinkedList.delete(5)).toBeNull(); + + circularLinkedList.append(1); + circularLinkedList.append(1); + circularLinkedList.append(2); + circularLinkedList.append(3); + circularLinkedList.append(3); + circularLinkedList.append(3); + circularLinkedList.append(4); + circularLinkedList.append(5); + + expect(circularLinkedList.head.toString()).toBe('1'); + expect(circularLinkedList.tail.toString()).toBe('5'); + + let deletedNode = circularLinkedList.delete(3); + expect(deletedNode.value).toBe(3); + expect(circularLinkedList.toString()).toBe('1,1,2,4,5'); + + deletedNode = circularLinkedList.delete(3); + expect(deletedNode).toBeNull(); + expect(circularLinkedList.toString()).toBe('1,1,2,4,5'); + + deletedNode = circularLinkedList.delete(1); + expect(deletedNode.value).toBe(1); + expect(circularLinkedList.toString()).toBe('2,4,5'); + + expect(circularLinkedList.head.toString()).toBe('2'); + expect(circularLinkedList.tail.next).toBe(circularLinkedList.head); + expect(circularLinkedList.tail.toString()).toBe('5'); + + deletedNode = circularLinkedList.delete(5); + expect(deletedNode.value).toBe(5); + expect(circularLinkedList.toString()).toBe('2,4'); + + expect(circularLinkedList.head.toString()).toBe('2'); + expect(circularLinkedList.tail.toString()).toBe('4'); + + deletedNode = circularLinkedList.delete(4); + expect(deletedNode.value).toBe(4); + expect(circularLinkedList.toString()).toBe('2'); + + expect(circularLinkedList.head.toString()).toBe('2'); + expect(circularLinkedList.tail.toString()).toBe('2'); + + deletedNode = circularLinkedList.delete(2); + expect(deletedNode.value).toBe(2); + expect(circularLinkedList.toString()).toBe(''); + }); + + it('should delete non-existent node by value from circular linked list', () => { + const circularLinkedList = new CircularLinkedList(); + + expect(circularLinkedList.delete(5)).toBeNull(); + + circularLinkedList.append(1); + circularLinkedList.append(1); + circularLinkedList.append(3); + circularLinkedList.append(3); + circularLinkedList.append(3); + circularLinkedList.append(4); + circularLinkedList.append(5); + + expect(circularLinkedList.head.toString()).toBe('1'); + expect(circularLinkedList.tail.toString()).toBe('5'); + + const notDeletedNode = circularLinkedList.delete(2); + expect(notDeletedNode).toBe(null); + expect(circularLinkedList.toString()).toBe('1,1,3,3,3,4,5'); + }); + + it('should delete circular linked list tail', () => { + const circularLinkedList = new CircularLinkedList(); + + expect(circularLinkedList.deleteTail()).toBeNull(); + + circularLinkedList.append(1); + circularLinkedList.append(2); + circularLinkedList.append(3); + + expect(circularLinkedList.head.toString()).toBe('1'); + expect(circularLinkedList.tail.toString()).toBe('3'); + + const deletedNode1 = circularLinkedList.deleteTail(); + + expect(deletedNode1.value).toBe(3); + expect(circularLinkedList.toString()).toBe('1,2'); + expect(circularLinkedList.head.toString()).toBe('1'); + expect(circularLinkedList.tail.toString()).toBe('2'); + + const deletedNode2 = circularLinkedList.deleteTail(); + + expect(deletedNode2.value).toBe(2); + expect(circularLinkedList.toString()).toBe('1'); + expect(circularLinkedList.head.toString()).toBe('1'); + expect(circularLinkedList.tail.toString()).toBe('1'); + + const deletedNode3 = circularLinkedList.deleteTail(); + + expect(deletedNode3.value).toBe(1); + expect(circularLinkedList.toString()).toBe(''); + expect(circularLinkedList.head).toBeNull(); + expect(circularLinkedList.tail).toBeNull(); + }); + + it('should delete circular linked list head', () => { + const circularLinkedList = new CircularLinkedList(); + + expect(circularLinkedList.deleteHead()).toBeNull(); + + circularLinkedList.append(1); + circularLinkedList.append(2); + + expect(circularLinkedList.head.toString()).toBe('1'); + expect(circularLinkedList.tail.toString()).toBe('2'); + + const deletedNode1 = circularLinkedList.deleteHead(); + + expect(deletedNode1.value).toBe(1); + expect(circularLinkedList.toString()).toBe('2'); + expect(circularLinkedList.head.toString()).toBe('2'); + expect(circularLinkedList.tail.toString()).toBe('2'); + + const deletedNode2 = circularLinkedList.deleteHead(); + + expect(deletedNode2.value).toBe(2); + expect(circularLinkedList.toString()).toBe(''); + expect(circularLinkedList.head).toBeNull(); + expect(circularLinkedList.tail).toBeNull(); + }); + + it('should be possible to store objects in the list and to print them out', () => { + const circularLinkedList = new CircularLinkedList(); + + const nodeValue1 = { value: 1, key: 'key1' }; + const nodeValue2 = { value: 2, key: 'key2' }; + + circularLinkedList + .append(nodeValue1) + .prepend(nodeValue2); + + const nodeStringifier = (value) => `${value.key}:${value.value}`; + + expect(circularLinkedList.toString(nodeStringifier)).toBe('key2:2,key1:1'); + }); + + it('should find node by value', () => { + const circularLinkedList = new CircularLinkedList(); + + expect(circularLinkedList.find({ value: 5 })).toBeNull(); + + circularLinkedList.append(1); + expect(circularLinkedList.find({ value: 1 })).toBeDefined(); + + circularLinkedList + .append(2) + .append(3); + + const node = circularLinkedList.find({ value: 2 }); + + expect(node.value).toBe(2); + expect(circularLinkedList.find({ value: 5 })).toBeNull(); + }); + + it('should find node by callback', () => { + const circularLinkedList = new CircularLinkedList(); + + circularLinkedList + .append({ value: 1, key: 'test1' }) + .append({ value: 2, key: 'test2' }) + .append({ value: 3, key: 'test3' }); + + const node = circularLinkedList.find({ callback: (value) => value.key === 'test2' }); + + expect(node).toBeDefined(); + expect(node.value.value).toBe(2); + expect(node.value.key).toBe('test2'); + expect(circularLinkedList.find({ callback: (value) => value.key === 'test5' })).toBeNull(); + }); + + it('should create circular linked list from array', () => { + const circularLinkedList = new CircularLinkedList(); + circularLinkedList.fromArray([1, 1, 2, 3, 3, 3, 4, 5]); + + expect(circularLinkedList.toString()).toBe('1,1,2,3,3,3,4,5'); + }); + + it('should find node by means of custom compare function', () => { + const comparatorFunction = (a, b) => { + if (a.customValue === b.customValue) { + return 0; + } + + return a.customValue < b.customValue ? -1 : 1; + }; + + const circularLinkedList = new CircularLinkedList(comparatorFunction); + + circularLinkedList + .append({ value: 1, customValue: 'test1' }) + .append({ value: 2, customValue: 'test2' }) + .append({ value: 3, customValue: 'test3' }); + + const node = circularLinkedList.find({ + value: { value: 2, customValue: 'test2' }, + }); + + expect(node).toBeDefined(); + expect(node.value.value).toBe(2); + expect(node.value.customValue).toBe('test2'); + expect(circularLinkedList.find({ value: { value: 2, customValue: 'test5' } })).toBeNull(); + }); + + it('should find preferring callback over compare function', () => { + const greaterThan = (value, compareTo) => (value > compareTo ? 0 : 1); + + const circularLinkedList = new CircularLinkedList(greaterThan); + circularLinkedList.fromArray([1, 2, 3, 4, 5]); + + let node = circularLinkedList.find({ value: 3 }); + expect(node.value).toBe(4); + + node = circularLinkedList.find({ callback: (value) => value < 3 }); + expect(node.value).toBe(1); + }); + + it('should convert to array', () => { + const circularLinkedList = new CircularLinkedList(); + circularLinkedList.append(1); + circularLinkedList.append(2); + circularLinkedList.append(3); + expect(circularLinkedList.toArray().map((node) => node.value).join(',')).toBe('1,2,3'); + }); + + it('should reverse circular linked list', () => { + const circularLinkedList = new CircularLinkedList(); + + // Add test values to circular linked list. + circularLinkedList + .append(1) + .append(2) + .append(3); + + expect(circularLinkedList.toString()).toBe('1,2,3'); + expect(circularLinkedList.head.value).toBe(1); + expect(circularLinkedList.tail.value).toBe(3); + + // Reverse circular linked list. + circularLinkedList.reverse(); + + expect(circularLinkedList.toString()).toBe('3,2,1'); + expect(circularLinkedList.head.value).toBe(3); + expect(circularLinkedList.head.next.value).toBe(2); + expect(circularLinkedList.head.next.next.value).toBe(1); + expect(circularLinkedList.head.next.next.next).toBe(circularLinkedList.head); + + expect(circularLinkedList.tail.value).toBe(1); + expect(circularLinkedList.tail.next).toBe(circularLinkedList.head); + + // Reverse circular linked list back to initial state. + circularLinkedList.reverse(); + + expect(circularLinkedList.toString()).toBe('1,2,3'); + expect(circularLinkedList.head.value).toBe(1); + expect(circularLinkedList.head.next.value).toBe(2); + expect(circularLinkedList.head.next.next.value).toBe(3); + expect(circularLinkedList.head.next.next.next).toBe(circularLinkedList.head); + + expect(circularLinkedList.tail.value).toBe(3); + expect(circularLinkedList.tail.next).toBe(circularLinkedList.head); + }); + + it('should have circular reference', () => { + const circularLinkedList = new CircularLinkedList(); + + circularLinkedList.append(1); + circularLinkedList.append(2); + circularLinkedList.append(3); + circularLinkedList.append(4); + circularLinkedList.append(5); + + expect(circularLinkedList.tail.next).toBe(circularLinkedList.head); + + circularLinkedList.delete(1); + expect(circularLinkedList.tail.next).toBe(circularLinkedList.head); + + circularLinkedList.deleteTail(); + expect(circularLinkedList.tail.next).toBe(circularLinkedList.head); + + circularLinkedList.delete(5); + expect(circularLinkedList.tail.next).toBe(circularLinkedList.head); + + circularLinkedList.deleteHead(); + expect(circularLinkedList.tail.next).toBe(circularLinkedList.head); + }); +}); diff --git a/src/data-structures/circular-linked-list/__test__/CircularLinkedListNode.test.js b/src/data-structures/circular-linked-list/__test__/CircularLinkedListNode.test.js new file mode 100644 index 000000000..6b9810d8a --- /dev/null +++ b/src/data-structures/circular-linked-list/__test__/CircularLinkedListNode.test.js @@ -0,0 +1,46 @@ +import CircularLinkedListNode from '../CircularLinkedListNode'; + +describe('CircularLinkedListNode', () => { + it('should create list node with value', () => { + const node = new CircularLinkedListNode(1); + + expect(node.value).toBe(1); + expect(node.next).toBeNull(); + }); + + it('should create list node with object as a value', () => { + const nodeValue = { value: 1, key: 'test' }; + const node = new CircularLinkedListNode(nodeValue); + + expect(node.value.value).toBe(1); + expect(node.value.key).toBe('test'); + expect(node.next).toBeNull(); + }); + + it('should link nodes together', () => { + const node2 = new CircularLinkedListNode(2); + const node1 = new CircularLinkedListNode(1, node2); + + expect(node1.next).toBeDefined(); + expect(node2.next).toBeNull(); + expect(node1.value).toBe(1); + expect(node1.next.value).toBe(2); + }); + + it('should convert node to string', () => { + const node = new CircularLinkedListNode(1); + + expect(node.toString()).toBe('1'); + + node.value = 'string value'; + expect(node.toString()).toBe('string value'); + }); + + it('should convert node to string with custom stringifier', () => { + const nodeValue = { value: 1, key: 'test' }; + const node = new CircularLinkedListNode(nodeValue); + const toStringCallback = (value) => `value: ${value.value}, key: ${value.key}`; + + expect(node.toString(toStringCallback)).toBe('value: 1, key: test'); + }); +}); From 104baea471feb9bd8873a5095d0cf8cec3ddf87a Mon Sep 17 00:00:00 2001 From: Adam Ross <14985050+R055A@users.noreply.github.com> Date: Sat, 20 Jul 2024 03:08:42 +1200 Subject: [PATCH 3/4] Add README.md for circular linked list is missing image --- .../circular-linked-list/README.md | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 src/data-structures/circular-linked-list/README.md diff --git a/src/data-structures/circular-linked-list/README.md b/src/data-structures/circular-linked-list/README.md new file mode 100644 index 000000000..fd523590e --- /dev/null +++ b/src/data-structures/circular-linked-list/README.md @@ -0,0 +1,164 @@ +# Circular Linked List + +In computer science, a **circular linked list** is a variation of +the [linked list](../linked-list/README.md) in which the last node +in the list points back to the first node, creating a sequentially +circular structure. This means it operates the same as the linked +list with exception to pointing at the memory address of the first +node at the end instead of a null reference, allowing for traversal +starting at any node. It is useful in applications where the data +needs to be accessed in a loop or circular manner, such as round-robin +scheduling, for example. Circular linked lists can be either singly +or doubly linked, and in this case it is the former. + +![Circular Linked List](./images/circular-linked-list.jpeg) + +## Pseudocode for Basic Operations + +### Insert + +```text +Add(value) + Pre: value is the value to add to the list + Post: value has been placed at the tail of the list + n ← node(value) + if head = ø + n.next ← n + head ← n + else + n.next ← head + tail.next ← n + end if + tail ← n +end Add +``` + +```text +Prepend(value) + Pre: value is the value to add to the list + Post: value has been placed at the head of the list + n ← node(value) + if head = ø + n.next ← n + tail ← n + else + tail.next ← n + end + head ← n +end Prepend +``` + +### Search + +```text +Contains(head, value) + Pre: head is the head node in the list + value is the value to search for + Post: the item is either in the linked list, true; otherwise false + if head = ø + return false + end if + n ← head + repeat + if n.value = value + return true + end if + n ← n.next + until n = head + return false +end Contains +``` + +### Delete + +```text +Remove(head, value) + Pre: head is the head node in the list + value is the value(s) to remove from the list + Post: value is removed from the list, true, otherwise false + if head = ø + return false + end if + n ← head + while n.next != ø and n.next.value != value + n ← n.next + tail.next ← n + if n = tail + head ← ø + tail ← ø + return true + end if + n ← head + end while + previous ← tail + repeat + if n.value = value + previous.next ← n.next + if n = tail + tail ← previous + end if + else + previous ← n + n ← n.next + until n = head + if n = ø + return false + end if + return true +end Remove +``` + +### Traverse + +```text +Traverse(head) + Pre: head is the head node in the list + Post: the items in the list have been traversed + if head = ø + return + end if + n ← head + repeat + yield n.value + n ← n.next + until n = head +end Traverse +``` + +### Traverse in Reverse + +```text +ReverseTraversal(head, tail) + Pre: head and tail belong to the same list + Post: the items in the list have been traversed in reverse order + if tail = ø + return + end if + n ← tail + repeat + previous ← head + while previous.next != n + previous ← previous.next + end while + yield n.value + n ← previous + until n = tail +end ReverseTraversal +``` + +## Complexities + +### Time Complexity + +| Access | Search | Insertion | Deletion | +| :-------: | :-------: | :-------: | :-------: | +| O(n) | O(n) | O(1) | O(n) | + +### Space Complexity + +O(n) + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Linked_list) +- [YouTube](https://www.youtube.com/watch?v=HMkdlu5sP4A) From 1b0d97e2b8fcf41f023059c281ec89d319f95d9e Mon Sep 17 00:00:00 2001 From: Adam Ross <14985050+R055A@users.noreply.github.com> Date: Sat, 20 Jul 2024 04:24:59 +1200 Subject: [PATCH 4/4] Update README.md Add link to `src/data-structures/circular-linked-list` --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a20673989..e01dc0e41 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Remember that each data has its own trade-offs. And you need to pay attention mo `B` - Beginner, `A` - Advanced * `B` [Linked List](src/data-structures/linked-list) +* `B` [Circular Linked List](src/data-structures/circular-linked-list) * `B` [Doubly Linked List](src/data-structures/doubly-linked-list) * `B` [Queue](src/data-structures/queue) * `B` [Stack](src/data-structures/stack)