链表
创始人
2024-02-01 00:32:56

章节目录:

    • 一、链表
      • 1.1 概述
    • 二、单向链表
      • 2.1 实现思路
      • 2.2 代码示例
    • 三、双向链表
      • 3.1 实现思路
      • 3.2 代码示例
    • 四、单向环形链表
      • 4.1 约瑟夫问题
      • 4.2 实现思路
      • 4.3 代码示例
    • 五、结束语

一、链表

1.1 概述

  • 链表是一种物理存储单元上非连续非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

  • 链表由一系列结点组成(链表中每一个元素称为结点),结点可以在运行时动态生成。

  • 每个结点包括两个部分:一个是存储数据元素的数据域(data域),另一个是存储下一个结点地址的指针域(next域)。

  • 链表分带头节点的链表没有头节点的链表,根据实际的需求来确定。

  • 示意图

在这里插入图片描述

二、单向链表

2.1 实现思路

需求:使用带头节点的单向链表,完成对学生对象的增删改查操作。

  • 添加方式一(链尾添加)

在这里插入图片描述

  • 添加方式二(根据顺序(指定位置)添加)

在这里插入图片描述

  • 修改节点:遍历并通过数据域信息进行判断,找到该节点后完成相应的修改操作。
  • 删除节点

在这里插入图片描述

2.2 代码示例

需求:实现单向链表的增(顺序链尾添加、指定位置添加)删改查、找到单链表倒数第几的节点、获取当前链表大小(即有效数据个数)、链表反转(改变链表结构)、反向打印(不破坏链表结构)、合并两个链表并排序

