# [data structure and algorithm] linked list and bidirectional linked list

Posted by tjodolv on Thu, 06 Jan 2022 12:29:49 +0100

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`
• 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

• 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

# 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();
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 > (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());

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

# Realize bidirectional linked list

// Encapsulation and implementation of bidirectional linked list
type timeType = { value: unknown, prePointer?, nextPointer? };

class WayList {
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? },
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
// 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
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
} 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;
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;
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;

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;
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
// 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;

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 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());
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.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