【数据结构java篇】- 链表

文章目录

  • 1 链表(LinkedList)介绍
  • 2 单链表的应用实例
  • 3 单链表面试题(新浪、百度、腾讯)
  • 5 双向链表应用实例
    • 5.1 双向链表的操作分析和实现
  • 6 单向环形链表应用场景
    • 6.1 单向环形链表介绍
    • 6.2 Josephu问题

1 链表(LinkedList)介绍

链表是有序的列表,但是它在内存中是存储如下
【数据结构java篇】- 链表_第1张图片
小结上图:

  1. 链表是以节点的方式来存储,是链式存储
  2. 每个节点包含data域,next域:指向下一个节点
  3. 如图:发现链表的各个节点不一定是连续存储
  4. 链表分带头节点的链表没有头节点的链表,根据实际的需求来确定
    单链表(带头结点)逻辑结构示意图如下
    【数据结构java篇】- 链表_第2张图片

2 单链表的应用实例

使用带head头的单向链表实现–水浒英雄排行榜管理完成对英雄人物的增删改查操作,注:删除和修改,查找可以考虑学员独立完成,也可带学员完成

  1. 第一种方法在添加英雄时,直接添加到链表的尾部
    思路分析示意图:
    【数据结构java篇】- 链表_第3张图片
  2. 第二种方式在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)思路的分析示意图:
    【数据结构java篇】- 链表_第4张图片
  3. 修改节点功能
    思路(1)先找到该节点,通过遍历,(2)temp.name = newHeroNode.name;temp.nickname=newHeroNode.nickname
  4. 删除节点
    思路分析的示意图:
    【数据结构java篇】- 链表_第5张图片
  5. 完成的代码演示:
package com.sukang.linkedlist;

import java.util.Stack;

/**
 * @description: 单链表模拟水浒英雄案例
 * @author: sukang
 * @date: 2019-12-30 10:43
 */
public class SingleLinkedListDemo {

    public static void main(String[] args) {
        HeroNode heroNode1 = new HeroNode(1, "张三", "zs");
        HeroNode heroNode2 = new HeroNode(2, "李四", "ls");
        HeroNode heroNode3 = new HeroNode(3, "王五", "ww");
        HeroNode heroNode4 = new HeroNode(4, "刘六", "ln");

        SingleLinkedList singleLinkedList = new SingleLinkedList();

        //增加在尾部
       /* singleLinkedList.addHeroNode(heroNode3);
        singleLinkedList.addHeroNode(heroNode1);
        singleLinkedList.addHeroNode(heroNode2);
        singleLinkedList.addHeroNode(heroNode4);*/

        //按照顺序添加
        singleLinkedList.addHeroNodeByOrder(heroNode3);
        singleLinkedList.addHeroNodeByOrder(heroNode1);
        singleLinkedList.addHeroNodeByOrder(heroNode2);
        singleLinkedList.addHeroNodeByOrder(heroNode4);

        singleLinkedList.list();

        //删除一个节点
        singleLinkedList.delHeroNode(1);
        System.out.println("删除节点之后遍历");
        singleLinkedList.list();

        //修改一个节点
        HeroNode heroNode5 = new HeroNode(2, "马七", "mq");

        singleLinkedList.updateHeroNode(heroNode5);
        System.out.println("修改节点之后遍历");
        singleLinkedList.list();

        //测试一下单链表中有效结点
        System.out.println("单链表中有效结点个数为:" + SingleLinkedListDemo.getEffective(singleLinkedList.getHead()));

        //查看链表的倒数第1个元素
        System.out.println("查看链表的倒数第1个元素为:" +  SingleLinkedListDemo.getHeroNode(singleLinkedList.getHead(), 1));


        //测试链表反转
        System.out.println("反转前链表遍历");
        singleLinkedList.list();
        SingleLinkedListDemo.linkedReversal(singleLinkedList.getHead());
        System.out.println("反转后链表遍历");
        singleLinkedList.list();

        //测试链表反向打印
        System.out.println("反向打印前链表遍历");
        singleLinkedList.list();
        System.out.println("反向打印链表");
        SingleLinkedListDemo.linkedReversalPrint(singleLinkedList.getHead());
    }

    /**
     * @description: 面试题,求一个链表中有效数据个数
     * @param head
     * @return: int
     * @author: sukang
     * @date: 2020/1/2 14:25
     */
    public static int getEffective(HeroNode head){
        //判断是否为空链表
        if(head.next == null){
            return 0;
        }

        int count = 0;
        HeroNode temp = head.next;
        while (true){
            if(temp == null){
                break;
            }

            count ++;
            temp = temp.next;
        }

        return count;
    }

