目录
链表的概念
1.链表是有序链表,内存存储如下
解析:1)链表是以节点方式存储的(链式存储)
2)每个节点包含date和next
3)存储位置是随机的,不是连续存储
4)分类:有头节点和没有头节点的链表
逻辑结构图
单链表的实现
1.链表的初始化
//定义一个单链表,来管理HeroNode
class SingleLinkedList {
//初始化头节点,该节点不存放数据
HeroNode head = new HeroNode(0, "", "");
2.第一种链表的添加方式(直接添加到尾部)
//第一个添加方式:直接添加到链表的尾部
/*
* 思路分析:
* 1.找到链表的最后
* 2.令temp.next指向新节点
* */
public void add(HeroNode heroNode) {
HeroNode temp = head;//1.定义一个辅助变量temp来遍历,保证头节点不动
while (true) {
if (temp.next == null) {//到了链表的最后
break;
}
temp = temp.next;//遍历的关键条件
}
//循环退出了说明链表已经到最后了
temp.next = heroNode;//添加方式 temp.next 指向新节点
}
3.第二种链表的添加方式
//第二种添加方式:根据HeroNode的编号no进行添加
/*
* 思路分析:
* 1.找到合适的编号位置,即将要添加HeroNode的编号小于该链表的某一个HeroNode的编号
* 2.将需要添加的HeroNode的next指向找到的位置的后一个节点
* 3.将前一个节点的next指向该HeroNode
* */
//TODO 第二种方法与韩老师的不一样,暂时没发现有什么弊端(没用一个标记来决定死循环的退出,采用return来退出
public void addByNO(HeroNode heroNode) {
HeroNode temp = head;//1.定义一个辅助变量temp来遍历,保证头节点不动
while (true) {
if(temp.next == null){//到了链表的最后
break;
}
if (heroNode.no == temp.next.no){
System.out.println("编号已经存在,无法加入");
return;
}
if(heroNode.no < temp.next.no){//找到了需要添加的位置
break;
}
temp = temp.next;
}
//退出循环说明找到了位置
heroNode.next = temp.next;//将需要添加的HeroNode的next指向找到的位置的后一个节点
temp.next = heroNode;
}
4.链表的遍历显示
//遍历显示链表
/*
* 思路
* 1.定义辅助变量来帮助遍历
* 2.先判断链表是否为空
* */
public void showList() {
if (head.next == null) {//先判断链表是否为空
System.out.println("链表为空");
return;
}
HeroNode temp = head.next;//1.定义一个辅助变量temp来遍历,保证头节点不动
while (true) {
//TODO temp == null这个判断条件很重要,我第一次测试失败,写的temp.next == null,这样会导致遍历到最后一个
//节点的时候,判断下一个为空,就退出循环了,不会输出最后一个节点的信息
if (temp == null) {//判断是否到了链表的最后
break;
}
System.out.println(temp);//输出节点信息
temp = temp.next;//遍历的关键条件
}
}
}
5.修改节点
//修改节点信息
/*
* 思路
* 找到要修改的节点(根据no编号)
* 直接修改信息即可
* */
public void update(HeroNode newHeroNode) {
//判断是否为空
if (head.next == null) {//先判断链表是否为空
System.out.println("链表为空");
return;
}
//定义一个辅助变量来方便遍历
HeroNode temp = head.next;
while (true) {
if (temp == null) {//到了链表的最后,没找到
System.out.println("没找到需要修改的节点");
return;
}
if (temp.no == newHeroNode.no) {//找到了需要修改的节点
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
break;
}
temp = temp.next;
}
}
6.删除节点功能
//删除节点
/*
* 思路分析
* 我们要把待删除节点给删掉,然后将待删除节点的前一个节点的next指向传入的新节点,再将新节点的
* next指向待删除的后一个节点,所以我们需要找到待删除节点的前一个节点,而不是待删除节点(因为单链表只有next)
* 1.先找到待删除节点的前一个节点
* 2.然后将该节点next指向该节点的next.next节点
* 3.jvm会检测到被删除的节点没有引用指向了,会自动回收(省去了待删除节点的next置空操作)
* */
public void delete(int no) {
//定义一个辅助变量帮助遍历
HeroNode temp = head;
while (true) {
if (temp.next == null) {//表示已经到链表的最后了,包含了链表为空的情况
System.out.println("没找到该节点");
return;
}
if (temp.next.no == no) {//找到了待删除节点的前一个节点
temp.next = temp.next.next;//将该节点next指向该节点的next.next节点,即删除操作
break;
}
temp = temp.next;//temp后移,遍历
}
}
单链表的常用方法
1.求单链表中有效节点个数
//方法:求单链表中有效节点的个数(如果是带头结点的链表,需求不统计头节点)
/*
* 思路分析:
* 从第一个开始遍历,用一个变量来计数,到了末尾结束
* */
public static int getLength(HeroNode head) {
if (head.next == null) {//链表为空
return 0;
}
int length = 0;
HeroNode temp = head.next;//定义一个辅助变量,没有统计头节点
while (temp != null) {
length++;
temp = temp.next;
}
return length;
}
2.查找单链表中倒数第k个节点
//查找单链表中的倒数第k个结点(接收head节点,同时接收一个index)
/*
* 思路分析:
* 上面我们已经知道怎么得到一个链表的有效节点个数,那么我们找倒数第k个节点就很简单了
* 1.先获得链表有效节点个数size
* 2.找到第size - k个节点
* */
public static HeroNode findLastIndexNode(HeroNode head, int index) {
//判断链表是否为空
if (head.next == null) {
return null;//链表为空
}
int size = getLength(head);//第一次遍历获得链表有效节点个数
//对传进来的index做一个校验
if (index <= 0 || index > size) {
return null;//传入的index 有误
}
HeroNode temp = head.next;//定义辅助变量
for (int i = 0; i < size - index; i++) {
temp = temp.next;//后移
}
return temp;//出循环说明找到了,返回
}
3.单链表的反转
//单链表的反转
/*
* 思路分析:
* 1.创建一个新的链表(定义一个头节点)来帮助我们存储反转后的链表
* 2.遍历原链表,把每次遍历到的节点插入到新链表的最前面(排除头节点)
* 3.最后把原链表的头节点指向新链表的第一个节点
* */
public static void reversalList(HeroNode head) {
//空链表和一个节点的链表无需反转
if (head.next == null || head.next.next == null) {
return;
}
HeroNode revHead = new HeroNode(0, "", "");//定义一个新链表
HeroNode temp = head.next;//定义一个辅助变量
HeroNode next = null;//指向当前节点的下一个节点
while (temp != null) {
next = temp.next;//指向当前节点的下一个节点,起到暂时保存的作用
temp.next = revHead.next;//让该节点指向新链表的第一个节点
revHead.next = temp;//新链表的头节点指向该节点
temp = next;//temp 后移
}
//将原链表的头节点接上,实现单链表的反转
head.next = revHead.next;
}
4.逆序打印单链表
/*
* 思路分析
* 方法一:可以将链表反转之后再打印,但是这样会破坏链表原本的结构,不推荐
* 方法二:可以利用栈这个数据结构帮助我们逆序打印(因为栈有先进后出的特点)
* */
public static void reversePrint(HeroNode head){
//判断链表是否为空
if(head.next == null){
return;
}
Stack<HeroNode> heroNodes = new Stack<>();//创建一个栈
HeroNode temp = head.next;//创建一个辅助变量
while (temp != null){
heroNodes.add(temp);//将节点压入栈
temp = temp.next;//后移
}
while (heroNodes.size() > 0){
//打印出栈,因为栈有先进后出的特点,符合逆序打印的要求
System.out.println(heroNodes.pop());
}
}
单链表实现的个人总结心得
我就个人而言总结了单链表实现和一些方法的心得
1.有关单链表的遍历,得设置一个辅助遍历来进行,这样可以不破坏原来链表的结构(保证头节点不动),遍历一般都需要将辅助变量后移(循环的关键条件)
2.关于打印显示操作,有时候需要考虑链表是否为空
3.有时候进行对链表的顺序发生改变的操作,需要新建一个链表来进行辅助完成,即定义一个头节点
4.对于链表的节点的next = 另外一个节点的时候,有时候需要理解为指向,有时候需要理解为赋值才能更好的获得解题思路
5.对于单链表的最后一个节点的next等于空,这个细节需要时刻记住
6.由于单链表只有date域和next域,有时候需要考虑遍历到当前节点,又有时候需要遍历到当前节点的前一个节点(因为遍历到当前节点就找不到前一个节点了)