数据结构与算法:链表

  • 单链表
    • 单链表的结构特点
    • 单链表的增删改查
    • 单链表面试题
    • 单链表缺点分析
  • 双向链表
    • 双向链表的增删改查
    • 约瑟夫环问题

单链表

单链表的结构特点

  • 链表的结构特点
  1. 链表是有序的列表,在内存中的1存储如下图 ,是以节点的方式来存储的!
  2. 每个节点包含data域,next域:指向下一节点
  3. 如下图可以发现链表的各个节点不一定是连续存储的
  4. 链表分带头节点的链表和没有头节点的链表,根据实际需求来

    链表在逻辑结构上来看属于线性结构

单链表的增删改查

单链表构建以及显示

/*** @Date:2021/5/23* @Author:GuoHeLong** 单链表,实现水浒英雄排名*/
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 linkedList = new SingleLinkedList();linkedList.add(hero1);linkedList.add(hero2);linkedList.add(hero3);linkedList.add(hero4);//显示一把linkedList.list();}//定义一个单链表来管理英雄static class SingleLinkedList{//先初始化一个头节点不存放具体数据,头节点不能动private HeroNode head = new HeroNode(0,"","");//添加节点到单向链表/*当不考虑编号顺序时1:找到当前链表的最后节点2:将最后这个节点的next指向新的节点*/public void add(HeroNode heroNode){//因为head节点不能动,因此我们需要一个辅助遍历节点temp。// head一旦改变,就丢失了整个链表的入口,我们也就无法通过 head 找到链表了HeroNode temp = head;while (true){//找到链表的最后if (temp.next == null){break;}//若没有找到将temp后移temp = temp.next;}//当退出while循环时,temp就指向了链表的最后。此时将temp指向加入的节点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,每个Hero对象就是一个节点static 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 + '\'' +", next=" + 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 linkedList = new SingleLinkedList();//非顺序插入linkedList.addByOrder(hero3);linkedList.addByOrder(hero1);linkedList.addByOrder(hero4);linkedList.addByOrder(hero2);linkedList.addByOrder(hero2);//显示一把linkedList.list();}public void addByOrder(HeroNode heroNode){//因为头节点不能动,因此我们仍然通过定一个辅助指针来帮忙找到添加的位置//找到temp一定是位于添加位置的前一节点HeroNode temp = head;boolean flag = true;while (true){if (temp.next == null || temp.next.no > heroNode.no){//代表head下一节点就是要插入的位置break;}else if (temp.next.no == heroNode.no){flag = false;break;}temp = temp.next; //节点后移}if (!flag){System.out.println("====不能添加,该英雄编号已经存在"+heroNode.no);}else {//此时temp是要插入位置的上一节点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 linkedList = new SingleLinkedList();linkedList.add(hero1);linkedList.add(hero2);linkedList.add(hero3);linkedList.add(hero4);//显示一把linkedList.list();System.out.println("==============");HeroNode edit = new HeroNode(4, "公孙胜", "入云龙");linkedList.update(edit);linkedList.list();}//修改指定节点信息 根据no编号来修改,即no编号不能改.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) {System.out.println("已遍历全表");}if (temp.no == newHeroNode.no){flag = true;break;}temp = temp.next;}if (!flag){System.out.println("未找到要修改的节点");return;}temp.name = newHeroNode.name;temp.nickName = newHeroNode.nickName;}

