链表(Linked List)
上一篇文章分析了List源码,这一篇文章本来要分析Set的源码,发现Set的底层是使用HashMap实现的,于是准备先分析Map,但是发现map的实现类的底层数据结构是数组,链表,红黑树,撤了撤了,先讲一波数据结构,数组大家都比较熟悉,所以我们主要讲链表和树结构,这一篇文章先学习链表。
链表的定义
链表是以节点(node)存储的链式存储结构,一个node包含一个data域(存放数据)和一个next域(存放下一个node的指针),链表的各个节点不一定是连续的,它可以分为带头结点和不带头结点。头结点仅包含next域。
下面我们主要讲解单向链表、双向链表和单向循环链表(约瑟夫问题)
1、单向链表
单向链表的特点是链接方向是单向的,对链表的访问要从头部开始进行顺序读取,链表中的每个结点仅有一个指向下一个结点的指针。
1.1构造链表
-
创建节点类,包含了data和next,next指向下一个节点对象
class PersonNode {
public int number;//每个结点的序号,方便我们进行排序和查找
public String name;//结点的名称
public String nickName;//结点的别名
public PersonNode next;//指向下一结点的指针
public PersonNode(int number, String name, String nickName) {
this.number = number;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString() {
return "PersonNode{" +
"number=" + number +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
-
创建单向链表类,里面包含了增删改查的方法
class SingleLinkedList {
//创建头结点
private PersonNode headNode = new PersonNode(0, "", "");
public PersonNode getHeadNode() {
return headNode;
}
//添加节点的单向链表
/**
* 在链表尾部插入
*/
public void addNode(PersonNode personNode) {
......
}
/**
* 按照number顺序插入
*/
public void addByOrder(PersonNode personNode) {
......
}
/**
* 修改节点信息
*/
public void update(PersonNode personNode) {
......
}
/**
* 删除某一个节点
* 先根据number查找,在删除
*/
public void delete(PersonNode personNode) {
......
}
/**
* 遍历节点
*/
//显示链表中的有效节点
public void show(PersonNode personNode) {
......
}
1.2单向链表增删改查的实现
我们将会以图解的方式讲解,
-
在链表中增加一个节点
确定要插入的节点位置,循环遍历到该位置节点的上一个节点。
S->next=P->next
P->next=S
代码实现
/**
* 按照number顺序插入
*/
public void addByOrder(PersonNode personNode) {
//定义插入标识符
boolean flag = false;
//定义替代变量
PersonNode temp = headNode;
while (true) {
if (temp.next == null) {
break;
}
//满足条件
if (temp.next.number > personNode.number) {
break;
} else if (temp.next.number == personNode.number) {
flag = true;
break;
}
//后移
temp = temp.next;
}
//插入节点
if (flag) {
System.out.println("节点已经存在~~~~");
} else {
personNode.next = temp.next;
temp.next = personNode;
}
}
-
在链表中删除一个节点
确定删除的位置,循环遍历到该位置节点的上一个节点
P->next=p->next->next
代码实现
/**
* 删除某一个节点
* 先根据number查找,在删除
*/
public void delete(PersonNode personNode) {
boolean flag = false;
PersonNode temp = headNode;
while (true) {
if (temp.next == null) {
System.out.println("链表为空");
break;
}
if (temp.next.number == personNode.number) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
} else {
System.out.println("找不到该节点的信息");
}
}
-
修改链表中的一个节点信息
确定要修改节点的位置,先遍历找到该节点,修改该节点的信息。
/**
* 修改节点信息
*/
public void update(PersonNode personNode) {
boolean flag = false;
PersonNode temp = headNode;
//根据编号找到要修改的节点
while (true) {
if (temp.next == null) {
System.out.println("链表为空");
break;
}
if (temp.next.number == personNode.number) {
flag = true;
break;
}
temp = temp.next;
}
//修改节点信息
if (flag) {
temp.next.nickName = personNode.nickName;
temp.next.name = personNode.name;
} else {
System.out.println("找不到该节点的信息");
}
}
-
遍历链表
/**
* 遍历节点
*/
//显示链表中的有效节点
public void show(PersonNode personNode) {
if (personNode.next == null) {
System.out.println("该链表为空~~~");
}
PersonNode temp = personNode;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
System.out.println(temp);
}
}
2、双向链表
双向链表的定义
双向链表中的数据结点有两个指针,分别指向直接后继和直接前驱,所以从双向链表中的任一结点开始,都可以很方便的访问它的前驱结点和后继结点。
2.1构造双向链表
它和单向链表很相似,仅仅是增加了一个直接前驱结点
public int number;
public String name;
public String nickName;
public PersonNode next;
public PersonNode pre;
2.2双向链表增删改查的实现
-
双向链表的遍历
双向链表的遍历与单向链表相同,可以按照两个方向遍历,在这里就不在代码实现了。
-
双向链表的添加
1、在末尾添加
在双向链表的末尾添加C节点(定位到节点B)
B->next=C
C->pre=B
代码实现
/**
* 1、在末尾添加节点
*/
public void add(PersonNode2 personNode2){
PersonNode2 temp=headNode;
while (true){
if(temp.next==null){
break;
}
temp=temp.next;
}
//构成双向链表
temp.next=personNode2;
personNode2.pre=temp;
}
2、在两节点间添加
在节点B和节点C间添加D(程序定位到节点D),
顺序:先搞定D的前驱和后继节点,再搞定C的节点的前驱和B的后继节点。
D->pre=B
D->next=C
C->pre=D
B->next=D
代码实现
/**
*在两节间插入新的结点
*/
public void addByOrder(PersonNode2 personNode2){
PersonNode2 temp=headNode;
boolean flag=false;
boolean flag2=false;
boolean flag3=false;
while (true){
if (temp.next==null){
flag2=true;
break;
}
//加入两个节点间的条件
if(temp.next.number>personNode2.number){
flag3=true;
break;
}
if(temp.next.number==personNode2.number){
flag=true;
break;
}
temp=temp.next;
}
if(flag){
System.out.println("相同的节点已存在");
}
if(flag2){
//在末尾添加构成双向链表
temp.next=personNode2;
personNode2.pre=temp;
}
if(flag3){
//在两个有效节点间添加
personNode2.pre= temp;
personNode2.next=temp.next;
temp.next.pre=personNode2;
temp.next=personNode2;
}
}
-
双向链表的修改
同单链表修改相同
-
双向链表的删除
在两节点间删除(定位到节点S(D))
S->pre->next=S->next
S->next->pre=S->pre
代码实现
/**
* 删除节点
*/
public void delete(PersonNode2 personNode2){
PersonNode2 temp=headNode;
boolean flag=false;
while (true){
if(temp.next==null){
break;
}
temp=temp.next;
if(temp.number==personNode2.number){
flag=true;
break;
}
}
if(flag){
temp.pre.next=temp.next;
if(temp.next!=null){
temp.next.pre=temp.pre;
}
}else {
System.out.println("找不到删除节点的信息~~");
}
}
3、单向循环链表(无头结点)
定义
单向循环链表是单链表的另一种形式,其结构特点是最后一个节点不再是结束标记,而是指向整个链表的第一个结点,从而形成一个环。
约瑟夫问题是单向循环链表的一个典型的应用,所以下面通过学习约瑟夫问题来学习单向循环链表。
-
约瑟夫问题描述:
约瑟夫问题是一个非常著名的趣题,即由n个小男孩坐成一圈,按顺时针由1开始给他们编号。然后由第一个人开始报数,数到m的人出局。现在需要求的是最后一个出局的人的编号。给定两个参数 n和m,分别代表游戏的人数和报数大小。请返回最后一个出局的人的编号.
问题分析:
约瑟夫问题可以简单分为两个步骤
构造Boy节点类
private int number;
private Boy next;
-
构建一个单向循环链表
定义两个指针变量First和cur,first表示头结点,cur指向当前节点
/**
*构造第一个节点first
*/
first = boy;//第一个节点为first
first.setNext(first);//仅有一个节点时,first指向first,构成闭环
cur = first;//辅助变量指向first
/**
*构造其它节点
*/
cur.setNext(boy);//将新添加的节点设置为当前节点的后继节点
boy.setNext(first);//新添加的节点与first构成闭环
cur=boy;//将当前节点设置为新添加的节点
-
遍历链表,根据约定的规则退出循环链表,直至仅有一个节点
定义两个辅助节点变量first和helper,first指向每一轮要开始的节点,helper指向最后一个节点,当first与helper指向同一个节点时,该循环链表仅剩一个节点
构造函数
moveCycle(int no,int number,int sum)
1、确定从第几个节点开始
for(int i=0;i<no-1;i++){
first=first.getNext();
helper=helper.getNext();
}
2、循环,找出节点,直至仅剩一个节点
while (true){
if(first==helper){
System.out.printf("最后一个节点时%d\n",first.getNumber());
break;
}
//开始,每number下,出圈一次
for(int j=0;j<number-1;j++){
first=first.getNext();
helper=helper.getNext();
}
System.out.printf("%d出圈\n",first.getNumber());
first=first.getNext();
helper.setNext(first);
}
参考文献:
[1]Mark Allen Wesiss.数据结构与算法基础
[2]程杰.大话数据结构
笔者组建了技术交流群,请加笔者微信,笔者拉你进群学习,欢迎大家加入,一起进步。
看了这篇文章,你是否「博学」了
「扫码关注我」,每天博学一点点。