链表(单向链表&双向链表&单向环形链表及约瑟夫问题)

文章介绍了单向链表和双向链表的结构,包括数据域、next域和prev域。在单向链表中,通过尾插法添加节点,按顺序删除节点,线性搜索查询节点。而在双向链表中,增加了对prev域的管理,并使用折半查找提高查询效率。文章还涉及了约瑟夫问题的解决方案,利用环形链表模拟报数出圈的过程。
摘要由CSDN通过智能技术生成

单向链表 

如上图:

1.链表是以节点的方式来存储的;

2.每个节点包括了data域next域,其中data域就是存放元素的,next域就是指向下一个节点的;

3.链表的每个节点都不一定是连续存储的;

4.链表根据结构可以分为单向链表和双向链表,根据需求可以选择带头结点的链表和不带头节点的链表(环形链表)

流程分析

如上图我们如果想要使用代码实现单向链表:

1.需要定义节点、链表的长度、节点的data域以及next域以及头节点和尾节点的标识;

2.对于新增方法,我们选择尾插法,也就是让尾节点的next域指向新增的节点

3.对于删除方法,我们需要找到需要删除的节点,然后让它的上一个节点的next域指向它的next域

4.对于查询方法,我们遍历链表,从头节点,挨个挨个next节点的去找,直到找到目标节点

代码实现

public class StrineList<T> {

    Node<T> first;
    Node<T> last;

     int size;

    /**
     *
     * @Author:strine
     * 定义的节点
     * */
    public static class Node<T>{
        T data;
        Node<T> next;
        Node(Node<T> next,T data){
            this.data=data;
            this.next=next;
        }
    }
     
    /**
     *
     * @Author:strine
     * 新增方法
     * */
    public void put(T t){
        //获取最后一个节点,并临时保存
        Node<T> la =last;
        //根据传入的参数生成新的节点
        Node<T> newNode = new Node<>(null, t);
        //将该节点使用尾插法插入链表尾部
        last=newNode;
        //判断临时保存的链表尾节点是否为null
        if (la==null){
            //第一次添加
            first=newNode;
        }else {
            //如果不为null就把新节点添加到最开始的尾结点的后方
            la.next=newNode;
        }
        size++;
    }

    /**
     *
     * @Author:strine
     * 根据下标的删除方法
     * */
    public void remove(int index){
        //删除操作其实就是将上一个节点的指针指向目标节点的下一个节点
        //找到目标节点和上一个节点
        Node<T> node = getNode(index);
        Node<T> prev = getNode(index - 1);
        if (node==last){
            //若目标节点为尾节点,则让尾节点指向上一个节点
            last=prev;
        }else {
            // 否则就让上一个节点的next域指向目标节点的next域
        prev.next=node.next;
        }
        // 告诉GC去回收垃圾
        node=null;
        size--;
    }
    /**
     *
     * @Author:strine
     * 获取节点
     * */
    public Node<T> getNode(int index){
        //获取到头节点
        Node<T> tar = first;
        if (index<=size){
            // 挨着下个节点下个节点的遍历,直到找到目标节点
            for (int i = 0; i < index; i++) {
                tar=tar.next;
            }
            return tar;
        }
        return null;
    }
    /**
     *
     * @Author:strine
     * 获取目标值
     * */
    public T get(int index){
        Node<T> node = getNode(index);
        return node.data;
    }


}

双向链表

如上图:

1.双向链表在单向链表的基础上需要额外维护每个节点的prev域

2.因此我们在新增和删除的时候,不仅要关注next域的指向,还需要关注prev域的指向;

3.考虑到效率的问题,如果该链表长度无限增加,时间复杂度为O(n)那么查询效率很低,因此我们修改了一下查询方法,我们使用了折半查找算法

代码实现

public class StrineLinkedList<T> {

    /**
     * @first,@last
     * 整个链表里面的头节点和尾节点;
     * @Author:strine
     * */
    transient Node<T> first;//没有prev节点的就是头节点;

    transient Node<T> last;//没有next节点的就是尾节点;

    transient int size=0;

    /**
     *
     * 双向链表
     * */
    private static class Node<T>{
        //元素
        T item;
        //next节点
        Node<T> next;
        // prev节点
        Node<T> prev;
        Node(Node<T> prev,T element,Node<T> next){
            this.item=element;
            this.prev=prev;
            this.next=next;
        }
    }

    /**
     * 新增方法
     * */
   public void add(T t){
       Node<T> l = last;
       //采用尾插法
       Node<T> newNode = new Node<T>(l,t,null);
       last=newNode;
       if (l==null){
           //说明是第一次新增
           first=newNode;
       }else {
           l.next=newNode;
       }
       size++;
   }

