文章目录
- 一、链表(LinkedList)简介
- 二、单链表
- 2.1 应用实例
- 2.2 Java代码实现
- 2.3 面试题(新浪、百度、腾讯)
- 三、双向链表
- 3.1 思路分析
- 3.2 Java代码实现
- 四、单向环形链表
- 4.1 应用场景
- 4.2 约瑟夫(Josephu)问题
- 4.3 Java代码求解Josephu问题
一、链表(LinkedList)简介
链表是有序的列表,它在内存中的存储如下:
- 链表是以节点的方式来存储, 是链式存储
- 每个节点包含 data 域, next 域:指向下一个节点.
- 如图: 发现链表的各个节点不一定是连续存储.
- 链表分带头节点的链表和没有头节点的链表, 根据实际的需求来确定
单链表(带头结点) 逻辑结构示意图如下:
二、单链表
2.1 应用实例
使用带 head 头的单向链表实现 - 水浒英雄排行榜管理完成对英雄人物的增删改查操作
- 第一种方法在添加英雄时, 直接添加到链表的尾部
- 第二种方式在添加英雄时, 根据排名将英雄揷入到指定位置 (如果有这个排名, 则添加失败, 并给出提示)
- 修改节点功能
思路(1) 先找到该节点, 通过遍历, (2) temp.name = newHeroNode.name ; temp.nickname - 删除节点
2.2 Java代码实现
public class SingleLinkedList {
public static void main(String[] args) {
// 进行测试
// 创建单链表对象
SingleLinkedList singleLinkedList = new SingleLinkedList();
// 增加节点
System.out.println("增加节点:");
singleLinkedList.add(new HeroNode("wskh"));
singleLinkedList.add(new HeroNode("lso"));
singleLinkedList.add(new HeroNode("wrq"));
singleLinkedList.add(new HeroNode("sxc"));
singleLinkedList.list();
// 删除节点
System.out.println("删除节点:");
singleLinkedList.remove("wrq");
singleLinkedList.remove("sxc");
singleLinkedList.list();
}
// 当前节点的英雄
HeroNode heroNode;
// 子节点
SingleLinkedList next;
// 添加节点
public void add(HeroNode heroNode){
if(this.heroNode == null){
this.heroNode = heroNode;
next = new SingleLinkedList();
}else{
next.add(heroNode);
}
}
// 根据名字删除节点
public void remove(String name){
if(heroNode != null && name.equals(heroNode.name)){
if(next != null){
heroNode = next.heroNode;
next = next.next;
}else{
heroNode = null;
}
}else if(next != null){
next.remove(name);
}
}
// 输出链表中所有的英雄节点信息
public void list() {
if (this.heroNode != null) {
System.out.println(heroNode.name);
if (this.next != null) {
next.list();
}
}
}
// 英雄节点类
static class HeroNode {
String name;
public HeroNode(String name) {
this.name = name;
}
}
}
输出:
增加节点:
wskh
lso
wrq
sxc
删除节点:
wskh
lso
2.3 面试题(新浪、百度、腾讯)
- 求单链表中有效节点的个数
- 查找单链表中的倒数第k个结点
- 单链表的反转
- 从尾到头打印单链表
- 合并两个有序链表
三、双向链表
3.1 思路分析
使用带 head 头的双向链表实现 - 水汻英雄排行榜
管理单向链表的缺点分析:
- 单向链表, 查找的方向只能是一个方向, 而双向链表可以向前或者向后查找。
- 单向链表不能自我删除, 需要靠辅助节点, 而双向链表, 则可以自我删除, 所以前面我们单链表删除 时节点, 总是找到 temp,temp 是待删除节点的前一个节点(认真体会).
- 分析了双向链表如何完成遍历, 添加, 修改和删除的思路
分析 双向链表的遍历, 添加, 修改, 删除的操作思路
- 遍历 方和 单链表一样, 只是可以向前, 也可以向后查找
- 添加(默认添加到双向链表的最后)
(1) 先找到双向链表的最后这个节点
(2) temp.next newHeroNode
(3) newHeroNode.pre = temp; - 修改 思路和 原来的单向链表一样.
- 删除
(1) 因为是双向链表, 因此, 我们可以实现自我删除某个节点
(2) 直接找到要删除的这个节点, 比如 temp
(3) temp.pre.next = temp.next
(4) temp.next.pre = temp.pre;
3.2 Java代码实现
public class SingleCircularLinkedList {
public static void main(String[] args) {
// 进行测试
// 创建单链表对象
SingleCircularLinkedList singleCircularLinkedList = new SingleCircularLinkedList();
// 增加节点
System.out.println("增加节点:");
singleCircularLinkedList.add(singleCircularLinkedList, new HeroNode("wskh"));
singleCircularLinkedList.add(singleCircularLinkedList, new HeroNode("lso"));
singleCircularLinkedList.add(singleCircularLinkedList, new HeroNode("wrq"));
singleCircularLinkedList.add(singleCircularLinkedList, new HeroNode("sxc"));
singleCircularLinkedList.list();
// 删除节点
System.out.println("删除节点:");
singleCircularLinkedList.remove("wrq");
singleCircularLinkedList.remove("sxc");
singleCircularLinkedList.list();
// 继续删除节点
System.out.println("继续删除节点:");
singleCircularLinkedList.remove("wskh");
singleCircularLinkedList.remove("lso");
singleCircularLinkedList.list();
}
// 当前节点的英雄
HeroNode heroNode;
// 子节点
SingleCircularLinkedList next;
// 添加节点
public void add(SingleCircularLinkedList head, HeroNode heroNode) {
if (this.next == null) {
this.heroNode = heroNode;
this.next = head;
} else if (this.next.heroNode.name.equals(head.heroNode.name)) {
SingleCircularLinkedList singleCircularLinkedList = new SingleCircularLinkedList();
singleCircularLinkedList.heroNode = heroNode;
singleCircularLinkedList.next = head;
next = singleCircularLinkedList;
} else {
next.add(head, heroNode);
}
}
// 根据名字删除节点
public void remove(String name) {
if (heroNode != null) {
if (name.equals(heroNode.name)) {
if (heroNode.name.equals(next.heroNode.name)) {
// 最后一个节点
heroNode = null;
next = null;
return;
} else if (heroNode.name.equals(next.next.heroNode.name)) {
// 还剩两个点
heroNode = next.heroNode;
} else {
heroNode = next.heroNode;
next = next.next;
}
} else {
next.remove(name);
}
}
}
// 输出链表中所有的英雄节点信息
public void list() {
if (this.heroNode != null) {
Set<String> nameSet = new HashSet<>();
Queue<SingleCircularLinkedList> queue = new LinkedList<>();
queue.add(this);
while (!queue.isEmpty()) {
SingleCircularLinkedList poll = queue.poll();
if (!nameSet.contains(poll.heroNode.name)) {
nameSet.add(poll.heroNode.name);
System.out.println(poll.heroNode.name);
if (poll.next != null) {
queue.add(poll.next);
}
}
}
}
}
// 英雄节点类
static class HeroNode {
String name;
public HeroNode(String name) {
this.name = name;
}
}
}
输出:
增加节点:
wskh
lso
wrq
sxc
删除节点:
wskh
lso
继续删除节点:
四、单向环形链表
4.1 应用场景
Josephu 问题为: 设编号为 的 个人围坐一圈, 约定编号为 的人从 1 开始报数, 数 到 的那个人出列, 它的下一位又从 1 开始报数, 数到 的那个人又出列, 依次类推, 直到所有人出列为止, 由 此产生一个出队编号的序列。
提示: 用一个不带头结点的循环链表来处理 Josephu 问题: 先构成一个有 个结点的单循环链表, 然后由 结 点起从 1 开始计数, 计到
4.2 约瑟夫(Josephu)问题
提示
用一个不带头结点的循环链表来处理 Josephu 问题: 先构成一个有 个结点的单循环链表, 然后由 结点起从 1 开 始计数, 计到
4.3 Java代码求解Josephu问题
public class Josephu {
public static void main(String[] args) {
solveJosephu(5, 1, 2);
}
/**
* @param n 人数
* @param k 从第k个人开始报数
* @param m 数几下删除一次
* @Description
*/
public static void solveJosephu(int n, int k, int m) {
// 根据人数n初始化单向环形链表
SingleCircularLinkedList singleCircularLinkedList = new SingleCircularLinkedList();
for (int i = 1; i <= n; i++) {
singleCircularLinkedList.add(singleCircularLinkedList, new People(i));
}
// 开始约瑟夫问题的求解
singleCircularLinkedList.josephuRemove(1, k, m);
}
// 单向环形链表
static class SingleCircularLinkedList {
// 当前节点的英雄
People people;
// 子节点
SingleCircularLinkedList next;
// 添加节点
public void add(SingleCircularLinkedList head, People people) {
if (this.next == null) {
this.people = people;
this.next = this;
} else if (this.next.people.id == head.people.id) {
SingleCircularLinkedList singleCircularLinkedList = new SingleCircularLinkedList();
singleCircularLinkedList.people = people;
singleCircularLinkedList.next = head;
next = singleCircularLinkedList;
} else {
next.add(head, people);
}
}
// 根据名字删除节点
public void josephuRemove(int curCnt, int k, int m) {
if (people != null) {
if ((k > 0 && curCnt == k) || (k < 0 && curCnt == m)) {
if (k > 0) {
k = -1;
this.josephuRemove(1, k, m);
} else {
System.out.print(people.id);
if (people.id == next.people.id) {
// 最后一个节点,结束算法
System.out.print("\n");
people = null;
next = null;
return;
} else if (people.id == next.next.people.id) {
// 还剩两个点
System.out.print("->");
people = next.people;
} else {
System.out.print("->");
people = next.people;
next = next.next;
}
// 非最后一个节点,继续往后进行约瑟夫过程
this.josephuRemove(1, k, m);
}
} else {
next.josephuRemove(curCnt + 1, k, m);
}
}
}
// 输出链表中所有的英雄节点信息
public void list() {
if (this.people != null) {
Set<Integer> idSet = new HashSet<>();
Queue<SingleCircularLinkedList> queue = new LinkedList<>();
queue.add(this);
while (!queue.isEmpty()) {
SingleCircularLinkedList poll = queue.poll();
if (!idSet.contains(poll.people.id)) {
idSet.add(poll.people.id);
System.out.println(poll.people.id);
if (poll.next != null) {
queue.add(poll.next);
}
}
}
}
}
}
// 人类
static class People {
int id;
public People(int id) {
this.id = id;
}
}
}
输出:
2->4->1->5->3