-
链表 Linked List
链表介绍
-
链表时有序的列表,但是它在内存中是存储如下
小结
- 链表是以节点的方式来存储,是链式存储
- 每个节点包含 data域,next域:指向下一个节点
- 如图:发现链表的各个节点不一定是连续存储
- 链表带头节点的链表和没带头节点的链表,根据实际的需求来确定
单链表
单链表(带头节点)逻辑示意图
单链表的应用实例
使用带 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(hero4);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
//显示一把
singleLinkedList.list();
}
}
//定义 SingleLinkedListDemo 管理我们的英雄
class SingleLinkedList {
//先初始化一个头节点,头节点不要动,不存放具体的数据
private HeroNode head = new HeroNode(0, "", "");
//添加节点到单向链表
/*
* 思路:当不考虑编号顺序时
* 1. 找到当前链表的最后节点,
* 2. 将最后这个节点的next 指向 新的节点
* */
public void add(HeroNode heroNode) {
//因为 head节点不能动,因此我们需要一个辅助遍历 temp
HeroNode temp = head;
//遍历链表找到最后,
while (true) {
//找到链表的最后
if (temp.next == null) {
break;
}
//如果没有找到最后
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 next就不要显示了
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
添加-顺序添加
//第二种方时在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
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 = temp.next;
}
//判断 flag的值
if (flag) {//不能添加,说明编号已经存在
System.out.println("准备插入的英雄的编号" + heroNode.no + "已经存在不能添加");
} else {
//插入到链表中 temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
更新
思路:
- 先找到该节点,通过遍历
-
temp.neme = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
//说明
//修改节点的信息,根据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.println("没有找到编号" + newHeroNode.no + "的节点,不能修改");
}
}
删除
//删除节点
//思路
//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 = temp.next;
}
//判断 flag
if (flag) {
//可以删除
temp.next = temp.next.next;
} else {//未找到该节点
System.out.println("找删除的" + no + "节点未找到");
}
}
单链表的面试题
- 求单链表中有效节点的个数
//方法:获取单链表的节点的个数(如果是带头节点的链表,需要不统计头节点)
/**
* @param head 链表的头节点
* @return int 返回的就是有效节点的个数
*/
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;
}
- 查找单链表中的倒数第k个结点 【新浪面试题】
//查找单链表中的倒数第k个结点 【新浪面试题】
/**
* 思路:
* 1. 编写一个方法,接收 head节点,同时接收一个 index
* 2. index 表示倒数第 index个节点
* 3. 先把链表从到位遍历,得到链表的总的长度 [调用 getLength方法 获取单链表的个数]
* 4. 得到 size后,我们从链表的第一个开始遍历(size - index)个,就可以得到
* 5. 如果找到,则返回该节点,否则返回 null
*
* @param head 单链表头节点
* @param index 倒数第n个节点的下标
* @return 倒数第n个节点
*/
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; //假如3个有效数据 //3-1=2
for (int i = 0; i < size - index; i++) {
cur = cur.next;
}
return cur;
}
- 单链表的反转【腾讯面试题,有点难度】
//单链表的反转【腾讯面试题,有点难度】
/**
* 思路:
* 1. 先定义一个节点 reverseHead = new heroNode();
* 2. reverseHead节点;不存放具体的数据;作用就是表示单链表头 next
* 3. 从头到位遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表 reverseHead的最前端
* 4. 原来的链表的 head.next=reverseHead.next
*
* @param head 需要反转的头节点
*/
public static void reverseList(HeroNode head) {
//如果当前链表为空,或者只有一个节点,无需反转,直接返回
if (head.next == null || head.next.next == null) {
return;
}
//定义一个辅助的指针(变量),帮助我们遍历原来的链表
HeroNode cur = head.next;
HeroNode next = null;//指向当前节点[cur]的下一个节点
//定义:reverseHead节点;不存放具体的数据;作用就是表示单链表头 next
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;
}
- 从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
//从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
/**
* 思路:
* 1. 上面的题的要求就是逆序打印单链表
* 2. 方式1: 先将单链表进行反转操作,然后在遍历即可,这样做的问题是会破坏原来的单链表结构,不建议
* 3. 方式2: 可以利用栈这个数据结构,将各个节点压入到栈[先进后出,比如子弹进子弹夹]中,然后
* 利用栈的先进后出的特点,就实现了逆序打印的效果
* 采用方式二
*
* @param head 单链表头部
*/
public static void reversePrint(HeroNode head) {
if (head.next == null) {
return;//空链表不能打印
}
//创建一个栈,将各个节点压入栈中
//Stack特点; 先进后出
Stack<HeroNode> stack = new Stack<>();
HeroNode cur = head.next;
//将链表的所有节点压入栈中
while (cur != null) {
stack.push(cur);
//一定rur要后移,这样就能压入下一个节点
cur = cur.next;
}
//将栈中的节点打印,pop 出栈
while (stack.size() > 0) {
System.out.println(stack.pop());//弹栈
}
}
- 合并两个有序的单链表,合并之后的链表依然有序
和反转很像,创建一个新的链表发现链表中的哪一个更小就把他假如到新的链表中
双链表
__EOF__
最新更新
python爬虫及其可视化
使用python爬取豆瓣电影短评评论内容
nodejs爬虫
Python正则表达式完全指南
爬取豆瓣Top250图书数据
shp 地图文件批量添加字段
爬虫小试牛刀(爬取学校通知公告)
【python基础】函数-初识函数
【python基础】函数-返回值
HTTP请求:requests模块基础使用必知必会
SQL SERVER中递归
2个场景实例讲解GaussDB(DWS)基表统计信息估
常用的 SQL Server 关键字及其含义
动手分析SQL Server中的事务中使用的锁
openGauss内核分析:SQL by pass & 经典执行
一招教你如何高效批量导入与更新数据
天天写SQL,这些神奇的特性你知道吗?
openGauss内核分析:执行计划生成
[IM002]Navicat ODBC驱动器管理器 未发现数据
初入Sql Server 之 存储过程的简单使用
uniapp/H5 获取手机桌面壁纸 (静态壁纸)
[前端] DNS解析与优化
为什么在js中需要添加addEventListener()?
JS模块化系统
js通过Object.defineProperty() 定义和控制对象
这是目前我见过最好的跨域解决方案!
减少回流与重绘
减少回流与重绘
如何使用KrpanoToolJS在浏览器切图
performance.now() 与 Date.now() 对比