数据结构——链表(Linked List)

目录

数据结构——链表(Linked List)

一.链表的概念

二.单向链表

2.1单链表节点的尾部添加

2.2单链表节点的自动排序添加

2.3单链表节点的修改

2.4单链表节点的删除

三.双向链表

3.1理解

3.2双向链表增删改查

四.循环链表

约瑟夫(Josephu)环问题


一.链表的概念

链表是一个在物理存储单元中不连续,没有顺序的的存储结构,关于它的顺序是由链表中的指针链接实现的,是一种递归的数据结构,链表有一系列节点组成,而这些节点会在运行时动态生成。

节点包括两个部分:一部分存储数据元素的数据域(存储对象),另一部分是存储下一个节点地址的指针域(引用下一个节点)。

优点:和线性表顺序结构相比,链表结构插入,删除操作不需要移动所有节点,不需要初始化容量。
缺点:搜索时必须遍历节点,含有大量引用,占空间大。

链表分为三类:单向链表,双向链表,循环链表。

二.单向链表

单链表是有序的列表

虽然单链表是有序列表,但是其元素并不是连续存储的。

特点:

1.单链表是以节点的方式来存储的
2.每个节点包含data域(存储数据),next域(指向下一个节点)
3.单链表的各个节点不一定是连续存储的

2.1单链表节点的尾部添加

代码实现:

