Java实现单向链表和单向循环链表

首先来构造数据结构,这里单链表的节点类是以内部类的形式出现

数据结构

节点类:

节点类中应该有数据域和指针域

	/**
     * 节点类
     */
    class Entry {
        //数据域
        private int data;
        //指针域
        private Entry next;

        //无参构造函数
        public Entry() {
            this(0,null);
        }

        //有参构造函数
        public Entry(int data, Entry next) {
            this.data = data;
            this.next = next;
        }

        public int getData() {
            return data;
        }

        public void setData(int data) {
            this.data = data;
        }

        public Entry getNext() {
            return next;
        }

        public void setNext(Entry next) {
            this.next = next;
        }
    }
单链表类

      来写外部类,外部类就是单链表类,单链表类需要维护一个头结点,头结点的数据域是不存放数据的,头结点的指针域为空。

public class MiNiLinkedList {
    //头结点
    private Entry head;

    public MiNiLinkedList() {
        this.head = new Entry(0,null);
    }
}

注意,节点类是单链表类的一个内部类

操作

头插法

      插入节点是从头开始插入,思路很简单,直接把生成的新节点插入到头节点的后面,注意不能丢失原来头结点后面的节点,所以需要先把原来的节点链到新节点的后面,再把新节点接到头结点的后面。

时间复杂度:O(1)

    //从头插入节点
    public void insertHead(int data) {
        //new一个新节点
        Entry entry = new Entry(data, null);
        //防止后面的节点丢失,先把后面的节点接到新节点的后面
        entry.next = head.getNext();
        //把新节点接入到头结点的后面
        head.next = entry;
    }
尾插法

      从链表的末尾插入元素,首先需要定义一个指针插入到链表的末尾,然后把新生成的节点插入到末尾的后面。

由于需要遍历到链表的后面,所以时间复杂度是O(n)

    //从尾进行插入
    public void insertTail(int data) {
        //new一个新的节点
        Entry entry = new Entry(data, null);
        //定义一个指针,遍历到链表的最后
        Entry last = head;

        while(last.next != null) {
            last = last.next;
        }
        last.next = entry;
    }

      有时经常进行尾插操作时,为了不用每次插入都遍历到链表的最后,可以维护一个尾节点,如果维护尾结点需要注意几点:

  1. 在第一次头插的时候,需要移动尾结点到新节点
  2. 每一次尾插的时候都需要更新尾结点
  3. 在删除的时候,如果删除的节点是尾结点,那么需要把尾结点往前移动
删除第一个值为data的节点

      定义一个指针指向前一个节点,遍历链表,当下一个节点是要找的节点的时候,通过当前节点删除该节点。

    //删除第一个值为data的节点
    public boolean removeFirst(int data) {
        //定义一个指针,找到待删除节点的前一个节点
        Entry entry = head;
        while(entry.next!=null && entry.next.data!=data){
            entry = entry.next;
        }

        //删除待删节点
        if(entry.next == null) {
            //遍历到最后
            return false;
        } else {
            //删除该节点
            entry.next = entry.next.next;
            return true;
        }
    }

      可以看到我的代码实现是维护了一个指针,其实也可以维护两个指针,移动的时候两个指针同时移动,这样理解起来比较容易。

删除所有值为data的节点

      刚才删除第一个值为data的节点是遍历到对应的位置,指针就停下来,而删除所有值为data的节点,在遍历的过程中是不停止的,如果找到了值为data的节点,就删除该节点,然后继续往下遍历。

    //删除所有值为data的节点
    public void removeAll(int data) {
        //定义一个指针,找到待删除节点的前一个节点
        Entry entry = head;
        while(entry.next!=null){
            if(entry.next.data == data) {
                entry.next = entry.next.next;
            } else {
                entry = entry.next;
            }
        }
    }
查询

查询的逻辑比较简单,遍历整个链表即可

    public boolean query(int data) {
        //定义一个指针
        Entry cur = head.next;

        //逐一遍历
        while(cur != null) {
            if(cur.data == data) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

      在讲解完了单向链表了以后,单向循环链表就非常简单了,单向循环链表的特点是最后一个节点的指针域为head,所以在一些判断的过程中就需要把null该为head就可以了。一起来看看有什么变化

节点类的数据结构是相同的,直接来看循环链表的数据结构

数据结构

      循环链表的数据结构我们在初始化头结点head的时候,需要让head的指针域指向自己,成为一个自己和自己的环

public class CircleLinkedList {

    //头结点
    private Entry head;

    public CircleLinkedList() {
        this.head = new Entry(0,null);
        head.next = head;
    }
}
头插

      由于在构造函数中头结点已经自己指向了自己,插入不会涉及尾到头这一层关系的变动,所以头插的代码是完全不用变化的。

    //从头插入节点
    public void insertHead(int data) {
        //new一个新节点
        Entry entry = new Entry(data, null);
        //防止后面的节点丢失,先把后面的节点接到新节点的后面
        entry.next = head.getNext();
        //把新节点接入到头结点的后面
        head.next = entry;
    }
尾插

      尾插在单向链表中需要找到尾结点,我们刚才说过了尾结点的标志不是null,是head,看一下代码的改动

    //从尾进行插入
    public void insertTail(int data) {
        //1.第一处改动,新创建的节点指向头结点
        Entry entry = new Entry(data, head);
        Entry last = head;
		//2.第二处改动,结束的标志不是null,而是head
        while(last.next != head) {
            last = last.next;
        }
        last.next = entry;
    }

这些想不来的话画画图是很好理解的

删除第一个值为data的节点

直接改掉null为head即可

    //删除第一个值为data的节点
    public boolean removeFirst(int data) {
        //定义一个指针,找到待删除节点的前一个节点
        Entry entry = head;
        while(entry.next!=head && entry.next.data!=data){
            entry = entry.next;
        }

        //删除待删节点
        if(entry.next == head) {
            //遍历到最后
            return false;
        } else {
            //删除该节点
            entry.next = entry.next.next;
            return true;
        }
    }
删除所有值为data的节点

同样,改掉null

    //删除所有值为data的节点
    public void removeAll(int data) {
        //定义一个指针,找到待删除节点的前一个节点
        Entry entry = head;
        while(entry.next!=head){
            if(entry.next.data == data) {
                entry.next = entry.next.next;
            } else {
                entry = entry.next;
            }
        }
    }
查询

改null为head

    public boolean query(int data) {
        //定义一个指针
        Entry cur = head.next;
        //逐一遍历
        while(cur != head) {
            if(cur.data == data) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

到这,单向循环链表就创建完了,可以自行测试一下


版权声明:本文为weixin_42220532原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。