双向链表
构成
双向链表的每一个节点都由三部分构成:
- 前置节点指针pre
- 数据data
- 后置节点指针next
单向链表和双向链表对比
- 单向链表查找的方向只能是一个方向,而双向链表可以向前和向后查找
- 单向链表不能进行自我删除,需要依靠辅助节点。而双向链表,可以进行自我删除(正是因为单链表不能自我删除,所以删除节点时要找到待删除节点的前一个节点)
双向链表逻辑结构示意
代码实现
/***
* 双向链表
*
* @author laowa
*
*/
class DoubleLinkedList {
/**
* 头节点
*/
private Node2 head = new Node2(0, null, null);
public Node2 getHead() {
return this.head;
}
/**
* 遍历打印双向链表,与单链表相同
*/
public void list() {
// 链表判空
if (head.next == null) {
System.out.println("链表为空");
return;
}
Node2 helper = head.next;
while (true) {
// 打印节点信息
System.out.println(helper);
if (helper.next != null) {
helper = helper.next;
} else {
break;
}
}
}
/**
* 增加节点到链表尾部
*
* @param node
* 待增加的节点
*/
public void add(Node2 node) {
// 使用一个辅助节点,来辅助遍历链表,找到最后一个节点
Node2 helper = head;
// 当节点的下一个节点不为空时,表示节点不是最后一个节点,则往后移
while (helper.next != null) {
helper = helper.next;
}
// 找到最后一个节点后将新节点拼接在辅助节点后面
node.next = helper.next;
node.pre = helper;
helper.next = node;
}
/**
* 更改节点,和单向链表想通过
*
* @param node
* 待更改的节点新数据
*/
public void update(Node2 node) {
Node2 helper = head;
boolean isExist = false;
while (true) {
if (helper.next == null) {
break;
} else if (helper.next.no == node.no) {
isExist = true;
break;
}
helper = helper.next;
}
if (isExist) {
helper.next.name = node.name;
helper.next.nickName = node.nickName;
} else {
System.out.printf("编号为%d的节点不存在\n", node.no);
}
}
/**
* 通过编号删除节点
*
* @param no
* 待删除的节点编号
*/
public void deleteByNo(int no) {
// 将辅助节点直接指向链表的第一个有效节点,当前节点为待删除节点时,进行自我删除
Node2 helper = head.next;
boolean isExist = false;
while (true) {
if (helper == null) {
break;
} else if (helper.no == no) {
isExist = true;
break;
}
helper = helper.next;
}
if (isExist) {
// 将当前节点的前置节点的next直接指向当前节点的后置节点
helper.pre.next = helper.next;
// 这里一定要判断helper是否已经是最后一个节点,否则会出现空指针异常
if (helper.next != null) {
// 将当前节点的后置节点的pre指向当前节点的前置节点
helper.next.pre = helper.pre;
}
} else {
System.out.printf("编号为%d的节点不存在", no);
}
}
}
/***
* 双向链表节点
*
* @author laowa
*
*/
class Node2 {
int no;
String name;
String nickName;
/**
* 指向前一个节点
*/
Node2 pre;
/**
* 指向下一个节点
*/
Node2 next;
public Node2(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
this.next = null;
}
@Override
public String toString() {
return "no=" + no + "\tname=" + name + "\tnickName=" + nickName;
}
}
单向环形列表
构成
- 单向环形链表的节点构成与单向链表的节点一样
- 环形链表中,链表的最后一个节点的next域指向的是链表的第一个节点,如果链表只有一个节点,则这个节点的next指向自己
逻辑结构示意图
这是一个带头节点的单向循环链表
代码实现
/***
* 单向循环链表
* @author laowa
*
*/
class SingleCircularLinkedList{
/**
* 链表的头节点,或链表的第一个节点
*/
private Node head;
/**
* 构建一个带有头节点的单向循环链表
*/
public SingleCircularLinkedList() {
this.head = new Node(0,null,null);
//初始化一个循环链表时,链表的头结点也形成一个自环
head.next = head;
}
/**
* 构建一个不带头节点的单向循环链表
* @param node 链表的第一个节点
*/
public SingleCircularLinkedList(Node node) {
this.head = node;
head.next = head;
}
public Node getHead() {
return this.head;
}
/**
* 增加新的节点到环形链表的尾部
* @param node 待增加的节点
*/
public void add(Node node) {
//创建辅助节点来遍历,辅助节点表示当前节点的头一个节点
Node helper = head;
//当遍历到最后一个节点,即next指向头节点时,跳出循环
while(helper.next!=head) {
helper = helper.next;
}
//将新的节点插入链表中
node.next = head;
helper.next = node;
}
/**
* 通过编号删除节点
* @param no 待删除结点的编号
*/
public void deleteByNo(int no) {
Node helper = head;
boolean isExist = false;
while(helper.next!=head) {
if(helper.next.no==no) {
isExist = true;
break;
}
}
if(isExist) {
helper.next = helper.next.next;
}else {
System.out.printf("编号为%d的节点不存在",no);
}
}
/**
* 修改节点信息
* @param node 待修改的节点
*/
public void update(Node node) {
Node helper = head;
boolean isExist = false;
while(helper.next!=head) {
if(helper.next.no==node.no) {
isExist=true;
break;
}
}
if(isExist) {
helper.next.name=node.name;
helper.next.nickName = node.nickName;
}else {
System.out.printf("编号为%d的节点不存在",node.no);
}
}
/**
* 打印链表信息
*/
public void list() {
// 链表判空
if (head.next == head) {
System.out.println("链表为空");
return;
}
Node helper = head.next;
while (true) {
// 打印节点信息,终止规则改为next指向head而不是null
System.out.println(helper);
if (helper.next != head) {
helper = helper.next;
} else {
break;
}
}
}
}
/***
* 单向链表节点
*
* @author laowa
*
*/
class Node {
int no;
String name;
String nickName;
/**
* 指向下一个节点
*/
Node next;
public Node(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
this.next = null;
}
@Override
public String toString() {
return "no=" + no + "\tname=" + name + "\tnickName=" + nickName;
}
}
约瑟夫问题
设编号为1,2,…n的n个人围成一个圈,约定编号为k(1<=k<=n)的人从1开始保数,数到m的那个人出列,它的下一位又从1开始报数,数到m的人又出列,依次类推,直到所有人出列为止,由此产生一个出对编号的序列
解法
使用一个不带头节点的环形链表,先构成一个又n个节点的单向循环链表,然后由k节点从1开始计数,计到m时,对应节点从链表中删除,然后再往下计数直到链表为空
代码实现
/**
* 约瑟夫问题解决,使用一个不带头节点的循环链表 打印节点的出列顺序
*
* @param startNo
* 表示从第几个节点开始数
* @param countNum
* 每数几次进行依次出列
* @param nums
* 环中总共有多少个节点
* @param first
* 环的第一个节点
*/
public static void josephus(int startNo, int countNum, int nums, Node first) {
// 如果环的第一个节点为空,或报数的数量小于1,或开始的节点不存在则表示参数输入异常
if (first == null || countNum < 1 || startNo > nums) {
System.out.println("参数输入有误");
return;
}
// 首先,根据startNo,first节点移动到第一个报数的位置,移动startNo-1次
for (int i = 1; i < startNo; i++) {
first = first.next;
}
// 创建一个辅助节点,在报数时指向当前报数的节点的前一个节点,来将当前节点删除
Node helper = first;
// 先遍历一次链表,将helper节点指向first节点的前一个节点
while (helper.next != first) {
helper = helper.next;
}
// 创建一个标志量,表示当前节点应该报的数
int currentCount = 1;
// 遍历时就使用first节点进行,first指向的时当前报数的节点,helper节点指向的是当前报数的节点的前一个节点
// 当first节点的next指向的是自己时,链表中就只剩first最后一个节点了,所以停止循环
while (first.next != first) {
// 如果当前节点报的数是指定的countNum时,该节点出列
if (currentCount == countNum) {
// 将报数刷新,下一个节点将报1
currentCount = 1;
System.out.println("节点出圈————" + first);
// 删除当前节点
first = first.next;
helper.next = first;
} else {
currentCount++;
// 当前节点不是需要出列的节点,first和helper都后移一位
helper = helper.next;
first = first.next;
}
}
System.out.println("最后留下的节点是" + first);
}