Data structure and algorithm - basis - circular linked list (Supplement)

Keywords: Algorithm data structure linked list

The last issue discussed it as a whole Unidirectional linked list . On this basis, two points are added, one-way circular linked list and two-way circular linked list. It can be seen from the literal that the linked list forms a ring structure. The difference is whether the ring can only cycle in one direction or two directions.

One way circular linked list

The one-way circular linked list can be understood as pointing the last node of the one-way linked list to the first node to form a ring. Then, compared with the one-way linked list, the array data interface processed by the one-way circular linked list does different processing in the case of adding elements and deleting elements to ensure that it is still in the state of a ring.

add(int index, E element)

When adding an element, you need to deal with the case where the index is 0. When inserting an element at the position where the index is 0, the logical processing is to point the next of the new element to the original first position element, and the next of the last element to the new first position element.

Pay special attention to code processing. If you directly handle the first node pointing, the overall number of linked lists will increase, so it will be easy to make mistakes when taking the position of the last element. The process here is to create a newFirst node to maintain the original complete linked list.

@Override
public void add(int index, E element) {
  // You need to make a judgment first
  rangeCheckOfAdd(index);

  // When inserting into the position where index == 0, you need to point the next of the last node to first (index == 0)
  if (index == 0) {
    // Inserting the first does not mean that first is null. It also points to valuable
    // The reason for creating newFirst is that if you directly point to it with first, you will get the location where the last node is not size -1
    Node<E> newFirst = new Node<>(element, first);

    // Get the last node
    Node<E> last = size == 0 ? newFirst: node(size -1);
    last.next = newFirst;
    first = newFirst;
  }
  else {
    Node<E> prev = node(index-1);
    Node<E> node = new Node<>(element, prev.next);
    prev.next = node;
  }
  size ++;
}

Delete element (E remove(int index))

When deleting elements, you should also pay special attention to the deletion of index == 0. In the case of index == 0, if size == 1, the last element is deleted, and first must be directly equal to null, otherwise the ring will not be destroyed; If size= In the case of 1, you need to change the pointing of the last node to point to the new first pointer after processing.

@Override
public E remove(int index) {
  // You need to judge first
  rangeCheck(index);
  Node<E> old = first;
  if (index == 0) {
    // Only delete the corresponding coordinates, not all
    // size==1. When pointing, it cannot be deleted
    if (size == 1) {
      first = null;
    }
    else {
      Node<E> last = node(size -1);
      first = first.next;
      last.next = first;
    }
  }
  else {
    Node<E> prev = node(index -1);
    old = prev.next;
    prev.next = prev.next.next;
  }
  size --;
  return old.element;
}

Bidirectional circular linked list

The bidirectional circular linked list also forms a link. Each node in the link has a pointer to the previous element and a pointer to the next element. There will also be a last node in the data structure of the linked list to point to the last element.

The difference from other types of linked lists is still the addition and deletion of elements. There will be differences in logical processing, because more points will make code processing more convenient, but there are more points to consider logically. In addition, the bidirectional circular linked list can be handled by dichotomy in the processing of traversing elements.

@SuppressWarnings("unused")
public class CircleLinkList<E> extends AbstractList<E> {
	Node<E> first;
  // Add last pointer to linked list structure
	Node<E> last;
	
	private static class Node<E> {
    // The node structure adds a pointer to the previous node
		Node<E> prev;
		Node<E> next;
		E element;
		public Node(Node<E> prev, E element, Node<E> next) {
			this.prev = prev;
			this.element = element;
			this.next = next;
		}
}

add(int index, E element)

When adding elements to the two-way linked list, we mainly consider the case of index == size. In this case, we can judge whether there are elements in the linked list by whether last is null. After adding a node, you want to change its previous node point and the latter node point. When only one element is, its first and last are the same.

Here is a novel point. The case of index == 0 is judged by prev == last. Because the previous node of the first node is the last node.

@Override
public void add(int index, E element) {
  rangeCheckOfAdd(index);

  if (index == size) { // Add on last face
    Node<E> oldLast = last;
    last = new Node<>(oldLast, element, first);
    if (oldLast == null) { // There are no elements in the array yet
      first = last;
    }
    else {
      oldLast.next = last;
    }
    last.next = first;
    first.prev = last;
  }
  else {
    Node<E> next = node(index);
    Node<E> prev = next.prev;
    Node<E> node = new Node<>(prev, element, next);
    next.prev = node;
    prev.next = node;
    if (prev == last) { // index == 0
      first = node;
    }
  }
  size++;
}

Delete element (E remove(int index))

When deleting an element, if size == 1 is yes, you need to point to null for both the first node and the last node.

In other cases, if index == 0, you need to change the pointing of the first node; if index == size-1, you need to change the pointing of the last node.

@Override
public E remove(int index) {
  Node<E> node = node(index);
  if (size == 1) {
    first = null;
    last = null;
  }
  else {
    Node<E> prev = node.prev;
    Node<E> next = node.next;
    prev.next = next;
    next.prev = prev;
    if (node == first ) { // index == 0
      first = next;
    }

    if (node == last) { // index == size -1
      last = prev;
    }
  }
  size--;
  return node.element;
}

Traverse elements (node < E > node (int index))

Because you can traverse in two different directions in this ring, you can first judge the index and the middle position to determine whether to traverse from the beginning or from the end. This reduces the number of iterations. This is a good deal.

private Node<E> node(int index) {
  rangeCheck(index);

  if (index < size/2) {
    Node<E> node = first;
    for (int i = 0; i < index; i++) {
      node = node.next;
    }
    return node;
  }
  else {
    Node<E> node = last;
    for (int i = size -1; i > index; i++) {
      node = node.prev;
    }
    return node;
  }
}

Posted by johnnyblaze1980 on Mon, 20 Sep 2021 01:00:57 -0700