3.链表
1、单向链表
1.1 链表的介绍
链表在内存中的存储
特点
- 链表是以节点的方式来存储,是链式存储
- 每个节点包含 data 域 和 next 域。next域用来指向下一个节点
- 链表的各个节点不一定是连续存储的
- 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
带头结点的逻辑示意图
1.2 实现思路
创建(添加)
- 先创建一个Head头节点,表示单链表的头
- 后面我们每添加一个节点,就放在链表的最后
遍历
- 通过一个辅助变量,来遍历整个链表
有序插入
- 先遍历链表,找到应该插入的位置
- 要插入的节点的next指向插入位置的后一个节点
- 插入位置的前一个节点的next指向要插入节点
- 插入前要判断是否在队尾插入
根据某个属性节点修改值
- 先遍历节点,找到修改的位置
- 如果未找到修改节点,则不修改
删除某个节点
- 先遍历节点,找到要删除节点的前一个节点
- 进行删除操作
求倒数第n个节点的信息
- 遍历链表,求出链表的有效长度length(不算头结点)
- 遍历链表到第length-n的节点
翻转链表
- 创建一个新的头结点,作为新链表的头
- 从头遍历旧链表,将遍历到的节点插入新链表的头结点之后
- 注意需要用到两个暂存节点
- 一个用来保存正在遍历的节点
- 一个用来保存正在遍历节点的下一个节点
逆序打印
- 遍历链表,将遍历到的节点入栈
- 遍历完后,进行出栈操作,同时打印出栈元素
代码
public class Demo1 {
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
linkedList.traverseNode();
System.out.println();
//创建学生节点,并插入链表
StudentNode student1 = new StudentNode(1, "Nyima");
StudentNode student3 = new StudentNode(3, "Lulu");
linkedList.addNode(student1);
linkedList.addNode(student3);
linkedList.traverseNode();
System.out.println();
//按id大小插入
System.out.println("有序插入");
StudentNode student2 = new StudentNode(0, "Wenwen");
linkedList.addByOrder(student2);
linkedList.traverseNode();
System.out.println();
//按id修改学生信息
System.out.println("修改学生信息");
student2 = new StudentNode(1, "Hulu");
linkedList.changeNode(student2);
linkedList.traverseNode();
System.out.println();
//根据id删除学生信息
System.out.println("删除学生信息");
student2 = new StudentNode(1, "Hulu");
linkedList.deleteNode(student2);
linkedList.traverseNode();
System.out.println();
//获得倒数第几个节点
System.out.println("获得倒数节点");
System.out.println(linkedList.getStuByRec(2));
System.out.println();
//翻转链表
System.out.println("翻转链表");
LinkedList newLinkedList = linkedList.reverseList();
newLinkedList.traverseNode();
System.out.println();
//倒叙遍历链表
System.out.println("倒序遍历链表");
newLinkedList.reverseTraverse();
}
}
/**
* 创建链表
*/
class LinkedList {
//头节点,防止被修改,设置为私有的
private StudentNode head = new StudentNode(0, "");
/**
* 添加节点
* @param node 要添加的节点
*/
public void addNode(StudentNode node) {
//因为头节点不能被修改,所以创建一个辅助节点
StudentNode temp = head;
//找到最后一个节点
while (true) {
//temp是尾节点就停止循环
if(temp.next == null) {
break;
}
//不是尾结点就向后移动
temp = temp.next;
}
//现在temp是尾节点了,再次插入
temp.next = node;
}
/**
* 遍历链表
*/
public void traverseNode() {
System.out.println("开始遍历链表");
if(head.next == null) {
System.out.println("链表为空");
}
//创建辅助节点
StudentNode temp = head.next;
while(true) {
//遍历完成就停止循环
if(temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
/**
* 按id顺序插入节点
* @param node
*/
public void addByOrder(StudentNode node) {
//如果没有首节点,就直接插入
if(head.next == null) {
head.next = node;
return;
}
//辅助节点,用于找到插入位置和插入操作
StudentNode temp = head;
//节点的下一个节点存在,且它的id小于要插入节点的id,就继续下移
while (temp.next!=null && temp.next.id < node.id) {
temp = temp.next;
}
//如果temp的下一个节点存在,则执行该操作
//且插入操作,顺序不能换
if(temp.next != null) {
node.next = temp.next;
}
temp.next = node;
}
/**
* 根据id来修改节点信息
* @param node 修改信息的节点
*/
public void changeNode(StudentNode node) {
if(head == null) {
System.out.println("链表为空,请先加入该学生信息");
return;
}
StudentNode temp = head;
//遍历链表,找到要修改的节点
while (temp.next!= null && temp.id != node.id) {
temp = temp.next;
}
//如果temp已经是最后一个节点,判断id是否相等
if(temp.id != node.id) {
System.out.println("未找到该学生的信息,请先创建该学生的信息");
return;
}
//修改学生信息
temp.name = node.name;
}
/**
* 根据id删除节点
* @param node 要删除的节点
*/
public void deleteNode(StudentNode node) {
if(head.next == null) {
System.out.println("链表为空");
return;
}
StudentNode temp = head.next;
//遍历链表,找到要删除的节点
if(temp.next!=null && temp.next.id!=node.id) {
temp = temp.next;
}
//判断最后一个节点的是否要删除的节点
if(temp.next.id != node.id) {
System.out.println("请先插入该学生信息");
return;
}
//删除该节点
temp.next = temp.next.next;
}
/**
* 得到倒数的节点
* @param index 倒数第几个数
* @return
*/
public StudentNode getStuByRec(int index) {
if(head.next == null) {
System.out.println("链表为空!");
}
StudentNode temp = head.next;
//用户记录链表长度,因为head.next不为空,此时已经有一个节点了
//所以length初始化为1
int length = 1;
while(temp.next != null) {
temp = temp.next;
length++;
}
if(length < index) {
throw new RuntimeException("链表越界");
}
temp = head.next;
for(int i = 0; i<length-index; i++) {
temp = temp.next;
}
return temp;
}
/**
* 翻转链表
* @return 反转后的链表
*/
public LinkedList reverseList() {
//链表为空或者只有一个节点,无需翻转
if(head.next == null || head.next.next == null) {
System.out.println("无需翻转");
}
LinkedList newLinkedList = new LinkedList();
//给新链表创建新的头结点
newLinkedList.head = new StudentNode(0, "");
//用于保存正在遍历的节点
StudentNode temp = head.next;
//用于保存正在遍历节点的下一个节点
StudentNode nextNode = temp.next;
while(true) {
//插入新链表
temp.next = newLinkedList.head.next;
newLinkedList.head.next = temp;
//移动到下一个节点
temp = nextNode;
nextNode = nextNode.next;
if(temp.next == null) {
//插入最后一个节点
temp.next = newLinkedList.head.next;
newLinkedList.head.next = temp;
head.next = null;
return newLinkedList;
}
}
}
public void reverseTraverse() {
if(head == null) {
System.out.println("链表为空");
}
StudentNode temp = head.next;
//创建栈,用于存放遍历到的节点
Stack<StudentNode> stack = new Stack<>();
while(temp != null) {
stack.push(temp);
temp = temp.next;
}
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
}
}
/**
* 定义节点
*/
class StudentNode {
int id;
String name;
//用于保存下一个节点的地址
StudentNode next;
public StudentNode(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "StudentNode{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
结果
开始遍历链表
链表为空
开始遍历链表
StudentNode{id=1, name='Nyima'}
StudentNode{id=3, name='Lulu'}
有序插入
开始遍历链表
StudentNode{id=0, name='Wenwen'}
StudentNode{id=1, name='Nyima'}
StudentNode{id=3, name='Lulu'}
修改学生信息
开始遍历链表
StudentNode{id=0, name='Wenwen'}
StudentNode{id=1, name='Hulu'}
StudentNode{id=3, name='Lulu'}
删除学生信息
开始遍历链表
StudentNode{id=0, name='Wenwen'}
StudentNode{id=3, name='Lulu'}
获得倒数节点
StudentNode{id=0, name='Wenwen'}
翻转链表
开始遍历链表
StudentNode{id=3, name='Lulu'}
StudentNode{id=0, name='Wenwen'}
倒序遍历链表
StudentNode{id=0, name='Wenwen'}
StudentNode{id=3, name='Lulu'}
2、双向链表
2.1 双向链表
2.2 实现思路
遍历
- 和单向链表的遍历相同,需要一个辅助节点来保存当前正在遍历的节点
添加
- 双向链表多出了一个frnot,所以在添加时,要让新增节点的front指向链表尾节点
修改
- 和单向链表的修改相同
删除
- 使用temp来保存要删除的节点
- temp.pre.next指向temp.next
- temp.next指向temp.pre
代码
public class Demo2 {
public static void main(String[] args) {
BidirectionalList bidirectionalList = new BidirectionalList();
bidirectionalList.addNode(new PersonNode(1, "Nyima"));
bidirectionalList.addNode(new PersonNode(2, "Lulu"));
bidirectionalList.traverseNode();
System.out.println();
System.out.println("修改节点信息");
bidirectionalList.changeNode(new PersonNode(2, "Wenwen"));
bidirectionalList.traverseNode();
System.out.println();
//删除节点
System.out.println("删除节点");
bidirectionalList.deleteNode(new PersonNode(1, "Nyima"));
bidirectionalList.traverseNode();
}
}
class BidirectionalList {
private final PersonNode head = new PersonNode(-1, "");
/**
* 判断双向链表是否为空
* @return 判空结果
*/
public boolean isEmpty() {
return head.next == null;
}
/**
* 添加将诶点
* @param node 要被添加的节点
*/
public void addNode(PersonNode node) {
PersonNode temp = head;
if(temp.next != null) {
temp = temp.next;
}
//插入在最后一个节点的后面
temp.next = node;
node.front = temp;
}
public void traverseNode() {
System.out.println("遍历链表");
if (isEmpty()) {
System.out.println("链表为空");
return;
}
PersonNode temp = head.next;
while(temp != null) {
System.out.println(temp);
temp = temp.next;
}
}
/**
* 修改节点信息
* @param node 要修改的节点
*/
public void changeNode(PersonNode node) {
if(isEmpty()) {
System.out.println("链表为空");
return;
}
PersonNode temp = head.next;
//用于判定是否做了修改
boolean flag = false;
while (temp != null) {
if(temp.id == node.id) {
//匹配到节点,替换节点
temp.front.next = node;
node.next = temp.next;
flag = true;
}
temp = temp.next;
}
if(!flag) {
System.out.println("未匹配到改人信息");
}
}
/**
* 删除节点
* @param node 要删除的节点
*/
public void deleteNode(PersonNode node) {
if(isEmpty()){
System.out.println("链表为空");
return;
}
PersonNode temp = head.next;
//查看是否删除成功
boolean flag = false;
while(temp != null) {
if(temp.id == node.id) {
temp.front.next = temp.next;
temp.next = null;
flag = true;
}
temp = temp.next;
}
if(!flag) {
System.out.println("未找到该节点");
}
}
}
class PersonNode {
int id;
String name;
//指向下一个节点
PersonNode next;
//指向前一个节点
PersonNode front;
public PersonNode(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "PersonNode{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
输出
遍历链表
PersonNode{id=1, name='Nyima'}
PersonNode{id=2, name='Lulu'}
修改节点信息
遍历链表
PersonNode{id=1, name='Nyima'}
PersonNode{id=2, name='Wenwen'}
删除节点
遍历链表
PersonNode{id=2, name='Wenwen'}Copy
3、循环链表
3.1 循环链表
单链表的尾节点指向首节点,即可构成循环链表
3.2 约瑟夫环
N个人围成一圈,从第S个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉,求出被杀顺序
- 例如N=6,M=5,S=1,被杀掉的顺序是:6,4,5,2,1,3
大致思路
-
遍历链表找到指定位置的节点
-
用一个front保存指定节点的前一个节点,方便删除
-
当count==time时,删除此时正在遍历的节点,放入数组中,并将count的值初始化
-
用一个变量loopTime记录已经出圈了几个人,当其等于length时则是最后一个节点,直接放入数组并返回数即可
代码
public class Demo3 {
public static void main(String[] args) {
CircularList circularList = new CircularList();
AttenderNode node1 = new AttenderNode(1);
AttenderNode node2 = new AttenderNode(2);
AttenderNode node3 = new AttenderNode(3);
AttenderNode node4 = new AttenderNode(4);
circularList.addNode(node1);
circularList.addNode(node2);
circularList.addNode(node3);
circularList.addNode(node4);
System.out.println("约瑟夫环");
AttenderNode[] arr = circularList.killAttender(1, 4);
for(AttenderNode node : arr) {
System.out.println(node);
}
}
}
class CircularList {
private final AttenderNode head = new AttenderNode(-1);
AttenderNode temp;
public void addNode(AttenderNode node) {
if(head.next == null) {
head.next = node;
return;
}
temp = head.next;
//只有一个节点,还没成环
if(temp.next == null) {
temp.next = node;
node.next = head.next;
return;
}
while (temp.next != head.next) {
temp = temp.next;
}
//temp现在为尾节点
temp.next = node;
//node现在为尾节点,将其next指向首节点
node.next = head.next;
}
public int getListLength() {
if(head.next == null) {
return 0;
}
//判断是否只有一个节点
if(head.next.next == null) {
return 1;
}
//节点个数初始为2
int length = 2;
AttenderNode first = head.next;
AttenderNode temp = first.next;
while(true) {
//循环了一轮
if(temp.next.id == first.id) {
return length;
}
temp = temp.next;
length++;
}
}
/**
* 删除指定位置节点
* @param time 次数
* @param start 开始节点
* @return
*/
public AttenderNode[] killAttender(int time, int start) {
if(head.next == null) {
System.out.println("链表为空");
return null;
}
temp = head.next;
int length = getListLength();
//存放退出队列的节点
AttenderNode[] arr = new AttenderNode[length];
//从start开始计数
if(start > length) {
System.out.println("超出链表范围");
return null;
}
AttenderNode startNode = temp;
int count;
//如果只有一个节点,直接返回
if(temp.next == null) {
arr[0] = temp;
return arr;
}
//找到开始节点位置
for(count = 1; count<start; count++) {
startNode = startNode.next;
}
//找到start的前一个节点,方便删除操作
AttenderNode front = startNode.next;
while(front.next != startNode) {
front = front.next;
}
//开始选出节点出链表
temp = startNode;
//记录循环次数
int loopTime = 1;
int index = 0;
for(count=1; count<=time; count++) {
if(loopTime == length) {
//放入最后一个节点
arr[index] = temp;
return arr;
}
if(count == time) {
arr[index] = temp;
front.next = temp.next;
index++;
loopTime++;
//初始化,因为在循环开始时还会+1,所以这里初始化为0
count = 0;
}
temp =front.next;
}
return arr;
}
}
class AttenderNode {
int id;
AttenderNode next;
@Override
public String toString() {
return "KillerNode{" +
"id=" + id +
'}';
}
public AttenderNode(int id) {
this.id = id;
}
}
运行结果
约瑟夫环
KillerNode{id=4}
KillerNode{id=1}
KillerNode{id=2}
KillerNode{id=3}