Java数据结构-循环链表的设计与实现(头歌平台,详细注释)

文章详细介绍了单循环链表和双向循环链表的实现,包括添加、遍历、删除操作的代码示例,以及如何在单链表中实现循环结构以便于遍历。

目录

第1关:单循环链表的实现—链表的添加、遍历

任务描述

相关知识

单循环链表

添加操作

遍历循环链表

代码: 

第2关:单循环链表的实现—链表的删除

任务描述

相关知识

单循环链表删除操作

代码:  

第3关:双向循环链表的实现—链表的插入

任务描述

相关知识

双向循环链表

双向链表结点表示

双向循环链表图示

双向循环链表添加操作

代码:  

第4关:双向循环链表的实现—链表的删除

任务描述

相关知识

双向循环链表的删除操作

代码:  


单链表:由左到右指向,左指右,单向的

双链表:由左到右指向,左指右,又有由右到左指向,右指左,双向的

第1关:单循环链表的实现—链表的添加、遍历

任务描述

在操作单链表时,我们有时希望从单链表中的任一结点出发都能遍历整个链表,但对于单链表来说,只有从头结点开始才能扫描表中的全部结点。因此我们需要改动链表,使其首尾相接,这样就能满足我们的需求。

本关任务:完成带头结点的单循环链表的添加功能,遍历链表并输出。

相关知识
单循环链表

循环链表是一种首尾相接的链表。其特点是无需增加存储量,只需对表的链接方式稍作改变,即可使得表操作更加方便灵活。

在单链表中,将末尾结点的指针域null改为指向表头结点或开始结点,就得到单链形式的循环链表,称为单循环链表。

为使空表和非空表的处理方式一致,循环链表中也可以设置一个头结点。这样空循环链表仅有一个自成循环的头结点。 如下图:

添加操作

单循环链表的添加操作与普通的单链表操作类似,只需添加时注意处理尾结点,使其指向头结点。下面是添加操作示意图。

这里采用的是尾插法,即在链表的末尾添加结点。我们使tail指向链表尾结点,因此添加结点node时只需改动tail和新结点node之间的链接关系即可。由于有头结点,所以空表和非空表的处理方式是一样的。

 
 
  1. node.next=tail.next;
  2. tail.next=node;
  3. tail=node;
遍历循环链表

在循环链表中,链表的尾结点不是指向null。因此要输出链表元素时,其终止条件就不再是像非循环链表那样判断p==nullp.next==null,而是判别它们是否等于某一指定结点,如头结点head。如下图所示:

代码: 
package step1;

/**
 * Created by sykus on 2018/1/15.
 */
public class MyCircleLinkedList {
    private Node head;//头结点, 不存数据
    private Node tail;//尾结点, 指向链表的最后一个节点
    private int size;

    public MyCircleLinkedList() {
        head = new Node(Integer.MIN_VALUE, null);
        head.next = head;
        tail = head;
        size = 0;
    }

    /**
     * 添加到链表尾部
     *
     * @param item
     */
    public void add(int item) {
        /********** Begin *********/
        //添加到链表尾部
        Node newNode = new Node(item, null);//创建新结点,并赋元素
        newNode.next=tail.next;//让新结点指向当前尾结点的下一个结点(即头结点)(即循环单链表的最后一个结点需要指向头结点)
        tail.next=newNode;//让尾结点指向新结点
        tail=newNode;//让新结点变为尾结点
        size++;//元素个数+1

        /********** End *********/
    }

    /**
     * 遍历链表并输出元素
     */
    public void output() {
        /********** Begin *********/
        //遍历链表并输出元素
        Node n = head;//保存头结点
        while (n.next!= head) {//因为循环单链表尾部指向头,所以不为头则代表还没遍历完元素
        n = n.next;//让n变为当前结点
        System.out.println(n.item);//输出每个被遍历的元素
        }
        /********** End *********/
    }

    public boolean isEmpty() {
        return head.next == head;
    }

    public int size() {
        return size;
    }

    //结点内部类
    private static class Node {
        int item;
        Node next;

