数据结构中的链表
线性表的链式存储结构称为链表,由于线性表中的每个元素最多只有一个前去元素和一个后继元素,所以当采用链式存储结构时,一种最简单、最长用的方法就是在每个结点中除包含有数据域以外只设置一个指针域,用于指向其后继节点,这样构成的链表称为线性单向链表,简称单链表;另一种可以采用的方法是在每个节点中除包含数值域之外设置两个指针域,分别指向其前驱结点和后继结点,这样构成的链表称为双向链表,若一个结点中的某个指针域不需要指向其他任何结点,则将他的值置为空。用常量null表示。
在线性表的链式存储中,通常每个链表带有一个头结点,并通过头结点的指针(Java没指针,我现在用c语言中的指针来讲解,使之理解更容易)唯一标识该链表,称之为头指针,相应的指向首结点或者开始节点的指针称为首指针,指向尾结点的称为尾指针
PS:
1》链表是以结点的方式来存储,是链式存储
2》每个结点包含data域,next域:指向下一个节点
3》链表的每个结点不一定是连续存储
4》链表分带头结点和没有头节点的链表,根据实际需求来确定
用代码实现以上功能
一、单链表
package demo1;
public class demo3 {
public static void main(String[] args) {
//测试
//先创建结点
HeroNode hero1= new HeroNode(1,"宋江","及时雨");
HeroNode hero2= new HeroNode(2,"卢俊义","玉麒麟");
HeroNode hero3= new HeroNode(3,"吴用","智多星");
HeroNode hero4= new HeroNode(4,"林冲","豹子头");
//创建起始链表
SingleLinkedList singleLinkedList=new SingleLinkedList();
//加入
singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
//打印
singleLinkedList.list();
}
}
//定义SingleLinkedList 管理我们的108好汉
class SingleLinkedList{
//先初始化一个头结点,头结点不要动,不存放具体数据
private HeroNode head=new HeroNode(0, null, null);
//添加结点到单向链表
//思路:当不考虑编顺序时
//1.找到当前链表的最后结点
//2.将这最后的结点的next指向新的结点
public void add(HeroNode heroNode) {
//因为head结点不能动,因此我们需要一个辅助遍历temp
HeroNode temp=head;
//遍历链表到最后
while(true) {
if(temp.next==null) {
break;
}
//如果没找到就将temp后移
temp=temp.next;
}
//退出循环时 temp指向链表的最后
//将最后这个结点的next指向新的结点
temp.next=heroNode;
}
//显示链表【遍历】
public void list() {
//判断是否为空
if(head.next==null) {
System.out.println("链表为空");
}
//因为头结点不能动,因此需要一个辅助变量来遍历
HeroNode temp=head.next;
while(true) {
//判断是否到链表最后
if(temp==null) {
break;
}
//输出结点的信息
System.out.println(temp);
//将temp后移
temp=temp.next;
}
}
}
//定义HeroNode,每个HeroNode对象就是一个结点
class HeroNode {
public int id;
public String name;
public String nickname;
HeroNode next;//指向下一个结点(HeroNode类型)
//构造器
public HeroNode(int hid,String hname,String hnickname) {
this.id=hid;
this.name=hname;
this.nickname=hnickname;
}
//为了显示方法,我们重新toString
@Override
public String toString() {
return "HeroNode [id=" + id + ", name=" + name + ", nickname=" + nickname + "]";
}
}
向链表插入数据
思路:
1.首先找到新添加的结点的位置,是通过辅助变量(指针),通过遍历来搞定
2.新的结点.next=temp.next
3.将temp.next=新的结点
package demo1;
public class demo3 {
public static void main(String[] args) {
//测试
//先创建结点
HeroNode hero1= new HeroNode(1,"宋江","及时雨");
HeroNode hero2= new HeroNode(2,"卢俊义","玉麒麟");
HeroNode hero3= new HeroNode(3,"吴用","智多星");
HeroNode hero4= new HeroNode(4,"林冲","豹子头");
//创建起始链表
SingleLinkedList singleLinkedList=new SingleLinkedList();
//加入
// singleLinkedList.add(hero1);
// singleLinkedList.add(hero4);
// singleLinkedList.add(hero3);
// singleLinkedList.add(hero2);
//插入
singleLinkedList.addByOrder(hero1);
singleLinkedList.addByOrder(hero4);
singleLinkedList.addByOrder(hero3);
singleLinkedList.addByOrder(hero2);
//打印
singleLinkedList.list();
}
}
//定义SingleLinkedList 管理我们的108好汉
class SingleLinkedList{
//先初始化一个头结点,头结点不要动,不存放具体数据
private HeroNode head=new HeroNode(0, null, null);
//添加结点到单向链表
//思路:当不考虑编顺序时
//1.找到当前链表的最后结点
//2.将这最后的结点的next指向新的结点
public void add(HeroNode heroNode) {
//因为head结点不能动,因此我们需要一个辅助遍历temp
HeroNode temp=head;
//遍历链表到最后
while(true) {
if(temp.next==null) {
break;
}
//如果没找到就将temp后移
temp=temp.next;
}
//退出循环时 temp指向链表的最后
//将最后这个结点的next指向新的结点
temp.next=heroNode;
}
//第二种方式在添加好汉时,根据排名将好汉插入到指定位置(如果有这个排名,则排序失败)
public void addByOrder(HeroNode heroNode) {
//因为头结点不能动,因此仍然需要通过一个辅助指针(变量)来帮助寻找到添加的位置
//因为单链表,因为我们找到temp是位于添加位置的前一个结点,否则插入不成功
HeroNode temp=head;
boolean flag=false;//标志添加的编号是否存在,默认为false
while(true) {
if(temp.next==null) {//说明temp已经在链表的最后
break;
}
if(temp.next.id>heroNode.id) {//位置找到,就在temp后面插入
break;
}else if(temp.next.id==heroNode.id){//说明编号已经存在
flag=true;
}
temp=temp.next;//后移,遍历当前链表
}
//判断flag值
if(flag) {//不能插入,说明编号存在
System.out.println("所输入的好汉已经存在链表中");
}
else {
//插入到链表中
heroNode.next=temp.next;
temp.next=heroNode;
}
}
//显示链表【遍历】
public void list() {
//判断是否为空
if(head.next==null) {
System.out.println("链表为空");
}
//因为头结点不能动,因此需要一个辅助变量来遍历
HeroNode temp=head.next;
while(true) {
//判断是否到链表最后
if(temp==null) {
break;
}
//输出结点的信息
System.out.println(temp);
//将temp后移
temp=temp.next;
}
}
}
//定义HeroNode,每个HeroNode对象就是一个结点
class HeroNode {
public int id;
public String name;
public String nickname;
HeroNode next;//指向下一个结点(HeroNode类型)
//构造器
public HeroNode(int hid,String hname,String hnickname) {
this.id=hid;
this.name=hname;
this.nickname=hnickname;
}
//为了显示方法,我们重新toString
@Override
public String toString() {
return "HeroNode [id=" + id + ", name=" + name + ", nickname=" + nickname + "]";
}
}
修改结点
//修改结点的信息,根据编号进行修改,但不修改编号
public void update(HeroNode heroNode) {
HeroNode temp=head.next;
while(true) {
if(temp==null) {
break;//已经遍历完链表
}
if(temp.id==heroNode.id) {
break;
}
temp=temp.next;
}
temp.name=heroNode.name;
temp.nickname=heroNode.nickname;
}
自己测试
删除结点
思路:
1.我们需要先找到要删除的这个结点的前一个结点temp
2.temp.next=temp.next.next
//删除
public void delete(HeroNode heroNode) {
HeroNode temp=head.next;
while(true) {
if(temp.next==null) {
break;//遍历完成
}
if(temp.next.id==heroNode.id) {
temp.next=temp.next.next;
}
temp=temp.next;
}
}
单链表中常见的面试问题
1》求单链表中的结点的个数
static方法在静态类里面可以随意调用,不用创建对象
//获取单链表的结点的个数(如果是带头结点的链表,需求不统计头结点)
public static int getLenth(HeroNode head) {
if (head.next == null) {// 空链表
return 0;
}
int length = 0;
HeroNode cur = head.next;
while (cur != null) {
length++;
cur = cur.next;
}
return length;
}
还得在类中创建返回头节点的方法
//返回头节点
public HeroNode getHead() {
return head;
}
2》查找单链表的倒数第k个结点(新浪面试题)
//查找单链表的倒数第k个结点
//思路:
//1.编写一个方法,接收head结点,同时接收一个index
//2.index表示是倒数第index个结点
//3.先把链表从头到尾遍历,得到链表的总长度
//4.得到size后,我们从第一个开始遍历(size-index)个
//如果找到了,则返回该节点,否则返回null
public static HeroNode findIndex(HeroNode head,int index) {
//判断如果链表为空,返回null
if(head.next==null) {
return null;//没有找到
}
int size=getLenth(head);
//第二次遍历size-index
//先做一个index的校验
if(index<=0||index>size) {
return null;
}
//定义给辅助变量,for循环定位
HeroNode cur=head.next;
for(int i=0;i<size-index;i++) {
cur=cur.next;
}
return cur;
}
3》单链表的反转(腾讯面试题)
可以用栈的出入思想来解决
//将单链表进行反转
public static void reverseList(HeroNode head) {
//如果链表为空,或者只有一个结点,直接返回
if(head.next==null||head.next.next==null) {
return;
}
HeroNode cur=head.next;
HeroNode nxt=null;
HeroNode reverseHead=new HeroNode(0, null, null);
//遍历原来的链表,每遍历一个结点,就将其取出,并放在新链表的最前端
while(cur!=null) {
nxt=cur.next;
cur.next=reverseHead.next;
reverseHead.next=cur;
cur=nxt;
}
//将head.next指向reverseHead.next 实现单链表的反转
head.next=reverseHead.next;
}
4》从头到尾打印单链表,要求:1、反向遍历 2、stack栈
方法1:将单链表反转,在遍历输出,不建议,这样会破坏单链表结构
方法2:可以利用栈这个数据结构,将各个结点压入到栈中,然后利用栈的先进后出的特点,实现逆序打印的效果
//逆序打印
public static void reverPrint(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());//先进后出
}
}
单链表完整代码如下
package demo1;
import java.util.Stack;
public class demo3 {
public static void main(String[] args) {
// 测试
// 先创建结点
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
HeroNode hero5 = new HeroNode(4, "李逵", "黑旋风");
// 创建起始链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
// 加入
// singleLinkedList.add(hero1);
// singleLinkedList.add(hero4);
// singleLinkedList.add(hero3);
// singleLinkedList.add(hero2);
// 插入
singleLinkedList.addByOrder(hero1);
singleLinkedList.addByOrder(hero4);
singleLinkedList.addByOrder(hero3);
singleLinkedList.addByOrder(hero2);
// 修改
singleLinkedList.update(hero5);
// 删除
singleLinkedList.delete(hero3);
// 求结点的个数
System.out.println("有效节点个数为:"+getLenth(singleLinkedList.getHead()));
//测试是否得到了倒数第k个结点
HeroNode res=findIndex(singleLinkedList.getHead(),1);
System.out.println("res=="+res);
//逆序打印
System.out.println("逆序打印单链表:");
reverPrint(singleLinkedList.getHead());
// 打印
System.out.println("链表结构:");
singleLinkedList.list();
}
//逆序打印
public static void reverPrint(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());//先进后出
}
}
//将单链表进行反转
public static void reverseList(HeroNode head) {
//如果链表为空,或者只有一个结点,直接返回
if(head.next==null||head.next.next==null) {
return;
}
HeroNode cur=head.next;
HeroNode nxt=null;
HeroNode reverseHead=new HeroNode(0, null, null);
//遍历原来的链表,每遍历一个结点,就将其取出,并放在新链表的最前端
while(cur!=null) {
nxt=cur.next;
cur.next=reverseHead.next;
reverseHead.next=cur;
cur=nxt;
}
//将head.next指向reverseHead.next 实现单链表的反转
head.next=reverseHead.next;
}
//查找单链表的倒数第k个结点
//思路:
//1.编写一个方法,接收head结点,同时接收一个index
//2.index表示是倒数第index个结点
//3.先把链表从头到尾遍历,得到链表的总长度
//4.得到size后,我们从第一个开始遍历(size-index)个
//如果找到了,则返回该节点,否则返回null
public static HeroNode findIndex(HeroNode head,int index) {
//判断如果链表为空,返回null
if(head.next==null) {
return null;//没有找到
}
int size=getLenth(head);
//第二次遍历size-index
//先做一个index的校验
if(index<=0||index>size) {
return null;
}
//定义给辅助变量,for循环定位
HeroNode cur=head.next;
for(int i=0;i<size-index;i++) {
cur=cur.next;
}
return cur;
}
//获取单链表的结点的个数(如果是带头结点的链表,需求不统计头结点)
public static int getLenth(HeroNode head) {
if (head.next == null) {// 空链表
return 0;
}
int length = 0;
HeroNode cur = head.next;
while (cur != null) {
length++;
cur = cur.next;
}
return length;
}
}
//定义SingleLinkedList 管理我们的108好汉
class SingleLinkedList {
// 先初始化一个头结点,头结点不要动,不存放具体数据
private HeroNode head = new HeroNode(0, null, null);
//返回头节点
public HeroNode getHead() {
return head;
}
// 添加结点到单向链表
// 思路:当不考虑编顺序时
// 1.找到当前链表的最后结点
// 2.将这最后的结点的next指向新的结点
public void add(HeroNode heroNode) {
// 因为head结点不能动,因此我们需要一个辅助遍历temp
HeroNode temp = head;
// 遍历链表到最后
while (true) {
if (temp.next == null) {
break;
}
// 如果没找到就将temp后移
temp = temp.next;
}
// 退出循环时 temp指向链表的最后
// 将最后这个结点的next指向新的结点
temp.next = heroNode;
}
// 第二种方式在添加好汉时,根据排名将好汉插入到指定位置(如果有这个排名,则排序失败)
public void addByOrder(HeroNode heroNode) {
// 因为头结点不能动,因此仍然需要通过一个辅助指针(变量)来帮助寻找到添加的位置
// 因为单链表,因为我们找到temp是位于添加位置的前一个结点,否则插入不成功
HeroNode temp = head;
boolean flag = false;// 标志添加的编号是否存在,默认为false
while (true) {
if (temp.next == null) {// 说明temp已经在链表的最后
break;
}
if (temp.next.id > heroNode.id) {// 位置找到,就在temp后面插入
break;
} else if (temp.next.id == heroNode.id) {// 说明编号已经存在
flag = true;
}
temp = temp.next;// 后移,遍历当前链表
}
// 判断flag值
if (flag) {// 不能插入,说明编号存在
System.out.println("所输入的好汉已经存在链表中");
} else {
// 插入到链表中
heroNode.next = temp.next;
temp.next = heroNode;
}
}
// 修改结点的信息,根据编号进行修改,但不修改编号
public void update(HeroNode heroNode) {
HeroNode temp = head.next;
while (true) {
if (temp == null) {
break;// 已经遍历完链表
}
if (temp.id == heroNode.id) {
break;
}
temp = temp.next;
}
temp.name = heroNode.name;
temp.nickname = heroNode.nickname;
}
// 删除
public void delete(HeroNode heroNode) {
HeroNode temp = head.next;
while (true) {
if (temp.next == null) {
break;// 遍历完成
}
if (temp.next.id == heroNode.id) {
temp.next = temp.next.next;
}
temp = temp.next;
}
}
// 显示链表【遍历】
public void list() {
// 判断是否为空
if (head.next == null) {
System.out.println("链表为空");
}
// 因为头结点不能动,因此需要一个辅助变量来遍历
HeroNode temp = head.next;
while (true) {
// 判断是否到链表最后
if (temp == null) {
break;
}
// 输出结点的信息
System.out.println(temp);
// 将temp后移
temp = temp.next;
}
}
}
//定义HeroNode,每个HeroNode对象就是一个结点
class HeroNode {
public int id;
public String name;
public String nickname;
HeroNode next;// 指向下一个结点(HeroNode类型)
// 构造器
public HeroNode(int hid, String hname, String hnickname) {
this.id = hid;
this.name = hname;
this.nickname = hnickname;
}
// 为了显示方法,我们重新toString
@Override
public String toString() {
return "HeroNode [id=" + id + ", name=" + name + ", nickname=" + nickname + "]";
}
}
二、双链表
分析双链表的遍历,添加,修改,删除的操作思路:
pre指向前一个结点
1》遍历方向与单链表一样,只是可以向前,也可以向后查找
2》添加(默认添加到双链表的最后)
- 先找到双链表的最后节点;
- temp.next=new Heronode
- new Heronode.pre=temp
3》修改思路与原来的单链表相同
4》删除:
1>因为是双向链表,所以我们可以自我删除某个节点
2>直接找到要删除的节点,比如temp
3>temp.pre.next=temp.next
4>temp.pnext.pre=temp.pre
package demo1;
public class doubleLinkedListDemo1 {
public static void main(String[] args) {
System.out.println("双向链表的一个测试");
// 先创建节点
HeroNode2 heroNode1 = new HeroNode2(1, "宋江", "及时雨");
HeroNode2 heroNode2 = new HeroNode2(2, "卢俊义", "玉麒麟");
HeroNode2 heroNode3 = new HeroNode2(3, "吴用", "智多星");
HeroNode2 heroNode4 = new HeroNode2(4, "林冲", "豹子头");
//创建一个双向链表对象
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.add(heroNode1);
doubleLinkedList.add(heroNode2);
doubleLinkedList.add(heroNode3);
doubleLinkedList.add(heroNode4);
doubleLinkedList.list();
// 修改测试
System.out.println();
System.out.println("修改测试");
HeroNode2 newHeroNode = new HeroNode2(4, "公孙胜", "如云龙");
doubleLinkedList.update(newHeroNode);
doubleLinkedList.list();
// 测试删除
System.out.println("删除测试");
doubleLinkedList.delete(3);
doubleLinkedList.list();
// 有序添加测试
System.out.println();
System.out.println("有序添加测试");
HeroNode2 newHeroNode2 = new HeroNode2(0, "小工", "公公");
doubleLinkedList.addOrder(newHeroNode2);
doubleLinkedList.list();
}
}
// 创建一个双向链表的类
class DoubleLinkedList {
// 初始化一下头结点 头结点不要动 不存放具体的数据
private HeroNode2 head =new HeroNode2(0,null,null);
public HeroNode2 getHead() {
return head;
}
public void setHead(HeroNode2 head) {
this.head = head;
}
// 遍历双向链表的方法和单链表的方法一样
// 显示链表 遍历
public void list(){
// 判断链表为空
if(head.next == null){
System.out.println("链表为空");
return;
}
// 因为头结点不能动我们需要一个复制节点 temp
HeroNode2 temp = head.next;
while(true){
// 输出这个节点的信息
System.out.println(temp);
if(temp.next == null){
break;
}
// 将temp 后移 不然是一个死循环
temp = temp.next;
}
}
// 这个方法默认添加到双向链表的尾部
public void add(HeroNode2 heroNode){
// 因为 head节点不能动 我们需要一个辅助节点 temp
HeroNode2 temp = head;
// 遍历链表找到最后
while (true){
// 找到链表的最后
if(temp.next == null){
break;
}
// 如果没有找到 就将temp后移
temp = temp.next;
}
// 当退出white循环时, temp 就指向了链表的最后
// 形成一个双向链表
temp.next=heroNode;
heroNode.pre = temp;
}
// 按顺序添加双链表
void addOrder(HeroNode2 heroNode) {
HeroNode2 temp = head.next;
boolean flag = false; // 标识是否可以插入
if (head.next == null){
heroNode.pre = head;
head.next = heroNode;
}else {
while (true) {
if (temp.id == heroNode.id){
System.out.println("该编号已经存在 无法添加");
flag = true;
break;
}else if (temp.id > heroNode.id){
break;
}
if(temp.next == null){
break;
}
temp = temp.next;
}
if (!flag){
heroNode.next = temp;
heroNode.pre = temp.pre;
temp.pre.next=heroNode;
temp.pre=heroNode;
}
}
}
// 修改一个双向链表的接单的内容 和单向链表一样 只是节点的类型发生了改变
public void update (HeroNode2 newHeroNode){
if(head.next == null){
System.out.println("链表为空");
return;
}
// 找到需要修改的节点 根据id查询
HeroNode2 temp = head.next;
boolean flag = false; // 表示是否找到该节点
while (true) {
if (temp == null) {
break; // 表示链表已经遍历结束了 (不是最后一个节点 已经出到外面去了)
}
if (temp.id == newHeroNode.id){
// 找到要修改的节点
flag = true;
break;
}
temp = temp.next;
}
// 根据flag判断是否找到要修改的节点
if(!flag){
System.out.println("没有找到编号为["+newHeroNode.id+ "]的节点");
}else {
temp.name = newHeroNode.name;
temp.nickName = newHeroNode.nickName;
}
}
// 从双向链表中删除一个节点
// 对于双向链表可以直接找到要删除的节点 不用像单链表要找到要删除节点的前一个节点
// 找到后 自我删除即可
public void delete(int id){
HeroNode2 temp = head.next;
// 判断当前链表是否为空
if (temp == null) {
System.out.println("当前链表为空 无法删除");
}
boolean flag = false; // 标志是否找到待删除节点的
while (true) {
if (temp.id == id){
// 找到了待删除节点的前一个节点
flag = true;
break;
}
temp = temp.next;
}
if (flag){
// 可以删除
// temp.next = temp.next.next; [单链表]
temp.pre.next = temp.next;
if (temp.next != null) {
// 如果删除的不是最后一个节点
temp.next.pre = temp.pre;
}
}else {
System.out.println("没有找到该id=["+id+"]节点,无法删除");
}
}
}
// 定义一个HeroNode , 每个HeroNode 对象就是一个节点
class HeroNode2 {
public int id;
public String name;
public String nickName;
public HeroNode2 next; // 指向下一个节点 ,默认为null
public HeroNode2 pre; // 指向上一个节点 ,默认为null
// 构造器
public HeroNode2(int hNo, String hName, String hNickName){
this.id = hNo;
this.name = hName;
this.nickName = hNickName;
}
// 为了显示方便 重写toString方法
@Override
public String toString() {
return "HeroNode{" +
"id=" + id +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
三、环形链表
约瑟夫问题
构造一个单链表的思路:
1、先创建一个节点,让first执行该节点,并形成环形
2、后面我们每创建一个新的节点,就让该节点加入到已有的环形链表即可
遍历环形链表
1、先让一个辅助指针(变量)curboy,指向first节点
2、然后通过一个while循环该环形链表即可 curboy.next==first结束
一、构建环形链表
package demo1;
public class josepfu {
public static void main(String[] args) {
//测试构建环形链表和遍历
CircleSingleLinkedList circleSingleLinkedList=new CircleSingleLinkedList();
circleSingleLinkedList.addPerson(5);
circleSingleLinkedList.showPerson();
}
}
//创建一个环形的单向链表
class CircleSingleLinkedList{
//创建一个first节点,当前没有编号
private Person first=new Person(-1);
//添加人的节点,构建一个环形链表
public void addPerson(int num) {
//num做一个数据校验
if(num<1) {
System.out.println("num值不正确");
return;
}
Person curPerson=null;//辅助指针,帮助构建环形链表
//使用for循环来创建我们的环形链表
for(int i=1;i<=num;i++) {
//根据编号创建人节点
Person person=new Person(i);
//如果是第一个人
if(i==1) {
first=person;
first.setNext(first);//构建环
curPerson=first;//让curPerson指向第一个人
}else {
curPerson.setNext(person);
person.setNext(first);
curPerson=person;
}
}
}
//遍历当前的环形链表
public void showPerson() {
//判断是否为空
if(first==null) {
System.out.println("没有任何人");
}
//因为first不能移动,因此我们任然需要使用一个辅助指针来完成遍历
Person curPerson=first;
while(true) {
System.out.println("人的编号为:"+curPerson.getId());
if(curPerson.getNext()==first) {
//说明已经遍历而完成
break;
}
curPerson=curPerson.getNext();//curPerson后移
}
}
}
//创建一个Person类,表示一个节点
class Person{
private int id;//编号
private Person next;//指向下一个节点
public Person(int id) {
this.id=id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Person getNext() {
return next;
}
public void setNext(Person next) {
this.next = next;
}
}
二、出圈
package demo1;
public class josepfu {
public static void main(String[] args) {
//测试构建环形链表和遍历
CircleSingleLinkedList circleSingleLinkedList=new CircleSingleLinkedList();
circleSingleLinkedList.addPerson(5);
circleSingleLinkedList.showPerson();
//测试出圈
circleSingleLinkedList.countPerson(1, 2, 5);// 2->4->1->5
}
}
//创建一个环形的单向链表
class CircleSingleLinkedList{
//创建一个first节点,当前没有编号
private Person first=new Person(-1);
//添加人的节点,构建一个环形链表
public void addPerson(int num) {
//num做一个数据校验
if(num<1) {
System.out.println("num值不正确");
return;
}
Person curPerson=null;//辅助指针,帮助构建环形链表
//使用for循环来创建我们的环形链表
for(int i=1;i<=num;i++) {
//根据编号创建人节点
Person person=new Person(i);
//如果是第一个人
if(i==1) {
first=person;
first.setNext(first);//构建环
curPerson=first;//让curPerson指向第一个人
}else {
curPerson.setNext(person);
person.setNext(first);
curPerson=person;
}
}
}
//遍历当前的环形链表
public void showPerson() {
//判断是否为空
if(first==null) {
System.out.println("没有任何人");
}
//因为first不能移动,因此我们任然需要使用一个辅助指针来完成遍历
Person curPerson=first;
while(true) {
System.out.println("人的编号为:"+curPerson.getId());
if(curPerson.getNext()==first) {
//说明已经遍历而完成
break;
}
curPerson=curPerson.getNext();//curPerson后移
}
}
//根据用户的输入,计算出人的出圈顺序
/*
* startid:表示从第几个小孩开始数数
* countNum:表示数几下
* nums:表示最初有多少小孩子
*/
public void countPerson(int startid,int countNum,int num) {
//先对数据进行校验
if(first==null||startid<1||startid>num) {
System.out.println("请重新输入:");
}
//创建一个辅助指针,帮助人们出圈
Person helper=first;
//需求一个辅助指针(变量)helper,事先应该指向环形链表的最后的节点
while(true) {
if(helper.getNext()==first) {
//说明first指向了最后的节点
break;
}
helper=helper.getNext();
}
//人报数前,先让first和helper移动k-1次
for(int j=0;j<startid-1;j++) {
first=first.getNext();
helper=helper.getNext();
}
//当人报数时,让first和helper指针同时移动m-1次,然后出圈
//这里是一个循环操作,知道圈中只有一个节点
while(true) {
if(helper==first) {
//说明圈中只有一个节点
break;
}
//让first和helper指针同时移动countNum-1次
for(int j=0;j<countNum-1;j++) {
first=first.getNext();
helper=helper.getNext();
}
//这时first指向的节点就时人要出圈的节点
System.out.println("人出圈:"+first.getId());
//这是将人出圈
first=first.getNext();
helper.setNext(first);
}
System.out.println("最终留在圈中的人编号是:"+first.getId());
}
}
//创建一个Person类,表示一个节点
class Person{
private int id;//编号
private Person next;//指向下一个节点
public Person(int id) {
this.id=id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Person getNext() {
return next;
}
public void setNext(Person next) {
this.next = next;
}
}