# 单向链表
1、介绍
-
以节点的方式存储(链式存储)
-
其中包含data和next :data 域存放数据,next 域指向下一个节点
-
内存结构:非连续,与数组不同
-
逻辑结构:上一个next始终指向下一个节点
DummyHead :头结点不存放数据,仅仅作为当前链表的入口
head 字段的值不能改变,一旦改变,就丢失了整个链表的入口,我们也就无法通过 head 找到链表了
因为:head始终指向头节点,在代码中的作用是保证 每次遍历从头节点开始,所以后面需要使用临时变量辅助。
**内存结构图**(带头节点)
逻辑结构图(带头节点)***
2、链表应用实例
使用带 head 头的单向链表实现【水浒英雄排行榜管理】
2.1、创建英雄类
- no :英雄编号
- name :英雄名字
- nickName :英雄昵称
- next :指向下一个 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;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname +
'}';
}
}
2.2、遍历链表
2.2.1、思路及步骤
(从head开始循环,依次后移直到temp == null )
- 定义辅助变量 temp ,相当于一个指针,指向当前节点
- 何时遍历完成?temp == null 表明当前节点为 null ,即表示已到链表末尾
- 如何遍历?temp = temp.next ,每次输出当前节点信息之后,temp 指针后移
2.2.2、代码
public void ListShow(){
//判断链表是否为空
if(head.next == null) {
System.out.println("链表为空");
return;//return返回函数,break返回循环
}
HeroNode temp = head.next;
while(temp != null) {
//输出节点的信息
System.out.println(temp);
//将temp后移, 一定小心
temp = temp.next; //后移
}
}
2.3、尾部插入
2.3.1、思路及步骤
(找到最后的节点,在这之后插入即可)
- 定义辅助变量 temp ,相当于一个指针,指向当前节点
- 如何在链表末尾插入节点?
- 首先需要遍历链表,找到链表最后一个节点,当 temp.next == null时,temp 节点指向链表最后一个节点
- 然后在 temp 节点之后插入节点即可:temp.next = heroNode
2.3.2、代码
public void add(HeroNode heroNode) {
//因为head节点不能动,因此我们需要一个辅助遍历 temp
HeroNode temp = head;
//遍历链表,找到最后
while(temp.next != null) {
//如果没有找到最后, 将将temp后移
temp = temp.next;
}
//当退出while循环时,temp就指向了链表的最后
//将最后这个节点的next 指向 新的节点
temp.next = heroNode;
}
2.4顺序插入
2.4.1思路及步骤
从头开始遍历找到第一个编号比他大的,插入他之前即可,要考虑已经存在该编号
具体步骤如下
-
定义辅助变量 temp ,相当于一个指针,指向当前节点
-
首先需要遍历链表,找到链表中编号值比 heroNode.no 大的节点,暂且叫它 biggerNode ,然后把 heroNode 插入到 biggerNode 之前即可
-
怎么找 biggerNode ?当 temp.next.no > heroNode.no 时,这时 temp.next 节点就是 biggerNode 节点。
-
为什么是 temp.next 节点?只有找到 temp 节点和 temp.next(biggerNode )节点,才能在 temp 节点和 temp.next 节点之间插入 heroNode 节点
-
怎么插入?很重要,注意理解插入代码的实现,后续的反转中也需要用到
heroNode.next = temp.next; temp.next = heroNode;
2.4.2代码
public void addOrder(HeroNode heroNode){
// 辅助变量temp
HeroNode temp=head;
Boolean flag=false;
while (true) {
if(temp.next==null){
break;
}
if (temp.next.no > heroNode.no){
break;
}else if (temp.next.no == heroNode.no){
System.out.println("编号已经存在,无法添加");
flag=true;
break;
}
temp=temp.next;//后移,在循环中遍历
}
if(flag) { //不能添加,说明编号存在
System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", heroNode.no);
} else {
//插入到链表中, temp的后面
heroNode.next = temp.next;
temp.next = heroNode;//注意不能调换顺序
}
}
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(hero4);
singleLinkedList.addByOrder(hero2);
singleLinkedList.addByOrder(hero3);
// 显示一把
singleLinkedList.list();
}
2.5修改节点
找到对应编号的节点,直接将该节点的各个信息进行替换即可,需要用到该节点的全部信息,形参需要传入head
2.5.1、思路及步骤
- 遍历找到该节点
- 定义辅助变量 temp ,相当于一个指针,指向当前节点
- 如何找到指定节点?temp.no = newHeroNode.no
2.6删除节点
2.6.1、思路及步骤
- 遍历通过编号找到该节点
- temp.next=该节点
- temp.next=temp.next.next
因为只要通过编号便可找到节点,直接将下一个节点的地址赋值给上一个节点.next即可,与本节点无关,所以函数形参只需传递(int no)不需要整个节点。
2.6.2、代码
public void del(int no) {
Boolean flag = false;
if (head.next == null) {
System.out.println("链表为空");
return;//return返回函数,break返回循环
}
HeroNode temp = head;
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.println("要删除的节点不存在");
}
}
3、总结
- 遍历链表,执行操作时,判断条件有时候是 temp ,有时候是 temp.next ,Why?
- 对于插入、删除节点来说,需要知道当前待操作的节点(heroNode)前一个节点的地址(指针),如果直接定位至当前待操作的节点 heroNode ,那没得玩。。。因为不知道heroNode 前一个节点的地址,无法进行插入、删除操作,所以 while 循环中的条件使用 temp.next 进行判断
- 对于更新、遍历操作来说,我需要的仅仅就只是当前节点的信息,所以 while 循环中的条件使用 temp进行判断
4、全部代码
package com.Jason;
import java.util.Stack;
public class SingleLinkList {
public static void main(String[] args) {
HeroNode n1=new HeroNode(1,"宋江","及时雨");
HeroNode n2=new HeroNode(2,"卢俊义","玉麒麟");
HeroNode n3=new HeroNode(3,"吴用","智多星");
HeroNode n4=new HeroNode(4,"公孙胜","入云龙");
HeroNode n5=new HeroNode(4,"公","龙");
SingleLink singleLink=new SingleLink();
singleLink.addOrder(n1);
singleLink.addOrder(n4);
singleLink.addOrder(n2);
singleLink.addOrder(n3);
System.out.println("==========原始链表=========");
singleLink.ListShow();
// 测试更新
singleLink.update(n5);
System.out.println("==========更新之后的链表=========");
singleLink.ListShow();
System.out.println("==========删除之后的链表=========");
singleLink.del(3);
singleLink.ListShow();
//有效个数
System.out.println("有效个数为:"+SingleLink.GetLength(singleLink.getHead()));
System.out.println("====测试倒数第k个");
singleLink.PrintIndex(2);
System.out.println("==========反转之后的链表=========");
singleLink.ReverseList(singleLink.getHead());
singleLink.ListShow();
System.out.println("===========测试逆序打印单链表, 没有改变链表的结构=========");
reversePrint(singleLinkedList.getHead());
}
}
class SingleLink{
//初始化头节点
private HeroNode head =new HeroNode(0,"","");
public HeroNode getHead() {
return head;
}
public void add(HeroNode heroNode) {
//因为head节点不能动,因此我们需要一个辅助遍历 temp
HeroNode temp = head;
//遍历链表,找到最后
while(temp.next != null) {
//如果没有找到最后, 将将temp后移
temp = temp.next;
}
//当退出while循环时,temp就指向了链表的最后
//将最后这个节点的next 指向 新的节点
temp.next = heroNode;
}
//顺序插入
public void addOrder(HeroNode heroNode){
// 辅助变量temp
HeroNode temp=head;
Boolean flag=false;
while (true) {
if(temp.next==null){
break;
}
if (temp.next.no > heroNode.no){
break;
}else if (temp.next.no == heroNode.no){
System.out.println("编号已经存在,无法添加");
flag=true;
break;
}
temp=temp.next;//后移,在循环中遍历
}
if(flag) { //不能添加,说明编号存在
System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", heroNode.no);
} else {
//插入到链表中, temp的后面
heroNode.next = temp.next;
temp.next = heroNode;//注意不能调换顺序
}
}
// 更新
public void update(HeroNode NewHeroNode){
if(head.next == null) {
System.out.println("链表为空~");
return;
}
HeroNode temp=head;
while (true) {
if (temp.next == null) {
break;
}
if(temp.next.no==NewHeroNode.no){
temp.next.name = NewHeroNode.name;
temp.next.nickname = NewHeroNode.nickname;
break;
}
temp=temp.next;
}
}
// 删除
public void del(int no) {
Boolean flag = false;
if (head.next == null) {
System.out.println("链表为空");
return;//return返回函数,break返回循环
}
HeroNode temp = head;
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 ListShow(){
//判断链表是否为空
if(head.next == null) {
System.out.println("链表为空");
return;//return返回函数,break返回循环
}
HeroNode temp = head.next;
while(temp != null) {
//输出节点的信息
System.out.println(temp);
//将temp后移, 一定小心
temp = temp.next;
}
}
//求有效节点的个数
public static int GetLength(HeroNode head) {
if (head.next == null) {
System.out.println("链表为空");
return 0;
}
int size = 0;
HeroNode temp = head.next;
while (temp != null) {
size++;
temp = temp.next;
}
return size;
}
// 打(印倒数第k个
public void PrintIndex(int k){
int length=SingleLink.GetLength(getHead());
if(k==0||k>length){
System.out.println("节点不存在");
}
HeroNode temp = head.next;
for (int i=0; i <length-k; i++) {
temp=temp.next;
}
System.out.println("倒数第"+k+"个节点是:"+temp);
}
//链表反转
public void ReverseList(HeroNode head){
// 首先判断是否为空,或者只有一个
if (head.next == null || head.next.next == null) {
return;
}
HeroNode temp = head.next;
HeroNode next=null;
HeroNode reverseHead = new HeroNode(0, "", "");
while(temp!=null){
next=temp.next;
//同顺序插入
temp.next = reverseHead.next;// 将cur的下一个节点指向新的链表的最前端
reverseHead.next = temp; // 将cur 连接到新的链表上
temp = next;//后移
}
head.next = reverseHead.next;
}
public 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的特点是先进后出
}
}
}
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;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname +
'}';
}
}