public class SingleLinkedListDemo {public static void main(String[] args) {SingleLinkedList singleLinkedList = new SingleLinkedList();System.out.println("---[顺序添加]结果如下:---");singleLinkedList.add(new Student(1, "张三"));singleLinkedList.add(new Student(2, "李四"));singleLinkedList.add(new Student(4, "田七"));SingleLinkedList.foreach(singleLinkedList.getHead());// ---[顺序添加]结果如下:---// no=1, name=张三// no=2, name=李四// no=4, name=田七System.out.println();System.out.println("---[指定位置添加]结果如下:---");singleLinkedList.addByOrder(new Student(3, "(赵六)"));SingleLinkedList.foreach(singleLinkedList.getHead());// ---[指定位置添加]结果如下:---// no=1, name=张三// no=2, name=李四// no=3, name=(赵六)// no=4, name=田七System.out.println();System.out.println("---[链表反转]结果如下:---");SingleLinkedList.reverse(singleLinkedList.getHead());SingleLinkedList.foreach(singleLinkedList.getHead());// ---[链表反转]结果如下:---// no=4, name=田七// no=3, name=(赵六)// no=2, name=李四// no=1, name=张三System.out.println();System.out.println("---[链表反向打印]结果如下:---");SingleLinkedList.reversePrint(singleLinkedList.getHead());// ---[链表反向打印]结果如下:---// no=1, name=张三// no=2, name=李四// no=3, name=(赵六)// no=4, name=田七System.out.println();System.out.println("---[修改节点]结果如下:---");singleLinkedList.update(new Student(3, "(王九)"));SingleLinkedList.foreach(singleLinkedList.getHead());// ---[修改节点]结果如下:---// no=4, name=田七// no=3, name=(王九)// no=2, name=李四// no=1, name=张三System.out.println();System.out.println("---[删除节点]结果如下:---");singleLinkedList.delete(3);SingleLinkedList.foreach(singleLinkedList.getHead());// ---[删除节点]结果如下:---// no=4, name=田七// no=2, name=李四// no=1, name=张三System.out.println();System.out.println("---获取[当前链表中有效数据的个数]结果如下:---");System.out.println(SingleLinkedList.size(singleLinkedList.getHead()));// ---获取[当前链表中有效数据的个数]结果如下:---// 3System.out.println();System.out.println("---获取[当前链表中倒数第二个数据]结果如下:---");Student reciprocalNode = SingleLinkedList.findReciprocalNode(singleLinkedList.getHead(), 2);System.out.println(reciprocalNode);// ---获取[当前链表中倒数第二个数据]结果如下:---// no=2, name=李四}/*** 测试两个链表合并后排序。*/@Testpublic void testMerge() {SingleLinkedList s1 = new SingleLinkedList();s1.add(new Student(1, "a"));s1.add(new Student(3, "c"));s1.add(new Student(5, "e"));SingleLinkedList s2 = new SingleLinkedList();s2.add(new Student(2, "b"));s2.add(new Student(4, "d"));Student mergeHead = SingleLinkedList.mergeAndSorted(s1.getHead(), s2.getHead());assert mergeHead != null;System.out.println("---[链表合并排序后]结果如下:---");SingleLinkedList.foreach(mergeHead);// ---[链表合并排序后]结果如下:---// no=1, name=a// no=2, name=b// no=3, name=c// no=4, name=d// no=5, name=e}
}/*** 学生节点。*/
class Student {public int no;public String name;/*** 指向后一节点。*/public Student next;public Student(int no, String name) {this.no = no;this.name = name;}@Overridepublic String toString() {return "no=" + no +", name=" + name;}
}/*** 单向链表实现。*/
class SingleLinkedList {/*** 头节点(勿动,仅占位)。*/private final Student head = new Student(0, "");public Student getHead() {return head;}/*** 断言链表是否为空。** @param node 节点*/private static void assertLinkedListIsNull(Student node) {if (null == node || null == node.next) {throw new NullPointerException("链表为空!");}}/*** 迭代器。** @param head 头节点*/public static void foreach(Student head) {assertLinkedListIsNull(head);// 由于头节点不能动,所以这里要添加辅助指针来帮助进行遍历。Student pointer = head;while (null != pointer.next) {System.out.println(pointer.next);// 后移。pointer = pointer.next;}}/*** 链尾添加。** @param node 节点*/public void add(Student node) {Student pointer = this.head;while (null != pointer.next) {pointer = pointer.next;}// 循环终止时,表示到了链尾。pointer.next = node;}/*** 根据编号指定位置进行添加。** @param node 节点*/public void addByOrder(Student node) {Student pointer = this.head;boolean exist = false;while (null != pointer.next) {// 1.找到了插入位置。if (pointer.next.no > node.no) {break;// 2.节点编号已存在。} else if (pointer.next.no == node.no) {exist = true;break;}// 后移。pointer = pointer.next;}// 判断循环终止的条件。if (exist) {System.out.printf("编号[%d]的节点已存在,添加失败!\n", node.no);} else {// 插入。node.next = pointer.next;pointer.next = node;}}/*** 更新节点。** @param node 节点*/public void update(Student node) {Student pointer = this.head;boolean found = false;assertLinkedListIsNull(pointer);while (null != pointer.next) {// 找到了需要修改的节点。if (pointer.next.no == node.no) {found = true;break;}pointer = pointer.next;}if (found) {pointer.next.name = node.name;} else {System.out.printf("编号[%d]的节点不存在,修改失败。\n", node.no);}}/*** 删除节点。** @param no 编号*/public void delete(int no) {Student pointer = this.head;boolean found = false;assertLinkedListIsNull(pointer);while (null != pointer.next) {if (pointer.next.no == no) {found = true;break;}pointer = pointer.next;}if (found) {// 指向下下个节点,被跳过节点将失去引用被垃圾回收机制进行回收。pointer.next = pointer.next.next;} else {System.out.printf("编号[%d]的节点不存在,删除失败。\n", no);}}/*** 链表大小(即有效数据个数)。** @param head 节点* @return int*/public static int size(Student head) {Student pointer = head;if (null == pointer.next) {return 0;}int size = 0;while (null != pointer.next) {size++;pointer = pointer.next;}return size;}/*** 找到倒数第几的节点。** @param head  头节点* @param index 倒数第几* @return {@link Student}*/public static Student findReciprocalNode(Student head, int index) {assertLinkedListIsNull(head);int size = size(head);if (0 >= index || index > size) {return null;}Student pointer = head;// 假设3个数据,需要倒数第2个,3-2=1。则循环1次。for (int i = 0; i < (size - index); i++) {pointer = pointer.next;}return pointer.next;}/*** 链表反转(改变链表结构)。** @param head 头节点*/public static void reverse(Student head) {// 链表中没有节点或者只有一个节点时,无需反转。if (null == head.next || null == head.next.next) {return;}// 辅助指针。Student pointer = head.next;// 临时变量,用于存储当前节点的下一节点。Student temp;// 新的链表头。Student reverseHead = new Student(0, "");while (null != pointer) {temp = pointer.next;// 顺序取出后进行赋值。pointer.next = reverseHead.next;reverseHead.next = pointer;// 后移。pointer = temp;}// 节点指向,完成反转。head.next = reverseHead.next;}/*** 反向打印(不破坏链表结构)。** @param head 头*/public static void reversePrint(Student head) {assertLinkedListIsNull(head);Student pointer = head.next;Stack stack = new Stack<>();// 压栈。while (null != pointer) {stack.push(pointer);pointer = pointer.next;}// 弹栈。while (!stack.isEmpty()) {System.out.println(stack.pop());}}/*** 合并两个链表并排序。** @param head1 head1* @param head2 head2* @return {@link Student}*/public static Student mergeAndSorted(Student head1, Student head2) {if (null == head1.next && null == head2.next) {return null;}// 较小头节点。Student head = head1.no <= head2.no ? head1 : head2;// 较小头节点的下一节点。Student cur1 = head.next;// 较大头节点。Student cur2 = (head == head1) ? head2 : head1;// 辅助指针Student pointer = head;while (null != cur1 && null != cur2) {// 较小值插入链表。if (cur1.no <= cur2.no) {pointer.next = cur1;cur1 = cur1.next;} else {pointer.next = cur2;cur2 = cur2.next;}// 后移。pointer = pointer.next;}pointer.next = (null != cur1) ? cur1 : cur2;return head.next;}
}

三、双向链表

3.1 实现思路

与单向链表不同的是,双向链表增加了指向前一节点的属性。

