一、链表(LinkedList)介绍
链表是有序的列表,但是它在内存中的存储如下:
- 链表是以节点的方式来存储,是链式存储结构
- 每个节点包含data域,next域(指向下一个节点)
- 链表的各个节点不一定是连续存储的
- 链表分为带头节点和不带头节点的链表,根据实际的需求来确定
- 单链表(带头节点)的逻辑结构示意图如下所示:
二、单链表的应用实例
使用带头节点的单向链表实现水浒英雄排行榜的管理,对英雄人物进行增删改查的操作。
建立英雄人物的节点信息,代码实现如下:
package com.atguigu.linkedlist;
public class HeroNode {
public int no;
public String name;
public String nickname;
public HeroNode next;
public HeroNode(int no, String name, String nickname) {
super();
this.no = no;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
}
}
1.显示链表信息
遍历整个链表,打印链表中所有节点的信息
代码实现:
//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");
public void show(){
if(head.next==null){
System.out.println("链表为空");
}else{
HeroNode temp=head.next;
while(temp!=null){
System.out.println(temp);
temp=temp.next;
}
}
}
2.增加操作
第一种方法:添加英雄时,直接添加到链表的尾部
思路分析示意图:
代码实现:
//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");
//添加节点到单向链表
//1.找到当前链表的最后节点
//2.将最后这个节点的next指向新的节点
public void add(HeroNode heroNode){
HeroNode temp=head;
while(temp.next!=null){
temp=temp.next;
}
temp.next=heroNode;
}
测试代码:
package com.atguigu.linkedlist;
public class SingleLinkedListDemo {
public static void main(String[] args) {
SingleLinkedList singleLinkedList=new SingleLinkedList();
HeroNode heroNode1=new HeroNode(100, "宋江", "及时雨");
HeroNode heroNode2=new HeroNode(200, "武松", "行者");
HeroNode heroNode3=new HeroNode(300, "时迁", "鼓上蚤");
HeroNode heroNode4=new HeroNode(400, "孙二娘", "母夜叉");
System.out.println("未添加节点的链表信息");
singleLinkedList.show();
System.out.println("---------------");
System.out.println("在链表尾部添加节点后:");
singleLinkedList.add(heroNode1);
singleLinkedList.show();
System.out.println("---------------");
System.out.println("在链表尾部添加节点后:");
singleLinkedList.add(heroNode2);
singleLinkedList.show();
System.out.println("---------------");
System.out.println("在链表尾部添加节点后:");
singleLinkedList.add(heroNode3);
singleLinkedList.show();
System.out.println("---------------");
System.out.println("在链表尾部添加节点后:");
singleLinkedList.add(heroNode4);
singleLinkedList.show();
}
}
效果图:
第二种方法:在添加英雄时,根据排名将英雄插入到指定的位置(如果已经存在,则添加失败,并给出提示)
思路分析示意图:
代码实现:
//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");
public void addByOrder(HeroNode heroNode){
HeroNode temp=head;
boolean flag=false; //标志是否找到插入节点的位置
while(true){
if(temp.next==null){
break;
}
if(temp.next.no==heroNode.no){
flag=true;
break;
}
if(temp.next.no>heroNode.no){
break;
}
temp=temp.next;
}
if(flag==true){
System.out.println("该节点已存在,添加失败");
}else{
heroNode.next=temp.next;
temp.next=heroNode;
}
}
测试代码:
package com.atguigu.linkedlist;
public class SingleLinkedListDemo {
public static void main(String[] args) {
SingleLinkedList singleLinkedList=new SingleLinkedList();
HeroNode heroNode1=new HeroNode(100, "宋江", "及时雨");
HeroNode heroNode2=new HeroNode(200, "武松", "行者");
HeroNode heroNode3=new HeroNode(300, "时迁", "鼓上蚤");
HeroNode heroNode4=new HeroNode(400, "孙二娘", "母夜叉");
System.out.println("未添加节点的链表信息");
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode3);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode1);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode3);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode4);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode2);
singleLinkedList.show();
}
}
效果图:
3.删除操作
思路分析示意图:
代码实现(1):
//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");
//删除节点的信息,根据no编号来删除
public void delete(HeroNode heroNode){
if(head.next==null){
System.out.println("链表为空,删除失败");
}else{
HeroNode temp=head.next; //记录当前节点
HeroNode temp_ahead=head; //记录当前节点的前一个节点
boolean flag=false; //标志是否找到了待删除节点的位置
while(true){
if(temp==null){ //已经到链表的最后
break;
}
if(temp.no==heroNode.no){ //找到了待删除的节点
flag=true;
break;
}
temp_ahead=temp;
temp=temp.next;
}
if(flag==true){
temp_ahead.next=temp.next;
System.out.println("删除"+heroNode.name+"节点成功");
}else{
System.out.println("没有找到该节点信息,删除失败");
}
}
}
代码实现(2):
//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");
//删除节点的信息,根据no编号来删除
public void delete(HeroNode heroNode){
if(head.next==null){
System.out.println("链表为空,删除失败");
}else{
HeroNode temp=head.next; //记录当前节点
boolean flag=false; //标志是否找到了待删除节点的位置
while(true){
if(temp==null){ //已经到链表的最后
break;
}
if(temp.next.no==heroNode.no){ //找到了待删除的节点
flag=true;
break;
}
temp=temp.next;
}
if(flag==true){
temp.next=temp.next.next;
System.out.println("删除节点成功");
}else{
System.out.println("没有找到该节点信息,删除失败");
}
}
}
测试代码:
package com.atguigu.linkedlist;
public class SingleLinkedListDemo {
public static void main(String[] args) {
SingleLinkedList singleLinkedList=new SingleLinkedList();
HeroNode heroNode1=new HeroNode(100, "宋江", "及时雨");
HeroNode heroNode2=new HeroNode(200, "武松", "行者");
HeroNode heroNode3=new HeroNode(300, "时迁", "鼓上蚤");
HeroNode heroNode4=new HeroNode(400, "孙二娘", "母夜叉");
System.out.println("未添加节点的链表信息");
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode3);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode1);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode3);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode4);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode2);
singleLinkedList.show();
System.out.println("------------------------------------------------");
System.out.println("删除后的链表:");
singleLinkedList.delete(heroNode2);
singleLinkedList.show();
System.out.println("------------------------------------------------");
System.out.println("删除后的链表:");
singleLinkedList.delete(heroNode4);
singleLinkedList.show();
System.out.println("------------------------------------------------");
System.out.println("删除后的链表:");
singleLinkedList.delete(heroNode1);
singleLinkedList.show();
System.out.println("------------------------------------------------");
System.out.println("删除后的链表:");
singleLinkedList.delete(heroNode3);
singleLinkedList.show();
}
}
效果图:
4.修改操作
遍历整个链表,根据已知的节点编号信息,找到需要修改的节点位置,并修改;如果未找到该节点,则返回修改失败的信息。
代码实现:
//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");
//修改节点的信息,根据no编号来修改,即no编号不能改
public void update (HeroNode heroNode){
if(head.next==null){
System.out.println("链表为空");
}else{
HeroNode temp=head.next;
boolean flag=false;
while(true){
if(temp==null){
break;
}
if(temp.no==heroNode.no){
flag=true;
break;
}
temp=temp.next;
}
if(flag==true){
temp.name=heroNode.name;
temp.nickname=heroNode.nickname;
}else{
System.out.println("没有找到该节点信息,修改未成功");
}
}
}
测试代码:
package com.atguigu.linkedlist;
public class SingleLinkedListDemo {
public static void main(String[] args) {
SingleLinkedList singleLinkedList=new SingleLinkedList();
HeroNode heroNode1=new HeroNode(100, "宋江", "及时雨");
HeroNode heroNode2=new HeroNode(200, "武松", "行者");
HeroNode heroNode3=new HeroNode(300, "时迁", "鼓上蚤");
HeroNode heroNode4=new HeroNode(400, "孙二娘", "母夜叉");
System.out.println("未添加节点的链表信息");
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode3);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode1);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode3);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode4);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode2);
singleLinkedList.show();
System.out.println("------------------------------------------------");
System.out.println("修改后的链表:");
HeroNode heroNode5=new HeroNode(300, "卢俊义", "玉麒麟");
singleLinkedList.update(heroNode5);
singleLinkedList.show();
}
}
效果图:
5.查找操作
遍历整个链表,根据已知的节点编号信息,查找该编号对应节点的相关信息;如果未找到该节点,则返回查找失败的信息。
代码实现:
//先初始化一个头节点,头节点不存放具体的数据
private HeroNode head=new HeroNode(0, "", "");
//查找节点的信息,根据no编号来查找
public void get(int no){
if(head.next==null){
System.out.println("链表为空,查找的节点信息不存在");
}else{
HeroNode temp=head.next;
boolean flag=false;
while(true){
if(temp==null){
break;
}
if(temp.no==no){
flag=true;
break;
}
temp=temp.next;
}
if(flag==true){
System.out.println("该节点信息为:"+temp);
}else{
System.out.println("查找的节点信息不存在");
}
}
}
测试代码:
package com.atguigu.linkedlist;
public class SingleLinkedListDemo {
public static void main(String[] args) {
SingleLinkedList singleLinkedList=new SingleLinkedList();
HeroNode heroNode1=new HeroNode(100, "宋江", "及时雨");
HeroNode heroNode2=new HeroNode(200, "武松", "行者");
HeroNode heroNode3=new HeroNode(300, "时迁", "鼓上蚤");
HeroNode heroNode4=new HeroNode(400, "孙二娘", "母夜叉");
System.out.println("未添加节点的链表信息");
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode3);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode1);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode3);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode4);
singleLinkedList.show();
System.out.println("**********************************");
System.out.println("按英雄排位顺序添加节点后:");
singleLinkedList.addByOrder(heroNode2);
singleLinkedList.show();
System.out.println("------------------------------------------------");
System.out.println("查找英雄排位为400的节点信息:");
singleLinkedList.get(400);
System.out.println("查找英雄排位为222的节点信息:");
singleLinkedList.get(222);
}
}
效果图:
三、单链表面试题(新浪,百度,腾讯)
1.求链表中的有效节点个数
思路:根据头节点依次遍历整个链表,并用全局变量记录遍历的次数,即为有效节点的个数
代码实现:
//获取单链表的节点数(如果是带头节点的链表,不需要统计头节点)
public int getLength(HeroNode head){
if(head.next==null){
return 0;
}else{
int count=0;
HeroNode temp=head.next;
while(true){
if(temp==null){
break;
}
count++;
temp=temp.next;
}
return count;
}
}
2.查找单链表中的倒数第k个节点
思路1:可以首先计算出单链表的有效节点个数,并计算出倒数第k个节点是正序的第几个节点,并以此作为遍历的次数,找到该节点。
思路2:可以将单链表反转,倒数第k个节点就变成整数第k+1个节点(因为包含头节点)
代码实现:
//查找单链表中的倒数第k个节点
//1.先把链表从头到尾遍历,得到链表的总长度
//2.从链表的第一个开始遍历(getLength()-k+1)个,就可以得到倒数第k个节点
public HeroNode getLastIndexNode(int k){
int sum=this.getLength(head);
if(k>sum|| k<=0){
throw new RuntimeException("输入值不合法");
}else{
if(head.next==null){
return null;
}else{
HeroNode temp=head.next;
int temp_count=sum-k+1;
int count=1;
while(true){
if(count==temp_count){
break;
}
count++;
temp=temp.next;
}
return temp;
}
}
}
3.单链表的反转(腾讯面试题,有点难度)
思路:首先判断链表是否为空,如果链表为空,则不进行反转;如果链表不为空,则进行反转。反转时,首先创建一个新的反转头节点,并依次遍历原来的链表,将原来链表的节点摘下,插入到到新的反转链表,直到遍历完成。最后将新的反转链表换成原来的头节点即可。
代码实现1:
将单链表反转
public void reverseList(HeroNode head){
if(head.next==null){
System.out.println("链表为空,反转失败");
}else{
HeroNode reverseHead=new HeroNode(0, "", "");
HeroNode temp = head.next;
while(temp!=null){
head.next=temp.next;
temp.next=reverseHead.next;
reverseHead.next=temp;
temp=head.next;
}
head.next=reverseHead.next;
}
}
代码实现2:
public void reverseList(HeroNode head){
if(head.next==null || head.next.next==null){
return;
}else{
//定义一个辅助指针,帮助遍历原来的链表
HeroNode cur=head.next;
HeroNode next=null; //指向当前节点[cur]的下一个节点
HeroNode reverseHead=new HeroNode(0, "", "");
//遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
while(cur!=null){
next=cur.next; //暂时保存当前节点的下一个节点
cur.next=reverseHead.next; //将cur的下一个节点指向新的链表的最前端
reverseHead.next=cur; //将cur连接到新的链表上
cur=next; //让cur后移
}
head.next=reverseHead.next;
}
}
4.从尾到头打印单链表(百度面试题,要求方式1:反向遍历;方式2:Stack栈)
思路:方式1:先将单链表进行反转操作,然后再遍历即可,这样做的问题是会破坏原来的单链表的结构,不建议使用。方式2:可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果。
代码实现:
//使用栈结构,逆序打印单链表
public void reversePrintList(HeroNode head){
if(head.next==null){
System.out.println("当前链表为空,无法打印");
}else{
Stack<HeroNode> stack=new Stack<HeroNode>();
HeroNode temp=head.next;
while(temp!=null){
stack.add(temp);
temp=temp.next;
}
while(stack.size()>0){
System.out.println(stack.pop());
}
}
}
效果图:
5.合并两个有序的单链表,合并之后的链表依然有序
思路:如果两个单链表均为空链表,则不进行合并;如果两个单链表中的一个链表为空,则直接返回非空的链表;如果两个单链表均为非空链表,则选择其中一个链表作为主链表,将另一个链表的节点依次取出,并有序插入主链表中。
代码实现:
//合并两个单链表
public HeroNode merge(HeroNode heroNode1,HeroNode heroNode2){
if(heroNode1.next==null&&heroNode2.next==null){
return null;
}else if(heroNode1.next==null&&heroNode2!=null){
return heroNode2;
}else if(heroNode1.next!=null&&heroNode2==null){
return heroNode1;
}else{
while(true){
HeroNode temp=heroNode2.next;
if(temp!=null){
heroNode2.next=temp.next;
addByOrder(temp, heroNode1);
}else{
return heroNode1;
}
}
}
}
效果图:
四、双向链表的应用实例
使用带头节点的双向链表实现水浒英雄排行榜的管理,对英雄人物进行增删改查的操作。
建立英雄人物的节点信息,代码实现如下:
package com.atguigu.linkedlist;
public class HeroNode {
public int no;
public String name;
public String nickname;
public HeroNode next;
public HeroNode(int no, String name, String nickname) {
super();
this.no = no;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
}
}
1.显示链表信息
遍历整个链表,打印链表中所有节点的信息
代码实现:
public void show(HeroNode head){
if(head.next==null){
System.out.println("链表为空");
return;
}else{
HeroNode temp=head.next;
while(temp!=null){
System.out.println(temp);
temp=temp.next;
}
}
}
2.增加操作
第一种方法:添加英雄时,直接添加到链表的尾部
代码实现:
public void add(HeroNode head,HeroNode heroNode){
HeroNode temp=head;
while(temp.next!=null){
temp=temp.next;
}
temp.next=heroNode;
heroNode.pre=temp;
}
测试代码:
package com.atguigu.doublelinkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
HeroNode heroNode1=new HeroNode(46, "宋江", "及时雨");
HeroNode heroNode2=new HeroNode(89, "武松", "行者");
HeroNode heroNode3=new HeroNode(60, "时迁", "鼓上蚤");
HeroNode heroNode4=new HeroNode(90, "孙二娘", "母夜叉");
HeroNode heroNode5=new HeroNode(33, "张顺", "浪里白条");
System.out.println("添加节点前的双链表信息:");
doubleLinkedList.show(doubleLinkedList.getHead());
System.out.println("********************************************");
System.out.println("添加节点后的双链表信息:");
doubleLinkedList.add(doubleLinkedList.getHead(), heroNode1);
doubleLinkedList.add(doubleLinkedList.getHead(), heroNode2);
doubleLinkedList.add(doubleLinkedList.getHead(), heroNode3);
doubleLinkedList.add(doubleLinkedList.getHead(), heroNode4);
doubleLinkedList.add(doubleLinkedList.getHead(), heroNode5);
doubleLinkedList.show(doubleLinkedList.getHead());
}
}
效果图:
第二种方法:在添加英雄时,根据排名将英雄插入到指定的位置(如果已经存在,则添加失败,并给出提示)
注意中间位置插入节点的时候,需要考虑好操作的顺序。如下图所示,需要首先操作红色的部分。即先将temp.next与新节点连接起来,再将temp与新节点连接。
public void addByOrder(HeroNode head,HeroNode heroNode){
HeroNode temp=head;
boolean flag=false;
while(true){
if(temp.next==null){
break;
}
if(temp.next.no==heroNode.no){
throw new RuntimeException("链表中已存在该编号的节点,添加失败");
}
if(temp.next.no>heroNode.no){
flag=true;
break;
}
temp=temp.next;
}
if(flag==true){
temp.next.pre=heroNode;
heroNode.next=temp.next;
temp.next=heroNode;
heroNode.pre=temp;
}else{
temp.next=heroNode;
heroNode.pre=temp;
}
}
测试代码:
package com.atguigu.doublelinkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
HeroNode heroNode1=new HeroNode(46, "宋江", "及时雨");
HeroNode heroNode2=new HeroNode(89, "武松", "行者");
HeroNode heroNode3=new HeroNode(60, "时迁", "鼓上蚤");
HeroNode heroNode4=new HeroNode(90, "孙二娘", "母夜叉");
HeroNode heroNode5=new HeroNode(33, "张顺", "浪里白条");
System.out.println("添加节点前的双链表信息:");
doubleLinkedList.show(doubleLinkedList.getHead());
System.out.println("********************************************");
System.out.println("添加节点后的双链表信息:");
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode1);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode2);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode3);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode4);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode5);
doubleLinkedList.show(doubleLinkedList.getHead());
}
}
效果图:
3.修改操作
遍历整个链表,根据已知的节点编号信息,找到需要修改的节点位置,并修改;如果未找到该节点,则返回修改失败的信息。
代码实现:
public void update(HeroNode head,HeroNode heroNode){
if(head.next==null){
System.out.println("链表为空,修改失败");
}else{
HeroNode temp=head.next;
boolean flag=false;
while(true){
if(temp==null){
break;
}
if(temp.no==heroNode.no){
flag=true;
break;
}
temp=temp.next;
}
if(flag==true){
temp.name=heroNode.name;
temp.nickname=heroNode.nickname;
System.out.println("编号为:"+heroNode.no+"的节点修改成功");
}else{
System.out.println("该节点不存在,修改失败");
}
}
}
测试代码:
package com.atguigu.doublelinkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
HeroNode heroNode1=new HeroNode(46, "宋江", "及时雨");
HeroNode heroNode2=new HeroNode(89, "武松", "行者");
HeroNode heroNode3=new HeroNode(60, "时迁", "鼓上蚤");
HeroNode heroNode4=new HeroNode(90, "孙二娘", "母夜叉");
HeroNode heroNode5=new HeroNode(33, "张顺", "浪里白条");
System.out.println("添加节点前的双链表信息:");
doubleLinkedList.show(doubleLinkedList.getHead());
System.out.println("********************************************");
System.out.println("添加节点后的双链表信息:");
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode1);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode2);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode3);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode4);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode5);
doubleLinkedList.show(doubleLinkedList.getHead());
System.out.println("********************************************");
System.out.println("修改操作:");
doubleLinkedList.update(doubleLinkedList.getHead(), new HeroNode(90, "aaa", "333"));
doubleLinkedList.show(doubleLinkedList.getHead());
}
}
效果图:
4.删除操作
根据节点编号删除相应节点。
代码实现:
注意删除节点的操作需要考虑是否在链表的末尾,中间节点的删除操作和末尾节点的删除操作不一样。
public void delete(HeroNode head,int no){
if(head.next==null){
System.out.println("链表为空,删除失败");
}else{
HeroNode temp=head.next;
boolean flag=false;
while(true){
if(temp==null){
break;
}
if(temp.no==no){
flag=true;
break;
}
temp=temp.next;
}
if(flag==true){
temp.pre.next=temp.next;
//如果是最后一个节点,就不需要执行下面这行代码。否则会有空指针异常。
if(temp.next!=null){
temp.next.pre=temp.pre;
}
System.out.println("编号为:"+no+"的节点删除成功");
}else{
System.out.println("该节点不存在,删除失败");
}
}
}
测试代码:
package com.atguigu.doublelinkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
HeroNode heroNode1=new HeroNode(46, "宋江", "及时雨");
HeroNode heroNode2=new HeroNode(89, "武松", "行者");
HeroNode heroNode3=new HeroNode(60, "时迁", "鼓上蚤");
HeroNode heroNode4=new HeroNode(90, "孙二娘", "母夜叉");
HeroNode heroNode5=new HeroNode(33, "张顺", "浪里白条");
System.out.println("添加节点前的双链表信息:");
doubleLinkedList.show(doubleLinkedList.getHead());
System.out.println("********************************************");
System.out.println("添加节点后的双链表信息:");
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode1);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode2);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode3);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode4);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode5);
doubleLinkedList.show(doubleLinkedList.getHead());
System.out.println("********************************************");
System.out.println("删除操作:");
doubleLinkedList.delete(doubleLinkedList.getHead(), 46);
doubleLinkedList.show(doubleLinkedList.getHead());
}
}
效果图:
5.查找操作
遍历整个链表,根据已知的节点编号信息,查找该编号对应节点的相关信息;如果未找到该节点,则返回查找失败的信息。
代码实现:
public void getNode(HeroNode head,int no){
if(head.next==null){
System.out.println("链表为空,该节点不存在");
}else{
HeroNode temp=head.next;
boolean flag=false;
while(true){
if(temp==null){
break;
}
if(temp.no==no){
flag=true;
break;
}
temp=temp.next;
}
if(flag==true){
System.out.println(temp);
}else{
System.out.println("该节点不存在,查找失败");
}
}
}
测试代码:
package com.atguigu.doublelinkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
HeroNode heroNode1=new HeroNode(46, "宋江", "及时雨");
HeroNode heroNode2=new HeroNode(89, "武松", "行者");
HeroNode heroNode3=new HeroNode(60, "时迁", "鼓上蚤");
HeroNode heroNode4=new HeroNode(90, "孙二娘", "母夜叉");
HeroNode heroNode5=new HeroNode(33, "张顺", "浪里白条");
System.out.println("添加节点前的双链表信息:");
doubleLinkedList.show(doubleLinkedList.getHead());
System.out.println("********************************************");
System.out.println("添加节点后的双链表信息:");
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode1);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode2);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode3);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode4);
doubleLinkedList.addByOrder(doubleLinkedList.getHead(), heroNode5);
doubleLinkedList.show(doubleLinkedList.getHead());
System.out.println("********************************************");
System.out.println("查找操作:");
doubleLinkedList.getNode(doubleLinkedList.getHead(), 90);
}
}
效果图:
五、约瑟夫问题(Josephu)
Josephu 问题:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
提示:用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
示意图:
代码实现:
创建节点:
package com.atguigu.circlesinglelinkedlist;
public class Boy {
private int no;
private Boy next;
public Boy(int no) {
super();
this.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;
}
@Override
public String toString() {
return "Boy [no=" + no + "]";
}
}
约瑟夫问题求解:
package com.atguigu.circlesinglelinkedlist;
public class CircleSingleLinkedList {
//创建一个first节点,当前没有编号
private Boy first=null;
//添加节点,构建成环形链表
public void add(int nums){
if(nums<1){
System.out.println("nums的值不正确");
}else{
Boy temp=null;
for(int i=1;i<=nums;i++){
Boy boy = new Boy(i);
if(i==1){
first=boy;
first.setNext(first);
temp=first;
}else{
temp.setNext(boy);
boy.setNext(first);
temp=boy;
}
}
}
}
//显示链表内容
public void show(){
if(first==null){
System.out.println("链表为空");
}else{
Boy temp=first;
while(true){
if(temp.getNext()==first){
System.out.println(temp);
break;
}
System.out.println(temp);
temp=temp.getNext();
}
}
}
删除指定编号的节点
public int delete(int no){
if(first==null){
System.out.println("链表为空,删除失败");
return -1;
}else{
Boy temp=first;
boolean flag=false;
Boy first_pre=null;
//first_pre节点的初始位置(first节点的初始位置在1号节点)
while(true){
if(temp.getNext()==first){
first_pre=temp;
temp=temp.getNext();
break;
}
temp=temp.getNext();
}
//删除first指向的节点
if(temp.getNo()==no){
int b=temp.getNo();
first_pre.setNext(temp.getNext());
first=temp.getNext();
return b;
}
//删除非first指向的节点
while(true){
if(temp.getNext()==first){
break;
}
if(temp.getNext().getNo()==no){
flag=true;
break;
}
temp=temp.getNext();
}
if(flag==true){
int a=temp.getNext().getNo();
temp.setNext(temp.getNext().getNext());
return a;
}
else{
System.out.println("该节点不存在,删除失败");
return -1;
}
}
}
//获取当前链表实际长度
public int getLength(){
if(first==null){
return 0;
}else{
int count=1;
Boy temp=first;
while(temp.getNext()!=first){
count++;
temp=temp.getNext();
}
return count;
}
}
//约瑟夫问题求解
public void getJosephu(int startNo,int countNum,int nums){
if(startNo>nums || startNo<1){
System.out.println("输入的参数有误");
}else{
if(first==null){
System.out.println("链表为空");
}else{
Boy temp=first;
Boy first_pre=null;
//first_pre节点的初始位置(first节点的初始位置在1号节点)
while(true){
if(temp.getNext()==first){
first_pre=temp;
temp=temp.getNext();
break;
}
temp=temp.getNext();
}
//将first指针和first_pre指针指向给定位置(startNo和startNo的前一个位置)
while(true){
if(temp.getNo()==startNo){
break;
}else{
first_pre=temp;
temp=temp.getNext();
}
}
//根据实际队列长度,进行约瑟夫循环,直到队列中只有一个节点
while(this.getLength()>=2){
System.out.println("当前队列长度:"+getLength());
int count=1;
for(int i=1;i<=countNum;i++){
if(i==countNum){
System.out.println(delete(temp.getNo()));
temp=first_pre.getNext();
}else{
first_pre=temp;
temp=temp.getNext();
}
}
}
//将队列中最后一个元素去掉
System.out.println("当前队列长度:"+getLength());
System.out.println(delete(temp.getNo()));
}
}
}
}
测试代码:(总共5个节点,从第1节点开始,每2个节点产生一个出队节点)
package com.atguigu.circlesinglelinkedlist;
public class CircleSingleLinkedListDemo {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList=new CircleSingleLinkedList();
circleSingleLinkedList.add(5);
circleSingleLinkedList.show();
System.out.println("----------------------");
circleSingleLinkedList.getJosephu(1,2,5);
}
}
效果图:
测试代码:(总共5个节点,从第2节点开始,每3个节点产生一个出队节点)
package com.atguigu.circlesinglelinkedlist;
public class CircleSingleLinkedListDemo {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList=new CircleSingleLinkedList();
circleSingleLinkedList.add(5);
circleSingleLinkedList.show();
System.out.println("----------------------");
circleSingleLinkedList.getJosephu(2,3,5);
}
}
效果图:
分析:使用环形链表解决约瑟夫问题
- 建立环形链表。首先创建一个first节点,也是后续操作的入口。如果只添加一个节点,则在first节点上修改;如果是多个节点,第一个节点在first节点上修改,其他节点在first节点后添加,并使最后一个节点的next域指向first节点。
- 显示环形链表的节点信息。如果first节点为空,则说明该环形链表为空;否则从first节点开始,依次遍历环形链表,直到某节点的next域为first节点时结束遍历。
- 删除节点。如果first节点为空,则说明该环形链表为空,无法删除指定节点。否则,判断是否删除的节点为第一个节点。如果删除的是第一个节点,则需该节点的前一个节点的指针,遍历得到第一个节点的前一个节点指针,进而删除第一个节点。如果删除的节点不是第一个节点,则依次遍历找到要删除节点的前一个位置,进而删除该指定节点。
- 求解约瑟夫问题。首先需要找到开始的位置。然后根据实际链表长度进行循环删除节点的操作。如果链表的长度大于等于2,则根据指定的计数,找到要删除节点的位置,并删除。最后如果链表的长度为1,则将最后一个节点删除。进而得到约瑟夫问题的求解。