链表(单链表,双向链表,循环链表(约瑟夫问题))的设计与实现

链表

1. 单链表

  1. 链表的介绍:链式存储,是有序的,但在内存中确实无序的。内存中存储如图所示:在这里插入图片描述

  2. 链表是用节点来存储。其中结中有,data域和next域,data域为保存数据,next域中为指向下一个节点。

  3. 链表又分为带头节点和不带节点区别,区别不大,不同环境自己区分。带头节点链表示意图:在这里插入图片描述

  4. 具体结构和单链表增删查改代码结构看,如下就是代码实现:

创建节点并创建单链表和实现类:



    /**
     * 创建单链表节点类
     */
    class Node{
        private int data;//数据域
        private Node next;//next域指向下一个节点

        public Node(int data) {
            this.data = data;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "data=" + data +
                    '}';
        }
    }

创建一个单链表:

 /**
     * 创建单链表,用于增删查改各种操作
     */
    static class SingleLinkList{
        //1.先初始化头节点
        private Node head =new Node(0);
        //2.编写获取头节点方法
        public Node getHead() {
            return head;
        }
        /**
         * 3.功能:用于添加节点
         * 添加思路:找到最后一个节点,将最后一个节点的next域指向新添加的节点
         * @param node 新添加的节点
         */
        public void add(Node node) {
            //因为头节点不能动,所以借助临时变量去遍历找到最后一个节点
            Node temp=head;
            while (true){
                if (temp.next==null){
                    break;
                }
                temp=temp.next;

            }
            //添加节点,将最后一个节点指向新节点
            temp.next=node;
        }

        /**
         * 4.功能:按值排序插入节点到应该到的位置。思路一致,
         * 找到要插入位置的前一个节点,将该节点next指向找到前一个节点的next,前一个节点的next指向node
         * @param node
         */
        public void addByOrder(Node node) {
            //因为头节点不能动,所以借助临时变量去遍历找到最后一个节点
            Node temp=head;
            while(true){
                if(temp.next == null) {//说明temp已经在链表的最后
                    break; //
                }
                //如果满足该条件表示已经满足了,可以直接插入到temp后面了
                if (temp.next.data>node.data){
                    break;
                }else {
                    temp=temp.next;
                }
            }
            //找到这个位置的前一个位置,表示与后面相连
            node.next=temp.next;
            //将node连接到链表中,表示已经插入成功。
            temp.next=node;
        }

        /**
         * 5.功能:找到该数据匹配的节点并删除,因为用于理解就没考虑没找到,和节点在在最后的情况
         * @param data 要删除节点的数据
         */
        public  void deleteNode(int data){
            //因为头节点不能动,所以借助临时变量去遍历找到最后一个节点
            Node temp=head;
            while(true){
                if(temp.next == null) {//说明temp已经在链表的最后
                    break; //
                }
                //如果满足该条件的前一个节点
                if (temp.next.data==data){
                    break;
                }else {
                    temp=temp.next;
                }
            }
            //删除节点,将要删除节点,指向temp的next的next
            temp.next=temp.next.next;
        }
        /**
         * 查询,这里就不单独查一节点了没有任何意义 就来整个链表
         */
        public void list(){
            Node temp=head;
            while(temp.next!=null){
                //将temp后移, 一定小心
                temp=temp.next;
                //输出节点的信息
                System.out.println(temp);

            }
        }

        /**
         * 等于该值修改为新节点
         * @param date 数据
         * @param node 新节点
         */
       public void updateNode(int date,Node node){
           Node temp=head;
           while (true){
               if(temp.next.data==date){
                   break;
               }
               temp=temp.next;
           }
           node.next=temp.next.next;
           temp.next=node;
       }
    }

