Linked list
Like arrays, linked lists can be used to store a series of elements, but the implementation mechanism of linked lists and arrays is completely different
classification
- Unidirectional linked list - Bidirectional linked list - One way circular linked list - Bidirectional circular linked list
Arrays and linked lists
- To store multiple elements, arrays, or lists, are probably the most commonly used data structures. - Each language has a default implementation array structure - However, arrays also have many disadvantages. To create an array, you usually need to apply for a large continuous space (a whole block of memory) - The size is fixed. When the array capacity is insufficient, it needs to be expanded - `The cost of inserting or deleting elements at the beginning or middle of the array is constant, and a large number of element displacements are required`
- Advantages of linked list
- The elements of the linked list do not have to be a continuous space in the memory. It can make full use of the computer memory and realize flexible computer dynamic management
- Each element of the linked list consists of a node that stores the element itself and a reference (pointer) to the next element
- The linked list does not have to be sized at the time of creation, and the size can be extended infinitely (similar to javascript arrays)
- When inserting and deleting data, the time complexity of linked list can reach O(1), which is much more efficient than data structure
- Disadvantages of linked list
- When accessing an element at any position in the linked list, you need to access it from the beginning (you can't skip the first element to access any element)
- Cannot access any element by subscript
- structure
- It is equivalent to the train structure. The locomotive is connected through nodes (the pointer points to) the next carriage. There are passengers (data) on the carriage
The node points to the next carriage, and there are passengers (data) in the carriage, and so on. There is no node in the last carriage
- It is equivalent to the train structure. The locomotive is connected through nodes (the pointer points to) the next carriage. There are passengers (data) on the carriage
One way linked list diagram
Realize one-way linked list
Common operations of linked list
- append(ele): add a new data to the end of the linked list
- insert(position, ele): adds a new item to the specified position in the linked list
- get(position): get the element at the specified position
- indexOf(ele): returns the index of the element in the linked list. If there is no such element in the linked list, it returns - 1
- update(position, ele): modify the element of a certain position
- removeAt(position); Delete the element at one position in the linked list
- remove(ele): deletes an item from the linked list
- isEmpty(): returns true if the length of the linked list is 0; otherwise, returns false
- size(): returns the number of elements in the linked list, similar to the length attribute of the array
- toString(): output only the value of the element
/* * Manual implementation of one-way linked list * */ // Using class class to implement one-way linked list class List { private next: { data } = null; private length: number = 0; // ------------------------ // Linked list insertion data method append<T>(data: T): object { // Instance a new data let obj = new NNode(data); // Judge whether the length of the linked list is empty if (this.length == 0) { // If it is empty, the next of the header will point to the first data added this.next = obj; // Add one to the length of the linked list this.length++; // Return added data return obj; } else { // The length of the linked list is greater than one // Create a variable pointer centerPointer to point to the current linked list pointer let centerPointer: { data: T, next }; centerPointer = this.next as { data: T, next }; // Judge whether there is next data in the next of the current pointer variable (whether it is empty, // Null indicates that the data where the pointer is located is the last data) while (centerPointer.next) { // When the data in the current linked list loop is not the last data, the next pointer points to the current data centerPointer = centerPointer.next; } // Then point the last data pointer of the linked list to the newly added data centerPointer.next = obj; this.length++; // Returns the data pointed to by the last pointer return centerPointer; } } // ------------------------ // Add data to the specified location of the linked list insert<T>(position: number, data: T): void { // Judge whether the insertion position is reasonable if (position > this.length) { throw new Error('Data cannot be inserted at this location'); } else if (position < 0) { throw new Error('Unreasonable incoming data'); } // Create variable // Temporary node variable let timeNode: { data: T, next }, // Created instance variable newN: { data: unknown, next?: object }, // Location variable index: number, // A pointer that receives the data after the added value preNext: { data: T, next }; // The temporary node is equal to the current header pointer timeNode = this.next as { data: T, next }; // The incoming data instance as an object newN = new NNode(data); index = 0; preNext = null; // When the new insertion position is 0 if (position === 0) { // Make the currently created node point to the node pointed by the head; newN.next = this.next; // Redirect the head node to the newly inserted node this.next = newN; } else { // The purpose of the loop is to obtain the node before inserting the target node, so as to facilitate the use of the next pointer of the previous node, // Get insert target node while (index++ < position) { // Gets the node in front of the anchored node preNext = timeNode; // console.log(preNext) // Make the temporary node equal to the following node pointed to by the current pointer, // When index = = = positioning position, the temporary node becomes the node positioning the insertion position timeNode = timeNode.next; // console.log(timeNode) } // When the cycle conditions are not met, the corresponding positioning position is found // Point the node pointer before inserting the node to the current new node preNext.next = newN; // Point the pointer of the new node to the temporary node, which stores the node at the new location newN.next = timeNode; } // Length change this.length++; } // ------------------------ // Update a location element update<T>(position: number, value: T): void { // Create temporary nodes and indexes let index: number, timeNode: { data: T, next? }; // Initialize indexes and nodes index = 0; timeNode = this.next; while (index < position) { // Node lookup timeNode = timeNode.next; index++; } // Change the data of the discovered node timeNode.data = value; } // ------------------------ // Gets the element at the specified location get(position): unknown { // The principle is the same as that of upDate method let index: number, timeNode: { data: unknown, next? }; index = 0; timeNode = this.next; while (index < position) { timeNode = timeNode.next; index++; } return timeNode.data; } // ------------------------ // Gets the index of the specified element indexOf<T>(value: T): number { let timeNode: { data: T, next? }, index: number; timeNode = this.next; index = 0; do { if (timeNode.data === value) { return index; } // Make the current temporary node equal to the next node pointed to by the current pointer timeNode = timeNode.next; index++; } while (index < this.length); return -1; } // ------------------------ // Delete linked list at specified location removeAt(position: number): void { let timeNode: { data: unknown, next? }, index: number, preNode: { data: unknown, next? }; timeNode = this.next; index = 0; preNode = null; // Judge when the deletion position is 0 if (position === 0) { // Make the current head pointer point to this Pointer to next (this.next was skipped because timeNode = this.next) this.next = timeNode.next; } else { while (index++ < position) { // Gets the node that precedes the target node preNode = timeNode; // Get delete target node timeNode = timeNode.next; } // Make the pointer of the previous node point to the pointer of the deleted node preNode.next = timeNode.next; } // Length-- this.length--; } // ------------------------ //Delete an element remove<T>(value: T): void { let i = this.indexOf<T>(value); this.removeAt(i); } // ------------------------ // Print the number of linked list elements get size(): number { return this.length; } // ------------------------ // Splicing and printing linked list data toString(): string { // Create node storage pointer let node: { data: string, next? }, // Create characters to obtain the data of items in the linked list str: string | null; // Make the next pointer at this time refer to the node variable pointer node = this.next as { data: string, next? }; // Judge whether the pointer is null (that is, when no data is inserted in the linked list, the pointer in the header points to null) if (this.next === null) { // Add prompt character str = 'No data in linked list'; } else { // When the pointer is not null. Get the data of the node corresponding to the pointer str = node.data; // Traverse the pointer when the pointer is not empty while (node.next) { // Causes the current pointer to refer to the next level pointer node = node.next; //Splice the data corresponding to the pointer of each level into the string str += ',' + node.data; } } // Returns the final data result return str; } // ------------------------ //Determine whether the element is empty isEmpty(): boolean { return !!this.length; } } // Add a new node class class NNode { data: unknown; constructor(data: unknown) { this.data = data; } }
test
// Instance linked list let d1 = new List(); console.log('---------------add to---------------'); d1.append<string>('tom'); d1.append<string>('andy'); d1.append<string>('jeck'); d1.append<string>('zhansan'); d1.append<number>(101); d1.append<string>('lidy'); d1.append<string>('rose'); console.log(d1.toString()); console.log('---------------insert---------------'); console.log(d1.toString()); d1.insert<string>(4, 'Insert value 1'); d1.insert<string>(6, 'insert values w'); d1.insert<string>(0, 'head'); // d1. Insert < string > (30, 'wrong insertion value'); console.log(d1.toString()); console.log('---------------to update---------------'); console.log(d1.toString()); d1.update<string>(3, 'Update value 1'); console.log(d1.toString()); console.log('---------------obtain---------------'); let s1 = d1.get(2); //ject console.log(d1.get(3)); //Update value 1 console.log(d1.get(6)); //Insert value w console.log('s1:' + s1); console.log('---------------Get location---------------'); console.log(d1.toString()); console.log(d1.indexOf<number>(101), d1.get(5)); // 5 101 console.log('---------------Remove from specified location---------------'); console.log(d1.toString()); d1.removeAt(2); d1.removeAt(0); console.log(d1.toString()); console.log('---------------Specified content removal---------------'); console.log(d1.toString()); d1.remove<string>('insert values w'); console.log(d1.toString()); console.log('---------------Element length---------------'); console.log(d1.size); // 9 console.log('---------------Element is empty?---------------'); console.log(d1.isEmpty());
---------------Add---------------
tom,andy,jeck,zhansan,101,lidy,rose
---------------Insert---------------
tom,andy,jeck,zhansan,101,lidy,rose
Header, Tom, Andy, Jeck, Zhan, insert value 1101, insert value w,lidy,rose
---------------Renew---------------
Header, Tom, Andy, Jeck, Zhan, insert value 1101, insert value w,lidy,rose
Header, tom,andy, update value 1, Zhan, insert value 1101, insert value w,lidy,rose
---------------Get---------------
Update value 1
101
s1:andy
---------------Get location---------------
Header, tom,andy, update value 1, Zhan, insert value 1101, insert value w,lidy,rose
6 insert value 1
---------------Remove from specified location---------------
Header, tom,andy, update value 1, Zhan, insert value 1101, insert value w,lidy,rose
tom, update value 1, Zhan, insert value 1101, insert value w,lidy,rose
---------------Specified content removal---------------
tom, update value 1, Zhan, insert value 1101, insert value w,lidy,rose
tom, update value 1, Zhan, insert value 1101, lidy, rose
---------------Element length---------------
7
---------------Element is empty---------------
true
Note that when inserting and deleting a linked list, you should consider the pointer pointing when inserting the head and tail (the next level pointer of the previous node must point to the new node, the next level pointer of the new node must point to the next node, and both the head and tail must be considered).
Bidirectional linked list
- Unidirectional linked list
- You can only traverse from beginning to end or from end to end
- That is, the process of linked list connection is one-way
- The node can easily reach the next node, but it cannot go back to the previous node (it can only be traversed from the beginning)
- Bidirectional linked list
- It can traverse from beginning to end and from end to end
- A node has both a forward connected reference and a backward connected reference
- Each time you insert and delete a node, you need to process four references, which is more complex to implement
- Take up more space
Two way linked list diagram
Realize bidirectional linked list
// Encapsulation and implementation of bidirectional linked list type timeType = { value: unknown, prePointer?, nextPointer? }; class WayList { // Head pointer private headNext: timeType = null; // Tail pointer private tail: timeType = null; // Element length private length: number = 0; // Add node method at tail append<T>(value: T): T { // timeNode stores the current node let timeNode: { value: T, prePointer?, nextPointer? }, // preNode stores the node pointed to by the leading pointer of the current node (that is, the leading pointer of the current pointer) preNode: { value: T, prePointer?, nextPointer? }, //New node added nNode: timeType; // Instance node nNode = new Obj(value); // The current node is this headNext timeNode = this.headNext as { value: T, prePointer?, nextPointer? }; // The leading pointer points to the node, which is null by default preNode = null; // Judge whether the length of linked list elements is empty when adding nodes if (this.length === 0) { // If empty, point the header to the new node added for this.headNext = nNode; // The tail pointer points to the new node added this.tail = nNode; } else { // When it is not empty, the current node of the temporary is cycled while (timeNode.nextPointer) { // Obtain the previous node of the current node (if the timeNode does not next) preNode = timeNode; // Make the current temporary node equal to the next node pointed to by the pointer timeNode = timeNode.nextPointer; } // Making the current node point to the newly added node is equivalent to adding a new node at the end of the linked list timeNode.nextPointer = nNode; // Make the upper pointer of the current node point to the upper node of the current node timeNode.prePointer = preNode; // The upper pointer of the new node points to the current node nNode.prePointer = timeNode; // Make the tail pointer point to the newly added node this.tail = timeNode.nextPointer; } // Length + 1 this.length++; return value; } // Inserts data at the specified location insert<T>(position: number, value: T): T { // Create the current node, add a new node, and traverse the control variables let timeNode: timeType, nNode: timeType, index: number = 0, // Upper node preNode: timeType; // initialization timeNode = this.headNext; nNode = new Obj(value); preNode = null; // Cross boundary judgment if (position < 0 || position > this.length) { throw new Error('Data can be inserted at this location'); } // When inserting data from the first location if (position === 0) { // The current new node points to the node pointed by the header nNode.nextPointer = timeNode; // Change the upper pointing of the previous first element to a new node // In this way, the new element node is the first element node timeNode.prePointer = nNode; // Redirect the header element node to the new element this.headNext = nNode; } else if (position === this.length) { // The insertion position is the last item while (index++ < position) { // Gets the parent node of the current node preNode = timeNode; // Get current node timeNode = timeNode.nextPointer; } // The current node is null // The parent pointer of the new node is set to the previous node of the current node nNode.prePointer = preNode; // The child pointer of the previous node of the current node points to the new node preNode.nextPointer = nNode; // Add a node at the end, and the tail point changes this.tail = nNode; } else { // The new location is not the first while (index++ < position) { // Gets the parent node of the current node preNode = timeNode; // Get current node timeNode = timeNode.nextPointer; } // !!! Combination diagram // Make the next level pointer of the previous node point to the new node preNode.nextPointer = nNode; // The upper pointer of the new node points to the upper node nNode.prePointer = preNode; // The next level pointer of the new node points to the current node nNode.nextPointer = timeNode; // The upper pointer of the current node points to the new node timeNode.prePointer = nNode; } this.length++; return value; } //Update method ---- similar to the one-way linked list update method update<T>(position: number, value: T): T { let timeNode: timeType, index: number; timeNode = this.headNext; index = 0 // Cross boundary judgment if (position < 0 || position >= this.length) { throw new Error('There is no value for this location'); } while (index++ < position) { timeNode = timeNode.nextPointer; } timeNode.value = value; return value; } //Gets the element at the specified location get(position: number): unknown { let timeNode: timeType, index: number; timeNode = this.headNext; index = 0; // Cross boundary judgment if (position < 0 || position > this.length) { throw new Error('There is no value for this location'); } while (index++ < position) { timeNode = timeNode.nextPointer; } return timeNode.value; } //The specified element returns the corresponding subscript (returns the first element found that meets the condition) -- similar to the one-way linked list structure indexOf<T>(value: T): number { let timeNode: timeType, index: number = 0; timeNode = this.headNext; while (timeNode.value !== value) { timeNode = timeNode.nextPointer; index++; } return index; } //Delete element at specified location removeAt(position: number): number { let timeNode: timeType, //Upper node preNode: timeType, index: number; timeNode = this.headNext; preNode = null; index = 0; // Cross boundary judgment if (position < 0 || position > this.length) { throw new Error('There is no value for this location'); } //When deleting the element at the first two-way linked list position if (position === 0) { // Point the header to the second element timeNode = timeNode.nextPointer; //Point the header to the current node created for this.headNext = timeNode; // Make the upper point of the new first element null timeNode.prePointer = null; } else { while (index++ < position) { // Gets the upper level node of the current node preNode = timeNode; // Gets the node that currently meets the location conditions timeNode = timeNode.nextPointer; } //The task of deleting the current node can be met by making the next level of the previous node point to the next level of the current node preNode.nextPointer = timeNode.nextPointer; /* * When the last element is deleted, we require the tail pointer to become the latest tail node * If you delete a node that is not the last node, you should handle the pointer problem before and after it * */ // When not the last element is deleted if (position !== this.length - 1) { // Gets the next node of the current node timeNode = timeNode.nextPointer; // Make the upper node of the lower node point to the upper level of the current node timeNode.prePointer = preNode; } else { // When the last element is deleted, the change tail pointer points to the previous data of the deleted node this.tail = preNode; } } this.length--; return 1; } // Remove specified node remove<T>(value: T): number { let i: number = this.indexOf(value); this.removeAt(i); return 0 } //toString method -- similar to one-way list toString(): string { let timeNode: { value: unknown, prePointer?, nextPointer? }, str: string; timeNode = this.headNext; str = timeNode.value as string; while (timeNode.nextPointer) { timeNode = timeNode.nextPointer; str += ' ' + timeNode.value; } return str; } // Reverse traversal character backWordString(): string { let timeNode: timeType, str: string; timeNode = this.tail; str = this.tail.value + '\t'; while (timeNode.prePointer) { timeNode = timeNode.prePointer; str += timeNode.value + '\t'; } console.log(str); return ''; } get Head() { return this.headNext; } get tailNode() { return this.tail; } } // Add an instance node class class Obj { // Value of new node value: unknown; // The upper pointer of the new node prePointer: timeType; // Next level pointer of new node nextPointer: timeType; constructor(item: unknown) { this.value = item; this.prePointer = null; this.nextPointer = null; } }
test
let wlist = new WayList(); wlist.append<number>(101); wlist.append<string>('Zhang San'); wlist.append<string>('Li Si'); wlist.append<string>('Wang Wu'); wlist.append<string>('Zhao Liu'); console.log('---------------Insert element at specified location------------------'); console.log(wlist.toString()); // Head insertion wlist.insert<number>(0, 100); // Tail insertion wlist.insert(6, 'tail'); wlist.insert<string>(3, 'Insert value 1'); // wlist.insert<number>(7, 2000); console.log(wlist.toString()); console.log('Tail: ' + wlist.tailNode.value); console.log('---------------Update element------------------'); wlist.update(1, 102); wlist.update(0, 101); console.log(wlist.toString()); console.log('---------------Deletes the specified element location------------------'); console.log(wlist.indexOf<number>(101), wlist.indexOf('Wang Wu'));//0, 5 console.log('---------------Gets the element at the specified location------------------'); console.log(wlist.toString()); console.log(wlist.get(1));//102 console.log(wlist.get(4));//Li Si console.log('---------------Deletes the element at the specified location------------------'); wlist.insert<boolean>(4, false); console.log(wlist.toString()); wlist.removeAt(8); wlist.removeAt(4); console.log(wlist.toString()); console.log('Tail: ' + wlist.tailNode.value); console.log('---------------Deletes the specified element------------------'); wlist.remove<number>(101); wlist.remove<string>('Wang Wu'); console.log('Tail: ' + wlist.tailNode.value); // console.log(wlist.Head) console.log(wlist.toString()); // //Reverse traversal console.log(wlist.backWordString());
---------------Insert element at specified location------------------
101 Zhang San Li Si Wang Wu Zhao Liu
100 101 sheets three insert value 1 Li Siwang five Zhao six tails
Tail: tail
---------------Update element------------------
101 102 sheets three insert value 1 Li Siwang five Zhao six tails
---------------Deletes the specified element location------------------
0 5
---------------Gets the element at the specified location------------------
101 102 sheets three insert value 1 Li Siwang five Zhao six tails
102
Li Si
---------------Deletes the element at the specified location------------------
101 102 sheets three insert value 1 false Li Siwang five Zhao six tails
101 102 sheets three insert value 1 Li Siwang five Zhao six
Tail: Zhao Liu
---------------Deletes the specified element------------------
Tail: Zhao Liu
102 sheets of three insert value 1 Li Si Zhao Liu
Zhao Liuli Si inserts 1 sheet 3 102
Note that it is roughly the same as a one-way linked list, but note that there is an additional tail pointer, and each node has an additional upper pointer.
Also pay attention to the insertion and deletion of the head and tail