   public Node<T> getNode(int index){
       // 折半查找算法
        if (index<size>>1){
            Node<T> x = first;
            for (int i = 0; i <index; i++) {
                x=x.next;
            }
            return x;
        }else {
            Node<T> x = last;
            for (int i = size-1; i >index; i--) {
                x=x.prev;
            }
            return x;
        }
   }

   public T get(int index){
       Node<T> node = getNode(index);
       return node.item;
   }

   public T remove(int index){
       Node<T> node = getNode(index);
       T x = node.item;
       Node<T> prev = node.prev;
       Node<T> next = node.next;
       if (prev==null){
           first=next;
       }else {
           prev.next=next;
           node.prev=null;  // 告诉GC去清理垃圾
       }
       if (next==null){
           last=prev;
       }else {
           next.prev=prev;
           node.next=null;
       }
       node.item=null;
       size--;
       return x;
   }
   

单链表常见面试题

 

1.第一个问题,因为我们已经在链表中维护了size这个属性了,因此只需要返回该size即可;

2.第二个问题我们可以拿到当前链表的尾结点,然后进行反向遍历;

3.第三个问题逻辑稍微有点绕,我们直接来看代码:

    public StrineList<T> overTurn(){
        StrineList<T> newList = new StrineList<>();
        Node<T> la = last;
        newList.first=la;
        Node<T> newNext = newList.first;
        for (int i = size-1; i >= 0; i--) {
           Node<T> head = getNode(i);
            newNext.next=head;
            newNext=head;
            newList.size++;
        }
        return newList;
    }

该代码的主要逻辑就是:先获取到原链表的尾节点,然后赋给新链表的头节点,然后从后往前遍历原链表,获取一个节点就把那个节点赋给当前节点的next域;

4.第四个问题就直接从后往前遍历即可,或者使用栈(先进后出原理--后面再细讲)来设计;

单向环形链表&约瑟夫问题

设编号为1,2,3.....n的n个人围成一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又开始从1开始报数,数到m的那个人又出列,以此类推直到所有人出列,由此产生一个出列编号的序列

提示:

1.用一个不带头节点的循环链表来解决;

2.先构成一个有n个节点的单循环链表;

3.然后又K节点起从1开始计数,记到m的时候,对应节点从链表中删除;

4.然后再从被删除节点的下一个节点又从1开始计数,直到最后一个节点从链表中删除则算法结束;

我这里没有写删除方法,因此就直接去拿元素,然后存到另外一个单向链表中;

我们首先实现一个环形链表如下:

public class CircleList {

    Node<Integer> first;
    int size;

    public static class Node<Interger>{
        Node<Integer> next;
        Integer data;
        Node(Node<Integer>next,Integer t){
            this.next=next;
            this.data=t;
        }
    }

    public CircleList( int size) {
        this.size = size;
        if (size>0){
            first=new Node<>(null,1);
        }
        Node<Integer> head = first;
        for (int i = 1; i < size; i++) {
            Node<Integer> newNode = new Node<>(null, i);
            head.next=newNode;
            head=newNode;
        }
        head.next=first;
    }

约瑟夫方法:


    public StrineList<Integer> getJosephuList(int k,int m){
        //约瑟夫问题,从k开始报数,报数到m出列
        int count = 1;
        StrineList<Integer> list = new StrineList<>();
        int sum = 0;
        int i=k;
        for ( ; ; i++) {
            if (i>size){
                i=0;
                continue;
            }
            if (sum==size){
                break;
            }
            if (count==m){
             /*   第一次i=2,count=1,m=3没有找到
                第二次i=3,count=2.m=3没有找到
                第三次i=4,count=3找到了,输出4,K自增为=3,Count重置为1
                第四次i=3,count=1,m=3 没有找到
                第五次i=4,count=2,m=3没有找到
                第六次i=5,count=3,m=3,输出1,K自增为4,Count重置1;
                第七次i=4,count=1,m=3
                第八次i=5,count=2,m=3 没有找到
                第九次,i=0,count=3,m=3 输出1,K自增为5,Count重置1.
                第十次,i=5,c=1 m=3
                i=0 c=2 m=3
                i=1 c=3 m=3  输出2 K=0,Count重置1;
                i=0 c=1 m=3
                i=1 c=2 m=3
                i=2 c=3 m=3 输出3 k=1 count重置1 sum=size 结束循环
                */
                Integer ret = get(i);
                list.put(ret);
                count=1;
                k=k+1;
                i=k;
                if (k==size){
                    k=0;
                }
                sum++;
            }
            count++;
        }
        return list;
    }

 如果是删除的话,则在找到目标节点之后调用删除方法即可,直到原链表为空才结束循环

(这里我偷了个懒,没有实现删除方法,因此直接将找到的元素存到了单链表中,直到新链表的长度和原链表的长度相同则结束循环);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Strine

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

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

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

打赏作者

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

抵扣说明:

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

余额充值