ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

动态链表----单向循环链表的实现

2022-01-17 11:30:35  阅读:95  来源: 互联网

标签:---- head 指向 index 单向 next 链表 节点


1. 单向循环链表的定义:如果把单链表的最后一个节点的指针指向链表头部,而不是指向null,那么就构成了一个单向循环链表。如图:

 2. 单向循环链表的实现:单向循环链表实现与单向链表的实现相似,同样是实现线性表List的接口。

//单向循环链表的实现
public class LinkedSinglyCircularList<E> implements List<E> {

    /*将数据封装成节点 节点包括两部分 数据域和指针域
   数据域中存放的是数据内容
   指针域中存放的是当前节点的下一跳节点的地址 即指针域指向当前节点的下一跳节点
   因为是单向循环链表 因此尾节点的下一跳始终指向头节点
    */

    class Node{ //封装数据域和指针域的类
        E data; //表示节点的数据域
        Node next; //表示节点的指针域

        public Node(){ //节点的无参构造
            this(null, null);
        }
        public Node(E element){ //节点的带参构造 参数为数据的内容
            this(element, null);
        }
        public Node(E element, Node next){ //节点的带参构造 参数为数据的内容和下一个节点的地址
            this.data = element;
            this.next = next;
        }

        //格式化节点输出时的格式 即输出数据内容即可
        @Override
        public String toString() {
            return data.toString();
        }
    }

    //表示头指针 指向链表中的头节点
    private Node head;
    //表示尾指针 指向链表中的尾节点
    private Node tail;
    //表示链表中有效元素的个数
    private int size;

    public LinkedSinglyCircularList(){ //单向链表的无参构造 为head tail size初始化
        head = null;
        tail = null;
        size = 0;
    }

    public LinkedSinglyCircularList(E[] arr){ //单向链表的带参构造 参数为一个数组 将数组中的元素依次添加到链表中
        if(arr == null || arr.length == 0){
            throw new IllegalArgumentException("arr is null");
        }
        for (int i = 0; i < arr.length; i++){
            add(arr[i]);
        }
    }

    //向链表的表尾追加元素
    @Override
    public void add(E element) {
        add(size, element);
    }

    //向链表的指定位置添加元素
    @Override
    public void add(int index, E element) {
        if(index < 0 || index > size){ //判断索引是否存在
            throw new IllegalArgumentException("add index out of range");
        }

        Node node = new Node(element); //创建节点对象

        if(size == 0) { //当表空时 添加元素 只需头尾指针同时指向要添加的节点 尾节点的下一跳指向头节点即可
            head = node;
            tail = node;
            tail.next = head;
        }else if(index == 0) { //向表头添加元素
            /*
            先要将要添加节点的下一跳指向头节点 再将头指针指向要添加的节点 使尾节点下一跳指向头节点
             */
            node.next = head;
            head = node;
            tail.next = head;
        }else if(index == size) { //向表尾添加元素
            /*
            先将尾节点的下一跳赋值给要添加节点的下一跳
            先将尾节点的下一跳指向要添加的节点 再将尾指针指向要添加的节点
             */
            node.next = tail.next;
            tail.next = node;
            tail = node;
        }else { //向表中间添加元素
            /*
            先遍历到要添加的位置的前驱 将前驱节点的下一跳赋值给要添加节点的下一跳 前驱节点的下一跳指向要添加的节点
             */
            Node p = head;
            for(int i = 0; i < index - 1; i++){
                p = p.next;
            }
            node.next = p.next;
            p.next = node;
        }
        size++;
    }

    //删除链表中的指定元素
    @Override
    public void remove(E element) {
        remove(indexOf(element));
    }

    //删除链表中的指定位置的元素 并返回要删除元素的内容
    @Override
    public E remove(int index) {
        if(index < 0 || index >= size){ //判断索引是否存在
            throw new IllegalArgumentException("remove index out of range");
        }

        E ele = null;
        if(size == 1) { //当表中只有一个元素时删除
            /*
            当表中只有一个元素时删除:只需要先取出那一个元素 再将头指针head 尾指针tail同时指向null即可
             */
            ele = head.data;
            head = null;
            tail = null;
        }else if(index == 0) { //删除表头元素
            /*
            删除表头元素:
            1.定义一个新的节点使其也指向头节点
            2.取出这个新节点的元素
            3.更新头指针 使其指向当前头节点的下一个节点
            4.使新节点的指针域指向null
            5.最后将尾节点的下一跳指向新的头节点
             */
            Node n = head;
            ele = n.data;
            head = n.next;
            n.next = null;
            tail.next = head;
        }else if(index == size - 1) { //删除表尾元素
            /*
            删除表尾元素:
            1.先定义一个新节点p 使其开始指向头节点 遍历链表 直至新节点p指向链表的尾节点前驱节点
            2.取出尾节点的数据
            3.将尾节点的下一跳赋值给p节点的下一跳
            4.更新尾指针 是尾指针指向p节点
             */
            Node p = head;
            while (p.next != tail){
                p = p.next;
            }
            ele = tail.data;
            p.next = tail.next;
            tail = p;
        }else { //删除表中间元素
            /*
            删除表中间元素:
            1.先定义一个新节点p 使其开始指向头节点 遍历链表 直至遍历到指定索引的前驱节点的索引 每轮更新p节点
            2.再定义一个节点为要删除的节点n 即当前p节点的下一跳节点 取出要删除的节点n的内容
            3.使p节点的下一跳重新指向要删除的节点n的下一跳
            4.使要删除的节点n的下一跳指向null
             */
            Node p = head;
            for(int i = 0; i < index - 1; i++){
                p = p.next;
            }
            Node n = p.next;
            ele = n.data;
            p.next = n.next;
            n.next = null;
        }
        size--;
        return ele;
    }

