javalinked-listnodesdoubly-linked-listknuth

Java: How to implement Dancing Links algorithm (with DoublyLinkedLists)?


I am trying to implement Knuth's Dancing Links algorithm in Java.

According to Knuth, if x is a node, I can totally unlink a node by the following operations in C:

L[R[x]]<-L[x]
R[L[x]]<-R[x]

And revert the unlinking by:

L[R[x]]<-x
R[L[x]]<-x

What am I doing wrongly in my main method?

How do you implement the unlinking and revert in Java?

Here's my main method:

      ///////////////

      DoublyLinkedList newList = new DoublyLinkedList();

      for (int i = 0; i < 81; i++) {
        HashSet<Integer> set = new HashSet<Integer>();
        set.add(i);
        newList.addFirst(set);
      }

      newList.displayList();

      // start at 69
      newList.getAt(12).displayNode();

      //HOW TO IMPLEMENT UNLINK?
      //newList.getAt(12).previous() = newList.getAt(12).next().previous().previous();
      //newList.getAt(12).next() = newList.getAt(12).previous().next().next();

      newList.displayList();

      //HOW TO IMPLEMENT REVERT UNLINK?
      //newList.getAt(12) = newList.getAt(12).next().previous();
      //newList.getAt(12) = newList.getAt(12).previous().next();

      System.out.println();

      ///////////////

Here's the DoublyLinkedList class:

public class DoublyLinkedList<T> {

  public Node<T> first = null;
  public Node<T> last = null;

  static class Node<T> {
    private T data;
    private Node<T> next;
    private Node<T> prev;

    public Node(T data) {
      this.data = data;
    }

    public Node<T> get() {
      return this;
    }

    public Node<T> set(Node<T> node) {
      return node;
    }

    public Node<T> next() {
      return next;
    }

    public Node<T> previous() {
      return prev;
    }

    public void displayNode() {
      System.out.print(data + " ");
    }

    @Override
    public String toString() {
      return data.toString();
    }
  }

  public void addFirst(T data) {
    Node<T> newNode = new Node<T>(data);

    if (isEmpty()) {
      newNode.next = null;
      newNode.prev = null;
      first = newNode;
      last = newNode;

    } else {
      first.prev = newNode;
      newNode.next = first;
      newNode.prev = null;
      first = newNode;
    }
  }

  public Node<T> getAt(int index) {
    Node<T> current = first;
    int i = 1;
    while (i < index) {
      current = current.next;
      i++;
    }
    return current;
  }

  public boolean isEmpty() {
    return (first == null);
  }

  public void displayList() {
    Node<T> current = first;
    while (current != null) {
      current.displayNode();
      current = current.next;
    }
    System.out.println();
  }

  public void removeFirst() {
    if (!isEmpty()) {
      Node<T> temp = first;

      if (first.next == null) {
        first = null;
        last = null;
      } else {
        first = first.next;
        first.prev = null;
      }
      System.out.println(temp.toString() + " is popped from the list");
    }
  }

  public void removeLast() {
    Node<T> temp = last;

    if (!isEmpty()) {

      if (first.next == null) {
        first = null;
        last = null;
      } else {
        last = last.prev;
        last.next = null;
      }
    }
    System.out.println(temp.toString() + " is popped from the list");
  }
}

Solution

  • I am not familiar with Knuth's Dancing Links algorithm, but found this article which made it quiet clear. In particular I found this very helpful:

    Knuth takes advantage of a basic principle of doubly-linked lists. When removing an object from a list, only two operations are needed:

    x.getRight().setLeft( x.getLeft() )
    x.getLeft().setRight(> x.getRight() )

    However, when putting the object back in the list, all is needed is to do the reverse of the operation.

    x.getRight().setLeft( x )
    x.getLeft().setRight( x )

    All that is needed to put the object back is the object itself, because the object still points to elements within the list. Unless x’s pointers are changed, this operation is very simple.


    To implement it I added setters for linking / unlinking. See comments:

    import java.util.HashSet;
    
    public class DoublyLinkedList<T> {
    
          public Node<T> first = null;
          public Node<T> last = null;
    
          static class Node<T> {
            private T data;
            private Node<T> next;
            private Node<T> prev;
    
            public Node(T data) {
              this.data = data;
            }
    
            public Node<T> get() {
              return this;
            }
    
            public Node<T> set(Node<T> node) {
              return node;
            }
    
            public Node<T> next() {
              return next;
            }
    
            //add a setter
            public  void setNext(Node<T> node) {
                  next = node;
            }
            public Node<T> previous() {
              return prev;
            }
    
            //add a setter
            public  void setPrevious(Node<T> node) {
                  prev = node;
            }
    
            public void displayNode() {
              System.out.print(data + " ");
            }
    
            @Override
            public String toString() {
              return data.toString();
            }
          }
    
          public void addFirst(T data) {
            Node<T> newNode = new Node<T>(data);
    
            if (isEmpty()) {
              newNode.next = null;
              newNode.prev = null;
              first = newNode;
              last = newNode;
    
            } else {
              first.prev = newNode;
              newNode.next = first;
              newNode.prev = null;
              first = newNode;
            }
          }
    
          public Node<T> getAt(int index) {
            Node<T> current = first;
            int i = 1;
            while (i < index) {
              current = current.next;
              i++;
            }
            return current;
          }
    
          public boolean isEmpty() {
            return (first == null);
          }
    
          public void displayList() {
            Node<T> current = first;
            while (current != null) {
              current.displayNode();
              current = current.next;
            }
            System.out.println();
          }
    
          public void removeFirst() {
            if (!isEmpty()) {
              Node<T> temp = first;
    
              if (first.next == null) {
                first = null;
                last = null;
              } else {
                first = first.next;
                first.prev = null;
              }
              System.out.println(temp.toString() + " is popped from the list");
            }
          }
    
          public void removeLast() {
            Node<T> temp = last;
    
            if (!isEmpty()) {
    
              if (first.next == null) {
                first = null;
                last = null;
              } else {
                last = last.prev;
                last.next = null;
              }
            }
            System.out.println(temp.toString() + " is popped from the list");
          }
    
          public static void main(String[] args) {
    
              ///////////////
    
              DoublyLinkedList newList = new DoublyLinkedList();
    
              for (int i = 0; i < 81; i++) {
    
                  HashSet<Integer> set = new HashSet<Integer>();
                  set.add(i);
                  newList.addFirst(set);
              }
    
              newList.displayList();
    
              // start at 69
              Node node = newList.getAt(12);
              node.displayNode(); System.out.println();
    
              //HOW TO IMPLEMENT UNLINK?
              node.previous().setNext(node.next);
              node.next().setPrevious(node.previous());
    
              //The 2 statements above are equivalent to
              //Node p = node.previous();
              //Node n = node.next();
              //p.setNext(n);
              //n.setPrevious(p);
    
              newList.displayList();
    
              //HOW TO IMPLEMENT REVERT UNLINK?
              node.previous().setNext(node);
              node.next().setPrevious(node);
    
              newList.displayList(); System.out.println();
    
              ///////////////
          }
        }