单链表删除节点:找到要删除的no直接temp.next = temp.next.next即可,跳过的节点会被GC自动回收

 public void del(int no){//如何删除?temp = temp.next.next 即可删除 temp.next 节点,该节点没有引用指向它,会被垃圾回收机制回收HeroNode temp = head;boolean flag = false;while (true){if (temp.next == null){System.out.println("已找到链表最后");break;}if (temp.next.no == no){flag = true;break;}temp = temp.next;}if (flag){//跳过要删除的节点指向下一个节点,该节点会被GC回收temp.next = temp.next.next;list();}else {System.out.println("未找到要删除的节点");}}

单链表面试题

【新浪】面试题1:计算出链表中有效节点的个数(不包含头节点)

public int getLength(HeroNode head) {//初始化数量int count = 0;if (head.next == null) {return count;}HeroNode temp = head.next;while (temp != null) {temp = temp.next;count++;}return count;}

【新浪】面试题2: 查找单链表中倒数第n个节点
思路:链表有效节点的长度-n=要找的节点

public HeroNode findLastIndexNode(HeroNode head, int index) {if (head.next == null) {return null;}int length = getLength(head);if (length == 0 || index > length || index < 0) {return null;}HeroNode temp = head.next;for (int i = 0; i < length - index; i++) {temp = temp.next;}return temp;}

腾讯】将单链表反转
如:1-2-3反转为3-2-1
思路:
1定义临时链表,
2循环原链表将循环到的节点的next指向临时链表的首个有效节点
3把临时链表head的next指向步骤3所得链表
4将原链表head的next指向临时链表head的next

代码实现(个人写法)

 public void reversetList(HeroNode head) {if (head.next == null || head.next.next == null){return;}HeroNode reverHead = new HeroNode(0, "", "");HeroNode temp = head.next;while (temp != null) {//每次循环取到当前节点temp的value,定义一个临时的节点来存储HeroNode upNode = new HeroNode(temp.no, temp.name, temp.nickName);upNode.next = reverHead.next; //临时节点的next指向当前反转链表head的next。reverHead.next = upNode; //再将反转链表head的next指向临时节点temp = temp.next; //原链表节点后移}head.next = reverHead.next;}

尚硅谷老师写法)

//【腾讯】单链表反转(尚硅谷老师写法)public void reversetListBySGG(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;  //当前节点的next指向反转链表head的nextreverseHead.next = cur; //反转链表的head的next再指向cur就完成了换位cur = next; //cur后移}head.next = reverseHead.next; //最后将原链表head的next指向反转链表head的next}

【百度】将链表逆向打印
思路
利用栈数据结构,将各个节点压入栈中,利用栈先进后出的特点

public void reversePrint(HeroNode head){//利用栈数据结构,将各个节点压入栈中,利用栈先进后出的特点if (head.next == null){return;}Stack<HeroNode> stack = new Stack<>();HeroNode cur = head.next;while (cur != null){stack.push(cur);cur = cur.next;}while (stack.size() > 0){HeroNode pop = stack.pop();System.out.println(pop);}}

单链表缺点分析

  • 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找
  • 单项链表不能自我删除,需要依靠辅助接点,而双向链表则可以自我删除

双向链表


总结:多出了pre上一节点,灵活变通即可

双向链表的增删改查

代码示例

package com.ghl.likendList;/*** @Date:2021/5/31* @Author:GuoHeLong 双向链表*/
public class DoubleLinkedListDemo {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, "林冲", "豹子头");//加入链表DoubleLinkendList linkendList = new DoubleLinkendList();//=======================测试顺序插入===============================================================//顺序插入linkendList.add(hero1);linkendList.add(hero2);linkendList.add(hero3);linkendList.add(hero4);linkendList.list();//linkendList.del(2);//删除linkendList.update(new HeroNode2(1,"宋老黑","解绑星"));linkendList.update(new HeroNode2(4,"林教头","进军教头"));}//创建一个双向链表类static class DoubleLinkendList {//先初始化一个头节点不存放具体数据,头节点不能动private HeroNode2 head = new HeroNode2(0, "", "");//返回头节点public HeroNode2 getHead() {return head;}//添加一个节点到双向链表最后public void add(HeroNode2 heroNode) {HeroNode2 temp = head;while (true) {//找到最后节点if (temp.next == null) {break;}temp = temp.next;}//要加入的节点的pre指向链表最后一个节点heroNode.pre = temp;//链表的最后一个节点指向新加节点temp.next = heroNode;}//插入public void addByOrder(HeroNode2 hero) {System.out.println("插入节点");HeroNode2 temp = head;boolean flag = false;while (true) {if (temp.next == null || temp.next.no > hero.no) {flag = true;break;}if (temp.next.no == hero.no) {System.out.println("已有相同的不能插入");break;}temp = temp.next;}if (flag) {HeroNode2 heroNode2 = temp.next; //要插入位置的下一节点hero.pre = temp;hero.next = heroNode2;temp.next = hero;if (heroNode2 != null) {hero.next.pre = hero;}}list();}//修改双向链表的一个节点public void update(HeroNode2 upHeroNode) {System.out.println("================显示链表======================");HeroNode2 temp = head;if (temp.next == null) {System.out.println("要修改的链表为空~~~~");return;}boolean flag = false;while (true) {if (temp == null) {System.out.println("已经遍历全表");}if (temp.no == upHeroNode.no) {flag = true;break;}temp = temp.next; //后移}if (!flag) {System.out.println("未找到要删除的节点");return;}temp.name = upHeroNode.name;temp.nickName = upHeroNode.nickName;System.out.println("修改成功");list();}/*del=要删除节点删除节点。找到del的上一节点,然后上一节点的next指向del的下一节点,若del的下一节点不为null则下一节点的pre指向del的上一节点删除完毕,GC自动清理del*/public void del(int no) {System.out.println("=================删除节点===========================");HeroNode2 temp = head;if (temp.next == null) {System.out.println("链表为空~~~");return;}boolean flag = false;while (temp.next != null) {if (temp.next.no == no) {flag = true; //找到了del,此时指针停留在上一节点break;}temp = temp.next;}if (!flag) {System.out.println("未找到要删除节点");return;}HeroNode2 delNext = temp.next.next; //先拿到要del的下一节点if (delNext != null) { //防止最有一个节点是null导致nullPointdelNext.pre = temp; //del的下一节点指向del的上一节点}temp.next = delNext; //del的上一节点的next指向del的下一节点System.out.println("删除成功");list();}//遍历双向链表public void list() {System.out.println("================显示链表======================");if (head.next == null) {System.out.println("链表为空~~~~");}HeroNode2 temp = head.next;while (temp != null) {System.out.println(temp);temp = temp.next;}}}//定义HeroNode,每个Hero对象就是一个节点static class HeroNode2 {public int no; //英雄编号public String name;//英雄名public String nickName; //英雄昵称public HeroNode2 next; //指向下一节点,默认为nullpublic HeroNode2 pre; //指向上一节点,默认为nullpublic HeroNode2(int no, String name, String nickName) {this.no = no;this.name = name;this.nickName = nickName;}@Overridepublic String toString() {return "HeroNode2{" +"no=" + no +", name='" + name + '\'' +", nickName='" + nickName + '\'' +'}';}}
}

约瑟夫环问题


package com.ghl.likendList;/*** @Date:2021/6/5* @Author:GuoHeLong** 约瑟夫环实现*/
public class JosepFu {public static void main(String[] args) {CircleSingleLekinList lekinList = new CircleSingleLekinList();lekinList.addBoy(5);lekinList.list();lekinList.countBoy(1, 2, 3); // 2->4->1->5->3}//创建一个环形的单向链表static class CircleSingleLekinList{//先创建一个firest节点,当前没有编号private Boy firest = null;//添加小孩节点,构建成一个环形链表public void addBoy(int nums){if (nums < 1){System.out.println("nums值不正确");return;}Boy curBoy = null;for (int i = 1; i <= nums; i++) {Boy boy = new Boy(i);if (i == 1){firest = boy;firest.setNext(firest); //先构成环curBoy = firest; //让curBoy指向第一个小孩}else {curBoy.setNext(boy);boy.setNext(firest);curBoy = boy;}}}//遍历环形链表public void  list(){if (firest == null){System.out.println("没有小孩");return;}Boy curBoy = firest;while (true){System.out.println("编号为 :"+curBoy.getNo());if (curBoy.getNext() == firest){break;}curBoy = curBoy.getNext();}}/**** @param startNo  表示从第几个小孩开始数数* @param countNum 表示数几下* @param nums     表示最初有多少小孩在圈中*/public void countBoy(int startNo, int countNum, int nums) {// 先对数据进行校验if (firest == null || startNo < 1 || startNo > nums) {System.out.println("参数输入有误, 请重新输入");return;}// 创建要给辅助指针,帮助完成小孩出圈Boy helper = firest;// 需求创建一个辅助指针(变量) helper , 事先应该指向环形链表的最后这个节点while (true) {if (helper.getNext() == firest) { // 说明helper指向最后小孩节点break;}helper = helper.getNext();}// 小孩报数前,先让 firest 和 helper 移动 k - 1次(firest指针指向开始节点,helper也是)for (int j = 0; j < startNo - 1; j++) {firest = firest.getNext();helper = helper.getNext();}// 当小孩报数时,让firest 和 helper 指针同时 的移动 m - 1 次, 然后出圈// 这里是一个循环操作,直到圈中只有一个节点while (true) {if (helper == firest) { // 说明圈中只有一个节点break;}// 让 firest 和 helper 指针同时 的移动 countNum - 1(-1是因为自己也要数,也就是节点进位是countNum-1)for (int j = 0; j < countNum - 1; j++) {firest = firest.getNext();helper = helper.getNext();}// 这时firest指向的节点,就是要出圈的小孩节点System.out.printf("小孩%d出圈\n", firest.getNo());// 这时将firest指向的小孩节点出圈firest = firest.getNext();helper.setNext(firest);}System.out.printf("最后留在圈中的小孩编号%d \n", firest.getNo());}}static class Boy{private int no;private Boy next;public Boy() {}public Boy(int no) {this.no = no;}public int getNo() {return no;}public void setNo(int no) {this.no = no;}public Boy getNext() {return next;}public void setNext(Boy next) {this.next = next;}}
}

数据结构与算法第2章:链表相关推荐

  1. 比特数据结构与算法(第二章收尾)带头双向循环链表的实现

    1.链表的分类 链表的分类 ① 单向或者双向 ② 带头或者不带头 ③ 循环或者非循环 常用的链表: 根据上面的分类我们可以细分出8种不同类型的链表,这么多链表我们一个个讲解这并没有意义.我们实际中最常 ...

  2. 数据结构与算法之线性结构链表

    数据结构与算法之线性结构链表 这一篇文章主要介绍的是通过java实现单链表.循环链表和双向循环链表,仅供自己复习使用,如有什么不足之处,欢迎指出. 单链表: package xianxingjiego ...

  3. 数据结构与算法(C++)– 链表(Link)

    数据结构与算法(C++)– 链表(Link) 1.基础知识 表:把具有相同类型的序列 A0, A1, A2, - An 称为表 .n 是表的大小,n=0 称为空表. A0没有前驱,An没有后继. 前驱 ...

  4. 数据结构与算法之反转单向链表和双向链表

    数据结构与算法之反转单向链表和双向链表 目录 反转单向链表和双向链表 1. 反转单向链表和双向链表 题目描述 代码实现 public class Code_ReverseList {public st ...

  5. 408考研数据结构与算法之数组、链表、队列、栈知识点和算法详细教程(更新中)

    第一章:数据结构与算法概述 因为数据结构作为计算机专业的专业基础课程,是计算机考研的必考科目之一,如果打算报考计算机专业的研究生,你必须学好它. 数据结构是计算机软考.计算机等级考试等相关考试的必考内 ...

  6. 为什么我要放弃javaScript数据结构与算法(第二章)—— 数组

    第二章 数组 几乎所有的编程语言都原生支持数组类型,因为数组是最简单的内存数据结构.JavaScript里也有数组类型,虽然它的第一个版本并没有支持数组.本章将深入学习数组数据结构和它的能力. 为什么 ...

  7. python中的列表是采用链式结构实现的_Python数据结构与算法之列表(链表,linked list)简单实现...

    Python数据结构与算法之列表(链表,linked list)简单实现 Python 中的 list 并不是我们传统(计算机科学)意义上的列表,这也是其 append 操作会比 insert 操作效 ...

  8. 【数据结构与算法】数组与链表

    数组的定义和特性 数组(Array)是一种线性表数据结构.它用一组连续的内存空间,来存储一组具有相同类型的数据. 线性表(Linear List):数组.链表.队列.栈 非线性表:树 图 连续的内存空 ...

  9. 【python】数据结构和算法 + 浅谈单链表与双链表的区别

    有这么一句话说"程序=数据结构+算法",也有人说"如果把编程比作做菜,那么数据结构就好比食材(菜),算法就好比厨艺(做菜的技巧)". 当然这是笼统的说法,不过也 ...

最新文章

  1. uva 10152 ShellSort
  2. 《Beginning C# Objcets》学习笔记
  3. 046 实例11-自动轨迹绘制
  4. Spring的REST服务发现性,第5部分
  5. linux的命令窗口,(翻译)Linux命令行(二)
  6. 从今天开始学习iOS开发(iOS 7版)-- 构建一款App之App开发过程 (二)
  7. matlab怎么返回操作,Matlab中function函数使用操作方法
  8. Echarts数据可视化grid直角坐标系(xAxis、yAxis),开发全解+完美注释
  9. 《Java 核心技术 卷 Ⅱ:高级特性》(原书第8版) 已经上市了
  10. SpringCloud七:配置中心Eureka+Config+Bus+RabbitMQ
  11. 基于PGC相位生成载波调制及其解调算法实例分析
  12. 面试吹牛B,入职就倒霉了
  13. 【信号与系统】系统线性时不变、因果稳定性的判定
  14. 如何成为一名全栈工程师:专业建议与技能要求
  15. Java 将带有小数点的字符串转成Integer类型数值
  16. 在 SQL 中计算两个时间戳相隔的天时分秒
  17. 马云卸职CEO的启示
  18. HTML转换为WORD
  19. 什么是PON?EPON
  20. Java学习笔记——StringBuilder

热门文章

  1. ActiveMQ——如何监控ActiveMQ
  2. activiti-api-impl
  3. 六:分布式架构存储设计
  4. NLP系列 2.特征提取
  5. 数据结构与算法 实验5 树、二叉树和森林的基本操作
  6. Python-python程序打包为独立的EXE文件,并配上自定义的图标
  7. Oracle 游标遍历 显式游标 静态游标 OPEN v_cur(); WHILE v_cur%FOUND LOOP; LOOP FETCH v_cur INTO v_row
  8. 直接解决OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.OMP:
  9. 面向对象编程——类和对象
  10. RPM软件安装包-rpm指令操作