    //获取指定索引处的节点的值
    @Override
    public E get(int index) {
        if(index < 0 || index >= size){ //判断索引是否存在
            throw new IllegalArgumentException("get index out of range");
        }
        if(index == 0){ //获取表头元素 即头指针指向的节点的值
            return head.data;
        }else if(index == size - 1){ //获取表尾元素 即尾指针指向的节点的值
            return tail.data;
        }else{ //获取表中间某一索引的值
            /*
            获取表中间某一索引的值:定义一个新节点p使其指向头节点 从表头开始遍历 遍历到索引位置 每次更新p节点
            最后p节点就是要获取节点 返回该节点的值即可
             */
            Node p = head;
            for(int i = 0; i < index; i++){
                p = p.next;
            }
            return p.data;
        }
    }

    //修改指定位置的节点的值 并返回要修改节点的值
    @Override
    public E set(int index, E element) {
        if(index < 0 || index >= size){ //判断索引是否存在
            throw new IllegalArgumentException("set index out of range");
        }
        E ele = null;
        if(index == 0){ //修改表头节点的值 即先获取表头结点的值 再将表头结点的值修改为指定的值element即可
            ele = head.data;
            head.data = element;
        }else if(index == size - 1){ //修改表尾节点的值 即先获取表尾结点的值 再将表尾结点的值修改为指定的值element即可
            ele = tail.data;
            tail.data = element;
        }else{ //修改表中间位置的节点的值
            /*
            修改表中间位置的节点的值:定义一个新节点p使其指向头节点
            从头节点开始遍历 遍历到索引位置的前驱位置 每次更新p节点为p节点的下一个节点
            遍历结束后 p节点即指定要修改的索引处的节点 获取p节点的值
            修改p节点的值为element
             */
            Node p = head;
            for(int i = 0; i < index; i++){
                p = p.next;
            }
            ele = p.data;
            p.data = element;
        }
        return ele;
    }

    //获取链表有效元素的个数
    @Override
    public int size() {
        return size;
    }

    //获取指定元素第一次在链表出现的索引
    @Override
    public int indexOf(E element) {
        /*
        定义一个新节点p使其指向头节点
        遍历链表 直到p节点的数据与指定数据相等即可
        每次更新p节点为p节点的下一个节点 索引index的值
        判断 如果p节点再次指向头节点 则表示已经遍历了一圈了 都没有找到指定元素 则返回-1
         */
        int index = 0;
        Node p = head;
        while (!p.data.equals(element)){
            p = p.next;
            index++;
            if(p == head){
                return -1;
            }
        }
        return index;
    }

    //判断链表中是否包含指定元素
    @Override
    public boolean contains(E element) {
        return indexOf(element) != -1;
    }

    //判断链表是否为空
    @Override
    public boolean isEmpty() {
        return size == 0 && (head == null && tail == null);
    }

    //清空链表中的所有元素
    @Override
    public void clear() {
        head = null;
        tail = null;
        size = 0;
    }

    //对链表进行排序 选择排序的思想 时间复杂度为O(n^2)
    @Override
    public void sort(Comparator<E> c) {
        if(c == null){ //判断比较器是否为空
            throw new IllegalArgumentException("comparator can not be null");
        }
        if(size == 0 || size == 1){ //如果链表中有效元素的个数等于0或1 就不需要在对链表进行排序
            return;
        }
        Node nodeA = head; //定义一个节点A 使其指向头节点
        Node nodeB = nodeA.next; //定义一个节点B 使其指向节点A的下一个节点
        while (true){ //第一层循环 操作节点A
            while (true){ //第二层循环 操作节点B
                if(c.compare(nodeA.data, nodeB.data) > 0){ //如果节点A的值大于节点B的值 就交换节点A的数据和节点B的数据的位置
                    swap(nodeA, nodeB);
                }
                if(nodeB == tail){ //如果节点B等于尾节点 就中断内层循环
                    break;
                }
                //更新节点B为当前节点B的下一个节点
                nodeB = nodeB.next;
            }
            if(nodeA.next == tail){ //如果节点A的下一跳指向尾节点 就中断外层循环
                break;
            }
            nodeA = nodeA.next; //更新节点A为当前节点A的下一个节点
            nodeB = nodeA.next; //更新节点B为当前节点B的下一个节点
        }
    }