    /**
     * @description: 面试题:单链表中倒数第k个节点
     * @param head
     * @param k
     * @return:
     * @author: sukang
     * @date: 2020/1/2 14:36
     */
    public static HeroNode getHeroNode(HeroNode head, int k){
        //空链表
        if(head.next == null){
            return null;
        }

        //获取单链表中有效数据
        int count = getEffective(head);

        if(!(k > 0 && k < count)){
            throw new RuntimeException("k传值超过有效范围");
        }

        HeroNode temp = head.next;
        //从头遍历单链表遍历到(count - k),也就是倒数第k个节点
        for (int i = 0; i < count - k ; i++) {
            temp = temp.next;
        }

        return temp;
    }

    /**
     * @description: 面试题链表反转
     * @param
     * @return: void
     * @author: sukang
     * @date: 2020/1/3 8:37
     */
    public static void linkedReversal(HeroNode head){
        //思路:首先申明一个新链表的头节点,然后遍历旧链表,按顺序插入新链表头节点之后
        if(head.next == null || head.next.next == null){
            System.out.println("链表为空或则链表的有效长度为1,不需要进行反转");
            return;
        }

        //声明一个辅助节点,遍历单链表来用
        HeroNode temp = head.next;
        //声明一个当前节点指向temp节点
        HeroNode cur = null;
        //声明新链表的头节点
        HeroNode newHead = new HeroNode(0,"","");
        while (true){
            //链表遍历到了最后一个节点
            if(temp == null){
                break;
            }

            cur = temp; //将当前节点指向单链表的辅助节点
            temp = temp.next; //将辅助节点后移一个节点
            cur.next = newHead.next;//然后将当前节点的后一个节点指向新链表的后一个节点
            newHead.next = cur;//新链表的头结点的后一个节点指向当前节点
        }

        head.next = newHead.next;
    }

    /**
     * @description: 面试题:单链表反向打印
     * @param head
     * @return: void
     * @author: sukang
     * @date: 2020/1/3 9:38
     */
    public static void linkedReversalPrint(HeroNode head){
        //思路:将单链表压入栈中,然后从栈中取出打印,利用了栈的新进后出的特性
        if(head.next == null){
            return;
        }

        Stack<HeroNode> stack = new Stack<>();
        HeroNode temp = head;
        while (true){
            if(temp.next == null){
                break;
            }
            stack.add(temp.next);
            temp = temp.next;
        }

        while (stack.size() > 0){
            System.out.println(stack.pop());
        }
    }
}

//链表类
class SingleLinkedList{
   //声明一个头结点
   private HeroNode head = new HeroNode(0,"","") ;

   /**
    * @description: 返回头结点
    * @param
    * @return:
    * @author: sukang
    * @date: 2020/1/2 14:28
    */
   public HeroNode getHead(){
       return head;
   }

   /**
    * @description: 从链表尾处添加节点
    * @param heroNode
    * @return: void
    * @author: sukang
    * @date: 2019/12/30 14:18
    */
   public void addHeroNode(HeroNode heroNode){
      /* 思路:用一个辅助节点遍历链表,找到节点next为空及最后一个节点,
       将其next赋给新节点*/
        //申明一个过渡节点,因为head节点不能后移
       HeroNode temp = head;
       while (true){
           //如果下一个节点为空的话,那么就在这个节点上添加新节点
           if(temp.next == null){
               break;
           }

           //如果没有到最后一个节点,那么节点往后移
           temp = temp.next;
       }

       //把最后一个节点的下一个节点赋给新节点
       temp.next = heroNode;
   }

   /**
    * @description: 按照节点的顺序添加节点
    * @param heroNode
    * @return: void
    * @author: sukang
    * @date: 2019/12/30 14:31
    */
   public void addHeroNodeByOrder(HeroNode heroNode){
      /* 思路:同样因为head节点不能移动,所以我们也是先声明一个辅助节点temp,
       那么辅助节点temp就应该为插入节点的上一个节点,另外如果链表中存在相同
       大小的节点,那么将插入失败*/
      //声明一个辅助节点
       HeroNode temp = head;
       while (true){
           //链表已经遍历到最后
           if(temp.next == null){
               break;
           }

           if(temp.next.id == heroNode.id){
               System.out.printf("编号为%d的节点已经存在,不能添加", heroNode.id);
               return;
           }else if(temp.next.id > heroNode.id){
               break;
           }

           temp = temp.next;
       }

       heroNode.next = temp.next;
       temp.next = heroNode;
   }

