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

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

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

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

Topics: Algorithm data structure linked list