        Node(int item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}

以下是测试样例:

测试输入:2 0 5 1 1

预期输出:

  1. 2
  2. 0
  3. 5
  4. 1
  5. 1

第2关:单循环链表的实现—链表的删除

任务描述

在上一关,我们已经完成了单循环链表的添加、遍历功能,我们已经可以向表中添加数据并输出,现在我们将要实现其删除功能。

本关任务:删除循环链表中指定位置的结点,并返回其值。

相关知识
单循环链表删除操作

单循环链表的删除操作与普通的单链表操作基本类似,通过遍历找到要删除结点的直接前驱,然后改变前驱的链接情况。如下图:

这里删除的是尾结点,由于我们在构建单循环链表时是用tail指向尾结点的,所以在删除尾结点后需改变tail的指向,如果删除的不是尾结点则不需改变tail指向。

代码:  
package step2;

/**
 * Created by sykus on 2018/1/15.
 */
public class MyCircleLinkedList {
    private Node head;//头结点, 不存数据
    private Node tail;//尾结点, 指向链表的最后一个节点
    private int size;

    public MyCircleLinkedList() {
        head = new Node(Integer.MIN_VALUE, null);
        head.next = head;
        tail = head;
        size = 0;
    }

    /**
     * 添加到链表尾部
     *
     * @param item
     */
    public void add(int item) {
        Node node = new Node(item, tail.next);
        tail.next = node;
        tail = node;
        ++size;

    }

    /**
     * 遍历链表并输出元素
     */
    public void output() {
        Node p = head;
        while (p.next != head) {
            p = p.next;
            System.out.println(p.item);
        }
    }


    /**
     * 删除从头结点开始的第index个结点
     * index从0开始
     *
     * @param index
     * @return
     */
    public int remove(int index) {
        checkPosIndex(index);

        /********** Begin *********/
        //删除从头结点开始的第index个结点
        //index从0开始
        Node n = head;//保存头结点
        for(int i=0;i<index;i++){//寻找要删除的结点的前一个结点
        n = n.next;//保存当前结点
        }
        Node del = n.next;//保存要删除的结点
        n.next = del.next;//将要删除的结点的前一个结点的指向,指向要删除的结点的下一个结点
        --size;//元素个数-1
        return del.item;//返回删除元素
        /********** End *********/
    }

    public boolean isEmpty() {
        return head.next == head;
    }

    public int size() {
        return size;
    }

    private void checkPosIndex(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
    }

    //结点内部类
    private static class Node {
        int item;
        Node next;

        Node(int item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}

预期输入:2 0 5 1 1 预期输出:

  1. 0
  2. 2
  3. 5
  4. 1
  5. 1

第3关:双向循环链表的实现—链表的插入

任务描述

在单链表中,当我们要访问某一个结点的前驱结点时,要从头结点开始遍历;要删除链表中的一个结点时,仅给出该结点的指针还不行;在指定结点前插入一个新结点,同样需要从头开始遍历。对于这些问题,双向循环链表可以解决。

本关任务:实现双向循环链表的添加功能。

相关知识
双向循环链表

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。

双向链表结点表示

双向链表中的结点由三个域组成,两个链接域一个数据域,如下图:

prev链接域指向结点的直接前驱,next链接域链接该结点的直接后继。

双向循环链表图示

下图是带头结点head的双向循环链表示意图:

双向循环链表添加操作

双向循环链表的添加操作示意图如下:

代码:  
package step3;

/**
 * Created by sykus on 2018/1/15.
 */
public class MyDoubleLinkedList {

    private Node head;//头结点
    private Node tail;//指向链表的尾结点
    private int size;

    public MyDoubleLinkedList() {
        head = new Node(null, Integer.MIN_VALUE, null);
        head.next = head.prev = head;
        tail = head;
        size = 0;
    }

