文章目录
前言
链表和数组的区别在哪呢?大家知道,数组是连续的内存空间;而链表是按照节点的方式存放数据的,节点不一定是连续的。下面是对其详细介绍:
一、链表介绍
1.链表以节点方式来存储数据
2.每个节点包含data域、next域(指向下个节点)
3.如下图所示,链表的各个节点不一定是连续的(可以通过next找到下个节点,没有必要必须连续)
二、单链表的相关操作
我们利用单链表实现水浒英雄排行榜:
1.单链表的创建
“英雄节点类” 的代码如下:
class HeroNode{
public int no; //编号
public String name; //名字
public String nickname; //绰号
public HeroNode next; //指向下个节点
//构造器
public HeroNode(int n , String name , String nickname){
this.no = n;
this.name = name;
this.nickname = nickname;
}
// 为了显示方便,重写toString()方法
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
1.1 不考虑排名,直接添加英雄节点:
1.找到当前链表的最后节点(遍历)
2.将最后节点的 next 指向新的节点即可
代码如下:
//添加节点到单向链表
//思路:当不考虑编号时候
//1.找到当前链表的最后节点
//2.将最后节点的 next 指向新的节点
public void add(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助的遍历temp
HeroNode temp = head;
while(temp.next != null){
temp = temp.next;//找到最后一个节点
}
temp.next = heroNode;
}
1.2 按照排名(编号),顺序添加英雄节点
按照编号的顺序添加:
1. 找到新添加的节点的位置(添加节点的前一个位置),通过辅助指针temp(原来的数据肯定也是顺序的,比较一下就行)
2. 先令新的节点.next = temp.next;
3. 再将 temp.next = 新的节点
(注意找的temp是添加位置的前一个节点)
代码如下:
//按顺序添加节点
public void addByOrder(HeroNode heroNode){
HeroNode p = head; //不能改变头结点,所以用临时节点接收
boolean alreadyExist = false;//加入英雄的编号是否已经存在
while(true){
if(p.next == null){//如果p下面已经为空(找到了最后)
break;
}
if(p.next.no > heroNode.no){//下一个节点就是要找的,已经找到,退出循环即可
break;
}
if(p.next.no == heroNode.no){//希望添加的已经存在
alreadyExist = true;
break;
}
p = p.next;
}
if(alreadyExist == false){
//加入链表(p的后面)
heroNode.next = p.next;//先将新节点指向要添加的位置
p.next = heroNode; //再将添加的前一个位置的next指向新节点即可
return;
}else{
System.out.println("添加失败,已经存在");
return;
}
}
2.遍历单链表
1.判断是否为空,空链表的话直接返回
2.不为空,循环输出链表即可(利用上面的toString()方法)
代码如下:
public void show(){
if(head.next == null){//判断是否为空
System.out.println("链表为空");
return;
}
HeroNode temp = head.next;//不能改变单链表的头结点
while(true){
if(temp == null){
break;//遍历结束
}
System.out.println(temp);//打印节点信息(自动调用toString()方法)
temp = temp.next;
}
}
3.根据编号修改节点信息
根据编号修改,所以编号不可以变(编号要是变了就相当于添加)
1. 判断链表是否为空,为空处理即可
2. 链表不为空,则遍历查找到编号对应的节点即可
3. 找到之后更新节点信息即可
代码如下:
public void update(HeroNode newHeroNode){
if(head.next == null){
System.out.println("链表为空");
return;
}
HeroNode p = head.next;
boolean flag = false;//是否找到该节点
while(true){
if(p.no == newHeroNode.no){
flag = true;//找到了
break;
}
if(p == null){
break;//找到最后一个节点了,之恩那个退出循环(没找到)
}
p = p.next;
}
if(flag){//找到了就更新信息即可
p.name = newHeroNode.name;
p.nickname = newHeroNode.nickname;
}else{//没有找到,输出提示
System.out.println("未找到要修改的英雄,请重试");
return;
}
}
4.删除节点
删除节点的思路:
1. 我们先找到 待删除结点的前一个结点temp
2. temp.next = temp.next.next;
3. 被删除的节点,将不会有其它的引用指向,会被垃圾回收机制(gc)回收(不用手动释放)
代码如下:
//head不能动,辅助接点p
//我们比较的时候,p.next.no 和 待删除的节点的no值进行比较
public void delate(int no){
if(head.next == null){
System.out.println("链表为空,删除失败....");
return;
}
boolean flag = false;//标志是否找到
HeroNode p = head;
while(true){
if(p.next == null){//已经到链表最后了
break;
}
if(p.next.no == no){
flag = true;//找到了带删除节点的前一个节点p,令flag = true即可
break;
}
p = p.next;//p后移,遍历
}
if(flag){//找到,进行删除即可
p.next = p.next.next;
return;
}else{
System.out.println("删除失败,未找到...");
return;
}
}
总结
1. 头结点:不存储有效数据,只用来表示单链表的表头;2. 添加节点、删除节点,对于单链表来说都是找到待处理数据的前一个位置进行操作;
3. 最后注意一下插入数据的操作顺序(不能先断开链表内部的联系),需要先将更新的数据指向链表,然后再断开链表内部的联系;
面试题
点击蓝色字体即可跳转
1. 新浪面试题:查找单链表中倒数第K个节点
2. 腾讯面试题:单链表的逆置
3. 百度面试题:逆序打印单链表
完整测试代码
只进行了部分测试,测试数据可以自己补齐
package 链表;
import java.time.chrono.IsoChronology;
import java.util.Stack;
/**
* @author Weilei Zhang
* @create 2021-02-28 22:25
*/
public class SignalLinckedList {
public static void main(String[] args) {
//测试:
//先创建几个节点
HeroNode hero1 = new HeroNode(1 , "宋江" , "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义" , "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吴用" , "智多星");
//创建给链表
SignalLinkedListDemo signalLinckedListDemo = new SignalLinkedListDemo();
//加人
signalLinckedListDemo.add(hero1);
signalLinckedListDemo.add(hero2);
signalLinckedListDemo.add(hero3);
// signalLinckedListDemo.addByOrder(hero4);
// signalLinckedListDemo.delate(8);
// signalLinckedListDemo.update(hero4);
signalLinckedListDemo.show();
}
}
class SignalLinkedListDemo{
// 先初始化一个头结点,不要动头节点,不存放具体的数据
private HeroNode head = new HeroNode(0 , "" , "");
public HeroNode getHead(){
return head;
}
//添加节点到单向链表
//思路:当不考虑编号时候
//1.找到当前链表的最后节点
//2.将最后节点的 next 指向新的节点
public void add(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助的遍历temp
HeroNode temp = head;
while(temp.next != null){
temp = temp.next;
}
temp.next = heroNode;
}
//按顺序添加节点
public void addByOrder(HeroNode heroNode){
HeroNode p = head;
boolean alreadyExit = false;
while(true){
if(p.next == null){//如果p下面已经为空(找到了最后)
break;
}
if(p.next.no > heroNode.no){
break;
}
if(p.next.no == heroNode.no){//希望添加的已经存在
alreadyExit = true;
break;
}
p = p.next;
}
if(alreadyExit == false){
heroNode.next = p.next;
p.next = heroNode;
return;
}else{
System.out.println("添加失败,已经存在");
return;
}
}
//修改节点的信息,根据编号来修改(所以要求编号不可以变)
//1.根据newHeroNode的 no 属性来修改即可
public void update(HeroNode newHeroNode){
if(head.next == null){
System.out.println("链表为空");
return;
}
HeroNode p = head.next;
boolean flag = false;
while(true){
if(p.no == newHeroNode.no){
flag = true;
break;
}
if(p == null){
break;
}
p = p.next;
}
if(flag){
p.name = newHeroNode.name;
p.nickname = newHeroNode.nickname;
}else{
System.out.println("未找到要修改的英雄,请重试");
return;
}
}
//删除节点
public void delate(int no){
if(head.next == null){
System.out.println("链表为空,删除失败....");
return;
}
boolean flag = false;
HeroNode p = head;
while(true){
if(p.next == null){
break;
}
if(p.next.no == no){
flag = true;
break;
}
p = p.next;
}
if(flag){
p.next = p.next.next;
return;
}else{
System.out.println("删除失败,未找到...");
return;
}
}
//显示链表
public void show(){
if(head.next == null){
System.out.println("链表为空");
return;
}
HeroNode temp = head.next;
while(true){
if(temp == null){
break;
}
System.out.println(temp);
temp = temp.next;
}
}
}
class HeroNode{
public int no;
public String name;
public String nickname;
public HeroNode next;
//构造器
public HeroNode(int n , String name , String nickname){
this.no = n;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}