   /**
    * @description: 删除节点
    * @param id
    * @return: void
    * @author: sukang
    * @date: 2019/12/30 15:19
    */
   public void delHeroNode(int id){
        /*思路:同样因为head节点不能移动,所以我们也是先声明一个辅助节点temp,
        那么如果辅助节点的下一个节点的id存在,那么就删除这个节点*/
       HeroNode temp = head;
       boolean flag = false;//是否找到删除节点标志
       while (true){
           //链表已经遍历到最后
           if(temp.next == null){
               break;
           }

           if(temp.next.id == id){
               flag = true;
               break;
           }

           temp = temp.next;
       }

       if(flag){
           temp.next = temp.next.next;
       }else{
           System.out.printf("要删除的节点%d,不存在", id);
       }
   }

   /**
    * @description: 更新节点
    * @param heroNode
    * @return: void
    * @author: sukang
    * @date: 2019/12/30 16:04
    */
   public void updateHeroNode(HeroNode heroNode){
       //思路:同样是通过辅助节点temp遍历找到和更新节点相同id的节点,然后进行更新
       //声明辅助节点
       HeroNode temp = head;
       boolean flag = false; //判断是否找到更新的节点,默认为false
       while (true){
           if(temp == null){
               break;
           }

           if(temp.next.id == heroNode.id){
               flag = true;
               break;
           }

           temp = temp.next;
       }

       if(flag){
           temp.next.name = heroNode.name;
           temp.next.nickName = heroNode.nickName;
       }else{
           System.out.printf("没有找到编号为 %d 的节点, 不能更新", heroNode.id);
       }
   }

   /**
    * @description: 遍历链表
    * @param
    * @return: void
    * @author: sukang
    * @date: 2019/12/30 16:15
    */
   public void list(){
       HeroNode temp = head;
       if(temp.next == null){
           System.out.println("链表为空!");
           return;
       }

       while (true){
           if(temp.next == null){
               break;
           }

           System.out.println(temp.next);

           temp = temp.next;
       }
   }
}

//链表节点
class HeroNode{
    public int id; //唯一标志
    public String name; //名字
    public String nickName; //昵称

    HeroNode next; //下一个节点

    public HeroNode(int id, String name, String nickName) {
        this.id = id;
        this.name = name;
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "HeroNode{id: "+ id+ ",name: "+ name +", nickName: "+ nickName +"}";
    }
}

3 单链表面试题(新浪、百度、腾讯)

单链表的常见面试题有如下:

  1. 求单链表中有效节点的个数
    代码如下:
//方法:获取到单链表的节点的个数(如果是带头结点的链表,需求不统计头节点)
/**
 * 
 * @param head 链表的头节点
 * @return 返回的就是有效节点的个数
 */
public static int getLength(HeroNode head) {
	if(head.next == null) { //空链表
		return 0;
	}
	int length = 0;
	//定义一个辅助的变量, 这里我们没有统计头节点
	HeroNode cur = head.next;
	while(cur != null) {
		length++;
		cur = cur.next; //遍历
	}
	return length;
}
  1. 查找单链表中的倒数第k个结点【新浪面试题】
    代码演示:
//查找单链表中的倒数第k个结点 【新浪面试题】
//思路
//1. 编写一个方法,接收head节点,同时接收一个index 
//2. index 表示是倒数第index个节点
//3. 先把链表从头到尾遍历,得到链表的总的长度 getLength
//4. 得到size 后,我们从链表的第一个开始遍历 (size-index)个,就可以得到
//5. 如果找到了,则返回该节点,否则返回nulll
public static HeroNode findLastIndexNode(HeroNode head, int index) {
	//判断如果链表为空,返回null
	if(head.next == null) {
		return null;//没有找到
	}
	//第一个遍历得到链表的长度(节点个数)
	int size = getLength(head);
	//第二次遍历  size-index 位置,就是我们倒数的第K个节点
	//先做一个index的校验
	if(index <=0 || index > size) {
		return null; 
	}
	//定义给辅助变量, for 循环定位到倒数的index
	HeroNode cur = head.next; //3 // 3 - 1 = 2
	for(int i =0; i< size - index; i++) {
		cur = cur.next;
	}
	return cur;
	
}
  1. 单链表的反转【腾讯面试题,有点难度】
    思路分析图解
    【数据结构java篇】- 链表_第6张图片
    【数据结构java篇】- 链表_第7张图片
    代码实现