    /**
     * 添加元素到表尾
     *
     * @param item
     */
    public void add(int item) {

        /********** Begin *********/
        //添加元素到表尾
        Node newNode = new Node(null, item, null);//创建新结点,并赋元素
        tail.next = newNode;//尾结点指向新结点
        newNode.prev = tail;//新结点的前一个结点指向此时的尾结点(因为为双向链表)
        newNode.next = head;//新结点指向头结点(因为为循环链表)
        head.prev = newNode;//头结点的前一个结点指向新结点(同理)
        tail = newNode;//将尾结点变为新结点
        ++size;//元素+1
        /********** End *********/

    }

    /**
     * 打印双向链表
     *
     * @param flag true从左向右顺序打印, false从右向左顺序打印
     */
    public void printList(boolean flag) {
        Node f = head;
        if (flag) {//向右
            while (f.next != head) {
                f = f.next;
                System.out.print(f.item + " ");
            }
        } else {//向左
            while (f.prev != head) {
                f = f.prev;
                System.out.print(f.item + " ");
            }
        }
    }

    public int size() {
        return size;
    }

    //结点内部类
    private static class Node {
        int item;
        Node next;//直接后继引用
        Node prev;//直接前驱引用

        Node(Node prev, int item, Node next) {
            this.prev = prev;
            this.item = item;
            this.next = next;
        }
    }
}

测试输入:1 2 3 4 5 预期输出:

  1. 1 2 3 4 5
  2. 5 4 3 2 1

第4关:双向循环链表的实现—链表的删除

任务描述

上一关实现了双向循环链表的添加功能,可以向表中添加元素,现在实现双向循环链表的删除功能。

本关任务:在上一关的基础上,实现双向循环链表的删除功能。

相关知识
双向循环链表的删除操作

双向循环链表的删除操作示意图如下:

代码:  
package step4;

/**
 * Created by sykus on 2018/1/15.
 */
public class MyDoubleLinkedList {

    private Node head;//头结点
    private Node tail;//指向链表的尾结点
    private int size;

    public MyDoubleLinkedList() {
        head = new Node(null, Integer.MIN_VALUE, null);
        head.next = head.prev = head;
        tail = head;
        size = 0;
    }

    /**
     * 添加元素到表尾
     *
     * @param item
     */
    public void add(int item) {
        Node newNode = new Node(null, item, null);
        tail.next = newNode;
        newNode.prev = tail;
        newNode.next = head;
        head.prev = newNode;
        tail = newNode;
        ++size;
    }

    /**
     * 删除指定位置index出的结点,并返回其值
     *
     * @param index
     * @return
     */
    public int remove(int index) {
        checkPosIndex(index);//

        /********** Begin *********/
         Node n = head;//保存头结点
        for(int i=0;i<index;i++){//寻找要删除的结点的前一个结点
        n = n.next;//将当前结点保存
        }
        Node del=n.next;//保存要删除的结点
        Node nextnode=del.next;//保存要删除的结点的下一个       
        n.next=nextnode;//将当前要删除的结点的前一个结点的指向,指向要删除的结点的下一个结点
        nextnode.prev=n;//将上述结点的前一个结点指向,n为要删除的结点的前一个结点(即双链表需要连接前面的结点)
        return del.item;//返回要删除的结点
        /********** End *********/
    }

    /**
     * 打印双向链表
     *
     * @param flag true从左向右顺序打印, false从右向左顺序打印
     */
    public void printList(boolean flag) {
        Node f = head;
        if (flag) {//向右
            while (f.next != head) {
                f = f.next;
                System.out.print(f.item + " ");
            }
        } else {//向左
            while (f.prev != head) {
                f = f.prev;
                System.out.print(f.item + " ");
            }
        }
    }

    public int size() {
        return size;
    }

    private void checkPosIndex(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
    }

    //结点内部类
    private static class Node {
        int item;
        Node next;//直接后继引用
        Node prev;//直接前驱引用

        Node(Node prev, int item, Node next) {
            this.prev = prev;
            this.item = item;
            this.next = next;
        }
    }
}

预期输入:1 2 3 4 5 预期输出:

  1. 2 3 4 5
  2. 5 4 3 2

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星与星熙.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值