class SingleLinkedList{// 初始化一个头节点 不存放具体数据private HeroNode head = new HeroNode(0,"","");// 添加节点到单向链表public void add(HeroNode heroNode){//不考虑编号顺序:1.找到当前链表的最后节点2.将最后这个节点的next指向新的节点即可//2.因为head头节点不能动,因此我们需要一个辅助节点tempHeroNode temp = head;//遍历链表,找到尾节点while (true){//找到链表的尾节点if (temp.next == null){break;}//如果不是尾节点,则将temp向后移temp = temp.next;}//循环结束后,flog指向的是尾节点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;}@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickname='" + nickname + '\'' +'}';}
}public class LinkedListdemo01 {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.2单链表节点的自动排序添加

class SingleLinkedList1 {// 初始化一个头节点 不存放具体数据private HeroNode head = new HeroNode(0, "", "");// 第二种添加方式,根据排名进行添加public void addByOrder(HeroNode heroNode) {// 创建辅助节点帮助找到添加的位置// 因为是单链表,因此辅助节点的位置应该是添加位置的前一个节点HeroNode temp = head;boolean flag = false;// 标识英雄的编号是否存在while (true) {//找到链表的尾节点if (temp.next == null) {break;}if (temp.next.no > heroNode.no) {// 位置找到,就在temp的后面break;} else if (temp.next.no == heroNode.no) {// 编号已存在flag = true;break;}temp = temp.next;// 将temp后移}// 循环结束后,判断flagif(flag){// 编号存在,不能添加System.out.println("准备插入的英雄编号" + heroNode.no + "重复,不能加入");} else{// 插入到链表中heroNode.next = 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;}}
}//        创建链表SingleLinkedList1 singleLinkedList1 = new SingleLinkedList1();singleLinkedList1.addByOrder(hero1);singleLinkedList1.addByOrder(hero3);singleLinkedList1.addByOrder(hero4);singleLinkedList1.addByOrder(hero2);

运行结果:

HeroNode{no=1, name='亚索', nickname='托儿索'}
HeroNode{no=2, name='劫', nickname='儿童劫'}
HeroNode{no=3, name='沃利贝尔', nickname='狗熊'}
HeroNode{no=4, name='虚空遁地兽', nickname='挖掘机'}

2.3单链表节点的修改

// 修改节点的信息,根据no编号来修改public void update(HeroNode newHeroNode) {// 根据newHeroNode的编号进行修改// 判断链表是否为空if (head == 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;// 将temp后移}// 根据flag判断是否已经找到要修改的节点if (flag) {temp.name = newHeroNode.name;temp.nickname = newHeroNode.nickname;} else {// 没有找到节点System.out.println("没有找到");}}
HeroNode{no=1, name='亚索', nickname='托儿索'}
HeroNode{no=2, name='虚空恐惧', nickname='大虫子'}
HeroNode{no=3, name='沃利贝尔', nickname='狗熊'}
HeroNode{no=4, name='虚空遁地兽', nickname='挖掘机'}

2.4单链表节点的删除

1.先找到需要删除节点的前一个节点,通过一个辅助节点temp
2.temp.next = temp.next.next,也就是说需要删除节点的前一个节点本来指向的是删除节点,然后我们使其指向删除节点的下一个节点,直接跳过删除节点,这样该节点也就相当于被删除了
3.被删除的节点将不会有其它引用指向,会被垃圾回收器回收

// 删除节点public void delete(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;// 将temp后移}// 判断flagif (flag) {// 找到// 可以删除temp.next = temp.next.next;} else {// 未找到System.out.println("要删除的节点不存在");}}
HeroNode{no=2, name='劫', nickname='儿童劫'}
HeroNode{no=3, name='沃利贝尔', nickname='狗熊'}
HeroNode{no=4, name='虚空遁地兽', nickname='挖掘机'}

三.双向链表

3.1理解

双向链表可以向前或向后查找,与单向链表的区别就是它不仅有后继节点,还有前驱节点。

这样就既存储了下一个节点的地址,也存储了前一个节点的地址。而单向链表只能从一个方向查询。
双向链表可以自我删除,单向链表不可以,需要靠辅助节点来删除。

3.2双向链表增删改查

//定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode2 {public int no;public String name;public String nickname;public HeroNode2 next;// 指向下一个节点,默认为nullpublic HeroNode2 pre;//指向上一个节点,默认为null//构造器public HeroNode2(int no, String name, String nickname) {this.no = no;this.name = name;this.nickname = nickname;}@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickname='" + nickname + '\'' +'}';}
}
//创建一个双向链表的类
class DoubleLinkedList {//先初始化一个头结点,头结点不要动,不存在放具体的数据private HeroNode2 head =new  HeroNode2(0,"","");//返回头结点public HeroNode2 getHead(){return head;}//遍历双向链表的方法//显示链表(遍历)public void list(){
//        判断链表是否为空if (head.next == null){System.out.println("链表为空");return;}//因为头结点,不能动,因此我们需要一个辅助变量来遍历HeroNode2 temp = head.next;while (true){
//            判断是否链表最后if (temp == null){break;}//输出节点信息System.out.println(temp);//将temp后移,一定小心temp = temp.next;}}//第二种方式在添加英雄时,根据排名将英雄插入到指定位置//(如果有这个排名,则添加失败,并给出提示)public void addByOrder(HeroNode2 heroNode){//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置//因为单链表,因为我们找的temp 是位于 添加位置的前一个节点,否则插入不了HeroNode2 temp = head;boolean flag = false;// flag标志添加的编号是否存在,默认为falsewhile (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.next;//后移,遍历当前链表}//判断flag 的值if (flag){//不能添加,说明编号存在System.out.printf("准备插入英雄的编号%d已经存在,不能加入\n",heroNode.no);}else {heroNode.next = temp.next;//(若temp是最后一个节点则不需要执行这句话否则出现空指针)if (temp.next!=null)temp.next.pre = heroNode;heroNode.pre = temp;temp.next = heroNode;}}
//    添加一个节点到双向链表的最后public void add(HeroNode2 heroNode){
//        因为head节点不能动,因此我们需要一个辅助遍历tempHeroNode2 temp = head;// 遍历链表,找到最后while(true){// 找到链表的最后if (temp.next == null){break;}// 如果没有找到最后, 将将temp后移temp = temp.next;}
//        当退出while循环时,将temp后移
//        形成一个双向链表temp.next = heroNode;heroNode.pre = temp;}// 修改一个节点的内容, 可以看到双向链表的节点内容修改和单向链表一样// 只是 节点类型改成 HeroNode2public void update(HeroNode2 newHeroNode){
// 判断是否空if (head.next == null) {System.out.println("链表为空~");return;}// 找到需要修改的节点, 根据no编号// 定义一个辅助变量HeroNode2 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.printf("没有找到 编号 %d 的节点,不能修改\n", newHeroNode.no);}}// 从双向链表中删除一个节点,// 说明// 1 对于双向链表,我们可以直接找到要删除的这个节点// 2 找到后,自我删除即可public void del(int no){
// 判断当前链表是否为空if (head.next == null) {// 空链表System.out.println("链表为空,无法删除");return;}HeroNode2 temp = head.next; // 辅助变量(指针)boolean flag = false; // 标志是否找到待删除节点的while (true) {if (temp == null) { // 已经到链表的最后break;}if (temp.no == no) {// 找到的待删除节点的前一个节点tempflag = true;break;}temp = temp.next; // temp后移,遍历}// 判断flagif (flag) { // 找到// 可以删除// temp.next = temp.next.next;[单向链表]temp.pre.next = temp.next;// 这里我们的代码有问题?// 如果是最后一个节点,就不需要执行下面这句话,否则出现空指针if (temp.next != null) {temp.next.pre = temp.pre;}} else {System.out.printf("要删除的 %d 节点不存在\n", no);}}
}
public class LinkedListdemo {public static void main(String[] args) {
//        创建节点HeroNode2 hero1 = new HeroNode2(1,"亚索","托儿索");HeroNode2 hero2 = new HeroNode2(2,"劫","儿童劫");HeroNode2 hero3 = new HeroNode2(3,"沃利贝尔","狗熊");HeroNode2 hero4 = new HeroNode2(4,"虚空遁地兽","挖掘机");
//        创建链表DoubleLinkedList doubleLinkedList = new DoubleLinkedList();doubleLinkedList.addByOrder(hero1);doubleLinkedList.addByOrder(hero3);doubleLinkedList.addByOrder(hero2);doubleLinkedList.addByOrder(hero4);
//        显示链表doubleLinkedList.list();}
}

运行结果:

HeroNode{no=1, name='亚索', nickname='托儿索'}
HeroNode{no=2, name='劫', nickname='儿童劫'}
HeroNode{no=3, name='沃利贝尔', nickname='狗熊'}
HeroNode{no=4, name='虚空遁地兽', nickname='挖掘机'}

四.循环链表

将单链表中终点指针指向头结点,是整个链表形成环,这叫做单向循环链表。

约瑟夫(Josephu)环问题

约瑟夫问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3。

如何解决?
使用单向环形链表解决Josephu问题,用不带头结点的单向循环链表先构成一个有n个节点的链表,然后由k节点起从1开始计数,直到第m时,对应节点删除,然后从被删除的下一个节点开始从1开始计数,直到最后一个节点被删除结束算法。

class People {private int no;private People next;public People(int no) {this.no = no;}public int getNo() {return no;}public void setNo(int no) {this.no = no;}public People getNext() {return next;}public void setNext(People next) {this.next = next;}
}
//创建一个环形的单向链表
public class SingleLinkedlist {
//    创建一个frist节点,当前没有编号private People first = null;
//    添加人节点,构建一个环形的链表public void addPeople(int nums){
//        nums做数据校验if (nums < 1){System.out.println("nums的值不正确");return;}People curPeople = null;//辅助指针,形成循环链表
//        用for形成循环链表for (int i = 1; i <= nums; i++) {
//            根据编号,创建人的节点People people = new People(i);
//            如果是第一个人if (i == 1){first = people;first.setNext(first);//构成环curPeople = first;//让curpeople指向第一个人}else {curPeople.setNext(people);people.setNext(first);curPeople = people;}}}
//    遍历当前的环表public void showPeople(){
//        判断链表是否为空if (first == null){System.out.println("没有人");return;}
//        因为first不能动,因此我们仍需要使用一个辅助指针完成遍历People curPeople = first;while (true){System.out.printf("小孩编号 %d \n",curPeople.getNo());if (curPeople.getNext() == first){// 说明已经遍历完毕break;}curPeople = curPeople.getNext();// curBoy后移}}//startNo表示从这个人开始数,countNum表示第m个人被杀掉,nums表示人数public void countPeople(int startNo, int countNum, int nums) {if (first == null || startNo < 1 || startNo > nums) {System.out.println("参数输入有误,亲重新输入");return;}// 创建要给辅助指针,People helper = first;// 需求创建一个辅助指针(变量) helper , 事先应该指向环形链表的最后这个节点while (true) {if (helper.getNext() == first) { // 说明helper指向最后的人break;}helper = helper.getNext();}//人被点数前,先让 first 和  helper 移动 k - 1次for (int j = 0; j < startNo - 1; j++) {first = first.getNext();helper = helper.getNext();}//当人报数时,让first 和 helper 指针同时移动 m - 1 次, 然后被杀//这里是一个循环操作,知道圈中只有一个节点while (true) {if (helper == first) { //说明圈中只有一个节点break;}//让 first 和 helper 指针同时 的移动 countNum - 1for (int j = 0; j < countNum - 1; j++) {first = first.getNext();helper = helper.getNext();}//这时first指向的节点,就是被杀的人System.out.printf("第%d人被杀\n", first.getNo());//这时将first指向的小孩节点出圈first = first.getNext();helper.setNext(first); //}System.out.printf("最后一个人为%d \n", first.getNo());}
}
public class Killpeople {public static void main(String[] args) {SingleLinkedlist singleLinkedlist = new SingleLinkedlist();singleLinkedlist.addPeople(6);//加入6个人节点singleLinkedlist.showPeople();singleLinkedlist.countPeople(1,5,6);}
}

运行结果:

第5人被杀
第4人被杀
第6人被杀
第2人被杀
第3人被杀
最后一个人为1

数据结构——链表(Linked List)相关推荐