  • 两种链表区别

    • 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找

    • 单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除。所以前面我们单链表删除

      时节点,总是需要找到 pointer 的下一个节点。

  • 双向链表增删改查实现思路分析:

    • 遍历:实现与单链表一样,只是可以向前,也可以向后查找。
    • 添加:默认添加到双向链表的最后。
    • 修改:实现与单链表一样。
    • 删除:因为是双向链表,因此,我们可以实现自我删除某个节点。
  • 示意图

在这里插入图片描述

3.2 代码示例

需求:实现双向链表的增(顺序链尾添加、指定位置添加)删改查

public class DoubleLinkedListDemo {public static void main(String[] args) {DoubleLinkedList doubleLinkedList = new DoubleLinkedList();System.out.println("---[顺序添加]结果如下:---");doubleLinkedList.add(new Teacher(1, "张老师"));doubleLinkedList.add(new Teacher(2, "李老师"));doubleLinkedList.add(new Teacher(4, "田老师"));doubleLinkedList.foreach();// ---[顺序添加]结果如下:---// no=1, name=张老师// no=2, name=李老师// no=4, name=田老师System.out.println();System.out.println("---[指定位置添加]结果如下:---");doubleLinkedList.addByOrder(new Teacher(3, "(赵老师)"));doubleLinkedList.foreach();// ---[指定位置添加]结果如下:---// DEBUG INFO (addByOrder result) : prev.no=[2] ,cur.no=[3] ,next.no=[4]// no=1, name=张老师// no=2, name=李老师// no=3, name=(赵老师)// no=4, name=田老师System.out.println();System.out.println("---[修改节点]结果如下:---");doubleLinkedList.update(new Teacher(3, "(王老师)"));doubleLinkedList.foreach();// ---[修改节点]结果如下:---// no=1, name=张老师// no=2, name=李老师// no=3, name=(王老师)// no=4, name=田老师System.out.println();System.out.println("---[删除节点]结果如下:---");doubleLinkedList.delete(3);doubleLinkedList.foreach();// ---[删除节点]结果如下:---// DEBUG INFO (delete before) : prev.no=[2] ,cur.no=[3] ,next.no=[4]// no=1, name=张老师// no=2, name=李老师// no=4, name=田老师}
}/*** 双向链表实现。*/
class DoubleLinkedList {/*** 头节点勿动。*/private final Teacher head = new Teacher(0, "");public Teacher getHead() {return head;}/*** 链尾添加。** @param node 节点*/public void add(Teacher node) {Teacher pointer = this.head;while (null != pointer.next) {pointer = pointer.next;}pointer.next = node;node.prev = pointer;}/*** 根据编号指定位置进行添加。** @param node 节点*/public void addByOrder(Teacher node) {Teacher pointer = this.head;boolean exist = false;while (null != pointer.next) {if (pointer.next.no > node.no) {break;} else if (pointer.next.no == node.no) {exist = true;break;}pointer = pointer.next;}if (exist) {System.out.printf("编号[%d]的节点已存在,添加失败!\n", node.no);} else {// 后一节点双链指向。no.3 <-> no.2// 注意这里后一节点可能为空if (null != pointer.next) {node.next = pointer.next;node.next.prev = node;}// 前一节点双链指向。no.1 <-> no.2pointer.next = node;node.prev = pointer;// 结果验证。System.out.printf("DEBUG INFO (addByOrder result) : prev.no=[%d] ,cur.no=[%d] ,next.no=[%d]\n",node.prev.no,node.no,(null == node.next ? null : node.next.no));}}/*** 更新节点。** @param node 节点*/public void update(Teacher node) {Teacher pointer = this.head;assertLinkedListIsNull(pointer);boolean found = false;while (null != pointer.next) {if (pointer.next.no == node.no) {found = true;break;}pointer = pointer.next;}if (found) {// 只是当前节点的值变了,前后链接指向关系没有动。pointer.next.name = node.name;} else {System.out.printf("编号[%d]的节点不存在,修改失败。\n", node.no);}}/*** 删除节点。** @param no 编号*/public void delete(int no) {Teacher pointer = this.head.next;assertLinkedListIsNull(pointer);boolean found = false;while (null != pointer) {if (pointer.no == no) {found = true;break;}pointer = pointer.next;}if (found) {// before: no.2(pointer.prev) <-> no.3(pointer) <-> no.4(pointer.next)// after: no.2 <-> no.4System.out.printf("DEBUG INFO (delete before) : prev.no=[%d] ,cur.no=[%d] ,next.no=[%d]\n",pointer.prev.no,pointer.no,pointer.next.no);// 后链被删除的后一节点。pointer.prev.next = pointer.next;// 注意:如果是最后一个节点,就不需要执行下面这句话,否则出现空指针!if (pointer.next != null) {// 被删除的后一节点往前链。pointer.next.prev = pointer.prev;}} else {System.out.printf("编号[%d]的节点不存在,删除失败。\n", no);}}/*** 断言链表是否为空。** @param node 节点*/private static void assertLinkedListIsNull(Teacher node) {if (null == node || null == node.next) {throw new NullPointerException("链表为空!");}}/*** 迭代器。*/public void foreach() {Teacher pointer = this.head;assertLinkedListIsNull(pointer);while (null != pointer.next) {System.out.println(pointer.next);pointer = pointer.next;}}
}/*** 教师节点。*/
class Teacher {public int no;public String name;/*** 指向前一节点。*/public Teacher prev;/*** 指向后一节点。*/public Teacher next;public Teacher(int no, String name) {this.no = no;this.name = name;}@Overridepublic String toString() {return "no=" + no +", name=" + name;}
}

四、单向环形链表

4.1 约瑟夫问题

约瑟夫问题,是一个计算机科学和数学中的问题,在计算机编程的算法中,类似问题又称为约瑟夫环,又称“丢手绢问题”。

据说著名犹太历史学家 Josephus 有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与 Josephus 及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而 Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus 要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

