简要概括
单向链表
- 新建: 新建一个类用来充当链表的节点数据
- 添加:
- 无序的添加(插入到末尾):定义一个一个辅助指针
cur
,用来找到链表的末尾,将cur
的next指向你要添加到节点newNode
,cur.next=newNode
- 有序的添加(插入到链表中):定义一个辅助指针
cur
,将cur
指向你将要添加位置的前一个节点。先将新节点newNode
指向cur
的next节点。newNode.next=cur.next
,再将cur.next
指向新节点newNode
,cur.next=newNode
。
- 无序的添加(插入到末尾):定义一个一个辅助指针
- 修改: 需要定义一个辅助指针找到该节点,将该节点的信息进行修改就行。
- 删除: 定义一个辅助指针
cur
,将辅助指针指向你需要删除的下一个节点就可以,cur.next=cur.next.next
- 遍历: 定义一个辅助指针
cur
,依次进行输出就可以了
双向链表
- 新建: 和单向链表的数据节点一样,只是多了一个指向前一个节点的指针
pre
。 - 添加:
- 无序添加(添加到链表的末尾): 定义一个一个辅助指针
cur
,用来找到链表的末尾,将cur
的next指向你要添加到节点,cur.next=newNode
,然后将新的节点的pre
指向cur
,newNode.pre=cur
. - 有序添加(插入到链表中): 定义一个辅助指针
cur
,将cur
指向你将要添加位置的前一个节点。先将cur
节点后的节点与新添加的节点进行关联,newNode.next=cur.next
;cur.next.pre=newNode
;这时会有一个异常如果你添加的一个数据正好要添加到链表的最后所以这里应该进行判断是否为空。接下来要进行cur节点与新的节点进行关联,cur.next=newNode
;newNode.pre=cur
;完成关联。
- 无序添加(添加到链表的末尾): 定义一个一个辅助指针
- 修改:需要定义一个辅助指针找到该节点,将该节点的信息进行修改就行。
- 删除:定义一个辅助指针
cur
辅助指针 指向你要删除的节点,然后将你要删除节点的前一个节点的next
指向你需要删除的下一个节点,在将你要删除的下一个节点指向你要删除节点的前一个节点。这是你需要判断你删除的是否是最后一个节点。cur.next.pre=cur.pre
;然后cur.pre.next=cur.next
- 遍历: 定义一个辅助指针
cur
,依次进行输出就可以了。
单向环形链表
- 新建: 和单向链表一样。
- 添加: 基本和单行链表一样只有在添加
第一个
和最后一个
的时候有点区别,- 第一个: 因为是环形链表,第一个也需要成环,所以需要第一个的
next
指向自己。first.next=first
- 最后一个: 需要将最后一个的
next
指向first
.end.next=first
- 第一个: 因为是环形链表,第一个也需要成环,所以需要第一个的
- 修改: 同上
- 删除: 同单向链表
- 遍历: 同单向链表,结束条件位辅助指针的next==first;
代码实现
单向链表
新建 :
/**
* 创建节点,每个heroNode 就是一个节点
*/
class HeroNode {
public int no;
public String name;
public String nickName;
public HeroNode next;
public HeroNode(int hNo, String hName, String hNickName) {
this.no = hNo;
this.name = hName;
this.nickName = hNickName;
}
/**
* 为了显示方便重写toString
*
* @return String
*/
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
添加(不考虑编号)
/**
* 添加节点到单向链表
* 思路: 1、当不考虑编号的顺序时
* 1、找到当前链表的最后节点(需要一个辅助指针找到最后节点)
* 2、将最后节点的next 指向新的节点
* 2、考虑编号的顺序
* 1、找到新添加节点的位置(通过辅助指针)
* 2、新的节点的next 指向辅助指针的节点的后一个节点
* 3、temp.next指向新的节点
* @param heroNode 添加链表的节点
*/
public void add(HeroNode heroNode) {
//因为head 不能动,因此需要一个辅助指针
HeroNode temp = head;
//遍历链表,找到最后节点
while (temp.next != null) {
//找到链表的最后
temp = temp.next;
//当退出循环时,temp就指向了最后节点
}
temp.next=heroNode;
}
添加(考虑编号)
/**
* 添加节点到单向链表
* 思路: 1、当不考虑编号的顺序时
* 1、找到当前链表的最后节点(需要一个辅助指针找到最后节点)
* 2、将最后节点的next 指向新的节点
* 2、考虑编号的顺序
* 1、找到新添加节点的位置(通过辅助指针)
* 2、新的节点的next 指向辅助指针的节点的后一个节点
* 3、temp.next指向新的节点
* @param heroNode 添加链表的节点
*/
public void addByOrder(HeroNode heroNode) {
//因为头节点不能动,需要一个辅助指针来找到需要添加到位置
//因为时单链表,因为我们找到的temp 是位于添加位置的前一个节点,否则添加不进去
HeroNode temp = head;
//标志添加的编号是否存在,默认是false
boolean flag=false;
while (true) {
//说明temp已经在链表的最后
if (temp.next == null) {
break;
}
//位置已经找到,就在temp 后面
if (temp.next.no > heroNode.no) {
break;
//编号已经存在
} else if (temp.next.no == heroNode.no) {
flag = true;
break;
}
temp=temp.next;
}
//判断flag,编号已经存在不能添加
if (flag){
System.out.printf("插入到英雄的编号已经存在 %d,不能在添加了\n",heroNode.no);
}else {
//插入到链表中
heroNode.next=temp.next;
temp.next=heroNode;
}
}
修改
/**
* 修改节点的信息,根据编号来修改,编号不能修改。
* @param newHeroNode 修改的节点信息
*/
public void update(HeroNode newHeroNode){
//判断链表是否为空
if (head.next==null){
System.out.println("链表为空");
return;
}
//找到需要修改的节点
HeroNode temp = head.next;
//表示是否找到了你需要修改的节点
boolean flag=false;
while (true){
if (temp==null){
//已经遍历完,
break;
}
if (temp.no== newHeroNode.no){
//找到了
flag=true;
break;
}
temp=temp.next;
}
//根据标志位判断
if (flag){
temp.name= newHeroNode.name;
temp.nickName= newHeroNode.nickName;
}else {
System.out.printf("没有找到编号为%d的节点\n",newHeroNode.no);
}
}
删除
/**
* 1、我们需要先找到待删除节点的前一个节点 temp
* 2、temp.next=temp.next.next;
* 3、被删除的节点将不会有其他引用指向,会被垃圾回收机制回收
* @param no 待删除的节点
*/
public void delete(int no){
//头节点不能动,需要一个辅助节点temp
//我们在判断时需要用temp.next.no 和deleteHeroNode.next.no进行比较
//找到需要修改的节点
HeroNode temp = head;
//表示是否找到了你需要删除的节点的上一个节点
boolean flag=false;
while (temp.next != null) {
if (temp.next.no == no) {
//找到了待删除前一个节点
flag = true;
break;
}
temp = temp.next;
}
if (flag){
//可以删除
temp.next=temp.next.next;
}else {
System.out.printf("要删除的节点%d不存在\n",no);
}
}
遍历
/**
* 显示链表
*/
public void list(){
if (head.next==null){
System.out.println("链表为空");
return;
}
//因为头节点不能动,需要一个辅助的指针来进行遍历
HeroNode temp =head.next;
while (temp!=null){
System.out.println(temp.toString());
//将temp后移
temp=temp.next;
}
}
双向链表
新建
/**
* 创建节点,每个heroNode 就是一个节点
*/
class HeroNode2 {
public int no;
public String name;
public String nickName;
public HeroNode2 next;
public HeroNode2 pre;
public HeroNode2(int hNo, String hName, String hNickName) {
this.no = hNo;
this.name = hName;
this.nickName = hNickName;
}
/**
* 为了显示方便重写toString
*
* @return String
*/
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
添加(不考虑编号)
/**
* 添加节点到单向链表
* 思路: 1、当不考虑编号的顺序时
* 1、找到当前链表的最后节点(需要一个辅助指针找到最后节点)
* 2、将最后节点的next 指向新的节点
* 3、并且最后的节点指向前一个节点
* temp.next=newNode
* newNode.pre=temp;
*
* @param heroNode 添加链表的节点
*/
public void add(HeroNode2 heroNode) {
//因为head 不能动,因此需要一个辅助指针
HeroNode2 temp = head;
//遍历链表,找到最后节点
while (temp.next != null) {
//找到链表的最后
temp = temp.next;
//当退出循环时,temp就指向了最后节点
}
//形成一个双向链表
temp.next = heroNode;
heroNode.pre = temp;
}
添加(考虑编号)
/**
* 添加节点到单向链表
* 思路: 2、考虑编号的顺序
* 1、找到新添加节点newNode的位置(添加位置的前一个节点temp)(通过辅助指针)
* 2、 temp.next.pre=heroNode; temp.next=newNode.next; (先连接后面的节点)
* temp.next=newNode ; heroNode.pre=temp (在连接前面的节点)
* 3、但是这里会有一个问题空指针异常,需要先进性判断temp.next是否为空
* @param heroNode 添加链表的节点
*/
public void addByOrder(HeroNode2 heroNode) {
//因为头节点不能动,需要一个辅助指针来找到需要添加到位置
//因为时单链表,因为我们找到的temp 是位于添加位置的前一个节点,否则添加不进去
HeroNode2 temp = head;
//标志添加的编号是否存在,默认是false
boolean flag=false;
while (true) {
//说明temp已经在链表的最后
if (temp.next == null) {
break;
}
//位置已经找到,就在temp 后面
if (temp.next.no > heroNode.no) {
break;
//编号已经存在
} else if (temp.next.no == heroNode.no) {
flag = true;
break;
}
temp=temp.next;
}
//判断flag,编号已经存在不能添加
if (flag){
System.out.printf("插入到英雄的编号已经存在 %d,不能在添加了\n",heroNode.no);
}else {
if (temp.next!=null){
temp.next.pre=heroNode;
heroNode.next=temp.next;
}
temp.next=heroNode;
heroNode.pre=temp;
}
}
修改
/**
* 修改节点的信息,根据编号来修改,编号不能修改。
*
* @param newHeroNode 修改的节点信息
*/
public void update(HeroNode2 newHeroNode) {
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
//找到需要修改的节点
HeroNode2 temp = head.next;
//表示是否找到了你需要修改的节点
boolean flag = false;
while (true) {
if (temp == null) {
//已经遍历完,
break;
}
if (temp.no == newHeroNode.no) {
//找到了
flag = true;
break;
}
temp = temp.next;
}
//根据标志位判断
if (flag) {
temp.name = newHeroNode.name;
temp.nickName = newHeroNode.nickName;
} else {
System.out.printf("没有找到编号为%d的节点\n", newHeroNode.no);
}
}
/**
* 1、我们需要先找到待删除节点 temp
* 2、找到这个节点自我删除
*
* @param no 待删除的节点
*/
public void delete(int no) {
if (head.next == null) {
System.out.println("链表为空");
return;
}
//头节点不能动,需要一个辅助节点temp
//我们在判断时需要用temp.next.no 和deleteHeroNode.next.no进行比较
//找到需要修改的节点
HeroNode2 temp = head.next;
//表示是否找到了你需要删除的节点的上一个节点
boolean flag = false;
while (temp != null) {
if (temp.no == no) {
//找到了待删除前一个节点
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
//可以删除
temp.pre.next = temp.next;
if (temp.next != null) {
temp.next.pre = temp.pre;
}
} else {
System.out.printf("要删除的节点%d不存在\n", no);
}
}
/**
* 显示链表
*/
public void list(){
if (head.next==null){
System.out.println("链表为空");
return;
}
//因为头节点不能动,需要一个辅助的指针来进行遍历
HeroNode2 temp =head.next;
while (temp!=null){
System.out.println(temp.toString());
//将temp后移
temp=temp.next;
}
}
删除
/**
* 1、我们需要先找到待删除节点 temp
* 2、找到这个节点自我删除
*
* @param no 待删除的节点
*/
public void delete(int no) {
if (head.next == null) {
System.out.println("链表为空");
return;
}
//头节点不能动,需要一个辅助节点temp
//我们在判断时需要用temp.next.no 和deleteHeroNode.next.no进行比较
//找到需要修改的节点
HeroNode2 temp = head.next;
//表示是否找到了你需要删除的节点的上一个节点
boolean flag = false;
while (temp != null) {
if (temp.no == no) {
//找到了待删除前一个节点
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
//可以删除
temp.pre.next = temp.next;
if (temp.next != null) {
temp.next.pre = temp.pre;
}
} else {
System.out.printf("要删除的节点%d不存在\n", no);
}
}
遍历(正序)
/**
* 显示链表
*/
public void list(){
if (head.next==null){
System.out.println("链表为空");
return;
}
//因为头节点不能动,需要一个辅助的指针来进行遍历
HeroNode2 temp =head.next;
while (temp!=null){
System.out.println(temp.toString());
//将temp后移
temp=temp.next;
}
}
遍历(反序)
public void listRevers(){
if (head.next==null){
System.out.println("链表为空");
return;
}
//因为头节点不能动,需要一个辅助的指针来进行遍历
HeroNode2 temp =head;
while (temp.next!=null){
temp=temp.next;
}
//这时已经找到了最后
while (temp.pre!=null){
System.out.println(temp);
temp=temp.pre;
}
}
单向环形列表
单向环形列表用一个经典的例题进行代替。丢手绢(约瑟夫问题)
新建
/**
* 先创建一个节点类
*/
class Boy{
private int no;
private Boy next;
public Boy(int no){
this.no=no;
}
@Override
public String toString() {
return "Boy{" +
"no=" + no +
'}';
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
}
添加(给定一个数nums,新建一个nums大小的环形链表)
/**
* 创建一个环形链表
* @param nums 创建的节点数
*/
public void addBoy(int nums){
//判断nums 是否合法
if (nums<1){
System.out.println("nums不合法");
return;
}
Boy curBoy = null;
for (int i = 1; i <= nums; i++) {
//创建你将要添加的节点
Boy boy = new Boy(i);
if (i==1){
//当第一个节点也要闭环
first=boy;
first.setNext(first);
curBoy=first;
}else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy=boy;
}
}
}
编辑 和单向列表一样
删除节点(出圈)
/**
* * 根据用户输入输出小孩的出圈顺序
* * 思路: 1、定义一个辅助指针cur,和一个帮助指针helper.让cur指向开始报数的节点
* * 2、cur指向冲那个小孩开始报数的指针,helper.next=cur,因为删除需要前一个节点。
* * 3、需要移动 k-1 次。
* @param startNo 开始的编号
* @param countNum 数几下
* @param nums 总共有几个
*/
public void deleteBoy(int startNo, int countNum, int nums) {
//校验数据的合理性
if (first == null || startNo < 1 || startNo > nums || countNum > nums) {
System.out.println("你输入的数据不合理");
return;
}
//定义辅助查询指针
Boy cur=first;
//让cur 指向开始报数的人
for (int i = 0; i < startNo-1; i++) {
cur=cur.getNext();
}
//定义一个删除的辅助指针
Boy helper=first;
while (helper.getNext()!=cur){
helper=helper.getNext();
}
//循环出圈
while (helper != cur) {
for (int i = 0; i < countNum - 1; i++) {
cur = cur.getNext();
helper = helper.getNext();
}
//找到出圈的boy
System.out.printf("出圈的Boy编号%d\n", cur.getNo());
//将该Boy出圈
cur = cur.getNext();
helper.setNext(cur);
}
//圈中最后剩下的一个
System.out.printf("最后出圈的Boy编号%d\n",cur.getNo());
first=null;
}
测试
public class JosephDemo {
public static void main(String[] args) {
//测试
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.showBoy();
circleSingleLinkedList.deleteBoy(1,2,5);
circleSingleLinkedList.showBoy();
}
}
结果
小孩的编号位1
小孩的编号位2
小孩的编号位3
小孩的编号位4
出圈的Boy编号2
出圈的Boy编号4
出圈的Boy编号1
出圈的Boy编号5
最后出圈的Boy编号3
链表为空~~~
最后
总结:当单向链表添加删除时,辅助指针都要指向操作的前一个节点,但是双向链表删除时可以不用指向前一个节点,因为可以自己进行删除操作不需要辅助。
当节点进行关联时,最后先和后面的节点进行关联,因为指针一般都在前面。