  1. 常用的数据结构-链表

    链表( Linked List) 链表是一种数据元素按照链式存储结构进行存储的数据结构,这种存储结构具有在物理上存在非连续的特点.链表由一系列数据结点构成,每个数据结点包括数据域和指针域两部分.其中, ...

  2. 4-玩转数据结构-链表

    本章我们介绍链表 前面我们已经介绍了动态数组,栈和队列. 它们的底层依托静态数组;靠resize解决固定容量问题 链表是我们接触的第一个真正的动态数组. 为什么链表很重要 链表是重点,也是难点.它是最 ...

  3. 链表(Linked List)之单链表

    原文地址:传送门 链表(Linked List)介绍 链表是有序的列表,但是它在内存中是存储如下 小结: 链表是以节点的方式来存储,是链式存储 每个节点包含 data 域, next 域:指向下一个节 ...

  4. 数据结构链表之队列,Python3实现——7

    数据结构链表之队列 队列概述 定义:队列是一种基于先进先出(FIFO)的数据结构,队列只能在一段进行插入和删除操作的结构,第一个进入队列的元素在读取时会第一个被读取 队列可以使用顺序表(Python中 ...

  5. 数据结构链表代码_代码简介:链表数据结构如何工作

    数据结构链表代码 Here are three stories we published this week that are worth your time: 这是我们本周发布的三个值得您关注的故事 ...

  6. Python中常用的数据结构---链表

    Python中常用的数据结构-链表 常用的数据结构有数组.链表(一对一).栈和队列.哈希表.树(一对多).图(多对多)等结构. 在本目录下我们将讲解,通过python语言实现常用的数据结构. 2.链表 ...

  7. 初学数据结构--链表

    2019独角兽企业重金招聘Python工程师标准>>> 前言 在这一章,我将介绍另外一种非常重要的线性数据结构--链表.在之前介绍的动态数组,栈和队列这三种数据结构,底层其实依托于静 ...

  8. C++数据结构链表的基本操作

    这篇文章主要为大家介绍了C++数据结构链表基本操作的示例过程有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪 首先创建好一个节点 typedef struct node {in ...

  9. 六十九、数据结构链表的实现

    @Author:Runsen 编程的本质来源于算法,而算法的本质来源于数学,编程只不过将数学题进行代码化. ---- Runsen 文章目录 链表 Linked List 双链表 Doubly Lin ...

最新文章

  1. CSS vs. JS Animation: 哪个更快
  2. 从无到有,支付路由系统升级打怪之路|原创
  3. Sublime注释插件--DocBlockr
  4. 61 Celery Beat 任务调度
  5. 手把手教你用 elementUI 实现导航栏
  6. 微软企业库5.0学习笔记(三十三)数据访问模块
  7. 看图说cnblogs-强大的SEO功能【有实例】
  8. java多线程之Semaphore信号量详解
  9. 提取过程_上海生物发酵展浅谈中药提取分离的现状
  10. el-table的使用总结
  11. linux java keytool_JDK自带的keytool证书工具详解
  12. STM32串口驱动安装攻略
  13. 05因果图法和决策表法
  14. 判断移动终端是安卓还是iOS
  15. Linux虚拟机快速搭建RabbitMQ(解压版)完整流程(简单明了、不亲测能写这么多)
  16. iOS开发之静态库.a的制作教程
  17. python pandas 实现Excel自动填充功能
  18. Selenium 设置代理chrome
  19. C语言基础-#include<stdio.h>
  20. Handler---

热门文章

  1. python实战-educoder平台作业提醒小助手
  2. 计算机二级笔试题vf,有关计算机二级vF笔试的真题
  3. [附源码]JSP+ssm计算机毕业设计蛋糕商城系统6b4n8【源码、数据库、LW、部署】
  4. 计算机简单故障排除教案,计算机故障检测与排除教案,计算机故障排除方法
  5. outlook 导出邮件服务器通讯录,OUTLOOK 2010备份联系人日历邮件导入和导出基础教程...
  6. 百度网盘真实下载地址
  7. java正则表达式替换特殊字符_使用正则表达式替换报表名称中的特殊字符(推荐)...
  8. Shell正则表达式(grep)
  9. 谷歌如何应对鸿蒙系统,应对华为鸿蒙,谷歌新系统呼之欲出,安卓真命不久矣?...
  10. Android5.0 Lollipop(棒棒糖)