//将单链表反转
public static void reversetList(HeroNode head) {
	//如果当前链表为空,或者只有一个节点,无需反转,直接返回
	if(head.next == null || head.next.next == null) {
		return ;
	}
	
	//定义一个辅助的指针(变量),帮助我们遍历原来的链表
	HeroNode cur = head.next;
	HeroNode next = null;// 指向当前节点[cur]的下一个节点
	HeroNode reverseHead = new HeroNode(0, "", "");
	//遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead 的最前端
	//动脑筋
	while(cur != null) { 
		next = cur.next;//先暂时保存当前节点的下一个节点,因为后面需要使用
		cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
		reverseHead.next = cur; //将cur 连接到新的链表上
		cur = next;//让cur后移
	}
	//将head.next 指向 reverseHead.next , 实现单链表的反转
	head.next = reverseHead.next;
}
  1. 从尾到头打印单链表【百度,要求方式1:反向遍历。方式2:Stack栈】
    思路分析图解
    【数据结构java篇】- 链表_第8张图片
    代码实现
    写了一个小程序,测试Stack的使用
import java.util.Stack;

//演示栈Stack的基本使用
public class TestStack {

	public static void main(String[] args) {
		Stack<String> stack = new Stack();
		// 入栈
		stack.add("jack");
		stack.add("tom");
		stack.add("smith");

		// 出栈
		// smith, tom , jack
		while (stack.size() > 0) {
			System.out.println(stack.pop());//pop就是将栈顶的数据取出
		}
	}

}

单链表的逆序打印代码:

//方式2:
//可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果
public static void reversePrint(HeroNode head) {
	if(head.next == null) {
		return;//空链表,不能打印
	}
	//创建要给一个栈,将各个节点压入栈
	Stack<HeroNode> stack = new Stack<HeroNode>();
	HeroNode cur = head.next;
	//将链表的所有节点压入栈
	while(cur != null) {
		stack.push(cur);
		cur = cur.next; //cur后移,这样就可以压入下一个节点
	}
	//将栈中的节点进行打印,pop 出栈
	while (stack.size() > 0) {
		System.out.println(stack.pop()); //stack的特点是先进后出
	}
}

5 双向链表应用实例

5.1 双向链表的操作分析和实现

使用带head头的双向链表实现 – 水浒英雄排行榜
管理单向链表的缺点分析:

  1. 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
  2. 单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp,temp是待删除节点的前一个节点(认真体会).
  3. 分析了双向链表如何完成遍历,添加,修改和删除的思路
    【数据结构java篇】- 链表_第9张图片
    对上图的说明:
    分析双向链表的遍历,添加,修改,删除的操作思路===》代码实现
  4. 遍历方和单链表一样,只是可以向前,也可以向后查找
  5. 添加(默认添加到双向链表的最后)
    (1) 先找到双向链表的最后这个节点
    (2) temp.next=newHeroNode
    (3) newHeroNode.pre=temp;
  6. 修改思路和原来的单向链表一样.
    4)删除
    (1) 因为是双向链表,因此,我们可以实现自我删除某个节点
    (2) 直接找到要删除的这个节点,比如temp
    (3) temp.pre.next=temp.next
    (4) temp.next.pre=temp.pre;

双向链表的代码实现

package com.sukang.linkedlist;

/**
 * @description:
 * @author: sukang
 * @date: 2020-01-03 10:43
 */
public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        HeroNode2 heroNode1 = new HeroNode2(1, "张三", "zs");
        HeroNode2 heroNode2 = new HeroNode2(2, "李四", "ls");
        HeroNode2 heroNode3 = new HeroNode2(3, "王五", "ww");
        HeroNode2 heroNode4 = new HeroNode2(4, "刘六", "ln");

        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();

        //增加在尾部
       /* doubleLinkedList.addHeroNode(heroNode3);
        doubleLinkedList.addHeroNode(heroNode1);
        doubleLinkedList.addHeroNode(heroNode2);
        doubleLinkedList.addHeroNode(heroNode4);
        doubleLinkedList.list();*/

        //按照顺序添加
        doubleLinkedList.addHeroNodeByOrder(heroNode3);
        doubleLinkedList.addHeroNodeByOrder(heroNode1);
        doubleLinkedList.addHeroNodeByOrder(heroNode2);
        doubleLinkedList.addHeroNodeByOrder(heroNode4);

        doubleLinkedList.list();

        //删除一个节点
        doubleLinkedList.delHeroNode(1);
        System.out.println("删除节点之后遍历");
        doubleLinkedList.list();

        //修改一个节点
        HeroNode heroNode5 = new HeroNode(2, "马七", "mq");

        doubleLinkedList.updateHeroNode(heroNode5);
        System.out.println("修改节点之后遍历");
        doubleLinkedList.list();

    }
}