    //交换两个节点的值的位置
    private void swap(Node nodeA, Node nodeB) {
        E temp = nodeA.data;
        nodeA.data = nodeB.data;
        nodeB.data = temp;
    }

    //获取链表中指定索引范围的子链表 [fromIndex, toIndex]
    /*
    先定义一个节点A 使其指向头节点 遍历链表 更新节点A 使其到达fromIndex位置处
    再定义一个节点B 也使其指向头节点 遍历链表 更新节点B 使其到达toIndex位置处
    定义一个新节点使其指向节点A 从fromIndex位置遍历到toIndex位置 最后使其指向节点B
    将[fromIndex, toIndex]范围内的节点重新依次加入到新的子链表中
    该获取子链表的时间复杂度为O(n)
     */
    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        if(fromIndex < 0 || fromIndex > toIndex || toIndex >= size){ //判断两个索引是否存在
            throw new IllegalArgumentException("must 0 <= fromIndex <= toIndex <= size - 1");
        }
        LinkedSinglyCircularList<E> linkedList = new LinkedSinglyCircularList<E>();
        Node nodeA = head;
        for(int i = 0; i < fromIndex; i++){
            nodeA = nodeA.next;
        }
        Node nodeB = head;
        for(int i = 0; i < toIndex; i++){
            nodeB = nodeB.next;
        }
        Node p = nodeA;
        for(int i = fromIndex; i < toIndex; i++){
            linkedList.add(p.data);
            p = p.next;
        }
        return linkedList;
    }

    //格式化链表输出是的格式
    @Override
    public String toString() {
        StringBuilder str = new StringBuilder();
        str.append("LinkedSinglyList: " + size + " [");
        if(isEmpty()){
            str.append(']');
        }
        Node p = head;
        while (true){
            str.append(p.data);
            if(p == tail){
                str.append(']');
                break;
            }else{
                str.append(',');
                str.append(' ');
            }
            p = p.next;
        }
        return str.toString();
    }

    //获取当前这个数据结构/容器 的 迭代器
    //通过迭代器对象 更方便挨个取出每一个元素
    //同时 实现了Iterable 可以让当前的数据结构/容器 被foreach循环遍历
    @Override
    public Iterator<E> iterator() {
        return new LinkedSinglyCircularListIterator();
    }

    class LinkedSinglyCircularListIterator implements Iterator<E>{

        private Node cur; //定义一个游标节点
        private boolean flag = true; //表示是否在第一圈内 在第一圈是true 遍历到第二圈是false

        @Override
        public boolean hasNext() { //判断是否有下一个节点
            return flag; //flag == true时 表示有下一个节点
        }

        @Override
        public E next() { //获取下一个节点的值
            E ele = cur.data; //先获取节点cur的值
            cur = cur.next; //再更新cur节点为当前cur节点的下一个节点
            if(cur == head){ //判断cur游标节点是否重新指向头节点 如果是则表示已经遍历一圈了 反之还在第一圈
                flag = false;
            }
            return ele;
        }
    }

    //单向循环链表实现约瑟夫环问题
    /*
     * 据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与
     * Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了
     * 一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,
     * 然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。
     * 首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。
     * 接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,
     * 这个人就可以继续活着。Josephus要他的朋友先假装遵从,
     * 他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
     */

    /*
    先定义一个新节点p 使其指向头节点head
    先更新p节点为当前p节点的下一个节点 再定义要删除的的节点del del为当前p节点的下一个节点
    判断要删除的节点 如果是头节点 就使头节点指向要删除节点的下一个节点
    如果是尾节点 就尾节点指向要删除节点的前驱节点p
    将要删除节点的下一跳赋值给p节点的下一跳
    使要删除节点的下一跳指向null(相当于删除了del节点)
    再更新p节点为当前p节点的下一跳
    有效元素个数减一

    循环以上步骤 直至有效元素的个数等于2
   */
    public void josephusLoop(){
        if(size <= 2){
            return;
        }
        Node p = head;
        while (size != 2){
            p = p.next;
            Node del = p.next;
            if(del == head){
                head = del.next;
            }else if(del == tail){
                tail = p;
            }
            p.next = del.next;
            del.next = null;
            p = p.next;
            size--;
        }
    }

    //链表反转的问题
    /*
    我们可以定义一个虚拟头节点 将原链表的每一个节点(从头节点开始到尾节点结束)依次都插在虚拟头节点的下一跳节点
    最后形成原链表的倒序链表(倒序新链表)
     */
    public void reverse(){
        if(size == 0 || size == 1){
            return;
        }
        Node dummpyHead = new Node(); //虚拟头节点
        Node p = head;
        for(int i = 0; i < size; i++){
            Node n = new Node(p.data); //创建新的节点对象 组成新的倒序链表
            if(dummpyHead.next == null){
                tail = n;
            }
            n.next = dummpyHead.next;
            dummpyHead.next = n;
            p = p.next;
        }
        head = dummpyHead.next;
    }
}

标签:----,head,指向,index,单向,next,链表,节点
来源: https://blog.csdn.net/NancyLCL/article/details/122536185

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有