目录
1.简介
本文包括单链表的添加删除修改
链表(Linked List)
链表是有序列表,但在内存中存储情况如下
比如这里的头指针是150,我们就找到地址为150的这行,即data域是a1,它的next域是110,那么就指向地址为110的这行,它的data域是a2,next域是180,那么就指向地址为180的这行,data域是a3.以此类推。它的每个节点并不是连续存储的。就是地址不是连续的。
逻辑结构是这样的:
空的带头节点的单链表:
带头节点的单链表:
逻辑上一个接一个连续的,最后一个an的next域为null代表这个链表结束了。而物理上就像上面的图那样不是连续存储的。
小结一下就是,链表以节点方式存储,链式存储。每个节点包含data域和next域,指向下一个节点。链表的各个节点不一定连续存储。
还要注意链表分为带头节点的和没有头节点的,需要根据实际需求来确定。
也可以记成,链表像是老师点你起来回答问题,要求回答问题的同学不能重复,然后你回答之后可以指定下一个同学,下一个同学回答之后又可以指定其它同学,一个接一个。虽然都是你们班上的同学,但是因为同学随机指定,也不用让某一列的同学从头到尾站起来一个个回答问题,它是不用连续的。
2.例子
以水浒108英雄排序为例。
先创建英雄类HeroNode,一个英雄就是一个HeroNode。在里面设置一些属性,包括编号no,姓名name,昵称nickname,下一个节点next。
设置一个头节点head,不放具体数据,作用只是表示它是单链表的头。
这里Data就放no,name,nickname这几个属性,地址就放next属性。
3.不考虑编号顺序将数据添加到链表尾部思路
先创建一个head节点,作用就是表示单链表的头。
后面每添加一个节点,就先找到当前链表的最后节点,再将这个节点的next指向新节点。
但是因为head节点不能动,在第一个嘛,我们就需要一个辅助节点temp来遍历链表找到这个最后节点。遍历的话通过循环,条件就是,如果temp.next==null时就是到末尾了,就结束循环。没有到末尾,就temp=temp.next,移到下一个节点。这样一来,退出循环时temp指向的就是链表的最后节点。我们要添加新节点到链表末尾,就把这个temp.next指向新节点。temp.next=heroNode。
这样的添加就是按照添加时的顺序一个一个添加到后面的。比如有编号为1,2,3,4的节点,如果add的顺序为1,2,3,4,那它链表中就是1,2,3,4的顺序。
遍历结果:
如果add的顺序为1,4,2,3,那它链表中就是1,4,2,3的顺序。
遍历结果:
4.显示单链表思路
先判断链表是否为空,为空就输出链表为空,就不遍历了。如果不为空,通过一个辅助变量temp帮助遍历整个链表。temp=head.next。在循环中,先看是否到链表最后,如果temp==null就是到链表最后了,就退出循环。没有到链表最后就输出节点信息然后将temp后移temp=temp.next。
5.考虑编号顺序将数据添加到链表思路
图示:原来是1.4.7这三个节点
现在要插入新节点2
这里设成,在添加英雄时,根据排名将英雄插入指定位置。如果有这个排名(即编号)则添加失败并给出提示。
首先找到新添加的节点的位置,通过辅助变量遍历来找。因为是单向链表,可以根据上一个节点的位置找到下一个节点的位置,但是不能根据下一个节点的位置找到上一个节点的位置。所以temp是位于添加位置的前一个节点。那么怎么找到这个位置呢?通过循环,在循环之前设置一个变量flag用来判断要添加的编号是否已存在,flag初始为false。①如果temp.next==null说明已经在链表最后,退出循环。否则就比较temp下一个节点的编号与要插入节点的编号。②如果下一个节点的编号大于要插入节点的编号,那就找到了,就是这里,退出循环。③如果下一个节点编号等于要插入节点的编号,就是编号已经存在了,将flag置为true,也退出循环。④如果上面三个条件都不满足,就让temp继续后移,temp=temp.next。
好的退出循环之后,先通过flag判断编号是否已经存在,已经存在就输出“准备插入的编号已经存在”。如果编号不存在,新节点可以插入到temp后面了。方法就是将新节点插入到temp后面的位置,再使temp的后一个节点变成添加的这个新节点。
设置新节点的next=temp.next,再将temp.next=新节点。
heroNode.next=temp.next;
temp.next=heroNode;
虽然添加节点的顺序是1423,但是在链表中的顺序还是按照编号顺序1234。成功实现按照编号顺序插入。
6.根据编号修改链表中节点信息思路
这里是根据编号来修改节点信息。先判断链表是否为空,为空就不遍历了。不为空就使用辅助变量temp来遍历。用一个flag判断是否找到节点,初始为false。在循环中,①如果temp==null也就是找到链表结尾都没有找到要修改的节点,就退出循环。②如果temp的编号等于要修改节点的编号,就是找到了,就把flag置为true然后退出循环。③如果上面两个条件都不满足,就把temp后移,temp=temp.next。在循环外面,先判断flag的值,看有没有找到要修改的节点,找到了就把这里temp的除了编号之外的数据都换成修改的节点的数据。如果没有找到就输出没有找到这个节点。
这里把编号为2的节点的name和nickname修改了,输出的修改之后的信息。
7.将链表某个节点删除的思路
删除和修改比较像,都是先找到这个节点,然后操作。
比如要修改编号为4的节点
需要找到要删除节点的前一个节点temp,再将temp.next=temp.next.next.直接让temp的下一个节点变成下下个节点,这样以后遍历的时候就把原来temp后面的那个节点跳过了,直接到下下个节点了。被删除的节点会被垃圾回收机制回收。
具体一点就是:
先判断链表是否为空,为空就不遍历了。不为空就使用辅助变量temp来遍历。用一个flag判断是否找到节点,初始为false。在循环中,①如果temp==null也就是找到链表结尾都没有找到要修改的节点,就退出循环。②如果temp.next的编号等于要修改节点的编号,就是找到了,就把flag置为true然后退出循环。③如果上面两个条件都不满足,就把temp后移,temp=temp.next。在循环外面,先判断flag的值,看有没有找到要修改的节点,找到了就把这里temp.next=temp.next.next。如果没有找到就输出没有找到这个节点。
要注意的一点就是,在比较时是temp.next.no与要删除节点的no比较。
8.代码
public class LinkedList {
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();
System.out.println("按编号顺序插入");
SingleLinkedList singleLinkedList1=new SingleLinkedList();
//将节点插入链表
singleLinkedList1.addByOrder(hero1);
singleLinkedList1.addByOrder(hero4);
singleLinkedList1.addByOrder(hero2);
singleLinkedList1.addByOrder(hero3);
singleLinkedList1.list();
//测试修改节点
System.out.println("修改节点");
HeroNode newHeroNode=new HeroNode(2,"卢","玉麒麟--");
singleLinkedList1.update(newHeroNode);
singleLinkedList1.list();
//测试删除节点
System.out.println("删除节点1");
singleLinkedList1.delete(1);//删除第一个节点
singleLinkedList1.list();
System.out.println("删除节点4");
singleLinkedList1.delete(4);//删除第四个节点
singleLinkedList1.list();
System.out.println("删完");
singleLinkedList1.delete(2);//删除第二个节点
singleLinkedList1.delete(3);//删除第三个节点
singleLinkedList1.list();
}
}
//英雄类
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来显示方法
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
}
}
//管理英雄类
class SingleLinkedList {
//先初始化头节点,不存放具体数据
private HeroNode head = new HeroNode(0, "", "");
//添加节点。不考虑编号顺序时,先找到当前链表的最后节点,再将这个节点的next指向新节点
public void add(HeroNode heroNode) {
//辅助遍历temp
HeroNode temp = head;
//遍历链表找到最后
while (true) {
if (temp.next == null) {//找到最后了就退出
break;
}
//没到最后就后移到下一个节点
temp = temp.next;
}
//退出while循环时temp就指向链表最后
//我们要添加新节点,就将这个最后节点的next指向节点
temp.next = heroNode;
}
//添加节点,考虑编号顺序。也就是插入到链表非末尾位置
public void addByOrder(HeroNode heroNode) {
//辅助变量位于添加位置的前一个节点
HeroNode temp = head;
boolean flag = false;//标识添加的编号是否存在。如果存在就不能添加了
while (true) {
if (temp.next == null) {
//已经在链表最后,要添加的节点可能已经在链表最后
break;
}
if (temp.next.no > heroNode.no) {
//temp下一个节点的编号大于要插入的节点编号,就是找到了,要在这个temp后面插入
break;
} else if (temp.next.no == heroNode.no) {
//编号已存在
flag = true;//编号存在
}
temp = temp.next;//后移
}
//判断编号是否已存在
if (flag) {
System.out.printf("准备插入的编号 %d 已经存在,无法添加\n", heroNode.no);
} else {
//新节点插入链表中temp的后面
heroNode.next = temp.next;
temp.next = heroNode;//temp后面是新节点
}
}
//根据编号修改节点信息,即编号不能修改
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);
}
}
//删除节点
public void delete(int no){
//判断链表是否为空
if(head.next==null){
//如果链表为空就不遍历了
System.out.println("链表为空");
return;
}
//找到需要修改的节点
//辅助变量
HeroNode temp=head;
boolean flag=false;//判断是否找到该节点
while(true){
if(temp.next==null){
break;//已经遍历完链表
}
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 (true) {
//看是否到链表最后
if (temp == null) {
break;
}
//不为空就输出节点信息
System.out.println(temp);
//后移
temp = temp.next;
}
}
}
9.运行结果
10.提炼一下
①直接在链表末尾添加节点就是找到末尾节点temp,再使temp.next=新节点
②根据编号或者排名将节点添加到链表中就找到要添加位置的前一个节点temp,设置新节点的next=temp.next,再将temp.next=新节点。
heroNode.next=temp.next;
temp.next=heroNode;
③修改节点就先通过遍历找到该节点temp,再把temp中的数据换成修改的的节点的数据。
④删除节点就先找到要删除节点的前一个节点temp,再temp.next=temp.next.next
下一篇写单链表的查询、反转、节点个数等单链表常见面试题^_^加油加油