  • 问题简述:设编号为 1,2,… n 的 n 个人围坐一圈,约定编号为 k(1<=k<=n)的人从 1 开始报数,数到 m 的那个人出列,它的下一位又从 1 开始报数,数到 m 的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

4.2 实现思路

  • 用一个不带头结点的循环链表来处理 Josephus 问题:先构成一个有 n 个结点的单循环链表,然后由 k 结点起从 1 开始计数,计到 m 时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从 1 开始计数,直到最后一个结点从链表中删除算法结束。

  • 构建单向环形链表:

    • 先创建第一个节点,让 first 指向该节点 ,并形成环
    • 后面每创建一个新节点,就把该节点加入到环形链表中。
  • 遍历单向环形链表:

    • 通过辅助指针,指向first节点。
    • 循环至指针指向下一轮 first节点出现为止。
  • 示意图

在这里插入图片描述

4.3 代码示例

public class Josephus {public static void main(String[] args) {CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();System.out.println("[添加节点]结果如下:");circleSingleLinkedList.add(5);circleSingleLinkedList.foreach();// [添加节点]结果如下:// no=1// no=2// no=3// no=4// no=5System.out.println();System.out.println("[出圈]结果如下:");// 圈中总计节点个数5,从1开始计数,每次数两下,被点到的编号出圈。// 期望结果:2 -> 4 -> 1  -> 5  -> 3circleSingleLinkedList.count(1, 2, 5);// [出圈]结果如下:// 编号[2]出圈// 编号[4]出圈// 编号[1]出圈// 编号[5]出圈// 圈中剩余编号为[3]}
}class CircleSingleLinkedList {/*** 首节点。*/private Node first = null;/*** 添加节点。** @param nums 生成的节点个数*/public void add(int nums) {// 值校验,添加的点需要大于1。if (1 > nums) {throw new IllegalArgumentException();}// 辅助指针。Node pointer = null;for (int i = 1; i <= nums; i++) {Node node = new Node(i, null);if (1 == i) {// 构造环(首尾相连)。first = node;first.setNext(first);// 并指向第一个节点。pointer = first;} else {// 节点往后添加。if (null != pointer) {pointer.setNext(node);// 闭环。node.setNext(first);pointer = node;}}}}/*** 迭代器。*/public void foreach() {if (null == this.first || null == this.first.getNext()) {throw new NullPointerException();}Node pointer = this.first;while (true) {System.out.println(pointer);// 一圈结束。if (first == pointer.getNext()) {break;}pointer = pointer.getNext();}}/*** 根据用户的输入,计算出圈的顺序。** @param start 从第几开始* @param nums  数几次* @param total 最初总共有多少个*/public void count(int start, int nums, int total) {if (null == this.first || 1 > start || start > total) {throw new IllegalArgumentException();}Node pointer = this.first;while (first != pointer.getNext()) {pointer = pointer.getNext();}// 先让首节点和辅助指针后移 start - 1 次。for (int i = 0; i < (start - 1); i++) {first = first.getNext();pointer = pointer.getNext();}// 圈中只有一个节点时退出。while (first != pointer) {// 让首节点和辅助指针后移 nums - 1 次。for (int i = 0; i < (nums - 1); i++) {first = first.getNext();pointer = pointer.getNext();}System.out.printf("编号[%d]出圈\n", first.getNo());first = first.getNext();pointer.setNext(first);}System.out.printf("圈中剩余编号为[%d] \n", first.getNo());}
}class Node {private int no;private Node next;public Node(int no, Node next) {this.no = no;this.next = next;}public int getNo() {return no;}public void setNo(int no) {this.no = no;}public Node getNext() {return next;}public void setNext(Node next) {this.next = next;}@Overridepublic String toString() {return "no=" + no;}
}

五、结束语


“-------怕什么真理无穷,进一寸有一寸的欢喜。”

微信公众号搜索:饺子泡牛奶

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
南美洲有哪些国家 南美洲一共有... 南美洲是南亚美利加洲的简称,位于西半球,南美洲大部分地区位于南半球,南美洲东临大西洋,西临太平洋,南...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
南美洲有哪些国家 南美洲一共有... 南美洲是南亚美利加洲的简称,位于西半球,南美洲大部分地区位于南半球,南美洲东临大西洋,西临太平洋,南...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...