一、 链表简介
链表是有序的列表,存储方式是链式存储
特点
1)链表是以节点的方式来存储
2)每个结点包含data域,next域(指向下一个结点)
3)链表的各个结点不一定是连续存储的
4)链表分为【带头结点】的链表和【没有头结点】的链表,根据实际需求来确定。
二、单向链表
1.添加节点
1)不考虑节点的编号顺序时
1)借助辅助指针temp找到当前链表的最后结点
2)将最后一个结点的next域指向一个新的结点
2)考虑节点编号顺序时
因为是单链表,所以我们找的辅助指针temp是位于添加位置的【前一个】结点,否则无法添加,简单理解为:单向链表只能指向后面的结点,不能指向前面的结点
2.修改节点
借助辅助指针temp,找到待修改节点即可。
3 删除节点
1)因为是单向链表,所以辅助变量temp需要找到需要删除的这个结点的【前一个】结点。如果temp找到的就是需要删除的结点,是删不掉的
4.求链表中有效节点个数
借助辅助指针temp,temp指向链表第一个有效节点,即head后面的节点,然后依次遍历链表并计数。
5.查找链表中倒数第k个节点
1)先获取链表的优先节点个数
2)从第一个有效节点开始遍历(length - index)次即可。也就是让辅助指针temp指向第一个有效节点,移动(length - index)次
6.单向链表反转
在原链表中遍历【取出】每一个结点,使用【添加操作】,添加到一个新的链表中,进而实现反转。
7.单向环形链表
1)可以解决Josephus问题
设编号为1,2,3,...n的n个人围坐一圈,约定出编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列位置,由此产生一个出队编号的序列。
2)创建单向环形链表
1)先创建第一个结点,让first指针指向该结点,形成环形
2)后面每创建一个新的结点,就把该节点,加入到已有的环形链表中即可。
3)遍历环形链表
1)先让辅助节点temp,指向first节点
2)然后通过while循环遍历该环形链表即可,当temp.next == first时,循环结束。
3)简单理解,就是first指向第一个,temp指向最后一个,然后同时移动同样的次数,保证首尾相连构成环形。
三、双向链表
1.单向链表的缺点
1)单向链表只有一个方向,即前向添加、查找节点
2)不能自我删除
2.双向链表的增删改查
1)添加节点
辅助指针temp指向头节点
1)默认添加到链表最后:
1)temp.setNext(newNode)
2)newNode.setPre(temp)
2)按编号顺序添加:
temp找到待添加位置的前一个节点
1)newNode.setNext(temp.getNext())
2)temp.setNext(newNode)
3)temp.getNext().setPre(newNode)
4)newNode.setPre(temp)
2)修改节点
和单向链表修改的方式一样
3)删除节点
因为是双向链表,所以可以实现自我删除,temp直接指向待删除的节点即可。
1)temp.getPre().setNext(temp.getNext())
2)temp.getNext().setPre(temp.getPre())
四、单向环形链表Java代码
public class CircleSingleLinkedList {
//创建一个first节点,先不用赋值
private CircleNode first = new CircleNode(-1);
/**
* 添加结点
* @param nums 表示添加多少个节点
*/
public void addNode(int nums){
if (nums < 1){
System.out.println("nums的值不合法!");
return;
}
CircleNode temp = null; //辅助节点,用于构建环形链表
//使用for循环,创建环形链表
for (int i = 1; i <= nums; i++) {
//根据编号创建节点
CircleNode newNode = new CircleNode(i);
//如果是第一个节点,情况比较特殊,创建后应该形成环形
if (i == 1){
first = newNode;
first.setNext(first); //构成环形
temp = first; //让temp指向第一个节点,因为first指针不能动,所以需要辅助节点构建环形链表
}else {
//1.先让temp节点指向添加的新节点
temp.setNext(newNode);
//2.再让新添加的节点指向first
newNode.setNext(first);
//3.在最后让temp指向当前的新节点,即temp后移一位
// temp = newNode; //与下面这条语句是等效的
temp = temp.getNext();
}
}
}
/**
* 遍历单向环形链表
*/
public void list(){
//首先判断链表是否为空,因为是环形链表,所以判空的条件不能写为:first.getNext() == null
//因为first.getNext()表示的是first节点的值,即初始值-1
if (first == null){
System.out.println("链表为空!");
return;
}
//因为first不能动,所以需要一个辅助节点
CircleNode temp = first;
while (true){
System.out.printf("节点的编号为%d\n", temp.getNode());
if (temp.getNext() == first){
//说明已经遍历完毕
break;
}else {
temp = temp.getNext(); //temp后移
}
}
}
/**
* 根据用户的输入,计算出节点出圈的顺序
* @param startNum 表示从第几个节点开始报数
* @param countNum 表示数几下
* @param nums 表示链表中总共有几个节点
*/
public void deleteByOrder(int startNum, int countNum, int nums){
if (first == null || startNum < 1 || startNum > nums){
System.out.println("参数输入不合法,请重新输入!");
return;
}
CircleNode temp = first;
//事先应该将temp指向最后一个节点
while (true){
if (temp.getNext() == first){
//说明temp到了最后一个节点了
break;
}else {
temp = temp.getNext();
}
}
//报数前,需要将first指向开始报数的那个节点,需要将first和temp同时移动startNum-1次
for (int i = 0; i < startNum -1; i++){
first = first.getNext();
temp = temp.getNext();
}
//报数时,first和temp指针同时移动countNum-1次,然后出圈
//这是个循环的操作,直到圈中只有一个节点
while (true){
if (temp == first){
//说明圈中只有一个节点
break;
}else {
//first和temp指针同时移动countNum-1次,然后出圈
for (int j = 0; j < countNum - 1; j++) {
first = first.getNext();
temp = temp.getNext();
}
//for循环结束后,first指向的就是要出圈的节点
System.out.printf("节点%d出圈成功!\n", first.getNode());
first = first.getNext();
temp.setNext(first);
}
}
System.out.printf("最后留在圈中的节点编号为%d\n", first.getNode());
}
}