[JS and linked list] Doubly linked list

Preface

Please read before reading this article

[JS and linked list] Ordinary linked list_AI3D_WebEngineer's blog-CSDN blog

ES6 Class inheritance

Class inheritance can use extends, allowing subclasses to inherit the properties and methods of the parent class.

Within the subclass (constructor), super() must be called to implement inheritance ( super() represents the parent class constructor )

class A {constructor() {this.name ='abc'}}
class B extends A {constructor() {super()}}
// -----------------------------------------
var b = new B() // {name:'abc'}
class A {constructor(userName) {this.name = userName}}
class B extends A {constructor(yourName) {super(yourName)}}
// ---------------------------------------------------
var b = new B('ddd') // {name:'ddd'}

Doubly linked list

The difference between a doubly linked list and an ordinary linked list is that the nodes of an ordinary linked list are linked to the next node (one-way), while the nodes of a doubly linked list are linked to the upper and lower nodes.

Let’s start with the Node node:

The following code

class DoublyNode extends Node {
    #prev;
    constructor(ele,next,prev) {
        super(ele,next);
        this.prev = prev;
    }
}
class DoublyLinkedList extends LinkedList {
    constructor(equalFn=compareFn) {
        super(equalFn);
        this.tail = undefined
    }
}

The constructor of the new DoublyLinkedList class will initialize the four properties equalsFn, head, count and tail.

The so-called doubly linked list can be from head to end, or from tail to head, and can even flexibly turn in the middle (because the doublyNode node has front and rear nodes), while a one-way linked list needs to be iterated again if the element it is looking for is missed during iteration.

Get linked list nodes by subscript

The getNodeAt or getElementAt method of the same linked list (inherited)


getNodeAt(index) {
     if (index>0 && index >=this.count) {return undefined}
     if (this.index === 0) {return this.head}
     let currentNode = this.head;
     for (var i =0;i<index;i++) {
           currentNode = currentNode.next
     }
    return currentNode
}

Insert new node anywhere

There is one more scenario than a linked list. It's the tail insertion.

Create new node:

const node = new DubblyNode(element);

Insert from the beginning:

①Empty linked list

this.head = node;
this.tail = node;

②Non-empty linked list

var currentNode = this.head;

node.next = currentNode;
currentNode.prev = node;
this.head = node

Insert from tail:

var currentNode = this.Tail;

currentNode.next = node;
node.pre = currentNode;
this.tail = node;

Insert in the middle:

const originNode = this.getNodeAt(index);
const preNode = this.getNodeAt(index - 1);
node.next = originNode;;
node.prev = preNode;
originNode.prev = node;
preNode.next = node;

Integration:

insert(element,index) {
    if (index < 0 || index > this.count) {return false}
    const node = new DoublyNode(element);
    let current = this.head;
    // 开头插入
    if (index === 0) {
         // 链表为空
        if (this.head == null) {
            this.head = node;
            this.tail = node;
        }else {
           // 链表非空
            node.next = currentNode;
            currentNode.prev = node;
            this.head = node
        }
    }
    if (index === this.count) {
        // 最后一项
        currentNode = this.Tail;
        currentNode.next = node;
        node.pre = currentNode;
        this.tail = node;
    }
    // 在中间插入
    const originNode = this.getNodeAt(index);
    const prevNode = this.getNodeAt(index - 1);
    node.next = originNode;
    node.prev = prevNode;
    originNode.prev = node;
    prevNode.next = node;
    // 结束
    this.count ++;
    return true
}

Remove element based on subscript

Three scenarios need to be judged:

①Remove the head

removeAt(index) {
    if (index < 0 || index > this.count || !this.count) {return undefined}
    let current = this.head;
    if (index === 0) {
        this.head = current.next;
        if (this.count === 1) {
            this.tail = undefined
        }
    }
    this.count --;
    return current.value
}
...
if (index === 0) {
        this.head = current.next;
        if (this.count === 1) {
           ...
        }else {
            this.head.prev = undefined
        }
 }
...

②Remove the tail

...
if (index === this.count - 1) {
   current = this.tail;
   this.tail=  current.prev;
   this.tail.next = undefined;
 }
...

③Normal removal

...
else  {
    current = this.getElementAt(index);
    const prevElement = current.prev;
    const nextElement = current.next;
    prevElement.next =  nextElement;
    nextElement.prev = prevElement;
}
...

To sort it out:

 removeAt(index) {
    if (index >= 0 && index < this.count) {
      let current = this.head;
      if (index === 0) {
        this.head = this.head.next;
        // if there is only one item, then we update tail as well //NEW
        if (this.count === 1) {
          // {2}
          this.tail = undefined;
        } else {
          this.head.prev = undefined;
        }
      } else if (index === this.count - 1) {
        // last item //NEW
        current = this.tail;
        this.tail = current.prev;
        this.tail.next = undefined;
      } else {
        current = this.getElementAt(index);
        const previous = current.prev;
        // link previous with current's next - skip it to remove
        previous.next = current.next;
        current.next.prev = previous; // NEW
      }
      this.count--;
      return current.element;
    }
    return undefined;
  }

circular linked list