两题小题了解一下单链表的应用:
1.查找单链表中的倒数第k个结点 【新浪面试题】代码实现如下:
思路就是遍历一次,算出为正多少,再去从length-k就是正的

 //方法:获取到单链表的节点的个数(如果是带头结点的链表,需求不统计头节点)
    /**
     *
     * @param head 链表的头节点
     * @return 返回的就是有效节点的个数
     */
    public static int getLength(Node head) {
        if(head.next == null) { //空链表
            return 0;
        }
        int length = 0;
        //定义一个辅助的变量, 这里我们没有统计头节点
        Node cur = head.next;
        while(cur != null) {
            length++;
            cur = cur.next; //遍历
        }
        return length;
    }
    public static Node findLastIndexNode(Node head, int index){
        //第二次遍历  size-index 位置,就是我们倒数的第K个节点
        int size = getLength(head);
        Node cur=head ;//因为目前是第零个
        for(int i=0;i<=size-index;i++ ){
            cur=cur.next;
        }
        return cur;
    }

2.单链表反转
1.第一种思路如果单单输出,可以借助栈先进后出原则,遍历一遍将节点塞入栈中,输出即可,没有难度,可以借助容器。
2.第二种思路:使用两个单链表,遍历一个节点,将他放入新链表的最前端,重复操作,在头节点指向另一个链表的头next域这样就可以完成了,jvm看没有指向就会回收老链表,推荐使用。
代码如下:

    //将单链表反转
    public static void reversetList(Node head){
        //定义一个辅助的指针(变量),帮助我们遍历原来的链表
        Node cur = head.next;
        Node next = null;// 指向当前节点[cur]的下一个节点
        Node reverseHead = new Node(0);
        while(cur != null) {
            next = cur.next;//先暂时保存当前节点的下一个节点,因为后面需要使用
            cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
            reverseHead.next = cur; //将cur 连接到新的链表上
            cur = next;//让cur后移
        }
        head.next = reverseHead.next;
    }

2. 双向链表

  1. 双向链表在单链表的基础上,增加了一个前驱pre,这样就方便了许多就不会之前单链表那种反转繁琐的问题,可以直接使用前驱和后继顺序和逆序遍历。
  2. 双向链表的节点结构就是,data域和前驱节点和后继节点
  3. 主要实现原理和结构重点看代码实现:
    创建双向链表节点结构如下:
 /**
     * 创建双链表节点
     */
    class Node{
        public int data;
        public Node pre;
        public Node next;

        public Node(int data) {
            this.data = data;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "data=" + data +
                    '}';
        }
    }

创建双向链表(对双向链表进行增删查改):

  /**
     * 创建双链表,并对双向链表进行增删查改
     */
    static class DoubleLinkedList {
        // 先初始化一个头节点, 头节点不要动, 不存放具体的数据
        private Node head = new Node(0);

        // 返回头节点
        public Node getHead() {
            return head;
        }

        // 添加一个节点到双向链表的最后.
        public void add(Node node) {
            Node temp = head;
            while (true) {
                // 找到链表的最后
                if (temp.next == null) {
                    break;
                }
            }
            temp.next = node;
            node.pre = temp;
        }

        //按条件大小插入双向链表节点
        public void addByOrder(Node node) {
            Node temp = head;
            while (true) {
                // 找到链表的最后直接添加
                if (temp.next == null) {
                    temp.next=node;
                    node.pre=temp;
                    break;
                }
                //找到要插入的这个节点,在该节点后面插入新节点
                if (node.data > temp.data && node.data < temp.next.data) {
                    //将新增节点的next域指向插入后面值后一个节点
                    node.next = temp.next;
                    //将找到的后一个节点的前驱指向新增节点
                    temp.next.pre = node;
                    //将找到的结点指向新增节点
                    temp.next = node;
                    //将新增节点的前驱节点指向要插入之前这个节点
                    node.pre = temp;
                    break;

                } else {
                    temp = temp.next;
                }
            }
        }

        /**
         * 功能:删除节点
         *
         * @param data 删除节点值为data的节点
         */
        public void deleteNode(int data) {
            Node temp = head;
            while (true) {
                // 找到链表的最后
                if (temp.next == null) {
                    break;
                }
                //找到要删除的节点
                if (temp.data == data) {
                    //将temp的前驱节点指向temp的next节点
                    temp.pre.next = temp.next;
                    //将temp的next节点前驱指向temp的前驱
                    temp.next.pre = temp.pre;
                    break;

                } else {
                    temp = temp.next;
                }
            }
        }

        /**
         * 遍历该接节点
         */
        public void printList() {
            Node temp = head;
            while (temp.next != null) {
                temp = temp.next;
                System.out.println(temp);
            }
        }

        /**
         * 将值为data的节点修改为新节点
         *
         * @param data 对应的值
         * @param node 要新节点
         */
        public void updateNode(int data, Node node) {
            Node temp = head;
            while (true) {
                // 找到链表的最后
                if (temp.next == null) {
                    break;
                }
                //找到要修改的节点
                if (temp.data == data) {
                    //将新节点的后继指向老节点的后继
                    node.next = temp.next;
                    //将老节点的next节点前驱节点指向新节点
                    temp.next.pre = node;
                    //将老节点的前驱的next域指向新节点
                    temp.pre.next = node;
                    //将新节点的前驱指向老节点的前驱
                    node.pre = temp.pre;
                    break;
                }
                temp=temp.next;
            }


        }
    }