//双链表类链表类
class DoubleLinkedList{
    //声明一个头结点
    private HeroNode2 head = new HeroNode2(0,"","");

    /**
     * @description: 返回头结点
     * @param
     * @return:
     * @author: sukang
     * @date: 2020/1/2 14:28
     */
    public HeroNode2 getHead(){
        return head;
    }

    /**
     * @description: 从链表尾处添加节点
     * @param heroNode
     * @return: void
     * @author: sukang
     * @date: 2019/12/30 14:18
     */
    public void addHeroNode(HeroNode2 heroNode){
      /* 思路:用一个辅助节点遍历链表,找到节点next为空及最后一个节点,
       将其next赋给新节点*/
        //申明一个过渡节点,因为head节点不能后移
        HeroNode2 temp = head;
        while (true){
            //如果下一个节点为空的话,那么就在这个节点上添加新节点
            if(temp.next == null){
                break;
            }

            //如果没有到最后一个节点,那么节点往后移
            temp = temp.next;
        }

        //把最后一个节点的下一个节点赋给新节点
        temp.next = heroNode;
        heroNode.pre = temp;
    }

    /**
     * @description: 按照节点的顺序添加节点
     * @param heroNode
     * @return: void
     * @author: sukang
     * @date: 2019/12/30 14:31
     */
    public void addHeroNodeByOrder(HeroNode2 heroNode){
      /* 思路:同样因为head节点不能移动,所以我们也是先声明一个辅助节点temp,
       那么辅助节点temp就应该为插入节点的上一个节点,另外如果链表中存在相同
       大小的节点,那么将插入失败*/
        //声明一个辅助节点
        HeroNode2 temp = head;
        while (true){
            //链表已经遍历到最后
            if(temp.next == null){
                break;
            }

            if(temp.next.id == heroNode.id){
                System.out.printf("编号为%d的节点已经存在,不能添加", heroNode.id);
                return;
            }else if(temp.next.id > heroNode.id){
                break;
            }

            temp = temp.next;
        }

        heroNode.next = temp.next;
        temp.next = heroNode;
        if(heroNode.next != null){
            heroNode.next.pre = heroNode;
        }
        heroNode.pre = temp;
    }

    /**
     * @description: 删除节点
     * @param id
     * @return: void
     * @author: sukang
     * @date: 2019/12/30 15:19
     */
    public void delHeroNode(int id){
        /*思路:同样因为head节点不能移动,所以我们也是先声明一个辅助节点temp,
        那么如果辅助节点的下一个节点的id存在,那么就删除这个节点*/
        HeroNode2 temp = head.next;
        boolean flag = false;//是否找到删除节点标志
        while (true){
            //链表已经遍历到最后
            if(temp.next == null){
                break;
            }

            if(temp.id == id){
                flag = true;
                break;
            }

            temp = temp.next;
        }

        if(flag){
            temp.pre.next = temp.next;
            temp.next = temp.pre;
        }else{
            System.out.printf("要删除的节点%d,不存在", id);
        }
    }

    /**
     * @description: 更新节点
     * @param heroNode
     * @return: void
     * @author: sukang
     * @date: 2019/12/30 16:04
     */
    public void updateHeroNode(HeroNode heroNode){
        //思路:同样是通过辅助节点temp遍历找到和更新节点相同id的节点,然后进行更新
        //声明辅助节点
        HeroNode2 temp = head;
        boolean flag = false; //判断是否找到更新的节点,默认为false
        while (true){
            if(temp == null){
                break;
            }

            if(temp.next.id == heroNode.id){
                flag = true;
                break;
            }

            temp = temp.next;
        }

        if(flag){
            temp.next.name = heroNode.name;

            if(temp.next != null){
                temp.next.nickName = heroNode.nickName;
            }
        }else{
            System.out.printf("没有找到编号为 %d 的节点, 不能更新", heroNode.id);
        }
    }