Circular linked lists include one-way circular linked lists and two-way circular linked lists.

The difference between a one-way circular linked list and a one-way linked list is that next of the last element does not point to undefined, but to head.

The two-way circular linked list is a two-way linked list with additional tail.next pointing to the head element (originally undefined) and head.prev pointing to the tail element (originally undefined).

One-way circular linked list CircularLinkedList implementation:

CircularLinkedList类不需要任何额外的属性。直接扩展LinkedList类(单向链表)并覆盖插入方法和移除方法。

向单向循环链表中插入元素的逻辑和向单向链表中插入元素的逻辑是一样的。不同之处在于我们需要将循环链表尾部节点的next引用指向头部节点。

单向链表:

  insert(element, index) {
    if (index >= 0 && index <= this.count) {
      const node = new Node(element);
      if (index === 0) {
        const current = this.head;
        node.next = current;
        this.head = node;
      } else {
        const previous = this.getElementAt(index - 1);
        node.next = previous.next;
        previous.next = node;
      }
      this.count++;
      return true;
    }
    return false;
  }

循环单向链表:

insert(element, index) {
    if (index >= 0 && index <= this.count) {
      const node = new Node(element);
      let current = this.head;
      if (index === 0) {
        if (this.head == null) {
          // if no node  in list
          this.head = node;
          node.next = this.head;
        } else {
          node.next = current;
          current = this.getElementAt(this.size());
          // update last element
          this.head = node;
          current.next = this.head;
        }
      } else {
        const previous = this.getElementAt(index - 1);
        node.next = previous.next;
        previous.next = node;
      }
      this.count++;
      return true;
    }
    return false;
  }

其实就是多了在插入下标为0的情况处理

单向链表:

removeAt(index) {
    if (index >= 0 && index < this.count) {
      let current = this.head;
      if (index === 0) {
        this.head = current.next;
      } else {
        const previous = this.getElementAt(index - 1);
        current = previous.next;
        previous.next = current.next;
      }
      this.count--;
      return current.element;
    }
    return undefined;
 }

单向循环链表:

removeAt(index) {
    if (index >= 0 && index < this.count) {
      let current = this.head;
      if (index === 0) {
        if (this.size() === 1) {
          this.head = undefined;
        } else {
          const removed = this.head;
          current = this.getElementAt(this.size() - 1);
          this.head = this.head.next;
          current.next = this.head;
          current = removed;
        }
      } else {
        // no need to update last element for circular list
        const previous = this.getElementAt(index - 1);
        current = previous.next;
        previous.next = current.next;
      }
      this.count--;
      return current.element;
    }
    return undefined;
  }

详细代码见:链表代码汇总

有序链表

有序链表是指保持元素有序的链表结构。除了使用排序算法之外。我们还可以将元素插入到正确的位置来保证链表的有序性。

所以我们只需要在单向链表的基础上重写insert、push的逻辑,使得整个链表的插入变得有序。

insert(element, index) {
    if (index >= 0 && index <= this.count) {
      const node = new Node(element);
      if (index === 0) {
        const current = this.head;
        node.next = current;
        this.head = node;
      } else {
        const previous = this.getElementAt(index - 1);
        node.next = previous.next;
        previous.next = node;
      }
      this.count++;
      return true;
    }
    return false;
 }

原本的插入逻辑可以自定义插入的位置。但是有序插入只能通过计算得出插入下标。

为了提现有序,我们需要定义一套比较方法。

export const Compare = {
  LESS_THAN: -1,
  BIGGER_THAN: 1,
  EQUALS: 0
};
export function defaultCompare(a, b) {
  if (a === b) {
    return Compare.EQUALS;
  }
  return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN;
}
export default class SortedLinkedList extends LinkedList {
  constructor(equalsFn, compareFn = defaultCompare) {
    super(equalsFn);
    this.compareFn = compareFn;
  }
}

我们来看insert的改写:

  insert(element, index = 0) {
    if (this.isEmpty()) {
      return super.insert(element, index === 0 ? index : 0);
    }
    const pos = this.getIndexNextSortedElement(element);
    return super.insert(element, pos);
  }

insert方法为什么要给insert一个默认值0?

因为我们需要继承改写insert方法(super),所以我们不能改变继承的insert的传参结构。但是我们又不能让index生效。

getIndexNextSortedElement的方法是为了纠正插入元素的下标的

getIndexNextSortedElement(element) {
    let current = this.head;
    let i = 0;
    for (; i < this.size() && current; i++) {
      const comp = this.compareFn(element, current.element);
      if (comp === Compare.LESS_THAN) {
        return i;
      }
      current = current.next;
    }
    return i;
  }

同理,push也需要纠正一下push的下标

push(element) {
    if (this.isEmpty()) {
      super.push(element);
    } else {
      const index = this.getIndexNextSortedElement(element);
      super.insert(element, index);
    }
 }

完整代码见链表代码汇总

小结:

链表相比数组最重要的优点是无需移动链表中的元素,就能轻松添加和移除元素。当你需要频繁操作(删、增)元素的时候,最好的选择就是链表。

Guess you like

Origin blog.csdn.net/weixin_42274805/article/details/129682557