3. 单向循环链表
在这里插入图片描述思路:创建一个循环链表,使用最后一个指向头节点,组成循环链表,报数到几就出栈,剩下一个指向自己,直到循环列表为空就说明全部出列了。
代码如下:
1.创建人对象节点:

class Boy{
    int no;
    Boy next;

    public Boy(int no) {
        this.no = no;
    }

    @Override
    public String toString() {
        return "Boy{" +
                "no=" + no +
                '}';
    }
}

2.**创建循环列表,**并写该算法,出队列,思路就思路,用一个节点first表示要出队列的节点,一个辅助指针指向尾,一直循环这样的情况就可以出队,可以细看代码就清晰了。

/**
 * 循环列表
 */
class JosePhuLinkList{
    // 创建一个first节点,当前没有编号
    private Boy first = null;

    /**
     * 功能:创建小孩
     * @param num
     */
    public  void addBoy(int num){
        Boy curBoy = null; // 辅助指针,帮助构建环形链表
        for(int i=1;i<=num;i++){
            Boy boy = new Boy(i);
            //如果是第一个节点,将第一个节点插入,并next指向自己,组成循环链表
            if(i==1){
                first=boy;//第一个节点
                first.next=first;//自己指向自己用于构建循环链表
                curBoy=first;//让curBoy指向第一个小孩
            }else {
                curBoy.next=boy;//连接新节点
                boy.next=first;//指向头节点,继续循环链表
                curBoy=boy;//后移一直指向尾结点

            }
        }
    }
    public void showBoys(){
        // 因为first不能动,因此我们仍然使用一个辅助指针完成遍历
        Boy curBoy = first;
        while(true){
            System.out.println(curBoy);
            //因为循环链表,单最后一个结点的next为头时候就表明结束,没人了
            if(curBoy.next==first){
                break;
            }
            curBoy=curBoy.next;//对他进行后移

        }
    }

    /**
     *功能:根据用户输入的起始节点位置,数几次,进行出列
     * @param startNo 开始数数的节点
     * @param countNum 表示数几次例如(数2,就数到2的出列)
     * @param nums  表示圈中有几个人
     */
    public void countBoy(int startNo, int countNum, int nums) {
        // 创建要给辅助指针,帮助完成小孩出圈
        Boy helper = first;//为指向最后一个节点
        while (true) {
            if (helper.next == first) { // 说明helper指向最后小孩节点
                break;
            }
            helper = helper.next;
        }
        //小孩报数前,先让 first 和  helper 移动 k - 1次,缺点头尾,头往后移,尾也是往后移
        for(int j = 0; j < startNo - 1; j++) {
            first = first.next;
            helper = helper.next;
        }
        //当小孩报数时,让first 和 helper 指针同时 的移动  m  - 1 次, 然后出圈
        //这里是一个循环操作,知道圈中只有一个节点
        while (true){
            if(helper == first) { //说明圈中只有一个节点
                break;
            }
            //往后移
            for(int i=0;i<countNum-1;i++){
                first = first.next;
                helper = helper.next;
            }
            //这时first指向的节点,就是要出圈的小孩节点
            System.out.println(first.no);
            //这时将first指向的小孩节点出圈
            first = first.next;
            helper.next=first;
        }
        System.out.println(first.no);//最后一个出圈的就是对尾就是头只有一个节点就出
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Z J X

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

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

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

打赏作者

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

抵扣说明:

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

余额充值