    /**
     * @description: 遍历链表
     * @param
     * @return: void
     * @author: sukang
     * @date: 2019/12/30 16:15
     */
    public void list(){
        HeroNode2 temp = head;
        if(temp.next == null){
            System.out.println("链表为空!");
            return;
        }

        while (true){
            if(temp.next == null){
                break;
            }

            System.out.println(temp.next);

            temp = temp.next;
        }
    }
}

//双向链表节点
class HeroNode2{
    public int id; //唯一标志
    public String name; //名字
    public String nickName; //昵称

    HeroNode2 next; //下一个节点
    HeroNode2 pre; //上一个节点

    public HeroNode2(int id, String name, String nickName) {
        this.id = id;
        this.name = name;
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "HeroNode2{id: "+ id+ ",name: "+ name +", nickName: "+ nickName +"}";
    }
}

6 单向环形链表应用场景

Josephu(约瑟夫、约瑟夫环)问题
Josephu问题为:设编号为1,2,…n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
提示:用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
【数据结构java篇】- 链表_第10张图片

6.1 单向环形链表介绍

【数据结构java篇】- 链表_第11张图片

6.2 Josephu问题

约瑟夫问题的示意图
【数据结构java篇】- 链表_第12张图片
Josephu问题
Josephu问题为:设编号为1,2,…n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

提示
用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。

约瑟夫问题-创建环形链表的思路图解
【数据结构java篇】- 链表_第13张图片
约瑟夫问题-小孩出圈的思路分析图
【数据结构java篇】- 链表_第14张图片
Josephu问题的代码实现

package com.sukang.linkedlist;

/**
 * @description: 约瑟夫问题(丢手帕)
 * @author: sukang
 * @date: 2020-01-04 13:48
 */
public class Josephu {
    public static void main(String[] args) {
        CircleLinkedList circleLinkedList = new CircleLinkedList();
        circleLinkedList.create(5);
        circleLinkedList.list();

        circleLinkedList.outCircle(1,2,5);
    }

}

//环形单链表
class CircleLinkedList{
    //申明一个开始节点
    Boy first = null;

    /**
     * @description: 根据传递的数量创建环形单链表
     * @param num
     * @return: void
     * @author: sukang
     * @date: 2020/1/4 14:41
     */
    public void create(int num){
        if(num < 1){
            System.out.println("数量小于1,没法创建");
            return;
        }

        Boy temp = null;
        for (int i = 0; i < num; i++) {
            Boy boy = new Boy(i+1);
            if(i == 0){
                first = boy;
                temp = first;
            }else{
                temp.setNext(boy);
                temp = boy;
            }
        }

        temp.setNext(first);
    }

    /**
     * @description: 遍历环形单链表
     * @param
     * @return: void
     * @author: sukang
     * @date: 2020/1/4 14:40
     */
    public void list(){
        if(first == null){
            System.out.println("环形单链表为空");
            return;
        }

        Boy temp = first;
        while (true){
            System.out.println(temp);
            if(temp.getNext() == first){
                System.out.println("遍历到最后了");
                break;
            }
            temp = temp.getNext();
        }
    }

    /**
     * @description: 
     * @param startNo
     *         表示从第几个小孩开始数
     * @param countNum
     *         表示数了几个数字
     * @param nums
     *         表示圈内一共有几个
     * @return: void
     * @author: sukang
     * @date: 2020/1/4 15:32
     */    
    public void outCircle(int startNo, int countNum, int nums){
        if(first == null || startNo < 1 || startNo > nums){
            System.out.println("传入的数据有问题");
            return;
        }

        //声明一个辅助节点,让其指向环形单链表的最后一个孩子节点
        Boy temp = first;
        while (true){
            if(temp.getNext() == first){
                break;
            }

            temp = temp.getNext();
        }

        //从第startNo元素开始数
        for (int i = 0; i < startNo -1 ; i++) {
            temp = temp.getNext();
            first = first.getNext();
        }

        while (true){
            if(temp == first){
                break;
            }

            for (int i = 0; i < countNum -1 ; i++) {
                temp = temp.getNext();
                first = first.getNext();
            }

            System.out.println(first);

            temp.setNext(first.getNext());
            first = first.getNext();
        }

        System.out.println(first);
    }
}

//孩子节点
class Boy{
    private int index;
    private Boy next;

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

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public Boy getNext() {
        return next;
    }

    public void setNext(Boy next) {
        this.next = next;
    }

    @Override
    public String toString() {
        return "Boy[index:"+index+"]";
    }
}

你可能感兴趣的:(数据结构与算法)