线性表是具有相同特性的数据元素的一个有限系列,可以简单的理解为就是一堆数据的存放方式,是一种典型并常见的一种数据存储结构。比如说常见的英文字母表或者成绩单之类的。线性表具有以下特性:
线性表的存储结构普遍分为顺序表和链表,两者的主要区别在于:顺序表在内存中的存储地址是顺序的,就是说地址是一个接一个的,比如:101,102,103。而链表在内存中的存储地址是顺序,不相连的,比如:101,103,102,他们主要是通过指针的方式连接在一起的。
顺序表是线性表的顺序存储方式,主要是将线性表里的元素按照需对应的逻辑顺序存储在一块连续的存储空间中,其特点是存储地址有序和在建立时需注意顺序表的长度问题。如下所示,在使用顺序表时,我们要注意地址的有序性(主要以地址的方式来存储)和这个顺序表的大小(长度应为5)
常见的实现方式是使用数组来实现,数组的类型就是线性表的数据类型,数组的大小就是线性表的长度,但是需要注意的是,数组的下标是从0开始算的,而线性表的下标是从1开始算的 ,其他的相关操作和数组的操作类似,但这并不代表所有的数组都可以当做是一个线形表。
链表是线性表的链式存储结构,在链表中,主要是以结点和指针的方式来进行存储的,每个存储结点包含数据域(存储元素本身的信息)和一个指针域(存储下一个地址信息),而且链表在内存中的各个节点是不相连的,就是说就算存储地址相同,但有指针域的存在,元素之间的连续性并不是按照地址上来的。
链表与顺序表的区别在于:由于顺序表的存储时有序的,在查找数据上会比较方便,以数组为例子,由于有数组下标等因素的存在,我们在查找因素会很快,当如果我们想要添加元素等操作时,涉及到元素移位和数组扩容等问题,效率会很慢。相反,链表在添加元素方面效率相对来说很快,而在查找因素上就相对来说慢了。
链表又分为单链表,双链表,循环链表等,每一种链表都不同。主要是看存储结点的前继结点和后继结点的数量和连线方式。
在单链表的设计中,主要是在结点上设置一个指针域,由指针指向后继结点(下一个元素),以此类推,将数据一个一个链起来,这就是单链表。单链表有的头指针指向头结点,头结点的数据域一般设为0,从1开始才设置数据域,所以链表的起点是1,尾结点的指针域为空值,因为没有后继元素相连了。
单链表又分为带头结点和不带头结点两种,不管带不带头结点,头指针都指向第一个结点(有头结点就指向头结点),头结点的设置上,数据域可以为空,但指针域必须指向接下来的第一个结点。
//c语言写法:
s->next = p->next;
p->next = s;
单链表结点的插入如上所示,首先让a结点(新结点)的指针域(s->next)指向a1结点(p->next(因为在链表中,头结点0的next指针域已经指向了a1结点,所以p->next代表了a1的整个结点)),然后让0(头结点)的指针域指向a结点(s),从而实现结点的插入操作。
注意: 不能先执行p->next = s;然后再执行s->next = p->next;,这样子相当于指向a1结点的指针给弄没了,相当于执行了s->next=s。(链表的结点插入操作最重要的就是不断链,通常情况下都是断开后链再断前链)
//Java写法:(这里的head指头结点,temp指临时变量,s指新结点)
temp = head;
s.next = temp.next;
temp.next = s;
在Java中,因为没有指针的存在,我们可以使用一个临时变量和循环的方式来找到新结点的插入位置,这个临时变量普遍都是用来替代头结点或者第一个结点,可以理解为头结点的一个副本,找到要插入的位置后,首先让新结点的next域等于临时变量的next域,然后再把新结点等于临时变量的next域。(Java操作和C语言操作的思路是一样的,只不过是用临时变量来替代指针而已)
//C语言写法:先临时保存被删结点,然后删除q,最后释放内存空间
q=p->next;
p->next=q->next;
free(q);
在单链表中,如果想要删除一个结点,只需将该结点的前继结点的next域指向该结点的后继结点即可,如上图所示:将头结点的next指向a2即可。
//Java写法
temp.next=temp.next.next;
在Java中,删除一个结点只需将链表中的该结点的下一个结点赋值给该结点的上一个结点即可,因为有GC回收的存在,该结点会被GC回收掉。
单链表的创建方式有两种,分别是头插法和尾插法,两种之间的区别在于:头插法只要是将节点一个个连接在头结点之后,而尾插法则是将节点链接在最后一个结点上,简单来说尾插法就是新增一个尾结点(并不是说这就代表一个链表有两个尾结点,而是将尾结点后移一位而已)。
1.头插法: 头插法主要是将结点(数据存放到结点,比如下面的a1)插入链表的表头上,就是说插入到头结点之后。
//C语言写法
s->next = p->next;
p->next = s;
//头插法:其中node表示结点信息,里面包含数据源的变量和一个next变量
public void CreateInFirst(Node node) {
//定义辅助变量
Node temp = head;
node.next = temp.next;
temp.next = node;
}
2.尾插法: 尾插法主要是将结点插入到链表的后面,在C语言中,可以定义一个尾指针指向最后那个结点,然后将尾结点的next域设置为空。而在Java中,我们只需要判断结点的next是否为空,然后将结点添加进去即可。
//C语言写法
r->next = s;
r=s;
public void CreateInTail(Node node){
Node temp = head;
while (true){
if(temp.next == null){
break;
}
//当没有找到,temp后移
temp = temp.next;
}
//当跳出循环时,temp已经指向最后
temp.next = node;
}
和其他数据结构一样,链表也有其相关的操作,如常见的增删改查,链表反转,查找指定元素等等,以下便是链表常见的crud的一个操作。
package Juc;
public class test01 {
public static void main(String[] args) {
OperateLinkedList linkedList = new OperateLinkedList();
//定义结点信息
Node node1 = new Node("1", "张三");
Node node2 = new Node("2", "李四");
Node node3 = new Node("3", "赵五");
//头插法
// linkedList.CreateInFirst(node1);
// linkedList.CreateInFirst(node2);
// linkedList.CreateInFirst(node3);
//尾插法
System.out.print("尾插法创建:");
linkedList.CreateInTail(node1);
linkedList.CreateInTail(node2);
linkedList.CreateInTail(node3);
linkedList.list();
//查询结点位置
linkedList.Query(node2);
//修改结点
Node node4 = new Node("2", "哈哈哈");
linkedList.Update(node4);
//删除结点
linkedList.Del(node4);
}
}
//结点信息
class Node {
public String id;
public String name;
public Node next;
public Node(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
"}";
}
}
class OperateLinkedList {
//声明头结点:头结点不能动,可以始于一个辅助变量来操作
Node head = new Node("", "");
//头插法
public void CreateInFirst(Node node) {
//定义辅助变量
Node temp = head;
node.next = temp.next;
temp.next = node;
}
//尾插法
public void CreateInTail(Node node) {
Node temp = head;
while (true) {
if (temp.next == null) {
break;
}
//当没有找到,temp后移
temp = temp.next;
}
//当跳出循环时,temp已经指向最后
temp.next = node;
}
//查找结点在链表中的位置
public void Query(Node node) {
Node temp = head;
int i = 0;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.id == node.id && temp.name == node.name) {
flag = true;
break;
}
//辅助变量向后移位
temp = temp.next;
i++;
}
if (flag) {
System.out.println("当前结点在链表中处于第" + i + "位");
} else {
System.out.println("该结点不存在");
}
}
//修改结点指定元素,根据ID修改
public void Update(Node node) {
Node temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.id == node.id) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = node.name;
System.out.print("修改成功,链表数据为:");
list();
} else {
System.out.println("该元素不存在");
}
}
//删除结点
public void Del(Node node) {
Node temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.id == node.id) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
System.out.print("删除结点后的链表为:");
list();
} else {
System.out.println("该结点不存在");
}
}
//输出链表
public void list() {
Node temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.print(temp);
temp = temp.next;
}
System.out.println();
}
}
1.单链表反转: 单链表的反转思路主要是:定义一个临时变量(可以理解为指针),用来遍历原链表,然后再定义一个临时变量充当头结点,然后将在原链表遍历出来的结点使用头插法的方式插入临时链表的头结点,当遍历结束后,再将原链表的头结点指向临时链表的第一个结点(不是头结点,是头结点后面的第一个结点)。
package Juc;
public class test01 {
public static void main(String[] args) {
OperateLinkedList linkedList = new OperateLinkedList();
//定义结点信息
Node node1 = new Node("1", "张三");
Node node2 = new Node("2", "李四");
Node node3 = new Node("3", "赵五");
//尾插法
System.out.print("尾插法创建:");
linkedList.CreateInTail(node1);
linkedList.CreateInTail(node2);
linkedList.CreateInTail(node3);
linkedList.list();
linkedList.reversetList(linkedList.getHead());
}
}
//结点信息
class Node {
public String id;
public String name;
public Node next;
public Node(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
"}";
}
}
class OperateLinkedList {
//声明头结点:头结点不能动,可以始于一个辅助变量来操作
Node head = new Node("", "");
public Node getHead(){
return head;
}
//尾插法
public void CreateInTail(Node node) {
Node temp = head;
while (true) {
if (temp.next == null) {
break;
}
//当没有找到,temp后移
temp = temp.next;
}
//当跳出循环时,temp已经指向最后
temp.next = node;
}
//单链表反转
public void reversetList(Node head){
//定义变量,用来遍历原链表
Node cur = head.next;
//定义变量,用来保存当前结点的下一个结点,防止链表断开
Node next = null;
//定义临时变量,用来构建临时链表
Node tempHeadNode = new Node("","");
while (cur != null){
//保存当前结点的下一个结点
next = cur.next;
//头插法插入结点
cur.next = tempHeadNode.next;
tempHeadNode.next = cur;
//变量后移(相当于指针后移)
cur = next;
}
//将原来的头结点指向临时链表的第一个结点
head.next = tempHeadNode.next;
System.out.print("链表反转:");
list();
}
//输出链表
public void list() {
Node temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.print(temp);
temp = temp.next;
}
System.out.println();
}
}
在双向链表中,每一个结点都包含一个一个前继指针域和一个后继指针域,一个指向该结点的前继结点,一个指向该结点的后继结点,从而将数据链接起来。在双向链表中,因为有两个指针域的存在,我们可以很方便的对链表中的数据结点进行遍历,删除等等。
双链表对比与单链表来说,双链表在查询数据时可以向前查找或者向后查找,而单链表则是只能往一个方向进行查询;在双链表中如果想要删除结点,不需要辅助变量,可以做到自我删除,而在单链表中,则需要一个变量来表示待删除点的前一个结点。
//C语言写法:
s->next = p->next;
p->next->pre = s;
s->pre = p;
p->next = s;
双链表的结点插入操作如上图所示:首先将新结点的next域(s->next)指向该结点的下一个结点(p->next),如何将新结点链接到下一个结点的pre域(p->next->pre),然后将上一个结点(p)链接到新结点的pre域(s->pre),最后将新结点链接到上一个结点的next域(p->next)。
//Java写法:结点里面包含了pre,next两个变量,node相当于新结点,类似于上面的s
//定义辅助变量
Node temp = head;
if(temp.next == null){
temp.next = node;
node.pre = temp;
}else {
node.next = temp.next;
temp.next.pre = node;
node.pre = temp;
temp.next = node;
}
站在Java的角度上来讲,其实思路是一样的。以插入一个个对象来说,首先先定义一个临时变量(temp)来作为头结点,先把该结点的下一个结点(temp.next)赋值给新结点的next变量,然后判断头结点的后面是否有结点的存在,不然会出现空指针异常,当头结点后面没有结点时,这时候可以使用后插法插入结点,如果头结点后面有结点时,这时候先将新结点的next值赋值给新结点的下一个结点的pre值,然后将头结点的赋值给新结点的pre值,最后将新结点赋值给头结点的next变量。
//C语言伪代码:
p->next = p->next->next; //或者:p->next = s->next;
s->next->pre = p;
在双链表的删除结点过程中,我们只需要将该结点的前继结点的next域指向该结点的后继结点,然后将该结点的后继结点的pre域指向该结点的前继结点的next域即可。
//Java伪代码:
temp = head.next;
temp.pre.next = temp.next;
temp.next.pre = temp.pre;
在Java的角度上来说,由于双链表有pre和next域,我们可以不用借用到前继结点,就可以做到结点的删除。首先定义一个辅助变量来遍历,这个辅助变量直接就代表所要删除的结点,然后利用pre和next两个变量来进行结点的删除操作。
和单链表相同,双链表的创建也有头插法和尾插法,过程和单链表是类似的,只是在结点的插入上有所不同而已,其实头插法和尾插法可以简单的理解为就是结点的插入方式不同。
1.头插法: 双链表头插法的过程与上述结点的插入操作一样,主要是将结点一个一个的插入到头结点的后面,所涉及到相关指针域(临时变量)的操作和上述结点的插入一样。
//C语言写法:
s->next = p->next;
p->next->pre = s;
s->pre = p;
p->next = s;
//Java写法:结点里面包含了pre,next两个变量,node相当于新结点,类似于上面的s
Node temp = head;
node.next = temp.next;
if(temp.next == null){
temp.next = node;
node.pre = temp;
}else {
temp.next.pre = node.next;
node.pre = temp;
temp.next = node;
}
2.尾插法: 尾插法则是将结点插入到双链表的最后面,然后将新结点的next域设为空即可。
//C语言写法:
r->next = s;
s->pre = r;
r = s;
在C语言的写法上,双链表的尾插法创建和单链表的思路是一样的,设置一个尾指针指向尾结点,然后根据尾结点来将新结点链起来。
//Java伪代码:
temp = head;
while(true){
if(tem.next == null){
break;
}
temp = temp.next;
}
temp.next = node;
node.pre = temp;
在Java的角度上来看,首先我们通过遍历来找到链表的最后那个结点,然后将新结点赋值给最后一个结点的next变量,然后将最后一个结点赋值给新结点的pre变量。
在代码的创建链表过程过程中,我们可以debug一下看一下双链表的整体结构:
双链表的CRUD操作和单链表的很类似,除了结点的添加和删除外,其他的操作和单链表的是一样的。
package JVM;
public class Demo02 {
public static void main(String[] args) {
OperateLinkedList linkedList = new OperateLinkedList();
//定义结点信息
Node node1 = new Node("1", "张三");
Node node2 = new Node("2", "李四");
Node node3 = new Node("3", "赵五");
//头插法
linkedList.CreateInFirst(node1);
linkedList.CreateInFirst(node2);
linkedList.CreateInFirst(node3);
linkedList.list();
//查询结点位置
linkedList.Query(node2);
//修改结点
Node node4 = new Node("2", "哈哈哈");
linkedList.Update(node4);
//删除结点
linkedList.Del(node2);
}
}
//结点信息
class Node {
public String id;
public String name;
public Node next;
public Node pre;
public Node(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
"}";
}
}
class OperateLinkedList {
//声明头结点:头结点不能动,可以始于一个辅助变量来操作
Node head = new Node("", "");
public Node getHead(){
return head;
}
//头插法
public void CreateInFirst(Node node) {
//定义辅助变量
Node temp = head;
if(temp.next == null){
temp.next = node;
node.pre = temp;
}else {
node.next = temp.next;
temp.next.pre = node;
node.pre = temp;
temp.next = node;
}
}
//查找结点在链表中的位置
public void Query(Node node) {
Node temp = head;
int i = 0;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.id == node.id && temp.name == node.name) {
flag = true;
break;
}
//辅助变量向后移位
temp = temp.next;
i++;
}
if (flag) {
System.out.println("当前结点在链表中处于第" + i + "位");
} else {
System.out.println("该结点不存在");
}
}
//修改结点指定元素,根据ID修改
public void Update(Node node) {
Node temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.id == node.id) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = node.name;
System.out.print("修改成功,链表数据为:");
list();
} else {
System.out.println("该元素不存在");
}
}
//删除结点
public void Del(Node node) {
Node temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.id == node.id) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.pre.next = temp.next;
if(temp.next != null){
temp.next.pre = temp.pre;
}
System.out.print("删除结点后的链表为:");
list();
} else {
System.out.println("该结点不存在");
}
}
//输出链表
public void list() {
Node temp = head.next;
while (true) {
if (temp == null) {
break;
}
System.out.print(temp);
temp = temp.next;
}
System.out.println();
}
}
循环链表是链表的另一种特殊结构,有循环单链表和循环双链表两种,大体上和单链表和双链表类似,只是循环链表是将链表的尾结点指向头结点,形成一个环。
循环单链表的构建是在单链表的构建基础上,将单链表尾结点的next域指向头结点,从而形成一个环形。即使在操作上,循环单链表和单链表都没有太大的区别,只是在做遍历判断时,要加一个指针或变量指向头结点,遍历结束条件已不再是为空,而是等于指向头结点的那个临时变量(指针)。
package Juc;
import java.util.HashMap;
import java.util.LinkedList;
public class test01 {
public static void main(String[] args) {
OperateLinkedList linkedList = new OperateLinkedList();
//定义结点信息
Node node1 = new Node("1", "张三");
Node node2 = new Node("2", "李四");
Node node3 = new Node("3", "赵五");
//头插法
linkedList.CreateInFirst(node1);
linkedList.CreateInFirst(node2);
linkedList.CreateInFirst(node3);
linkedList.list();
//查询结点位置
linkedList.Query(node2);
//修改结点
Node node4 = new Node("2", "哈哈哈");
linkedList.Update(node4);
//删除结点
linkedList.Del(node4);
}
}
//结点信息
class Node {
public String id;
public String name;
public Node next;
public Node(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
"}";
}
}
class OperateLinkedList {
//声明俩个变量,一个指向头结点,一个指向下一个结点,两者联合,构成循环单链表
private Node head;
private Node temp;
int size = 0;
public OperateLinkedList() {
head = temp = null;
}
public void CreateInFirst(Node node) {
if (size == 0) {
node.next = node;
temp = head = node;
size++;
} else {
temp.next = node;
//新增头节点
node.next = head;
//把头节点给hd,在链表头部增加的这节点变成新头结点
head = node;
//循环链表长度加1
size++;
}
}
//查找结点在链表中的位置(因为头结点是第一个元素结点,所以从1开始)
public void Query(Node node) {
Node temp = head;
int i = 1;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.id == node.id && temp.name == node.name) {
flag = true;
break;
}
//辅助变量向后移位
temp = temp.next;
i++;
}
if (flag) {
System.out.println("当前结点在链表中处于第" + i + "位");
} else {
System.out.println("该结点不存在");
}
}
//修改结点指定元素,根据ID修改
public void Update(Node node) {
Node temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.id == node.id) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = node.name;
System.out.print("修改成功,链表数据为:");
list();
} else {
System.out.println("该元素不存在");
}
}
//删除结点
public void Del(Node node) {
Node temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.next.id == node.id) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
System.out.print("删除结点后的链表为:");
list();
} else {
System.out.println("该结点不存在");
}
}
//输出链表
public void list() {
Node temp01 = head;
while (temp01.next != head) {
System.out.print(temp01);
temp01 = temp01.next;
}
System.out.println(temp01);
}
}
循环双链表与双链表的性质和循环单链表和单链表的性质一样,在创建上都是将尾结点的next域指向头结点构成一个环。除了链表的创建和遍历结束条件不同,其他的操作都一样。
package JVM;
public class Demo02 {
public static void main(String[] args) {
OperateLinkedList linkedList = new OperateLinkedList();
//定义结点信息
Node node1 = new Node("1", "张三");
Node node2 = new Node("2", "李四");
Node node3 = new Node("3", "赵五");
//头插法
linkedList.CreateInFirst(node1);
linkedList.CreateInFirst(node2);
linkedList.CreateInFirst(node3);
linkedList.list();
//查询结点位置
linkedList.Query(node2);
//修改结点
Node node4 = new Node("2", "哈哈哈");
linkedList.Update(node4);
//删除结点
linkedList.Del(node2);
}
}
//结点信息
class Node {
public String id;
public String name;
public Node next;
public Node pre;
public Node(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
"}";
}
}
class OperateLinkedList {
//声明头结点:头结点不能动,可以始于一个辅助变量来操作
private Node head;
int size = 0;
//头插法
public void CreateInFirst(Node node) {
if(size == 0){
head = node;
head.pre = head;
head.next = head;
size++;
}else {
//找到尾结点
Node tail = head.pre;
node.pre = tail;
node.next = head;
head.pre = node;
tail.next = node;
size++;
}
}
//查找结点在链表中的位置
public void Query(Node node) {
Node temp = head;
int i = 1;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.id == node.id && temp.name == node.name) {
flag = true;
break;
}
//辅助变量向后移位
temp = temp.next;
i++;
}
if (flag) {
System.out.println("当前结点在链表中处于第" + i + "位");
} else {
System.out.println("该结点不存在");
}
}
//修改结点指定元素,根据ID修改
public void Update(Node node) {
Node temp = head;
boolean flag = false;
while (true) {
if (temp.next == null) {
break;
}
if (temp.id == node.id) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = node.name;
System.out.print("修改成功,链表数据为:");
list();
} else {
System.out.println("该元素不存在");
}
}
//删除结点
public void Del(Node node) {
Node temp = head.next;
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.id == node.id) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.pre.next = temp.next;
if(temp.next != null){
temp.next.pre = temp.pre;
}
System.out.print("删除结点后的链表为:");
list();
} else {
System.out.println("该结点不存在");
}
}
//输出链表
public void list() {
Node temp01 = head;
while (temp01.next != head) {
System.out.print(temp01);
temp01 = temp01.next;
}
System.out.println(temp01);
}
}
问题描述: 设编号为1…n的n个人围坐一圈,约定编号为K(1<=K<=n)的人从1开始报数,数到m的那个人出列,他的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列,由此产生一个出队编号。
思路: 用一个不带头结点(头结点就是1)的循环链表来处理,首先构成一个带有n个结点的循环单链表,然后由k结点从1开始数,数到m时,删除对应结点,然后再从删除的下一个结点从1开始数,一直数到最后得出出队编号。
package Annotation;
public class test {
public static void main(String[] args) {
CreateList list = new CreateList();
list.addNode(5);
System.out.print("链表为:");
list.show();
System.out.print("出圈顺序为:");
list.escape(1,2,5);
}
}
class Persion {
private int no;
private Persion next;
public Persion(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Persion getNext() {
return next;
}
public void setNext(Persion next) {
this.next = next;
}
}
class CreateList{
private Persion first = new Persion(0);
public void addNode(int nums){
Persion temp = null;
for (int i = 1; i <= nums ; i++) {
Persion childNode = new Persion(i);
if(i==1){
first = childNode;
first.setNext(first);
temp = first;
}else {
temp.setNext(childNode);
childNode.setNext(first);
temp = childNode;
}
}
}
/**
*
* @param startNo 从哪个结点开始数
* @param countNum 数多少下出圈
* @param nums 总结点个数
*/
public void escape(int startNo,int countNum,int nums){
if(first == null || startNo < 1 || startNo>nums){
System.out.println("数据有错误");
}
Persion helper = first;
while (true){
if(helper.getNext() == first){
break;
}
helper = helper.getNext();
}
//报数前,先找到从哪个结点开始(startNo是开始结点,将first和helper移动startNo-1次)
for (int j = 0; j < startNo-1; j++) {
first = first.getNext();
helper = helper.getNext();
}
//找到起点后,开始循环出圈
while (true){
if(first == helper){
break;
}
//根据num开始出圈(让first和helper同时移动countnum-1次,找到要出圈的结点)
for (int j = 0; j <countNum-1 ; j++) {
first = first.getNext();
helper= helper.getNext();
}
//找到结点的位置(此时first指向的就是要出圈的结点,就是删除)
System.out.print(first.getNo());
//删除结点
first = first.getNext();
helper.setNext(first);
}
System.out.println(first.getNo());
}
public void show(){
Persion temp01 = first;
while (true){
System.out.print(temp01.getNo());
if(temp01.getNext() == first){
break;
}
temp01 = temp01.getNext();
}
}
}