1. 链表
链表( linked list) 是一种在物理上非连续、 非顺序的数据结构, 以节点的方式来存储,是链式存储。
链表是有序的列表,但是它在内存中是存储如下:
由图可知:
-
每个节点包含 data 域, next 域:指向下一个节点.
-
发现链表的 各个节点不一定是连续存储.
-
链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定.
1.1 单链表
1.1.1 介绍
单向链表的每一个节点又包含两部分, 一部分是存放数据的变量data, 另一部分是指向下一个节点的指针next。
单链表(带头结点) 逻辑结构 示意图如下:
private static class Node {
int data;
Node next;
}
链表的第1个节点被称为头节点, 最后1个节点被称为尾节点, 尾节点的next指针指向空。
1.1.2 应用实例
使用带head头的单向链表实现 –水浒英雄排行榜管理(增删改查)
-
添加英雄(增加)
方式:
-
直接添加到链表的 尾部
代码实现:
public class SingleLinkedListDemo { public static void main(String[] args) { // 进行测试 // 先创建节点 HeroNode hero1=new HeroNode(1,"宋江","及时雨"); HeroNode hero2=new HeroNode(2,"卢俊义","玉麒麟"); HeroNode hero3=new HeroNode(3,"吴用","智多星"); HeroNode hero4=new HeroNode(4,"林冲","豹子头"); // 创建一个链表 SingleLinkedList singleLinkedList=new SingleLinkedList(); // 添加英雄 singleLinkedList.add(hero1); singleLinkedList.add(hero2); singleLinkedList.add(hero3); singleLinkedList.add(hero4); // 显示链表 singleLinkedList.list(); } } // 定义 SingleLinkedList 管理英雄 class SingleLinkedList{ // 先初始化一个头结点,头结点,不存放具体的数据 private HeroNode head=new HeroNode(0,"",""); // 添加节点到单向链表 // 1. 不考虑编号顺序的情况,添加到链表的 尾部 // 思路: // a. 通过遍历找到当前链表的最后节点 // b. 将最后这个节点的 next 指向 新节点 public void add(HeroNode heroNode){ // 因为 head 节点不能动,因此需要一个辅助变量 temp HeroNode temp=head; // 遍历链表,找到链表最后节点 while(true){ // 找到链表最后节点 if(temp.next == null){ break; } // 如果没有找到最后节点,就将 temp 后移 temp=temp.next; } // 当退出 while 循环时,temp就指向了链表的最后节点 // 将最后这个节点的 next 指向 新的节点 temp.next=heroNode; } // 显示链表【遍历】 public void list(){ // 判断链表是否为空 if(head.next == null){ System.out.println("链表为空"); return; } // 因为头节点不能动,因此需要一个辅助变量来遍历 HeroNode temp = head.next; while (true){ // 判断是否到链表最后 if(temp == null){ break; } // 输出节点信息 System.out.println(temp); // 将 temp 后移 temp=temp.next; } } } // 定义HeroNode,每个 HeroNode 对象就是一个节点 class HeroNode{ public int no; // 编号 public String name; // 名字 public String nickname; // 昵称 public HeroNode next; // 指向下一个节点 // 构造器 public HeroNode(int no,String name,String nickname){ this.no=no; this.name=name; this.nickname=nickname; } // 为了显示方法,重写 toString @Override public String toString(){ return "HeroNode [ no = "+no+" , name = "+name+" , nickname = "+nickname+" ]"; } }
输出结果:
HeroNode [ no = 1 , name = 宋江 , nickname = 及时雨 ] HeroNode [ no = 2 , name = 卢俊义 , nickname = 玉麒麟 ] HeroNode [ no = 3 , name = 吴用 , nickname = 智多星 ] HeroNode [ no = 4 , name = 林冲 , nickname = 豹子头 ]
-
根据编号 将英雄插入到 指定位置 (如果已经存在这个编号,则添加失败,并给出提示)
代码实现:
public class SingleLinkedListDemo { public static void main(String[] args) { // 进行测试 // 先创建节点 HeroNode hero1=new HeroNode(1,"宋江","及时雨"); HeroNode hero2=new HeroNode(2,"卢俊义","玉麒麟"); HeroNode hero3=new HeroNode(3,"吴用","智多星"); HeroNode hero4=new HeroNode(4,"林冲","豹子头"); // 创建一个链表 SingleLinkedList singleLinkedList=new SingleLinkedList(); // 按编号的顺序添加英雄 singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero2); singleLinkedList.addByOrder(hero3); singleLinkedList.addByOrder(hero4); singleLinkedList.addByOrder(hero3); // 这里会提示错误信息 // 显示链表 singleLinkedList.list(); } } // 定义 SingleLinkedList 管理英雄 class SingleLinkedList{ // 先初始化一个头结点,头结点,不存放具体的数据 private HeroNode head=new HeroNode(0,"",""); // 添加节点到单向链表 // 2.考虑编号顺序的情况,根据编号 将英雄插入到 指定位置 (如果有这个编号,则添加失败,并给出提示) // 思路: // a. 通过辅助变量temp遍历找到新添加的节点的要添加的位置 // b. 新节点.next = temp.next; // temp.next = 新节点 public void addByOrder(HeroNode heroNode){ // 因为头节点不能动,通过一个辅助变量来帮组找到添加的位置 // 因为单链表,因为找的 temp 是位于 添加位置的前一个节点,否则插入不了 HeroNode temp=head; boolean flag= false; // flag 标识添加的编号是否存在,默认为false while (true){ if(temp.next==null){ // 说明 temp 已经在链表的最后 break; } if(temp.next.no> heroNode.no){ // 位置找到,就在 temp 的后面插入 break; }else if(temp.next.no == heroNode.no){ // 说明希望添加的 heroNode 的编号已经存在了 flag= true; // 说明编号存在 break; } temp=temp.next; // 后移,遍历当前链表 } // 判断 flag 的值 if(flag){ // 不能添加,说明编号存在 System.out.printf("准备插入的英雄的编号 %d 已经存在了,不能加入\n",heroNode.no); }else{ // 插入到链表中,temp的后面 heroNode.next=temp.next; temp.next=heroNode; } } // 显示链表【遍历】 public void list(){ // 判断链表是否为空 if(head.next == null){ System.out.println("链表为空"); return; } // 因为头节点不能动,因此需要一个辅助变量来遍历 HeroNode temp = head.next; while (true){ // 判断是否到链表最后 if(temp == null){ break; } // 输出节点信息 System.out.println(temp); // 将 temp 后移 temp=temp.next; } } } // 定义HeroNode,每个 HeroNode 对象就是一个节点 class HeroNode{ public int no; // 编号 public String name; // 名字 public String nickname; // 昵称 public HeroNode next; // 指向下一个节点 // 构造器 public HeroNode(int no,String name,String nickname){ this.no=no; this.name=name; this.nickname=nickname; } // 为了显示方法,重写 toString @Override public String toString(){ return "HeroNode [ no = "+no+" , name = "+name+" , nickname = "+nickname+" ]"; } }
输出结果:
准备插入的英雄的编号 3 已经存在了,不能加入 HeroNode [ no = 1 , name = 宋江 , nickname = 及时雨 ] HeroNode [ no = 2 , name = 卢俊义 , nickname = 玉麒麟 ] HeroNode [ no = 3 , name = 吴用 , nickname = 智多星 ] HeroNode [ no = 4 , name = 林冲 , nickname = 豹子头 ]
-
-
删除英雄(删除)
代码实现:
public class SingleLinkedListDemo { public static void main(String[] args) { // 进行测试 // 先创建节点 HeroNode hero1=new HeroNode(1,"宋江","及时雨"); HeroNode hero2=new HeroNode(2,"卢俊义","玉麒麟"); HeroNode hero3=new HeroNode(3,"吴用","智多星"); HeroNode hero4=new HeroNode(4,"林冲","豹子头"); // 创建一个链表 SingleLinkedList singleLinkedList=new SingleLinkedList(); // 按编号的顺序添加英雄 singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero2); singleLinkedList.addByOrder(hero3); singleLinkedList.addByOrder(hero4); // 显示链表 singleLinkedList.list(); // 删除一个节点 singleLinkedList.del(1); System.out.println("删除后的链表情况:"); singleLinkedList.list(); } } // 定义 SingleLinkedList 管理英雄 class SingleLinkedList{ // 先初始化一个头结点,头结点,不存放具体的数据 private HeroNode head=new HeroNode(0,"",""); // 添加节点到单向链表 // 2. 考虑编号顺序的情况,根据排名 将英雄插入到 指定位置 (如果有这个排名,则添加失败,并给出提示) // 思路: // a. 通过辅助变量temp遍历找到新添加的节点的要添加的位置 // b. 新节点.next = temp.next; // temp.next = 新节点 public void addByOrder(HeroNode heroNode){ // 因为头节点不能动,通过一个辅助变量来帮组找到添加的位置 // 因为单链表,因为找的 temp 是位于 添加位置的前一个节点,否则插入不了 HeroNode temp=head; boolean flag= false; // flag 标识添加的编号是否存在,默认为false while (true){ if(temp.next==null){ // 说明 temp 已经在链表的最后 break; } if(temp.next.no> heroNode.no){ // 位置找到,就在 temp 的后面插入 break; }else if(temp.next.no == heroNode.no){ // 说明希望添加的 heroNode 的编号已经存在了 flag= true; // 说明编号存在 break; } temp=temp.next; // 后移,遍历当前链表 } // 判断 flag 的值 if(flag){ // 不能添加,说明编号存在 System.out.printf("准备插入的英雄的编号 %d 已经存在了,不能加入\n",heroNode.no); }else{ // 插入到链表中,temp的后面 heroNode.next=temp.next; temp.next=heroNode; } } // 删除节点 // 思路 // 1. head 不能动,需要一个temp辅助节点找到待删除节点的前一个节点 // 2. 说明在比较时,是temp.next.no 和 需要删除的节点.no 比较 public void del(int no){ HeroNode temp =head; boolean flag = false; // 标识是否 找到待删除节点 while (true){ if(temp.next==null){ break; } if(temp.next.no==no){ // 找到的待删除节点的前一个节点 temp flag=true; break; } temp=temp.next; // temp后移,遍历当前链表 } // 判断 flag 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 (true){ // 判断是否到链表最后 if(temp == null){ break; } // 输出节点信息 System.out.println(temp); // 将 temp 后移 temp=temp.next; } } } // 定义HeroNode,每个 HeroNode 对象就是一个节点 class HeroNode{ public int no; // 编号 public String name; // 名字 public String nickname; // 昵称 public HeroNode next; // 指向下一个节点 // 构造器 public HeroNode(int no,String name,String nickname){ this.no=no; this.name=name; this.nickname=nickname; } // 为了显示方法,重写 toString @Override public String toString(){ return "HeroNode [ no = "+no+" , name = "+name+" , nickname = "+nickname+" ]"; } }
-
修改英雄信息(修改)
思路:
-
通过遍历,先找到该节点.
-
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
代码实现:
public class SingleLinkedListDemo { public static void main(String[] args) { // 进行测试 // 先创建节点 HeroNode hero1=new HeroNode(1,"宋江","及时雨"); HeroNode hero2=new HeroNode(2,"卢俊义","玉麒麟"); HeroNode hero3=new HeroNode(3,"吴用","智多星"); HeroNode hero4=new HeroNode(4,"林冲","豹子头"); // 创建一个链表 SingleLinkedList singleLinkedList=new SingleLinkedList(); // 按编号的顺序添加英雄 singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero2); singleLinkedList.addByOrder(hero3); singleLinkedList.addByOrder(hero4); // 显示链表 singleLinkedList.list(); System.out.println(); // 测试修改节点的代码 HeroNode newHeroNode = new HeroNode(2,"小卢","玉麒麟~"); singleLinkedList.update(newHeroNode); System.out.println("修改后的链表情况:"); // 显示链表 singleLinkedList.list(); } } // 定义 SingleLinkedList 管理英雄 class SingleLinkedList{ // 先初始化一个头结点,头结点,不存放具体的数据 private HeroNode head=new HeroNode(0,"",""); // 添加节点到单向链表 // 2. 考虑编号顺序的情况,根据排名 将英雄插入到 指定位置 (如果有这个排名,则添加失败,并给出提示) // 思路: // a. 通过辅助变量temp遍历找到新添加的节点的要添加的位置 // b. 新节点.next = temp.next; // temp.next = 新节点 public void addByOrder(HeroNode heroNode){ // 因为头节点不能动,通过一个辅助变量来帮组找到添加的位置 // 因为单链表,因为找的 temp 是位于 添加位置的前一个节点,否则插入不了 HeroNode temp=head; boolean flag= false; // flag 标识添加的编号是否存在,默认为false while (true){ if(temp.next==null){ // 说明 temp 已经在链表的最后 break; } if(temp.next.no> heroNode.no){ // 位置找到,就在 temp 的后面插入 break; }else if(temp.next.no == heroNode.no){ // 说明希望添加的 heroNode 的编号已经存在了 flag= true; // 说明编号存在 break; } temp=temp.next; // 后移,遍历当前链表 } // 判断 flag 的值 if(flag){ // 不能添加,说明编号存在 System.out.printf("准备插入的英雄的编号 %d 已经存在了,不能加入\n",heroNode.no); }else{ // 插入到链表中,temp的后面 heroNode.next=temp.next; temp.next=heroNode; } } // 修改节点的信息,根据 no编号 来修改,即 no编号 不能改 // 说明 // 1. 根据 newHeroNode 的 no 来修改即可 public void update(HeroNode newHeroNode){ // 判断是否空 if(head.next == null){ System.out.println("链表为空~"); return; } // 找到需要修改的节点,根据 no 编号 // 定义一个辅助变量 HeroNode temp = head.next; boolean flag=false; // 表示是否找到该节点 while(true){ if (temp==null){ break; // 到链表的已经遍历完链表 } if(temp.no==newHeroNode.no){ // 找到 flag=true; break; } temp=temp.next; } // 根据 flag 判断是否找到要修改的节点 if(flag){ temp.name=newHeroNode.name; temp.nickname=newHeroNode.nickname; }else{ // 没有找到 System.out.printf("没有找到 编号 %d 的节点,不能修改\n",newHeroNode.no); } } // 显示链表【遍历】 public void list(){ // 判断链表是否为空 if(head.next == null){ System.out.println("链表为空"); return; } // 因为头节点不能动,因此需要一个辅助变量来遍历 HeroNode temp = head.next; while (true){ // 判断是否到链表最后 if(temp == null){ break; } // 输出节点信息 System.out.println(temp); // 将 temp 后移 temp=temp.next; } } } // 定义HeroNode,每个 HeroNode 对象就是一个节点 class HeroNode{ public int no; // 编号 public String name; // 名字 public String nickname; // 昵称 public HeroNode next; // 指向下一个节点 // 构造器 public HeroNode(int no,String name,String nickname){ this.no=no; this.name=name; this.nickname=nickname; } // 为了显示方法,重写 toString @Override public String toString(){ return "HeroNode [ no = "+no+" , name = "+name+" , nickname = "+nickname+" ]"; } }
输出结果:
HeroNode [ no = 1 , name = 宋江 , nickname = 及时雨 ] HeroNode [ no = 2 , name = 卢俊义 , nickname = 玉麒麟 ] HeroNode [ no = 3 , name = 吴用 , nickname = 智多星 ] HeroNode [ no = 4 , name = 林冲 , nickname = 豹子头 ] 修改后的链表情况: HeroNode [ no = 1 , name = 宋江 , nickname = 及时雨 ] HeroNode [ no = 2 , name = 小卢 , nickname = 玉麒麟~ ] HeroNode [ no = 3 , name = 吴用 , nickname = 智多星 ] HeroNode [ no = 4 , name = 林冲 , nickname = 豹子头 ]
-
-
查看某个英雄(查找)
借助辅助变量 temp 遍历链表查找即可.
1.1.3 面试题
-
求单链表中有效节点的个数
public class SingleLinkedListDemo { public static void main(String[] args) { // 先创建节点 HeroNode hero1=new HeroNode(1,"宋江","及时雨"); HeroNode hero2=new HeroNode(2,"卢俊义","玉麒麟"); HeroNode hero3=new HeroNode(3,"吴用","智多星"); HeroNode hero4=new HeroNode(4,"林冲","豹子头"); // 创建一个链表 SingleLinkedList singleLinkedList=new SingleLinkedList(); // 按编号的顺序添加英雄 singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero2); singleLinkedList.addByOrder(hero3); singleLinkedList.addByOrder(hero4); // 测试一下 求单链表中有效节点的个数 System.out.println("有效的节点个数="+getLength(singleLinkedList.getHead())); } // 方法:获取到单链表的节点的个数(如果是带头节点的链表,需求不统计头节点) /** * @param head 链表的头节点 * @return 返回的就是有效节点的个数 */ public static int getLength(HeroNode head){ if(head.next==null){ // 空链表 return 0; } int length=0; //定义一个辅助的变量 HeroNode cur = head.next; while (cur!=null){ length++; cur=cur.next; } return length; } } // 定义 SingleLinkedList 管理英雄 class SingleLinkedList{ // 先初始化一个头结点,头结点,不存放具体的数据 private HeroNode head=new HeroNode(0,"",""); // 返回头节点 public HeroNode getHead(){ return head; } // 添加节点到单向链表 // 2. 考虑编号顺序的情况,根据排名 将英雄插入到 指定位置 (如果有这个排名,则添加失败,并给出提示) // 思路: // a. 通过辅助变量temp遍历找到新添加的节点的要添加的位置 // b. 新节点.next = temp.next; // temp.next = 新节点 public void addByOrder(HeroNode heroNode){ // 因为头节点不能动,通过一个辅助变量来帮组找到添加的位置 // 因为单链表,因为找的 temp 是位于 添加位置的前一个节点,否则插入不了 HeroNode temp=head; boolean flag= false; // flag 标识添加的编号是否存在,默认为false while (true){ if(temp.next==null){ // 说明 temp 已经在链表的最后 break; } if(temp.next.no> heroNode.no){ // 位置找到,就在 temp 的后面插入 break; }else if(temp.next.no == heroNode.no){ // 说明希望添加的 heroNode 的编号已经存在了 flag= true; // 说明编号存在 break; } temp=temp.next; // 后移,遍历当前链表 } // 判断 flag 的值 if(flag){ // 不能添加,说明编号存在 System.out.printf("准备插入的英雄的编号 %d 已经存在了,不能加入\n",heroNode.no); }else{ // 插入到链表中,temp的后面 heroNode.next=temp.next; temp.next=heroNode; } } } // 定义HeroNode,每个 HeroNode 对象就是一个节点 class HeroNode{ public int no; // 编号 public String name; // 名字 public String nickname; // 昵称 public HeroNode next; // 指向下一个节点 // 构造器 public HeroNode(int no,String name,String nickname){ this.no=no; this.name=name; this.nickname=nickname; } }
-
查找单链表中的倒数第k个结点 【新浪面试题】
public class SingleLinkedListDemo { public static void main(String[] args) { // 先创建节点 HeroNode hero1=new HeroNode(1,"宋江","及时雨"); HeroNode hero2=new HeroNode(2,"卢俊义","玉麒麟"); HeroNode hero3=new HeroNode(3,"吴用","智多星"); HeroNode hero4=new HeroNode(4,"林冲","豹子头"); // 创建一个链表 SingleLinkedList singleLinkedList=new SingleLinkedList(); // 按编号的顺序添加英雄 singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero2); singleLinkedList.addByOrder(hero3); singleLinkedList.addByOrder(hero4); // 显示链表 singleLinkedList.list(); // 测试一下 查找单链表中的倒数第k个结点 HeroNode res=findLastIndexNode(singleLinkedList.getHead(),1); System.out.println(); System.out.println("res="+res); } // 查找单链表中的倒数第k个结点【新浪面试题】 // 思路: // 1. 编写一个方法:接收 head 节点,同时接收一个 index // 2. index 表示是倒数第 index 个节点 // 3. 先把链表从头到尾遍历,得到链表的总的长度 // 4. 得到 size 后,从链表的第一个开始遍历(size-index)个 // 5. 如果找到了,则返回该节点,否则返回 null public static HeroNode findLastIndexNode(HeroNode head,int index){ // 判断如果链表为空,则返回null if(head.next==null){ return null; // 没有找到 } // 第一个遍历得到链表的长度(节点个数) int size =getLength(head); // 第二次遍历 size-index 位置,就是倒数的第 K 个节点 // 先做一个index的校验 if(index<=0|| index>size){ return null; } // 定义一个辅助变量,for循环定位到倒数的index HeroNode cur = head.next; for(int i=0;i<size-index; i++){ cur=cur.next; } return cur; } // 方法:获取到单链表的节点的个数(如果是带头节点的链表,需求不统计头节点) /** * @param head 链表的头节点 * @return 返回的就是有效节点的个数 */ public static int getLength(HeroNode head){ if(head.next==null){ // 空链表 return 0; } int length=0; //定义一个辅助的变量 HeroNode cur = head.next; while (cur!=null){ length++; cur=cur.next; } return length; } } // 定义 SingleLinkedList 管理英雄 class SingleLinkedList{ // 先初始化一个头结点,头结点,不存放具体的数据 private HeroNode head=new HeroNode(0,"",""); // 返回头节点 public HeroNode getHead(){ return head; } // 添加节点到单向链表 // 2. 考虑编号顺序的情况,根据排名 将英雄插入到 指定位置 (如果有这个排名,则添加失败,并给出提示) // 思路: // a. 通过辅助变量temp遍历找到新添加的节点的要添加的位置 // b. 新节点.next = temp.next; // temp.next = 新节点 public void addByOrder(HeroNode heroNode){ // 因为头节点不能动,通过一个辅助变量来帮组找到添加的位置 // 因为单链表,因为找的 temp 是位于 添加位置的前一个节点,否则插入不了 HeroNode temp=head; boolean flag= false; // flag 标识添加的编号是否存在,默认为false while (true){ if(temp.next==null){ // 说明 temp 已经在链表的最后 break; } if(temp.next.no> heroNode.no){ // 位置找到,就在 temp 的后面插入 break; }else if(temp.next.no == heroNode.no){ // 说明希望添加的 heroNode 的编号已经存在了 flag= true; // 说明编号存在 break; } temp=temp.next; // 后移,遍历当前链表 } // 判断 flag 的值 if(flag){ // 不能添加,说明编号存在 System.out.printf("准备插入的英雄的编号 %d 已经存在了,不能加入\n",heroNode.no); }else{ // 插入到链表中,temp的后面 heroNode.next=temp.next; temp.next=heroNode; } } // 显示链表【遍历】 public void list(){ // 判断链表是否为空 if(head.next == null){ System.out.println("链表为空"); return; } // 因为头节点不能动,因此需要一个辅助变量来遍历 HeroNode temp = head.next; while (true){ // 判断是否到链表最后 if(temp == null){ break; } // 输出节点信息 System.out.println(temp); // 将 temp 后移 temp=temp.next; } } } // 定义HeroNode,每个 HeroNode 对象就是一个节点 class HeroNode{ public int no; // 编号 public String name; // 名字 public String nickname; // 昵称 public HeroNode next; // 指向下一个节点 // 构造器 public HeroNode(int no,String name,String nickname){ this.no=no; this.name=name; this.nickname=nickname; } // 为了显示方法,重写 toString @Override public String toString(){ return "HeroNode [ no = "+no+" , name = "+name+" , nickname = "+nickname+" ]"; } }
-
单链表的反转【腾讯面试题,有点难度】
图解:
具体代码:
public class SingleLinkedListDemo { public static void main(String[] args) { // 先创建节点 HeroNode hero1=new HeroNode(1,"宋江","及时雨"); HeroNode hero2=new HeroNode(2,"卢俊义","玉麒麟"); HeroNode hero3=new HeroNode(3,"吴用","智多星"); HeroNode hero4=new HeroNode(4,"林冲","豹子头"); // 创建一个链表 SingleLinkedList singleLinkedList=new SingleLinkedList(); // 按编号的顺序添加英雄 singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero2); singleLinkedList.addByOrder(hero3); singleLinkedList.addByOrder(hero4); // 测试一下 单链表的反转 System.out.println("原来链表的情况~~~"); singleLinkedList.list(); System.out.println(); System.out.println("反转单链表~~~"); reversetList(singleLinkedList.getHead()); singleLinkedList.list(); } // 将单链表进行反转 public static void reversetList(HeroNode head){ // 如果当前链表为空,或者只有一个节点,无需反转,直接返回 if(head.next==null || head.next.next==null){ return; } // 定义一个辅助变量 帮助遍历原来的链表 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 ,实现单链表的反转 head.next=reverseHead.next; } // 定义 SingleLinkedList 管理英雄 class SingleLinkedList{ // 先初始化一个头结点,头结点,不存放具体的数据 private HeroNode head=new HeroNode(0,"",""); // 返回头节点 public HeroNode getHead(){ return head; } // 添加节点到单向链表 // 2. 考虑编号顺序的情况,根据排名 将英雄插入到 指定位置 (如果有这个排名,则添加失败,并给出提示) // 思路: // a. 通过辅助变量temp遍历找到新添加的节点的要添加的位置 // b. 新节点.next = temp.next; // temp.next = 新节点 public void addByOrder(HeroNode heroNode){ // 因为头节点不能动,通过一个辅助变量来帮组找到添加的位置 // 因为单链表,因为找的 temp 是位于 添加位置的前一个节点,否则插入不了 HeroNode temp=head; boolean flag= false; // flag 标识添加的编号是否存在,默认为false while (true){ if(temp.next==null){ // 说明 temp 已经在链表的最后 break; } if(temp.next.no> heroNode.no){ // 位置找到,就在 temp 的后面插入 break; }else if(temp.next.no == heroNode.no){ // 说明希望添加的 heroNode 的编号已经存在了 flag= true; // 说明编号存在 break; } temp=temp.next; // 后移,遍历当前链表 } // 判断 flag 的值 if(flag){ // 不能添加,说明编号存在 System.out.printf("准备插入的英雄的编号 %d 已经存在了,不能加入\n",heroNode.no); }else{ // 插入到链表中,temp的后面 heroNode.next=temp.next; temp.next=heroNode; } } // 显示链表【遍历】 public void list(){ // 判断链表是否为空 if(head.next == null){ System.out.println("链表为空"); return; } // 因为头节点不能动,因此需要一个辅助变量来遍历 HeroNode temp = head.next; while (true){ // 判断是否到链表最后 if(temp == null){ break; } // 输出节点信息 System.out.println(temp); // 将 temp 后移 temp=temp.next; } } } // 定义HeroNode,每个 HeroNode 对象就是一个节点 class HeroNode{ public int no; // 编号 public String name; // 名字 public String nickname; // 昵称 public HeroNode next; // 指向下一个节点 // 构造器 public HeroNode(int no,String name,String nickname){ this.no=no; this.name=name; this.nickname=nickname; } // 为了显示方法,重写 toString @Override public String toString(){ return "HeroNode [ no = "+no+" , name = "+name+" , nickname = "+nickname+" ]"; } }
-
从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
思路【逆序打印单链表】: 方式1: 先将单链表进行反转操作,然后再遍历即可,这样的做的问题是会破坏原来的单链表的结构,不建议. 方式2:可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果.
具体代码:
import java.util.Stack; public class SingleLinkedListDemo { public static void main(String[] args) { // 先创建节点 HeroNode hero1=new HeroNode(1,"宋江","及时雨"); HeroNode hero2=new HeroNode(2,"卢俊义","玉麒麟"); HeroNode hero3=new HeroNode(3,"吴用","智多星"); HeroNode hero4=new HeroNode(4,"林冲","豹子头"); // 创建一个链表 SingleLinkedList singleLinkedList=new SingleLinkedList(); // 按编号的顺序添加英雄 singleLinkedList.addByOrder(hero1); singleLinkedList.addByOrder(hero2); singleLinkedList.addByOrder(hero3); singleLinkedList.addByOrder(hero4); System.out.println("原来链表的情况~~~"); singleLinkedList.list(); // 测试 逆序打印单链表 System.out.println(); System.out.println("逆序打印单链表,没有改变链表本身的结构~~~"); reversePrint(singleLinkedList.getHead()); } //方式2: // 可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果. public static void reversePrint(HeroNode head){ if(head.next==null){ return; // 空链表,不能打印 } // 创建一个栈,将各个节点压入栈 Stack<HeroNode> stack=new Stack<HeroNode>(); HeroNode cur=head.next; // 将链表的所有节点压入栈 while (cur!=null){ stack.push(cur); cur=cur.next; // cur后移,这样就可以压入下一个节点 } // 将栈中的节点进行打印,pop出栈 while (stack.size()>0){ System.out.println(stack.pop()); // stack的特点:先进后出 } } // 定义 SingleLinkedList 管理英雄 class SingleLinkedList{ // 先初始化一个头结点,头结点,不存放具体的数据 private HeroNode head=new HeroNode(0,"",""); // 返回头节点 public HeroNode getHead(){ return head; } // 添加节点到单向链表 // 2. 考虑编号顺序的情况,根据排名 将英雄插入到 指定位置 (如果有这个排名,则添加失败,并给出提示) // 思路: // a. 通过辅助变量temp遍历找到新添加的节点的要添加的位置 // b. 新节点.next = temp.next; // temp.next = 新节点 public void addByOrder(HeroNode heroNode){ // 因为头节点不能动,通过一个辅助变量来帮组找到添加的位置 // 因为单链表,因为找的 temp 是位于 添加位置的前一个节点,否则插入不了 HeroNode temp=head; boolean flag= false; // flag 标识添加的编号是否存在,默认为false while (true){ if(temp.next==null){ // 说明 temp 已经在链表的最后 break; } if(temp.next.no> heroNode.no){ // 位置找到,就在 temp 的后面插入 break; }else if(temp.next.no == heroNode.no){ // 说明希望添加的 heroNode 的编号已经存在了 flag= true; // 说明编号存在 break; } temp=temp.next; // 后移,遍历当前链表 } // 判断 flag 的值 if(flag){ // 不能添加,说明编号存在 System.out.printf("准备插入的英雄的编号 %d 已经存在了,不能加入\n",heroNode.no); }else{ // 插入到链表中,temp的后面 heroNode.next=temp.next; temp.next=heroNode; } } // 显示链表【遍历】 public void list(){ // 判断链表是否为空 if(head.next == null){ System.out.println("链表为空"); return; } // 因为头节点不能动,因此需要一个辅助变量来遍历 HeroNode temp = head.next; while (true){ // 判断是否到链表最后 if(temp == null){ break; } // 输出节点信息 System.out.println(temp); // 将 temp 后移 temp=temp.next; } } } // 定义HeroNode,每个 HeroNode 对象就是一个节点 class HeroNode{ public int no; // 编号 public String name; // 名字 public String nickname; // 昵称 public HeroNode next; // 指向下一个节点 // 构造器 public HeroNode(int no,String name,String nickname){ this.no=no; this.name=name; this.nickname=nickname; } // 为了显示方法,重写 toString @Override public String toString(){ return "HeroNode [ no = "+no+" , name = "+name+" , nickname = "+nickname+" ]"; } }
-
合并两个有序的单链表,合并之后的链表依然有序
1.2 双向链表
双向链表:每一个节点除了拥有data和next指针, 还拥有指向前置节点的prev指针。
结构图:
-
单向链表和双向链表的比较:
-
单向链表:查找的方向只能是一个方向;
双向链表:可以向前或者向后查找;
-
单向链表:不能自我删除,需要靠辅助节点 ;
双向链表:可以自我删除;
-
-
双向链表的操作:
-
遍历:和 单链表一样,但其可以向前,也可以向后查找;
-
添加 (默认添加到双向链表的最后)
-
先找到双向链表的最后这个节点,比如temp
-
temp.next = newHeroNode;
-
newHeroNode.pre = temp;
-
-
修改 :和 单向链表一样;
-
删除:
- 因为是双向链表,因此可以实现 自我删除某个节点
- 直接找到要删除的这个节点,比如temp
- temp.pre.next = temp.next;
- temp.next.pre = temp.pre;
-
具体代码:
package roselin.linkedlist;
public class DoubleLinkedListDemo {
public static void main(String[] args) {
System.out.println("双向链表的测试:");
// 先创建节点
HeroNode1 hero1=new HeroNode1(1,"宋江","及时雨");
HeroNode1 hero2=new HeroNode1(2,"卢俊义","玉麒麟");
HeroNode1 hero3=new HeroNode1(3,"吴用","智多星");
HeroNode1 hero4=new HeroNode1(4,"林冲","豹子头");
// 创建一个链表
DoubleLinkedList doubleLinkedList=new DoubleLinkedList();
// 添加
doubleLinkedList.add(hero1);
doubleLinkedList.add(hero2);
doubleLinkedList.add(hero3);
doubleLinkedList.add(hero4);
// 遍历
doubleLinkedList.list();
// 修改
HeroNode1 newHeroNode=new HeroNode1(4,"公孙胜","入云龙");
doubleLinkedList.update(newHeroNode);
System.out.println();
System.out.println("修改后:");
doubleLinkedList.list();
// 删除
doubleLinkedList.del(2);
System.out.println();
System.out.println("删除后:");
doubleLinkedList.list();
}
}
// 创建一个双向链表的类
class DoubleLinkedList {
// 先初始化一个头结点,头结点,不存放具体的数据
private HeroNode1 head = new HeroNode1(0, "", "");
// 返回头节点
public HeroNode1 getHead() {
return head;
}
// 遍历双向链表的方法(和单链表一样)
// 显示链表【遍历】
public void list() {
// 判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
// 因为头节点不能动,因此需要一个辅助变量来遍历
HeroNode1 temp = head.next;
while (true) {
// 判断是否到链表最后
if (temp == null) {
break;
}
// 输出节点信息
System.out.println(temp);
// 将 temp 后移
temp = temp.next;
}
}
// 添加一个节点到双向链表的最后
public void add(HeroNode1 heroNode1) {
// 因为 head 节点不能动,因此需要一个辅助变量 temp
HeroNode1 temp = head;
// 遍历链表,找到链表最后节点
while (true) {
// 找到链表最后节点
if (temp.next == null) {
break;
}
// 如果没有找到最后节点,就将 temp 后移
temp = temp.next;
}
// 当退出 while 循环时,temp就指向了链表的最后节点
// 形成一个双向链表
temp.next = heroNode1;
heroNode1.pre = temp;
}
// 修改一个节点
public void update(HeroNode1 newHeroNode){
// 判断是否空
if(head.next == null){
System.out.println("链表为空~");
return;
}
// 找到需要修改的节点,根据 no 编号
// 定义一个辅助变量
HeroNode1 temp = head.next;
boolean flag=false; // 表示是否找到该节点
while(true){
if (temp==null){
break; // 到链表的已经遍历完链表
}
if(temp.no==newHeroNode.no){
// 找到
flag=true;
break;
}
temp=temp.next; // 后移,遍历当前链表
}
// 根据 flag 判断是否找到要修改的节点
if(flag){
temp.name=newHeroNode.name;
temp.nickname=newHeroNode.nickname;
}else{ // 没有找到
System.out.printf("没有找到 编号 %d 的节点,不能修改\n",newHeroNode.no);
}
}
// 删除节点
// 说明:
// 1. 对于双向链表,直接找到要删除的这个节点
// 2. 找到后,自我删除即可
public void del(int no){
// 判断当前链表是否为空
if(head.next == null){ // 空链表
System.out.println("链表为空,无法删除");
return;
}
HeroNode1 temp =head.next;
boolean flag = false; // 标识是否 找到待删除节点
while (true){
if(temp==null){
break;
}
if(temp.no==no){
// 找到的待删除节点的前一个节点 temp
flag=true;
break;
}
temp=temp.next; // temp后移,遍历当前链表
}
// 判断 flag
if(flag){ // 找到
// 可以删除
// temp.next=temp.next.next; // 单向链表
temp.pre.next=temp.next;
// 如果是最后一个节点,就不需要执行这句话 temp.next.pre=temp.pre; ,否则会出现空指针异常
if(temp.next!=null){
temp.next.pre=temp.pre;
}
}else{
System.out.printf("要删除的 %d 节点不存在\n",no);
}
}
}
// 定义HeroNode1,每个 HeroNode 对象就是一个节点
class HeroNode1 {
public int no; // 编号
public String name; // 名字
public String nickname; // 昵称
public HeroNode1 next; // 指向下一个节点,默认为null
public HeroNode1 pre; // 指向前一个节点,默认为null
// 构造器
public HeroNode1(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
// 为了显示方法,重写 toString
@Override
public String toString() {
return "HeroNode [ no = " + no + " , name = " + name + " , nickname = " + nickname + " ]";
}
}
拓展:
双向链表的第二种添加方式,按照编号顺序(按照单链表的顺序添加,稍作修改即可).
★ 数组 VS 链表
- 从表格可以看出, 数组的优势在于能够快速定位元素,对于读操作多、 写操作少的场景来说, 用数组更合适一些。
- 链表的优势在于能够灵活地进行插入和删除操作, 如果需要在尾部频繁插入、 删除元素, 用链表更合适一些。
1.3 单向环形链表
Josephu(约瑟夫、约瑟夫环) 问题:
设编号为1,2,… n 的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
提示:
用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
举例:
-
n = 5 , 即有5个人 ;
-
k = 1, 从第一个人开始报数;
-
m = 2, 数2下;
具体代码:
public class Josephu {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList=new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5); // 加入5个小孩节点
circleSingleLinkedList.showBoy();
circleSingleLinkedList.countBoy(1,2,5);
}
}
// 创建一个环形的单向链表
class CircleSingleLinkedList{
// 创建一个first节点,当前没有编号
private Boy first=null;
// 添加小孩节点,构建一个环形的链表
public void addBoy(int nums){
// nums 做一个数据校验
if(nums<1){
System.out.println("nums 的值不正确");
return;
}
Boy curBoy=null; // 辅助指针,帮助构建环形链表
// 使用for循环来创建环形链表
for(int i =1; i<=nums;i++){
// 根据编号,创建小孩节点
Boy boy=new Boy(i);
// 如果是第一个小孩
if(i==1){
first=boy;
first.setNext(first);
curBoy=first; // 让 curBoy指向第一个小孩
}else{
curBoy.setNext(boy);
boy.setNext(first);
curBoy=boy;
}
}
}
// 遍历当前 环形链表
public void showBoy(){
// 判断链表是否为空
if(first==null){
System.out.println("没有任何小孩~~");
return;
}
// 因为 first不能动,因此仍然使用一个辅助指针完成遍历
Boy curBoy=first;
while (true){
System.out.printf("小孩的编号 %d \n",curBoy.getNo());
if(curBoy.getNext()==first){ // 说明已经遍历完毕
break;
}
curBoy =curBoy.getNext(); // curBoy后移
}
}
// 根据用户的输入,计算小孩出圈的顺序
/**
*
* @param startNo 表示从第几个小孩开始数数
* @param countNum 表示数几下
* @param nums 表示最初有几个小孩在圈中
*/
public void countBoy(int startNo,int countNum,int nums){
// 先对数据进行校验
if(first==null || startNo<1||startNo>nums){
System.out.println("参数输入有误,请重新输入~~~");
return;
}
// 创建辅助变量 helper,帮助完成小孩出圈
Boy helper =first;
// helper变量事先应该指向环形链表的最后这个节点
while (true){
if(helper.getNext()==first){ // 说明helper指向最后节点
break;
}
helper=helper.getNext();
}
// 小孩报数前,先让 first 和 helper 移动k-1次
for(int j=0;j<startNo-1;j++){
first=first.getNext();
helper=helper.getNext();
}
// 当小孩报数时,让first和helper指针同时 的移动 m-1次,然后出圈
// 这里是一个循环操作,知道圈中只有一个节点
while (true){
if(helper==first){ // 说明圈中只有一个节点
break;
}
// 让 first 和 helper 指针同时 的移动 counNum-1,
for (int j = 0;j<countNum-1;j++){
first=first.getNext();
helper=helper.getNext();
}
// 这时first 指向的节点就是要出圈的小孩节点
System.out.printf("小孩%d 出圈\n",first.getNo());
// 这时将 first 指向的小孩节点出圈
first=first.getNext();
helper.setNext(first);
}
System.out.printf("最后留在圈中的小孩编号%d \n",helper.getNo());
}
}
// 创建一个 Boy类,表示一个节点
class Boy{
private int no; //编号
private Boy next; // 指向下一个节点,默认null
public Boy(int no){
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;
}
}