数据结构与算法-循环链表

803 阅读3分钟

1,单向循环链表

  • 单向循环链表就是尾节点的next指向头节点

  • 当循环链表只有一个节点的时候,next指向自己

1.1,插入方法

public void add(int index, E element) {
    rangeCheckForAdd(index);
    if (index == 0) {
    	// 创建新节点, 并指向原链表的first节点
    	Node<E> node = new Node<>(element, first);
    	// 取出最后一个节点
    	Node<E> last = size == 0 ? first : node(size - 1);
    	// 最后一个节点的next指向新节点
    	last.next = node;
    	// first指针指向新节点
    	first = node;
    }else {
    	Node<E> prev = node(index - 1);
    	prev.next = new Node<>(element, prev.next);
    }
    size++;
}
  • 插入方法在index==0时与之前单向链表不同,需要把尾节点的next指向插入的新节点

1.2,删除元素

public E remove(int index) {
    Node<E> node = first;
    if (index == 0) {
    	if (size == 1) {//只有一个节点
            first = null;//将first指向null,就是删除
    	}else {
            Node<E> last = node(size - 1);//获取尾节点
            first = first.next;//因为index==0,first指向头节点的下一个节点
            last.next = first;//尾加点指向first
    	}
    }else {
        Node<E> prev = node(index - 1);
        node = prev.next;
        prev.next = node.next;
    }
    size--;
    return node.element;
}
  • 删除元素,要保证尾节点指向头节点,要特殊处理size==1时,first指向null
  • 在index!=0时,与双向链表的删除一至

2,双向循环链表

  • 双向循环链表就是 头节点的prev指向尾节点尾节点的next指向头节点

  • 当只有一个节点时,first和last都指向同一个节点,prev和next也指向同一个节点

2.1,插入元素

public void add(int index, E element) {
    rangeCheckForAdd(index);
    // 如果 index == size, 说明添加的索引是最后位置
    if (index == size) {
    	// 创建新节点, prev指向原链表的尾节点, next指向首节点
    	Node<E> node = new Node<>(last, element, first);
    	// 当原链表没有任何节点
    	if (size == 0) {
            first = node;
            last = node;
            node.prev = node;
            node.next = node;
        }else {
            // 原链表尾节点next指向node
            last.next = node;
            // 原链表头结点prev指向node
            first.prev = node;
            // last指向新的尾节点
            last = node;
        }
    }else {
        // 添加新节点后的下一个节点
        Node<E> next = node(index);
        // 添加新节点后的上一个节点
        Node<E> prev = next.prev;
        // 创建新节点, 新节点的上一个节点时prev, 新节点的下一个节点是next
        Node<E> node = new Node<>(prev, element, next);
        // next的上一个节点是新节点
        next.prev = node;
        // prev的下一个节点是新节点
        prev.next = node;
        // 当next == first时, 说明新添加节点的索引是0
        if (next == first) { 
        	first = node;
        }
    }
    size++;
}
  • 当index==size时,在索引的最后的位置添加节点。index==0只有一个节点时,first,last,node.prev ,node.next都指向同一个节点。index!=0时,需要 尾节点的next指向头节点, 头节点的prev指向尾节点。

2.1,删除元素

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;
        next.prev = prev;
        prev.next = next;
        // 如果node == first, 说明删除的是第一个节点
        if (node == first) {
            first = next;
        }
        // 如果next == last, 说明删除的是最后一个节点
        if (next == last) {
            last = prev;
        }	
    }
    size--;
    return node.element;
}
  • 当size==1,只有一个节点时,只需要first和last都指向null
  • 当size!=1时,需要处理被删除节点的前一个节点和后一个的prev和next的指向
  • 当删除头节点时,需要把first指向头节点的后面一个节点
  • 当删除尾节点时,需要把last指向尾节点的前面一个节点

3,约瑟夫问题

问题描述:一个圆环有8个元素,从第一个元素开始,依次删除数到的第三个元素。所以依次删除的顺序为:3,6,1,5,2,8,4,最后只剩下元素7

3.1,如何发挥循环链表的最大威力?

  1. 可以考虑增设1个成员变量,3个方法

  2. current:用于指向某个节点

  3. void reset() :让current指向头节点first

  4. E next() :让current往后走一步,即current = current.next

  5. E remove() : 删除current指向的节点,删除成功后让current指向下一个节点

    public void reset() {        
        current = first;    
    }        
    
    public E next() {        
        if (current == null) return null;                
        current = current.next;        
        return current.element;    
    }        
    
    public E remove() {        
        if (current == null) return null;                
        Node<E> next = current.next;         
        E element = remove(current);        
        if (size == 0) {            
            current = null;//size==0,current指向null        
        } else {            
            current = next;//current指向下一个节点        
        }                
        return element;    
    }
    

3.2,约瑟夫问题解答

使用双向循环链表进行解答,我们已经添加了三个方法使用

    static void josephus() {        
        CircleLinkedList<Integer> list = new CircleLinkedList<>();        
        for (int i = 1; i <= 8; i++) {            
            list.add(i);//添加8个元素        
        }                
        // 指向头结点(指向1)        
        list.reset();                
        while (!list.isEmpty()) {            
            list.next();//指向第二个节点            
            list.next();//指向第三个节点
            list.remove()

        }    
    }
  • 由于时双向循环链表,删除第三个节点,只需要当前节点向后指两次即可,所以list调用next()方法两次