1、链表(Linked List)介绍
1.1、内存结构
1.2、逻辑结构
1.3、链表特点
- 链表是以节点的方式来存储,是链式存储
- data 域存放数据,next 域指向下一个节点
- 链表分带头节点的链表和没有头节点的链表, 根据实际的需求来确定
2、链表应用场景
2.1、水浒英雄榜
- 使用带 head 头的单向链表实现【水浒英雄排行榜管理】
2.2、链表节点定义
- 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.3、链表定义
- DummyHead :头结点不存放数据,仅仅作为当前链表的入口
- head 字段的值不能改变,一旦改变,就丢失了整个链表的入口,我们也就无法通过 head 找到链表了
class SingleLinkedList {
private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return head;
}
2.4、遍历链表
2.4.1、代码思路
- 定义辅助变量 temp ,相当于一个指针,指向当前节点
- 何时遍历完成?temp == null 表明当前节点为 null ,即表示已到链表末尾
- 如何遍历?temp = temp.next ,每次输出当前节点信息之后,temp 指针后移
2.4.2、代码实现
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;
}
}
2.5、尾部插入
2.5.1、代码思路
- 定义辅助变量 temp ,相当于一个指针,指向当前节点
- 如何在链表末尾插入节点?
- 首先需要遍历链表,找到链表最后一个节点,当 temp.next == null时,temp 节点指向链表最后一个节点
- 然后在 temp 节点之后插入节点即可:temp.next = heroNode
2.5.2、代码实现
public void add(HeroNode heroNode) {
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = 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.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
singleLinkedList.list();
}
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
2.6、按顺序插入
2.6.1、代码思路
- 定义辅助变量 temp ,相当于一个指针,指向当前节点
- 应该如何执行插入?(待插入节点为 heroNode)
- 首先需要遍历链表,找到链表中编号值比 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.6.2、代码实现
public void addByOrder(HeroNode heroNode) {
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) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", heroNode.no);
} else {
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();
}
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
2.7、修改节点信息
2.7.1、代码思路
- 定义辅助变量 temp ,相当于一个指针,指向当前节点
- 如何找到指定节点?temp.no = newHeroNode.no
2.7.2、代码实现
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 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);
HeroNode newHeroNode = new HeroNode(2, "小卢", "玉麒麟~~");
singleLinkedList.update(newHeroNode);
singleLinkedList.list();
}
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=小卢, nickName=玉麒麟~~]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
2.8、删除节点
2.8.1、代码思路
- 定义辅助变量 temp ,相当于一个指针,指向当前节点
- 如何找到待删除的节点?遍历链表,当 temp.next == no 时,temp.next 节点就是待删除的节点
- 如何删除?temp = temp.next.next 即可删除 temp.next 节点,该节点没有引用指向它,会被垃圾回收机制回收
2.8.2、代码实现
public void del(int no) {
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 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.del(1);
singleLinkedList.del(4);
singleLinkedList.list();
}
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
2.9、总结
- 遍历链表,执行操作时,判断条件有时候是 temp ,有时候是 temp.next ,Why?
- 对于插入、删除节点来说,需要知道当前待操作的节点(heroNode)前一个节点的地址(指针),如果直接定位至当前待操作的节点 heroNode ,那没得玩。。。因为不知道heroNode 前一个节点的地址,无法进行插入、删除操作,所以 while 循环中的条件使用 temp.next 进行判断
- 对于更新、遍历操作来说,我需要的仅仅就只是当前节点的信息,所以 while 循环中的条件使用 temp进行判断
- 头结点与首节点
3、单链表面试题
3.1、求单链表中有效节点的个数
3.1.1、代码思路
3.1.2、代码实现
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;
}
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("有效的节点个数=" + getLength(singleLinkedList.getHead()));
}
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
有效的节点个数=4
3.2、查找单链表中的倒数第 k 个结点
3.2.1、代码思路
- 查找单链表中的倒数第k个结点 【新浪面试题】
- 首先,获取整个链表中元素的个数 size
- 在使用 for 循环定位至倒数第 index(形参) 个节点,返回即可
- for 循环的条件应如何确定?for (int i = 0; i < x; i++) 中 x 的值应是多少?我们需要定位至倒数第 index 个节点,在 for 循环之前,我们已经定位置首节点,还需再走 (size - index ) 步,定位至倒数第 index 个节点
- 举例说明:链表中一共有 4 个元素,想要定位至倒数第 2 个节点,那么需要在首节点之后走两步,到达倒数第 2 个节点
3.2.2、代码实现
public static HeroNode findLastIndexNode(HeroNode head, int index) {
if (head.next == null) {
return null;
}
int size = getLength(head);
if (index <= 0 || index > size) {
return null;
}
HeroNode cur = head.next;
for (int i = 0; i < size - index; i++) {
cur = cur.next;
}
return cur;
}
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();
HeroNode res = findLastIndexNode(singleLinkedList.getHead(), 2);
System.out.println("res=" + res);
}
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
res=HeroNode [no=3, name=吴用, nickName=智多星]
3.3、单链表的反转
3.3.1、代码思路
- 单链表的反转【腾讯面试题,有点难度】
- 定义一个新的头结点 reverseHead ,一点一点将链表反转后,再串起来
- 怎么个串法?
- 在原链表中每读取一个节点(cur),先保存其下一个节点的地址(next),然后将 cur 节点放在新链表的最前面
- 然后执行遍历:cur = next ,即指针后移
- 遍历完成后,新链表即是反转后的链表
- 如何将 cur 节点插入在新链表的最前面
- cur.next = reverseHead.next;
- reverseHead.next = cur;
- while 循环终止条件? cur == null :已遍历至链表尾部
3.3.2、代码实现
public static void reversetList(HeroNode head) {
if (head.next == null || head.next.next == null) {
return;
}
HeroNode cur = head.next;
HeroNode next = null;
HeroNode reverseHead = new HeroNode(0, "", "");
while (cur != null) {
next = cur.next;
cur.next = reverseHead.next;
reverseHead.next = cur;
cur = next;
}
head.next = reverseHead.next;
}
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);
System.out.println("原来链表的情况~~");
singleLinkedList.list();
System.out.println("反转单链表~~");
reversetList(singleLinkedList.getHead());
singleLinkedList.list();
}
原来链表的情况~~
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
反转单链表~~
HeroNode [no=4, name=林冲, nickName=豹子头]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=1, name=宋江, nickName=及时雨]
3.4、单链表的反转(参考代码)
3.4.1、代码思路
- 单链表的反转【腾讯面试题,有点难度】
- 原链表为 cur 指向 next ,反转链表不就是把 next 指向 cur 吗?
- 由于 next 指向 cur 时,next 将丢失其下一节点的地址,所以需要先将 nnext 保存起来
- next ==null 时链表已经反转完毕,最后将头结点指向 cur 节点即可
3.4.2、代码实现
public static void myReversetList(HeroNode head) {
if (head.next == null || head.next.next == null) {
return;
}
HeroNode cur = head.next;
HeroNode next = cur.next;
cur.next = null;
while (next != null) {
HeroNode nnext = next.next;
next.next = cur;
cur = next;
next = nnext;
}
head.next = cur;
}
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);
System.out.println("原来链表的情况~~");
singleLinkedList.list();
System.out.println("反转单链表~~");
reversetList(singleLinkedList.getHead());
singleLinkedList.list();
}
原来链表的情况~~
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
反转单链表~~
HeroNode [no=4, name=林冲, nickName=豹子头]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=1, name=宋江, nickName=及时雨]
3.5、从尾到头打印单链表
3.5.1、栈的基本使用
public static void main(String[] args) {
Stack<String> stack = new Stack();
stack.add("jack");
stack.add("tom");
stack.add("smith");
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
smith
tom
jack
3.5.2、代码思路
- 从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
- 方式一:先将单链表进行反转操作,然后再遍历输出,问题:破坏原链表结构,不可取
- 方式二:遍历链表,去除节点压入栈中,利用栈先进后出的特点,实现逆序打印
3.5.3、代码实现
public static 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;
}
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
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);
System.out.println("原来链表的情况~~");
singleLinkedList.list();
System.out.println("测试逆序打印单链表, 没有改变链表的结构~~");
reversePrint(singleLinkedList.getHead());
}
原来链表的情况~~
HeroNode [no=1, name=宋江, nickName=及时雨]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=4, name=林冲, nickName=豹子头]
测试逆序打印单链表, 没有改变链表的结构~~
HeroNode [no=4, name=林冲, nickName=豹子头]
HeroNode [no=3, name=吴用, nickName=智多星]
HeroNode [no=2, name=卢俊义, nickName=玉麒麟]
HeroNode [no=1, name=宋江, nickName=及时雨]
3.6、合并两个有序的单链表
3.6.1、代码思路
- 合并两个有序的单链表,合并之后的链表依然有序【课后练习】
3.6.2、代码实现
3.7、单向链表所有代码
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);
System.out.println("原来链表的情况~~");
singleLinkedList.list();
System.out.println("反转单链表~~");
reversetList(singleLinkedList.getHead());
singleLinkedList.list();
System.out.println("测试逆序打印单链表, 没有改变链表的结构~~");
reversePrint(singleLinkedList.getHead());
singleLinkedList.addByOrder(hero1);
singleLinkedList.addByOrder(hero4);
singleLinkedList.addByOrder(hero2);
singleLinkedList.addByOrder(hero3);
singleLinkedList.list();
HeroNode newHeroNode = new HeroNode(2, "小卢", "玉麒麟~~");
singleLinkedList.update(newHeroNode);
System.out.println("修改后的链表情况~~");
singleLinkedList.list();
singleLinkedList.del(1);
singleLinkedList.del(4);
System.out.println("删除后的链表情况~~");
singleLinkedList.list();
System.out.println("有效的节点个数=" + getLength(singleLinkedList.getHead()));
HeroNode res = findLastIndexNode(singleLinkedList.getHead(), 3);
System.out.println("res=" + res);
}
public static 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;
}
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
public static void reversetList(HeroNode head) {
if (head.next == null || head.next.next == null) {
return;
}
HeroNode cur = head.next;
HeroNode next = null;
HeroNode reverseHead = new HeroNode(0, "", "");
while (cur != null) {
next = cur.next;
cur.next = reverseHead.next;
reverseHead.next = cur;
cur = next;
}
head.next = reverseHead.next;
}
public static void myReversetList(HeroNode head) {
if (head.next == null || head.next.next == null) {
return;
}
HeroNode cur = head.next;
HeroNode next = cur.next;
cur.next = null;
while (next != null) {
HeroNode nnext = next.next;
next.next = cur;
cur = next;
next = nnext;
}
head.next = cur;
}
public static HeroNode findLastIndexNode(HeroNode head, int index) {
if (head.next == null) {
return null;
}
int size = getLength(head);
if (index <= 0 || index > size) {
return null;
}
HeroNode cur = head.next;
for (int i = 0; i < size - index; i++) {
cur = cur.next;
}
return cur;
}
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;
}
}
class SingleLinkedList {
private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return head;
}
public void add(HeroNode heroNode) {
HeroNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = 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) {
break;
} else if (temp.next.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
System.out.printf("准备插入的英雄的编号 %d 已经存在了, 不能加入\n", heroNode.no);
} else {
heroNode.next = temp.next;
temp.next = heroNode;
}
}
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 del(int no) {
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;
}
}
}
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 + "]";
}
}
|