链表(Linked List)

链表是有序的列表,但是它在内存中是存储如下

介绍

  • 链表是以节点的方式来存储,是链式存储
  • 每个节点包含data域,next域:指向下一个节点.
  • 如图:发现链表的各个节点不一定是连续存储
  • 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定

1.单链表

单链表(带头结点)逻辑结构示意图如下

1.1单链表的创建和遍历

  • 添加

    • 先创建一个head头节点,作用就是表示单链表的头
    • 后面我们每添加一个节点,就直接加入到链表的最后
  • 遍历
    • 通过一个辅助变量遍历,帮助遍历整个链表

代码实现

要创建两个对象一个是节点对象,一个是链表对象

做add添加时,先找到链表的最后,如果这个链表没有最后,那么我们加入的这个node节点就是这次的头指针指向下一个节点

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(hero2);singleLinkedList.add(hero3);singleLinkedList.add(hero4);singleLinkedList.list();}
}//定义SingleLinkedList 管理我们的Hero
class SingleLinkedList{//先初始化一个头节点,头节点不要动,不存放具体的数据private HeroNode head = new HeroNode(0,"","");//返回头节点public HeroNode getHead(){return head;}//添加节点到单向链表//思路:当不考虑编号顺序时//1.找到当前链表的最后节点//2.将最后这个节点的next 指向新的节点public void add(HeroNode heroNode){//因为head节点不能动,因此我们需要一个辅助变量tempHeroNode temp = head;//遍历链表.找到最后while(true){//找到链表的最后if(temp.next==null){break;}//如果没有找到最后,将temp后移temp = temp.next;}//当退出while循环时,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;}}}//定义一个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;}//为了显示方法,我们重新toString@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickName='" + nickName + '\'' +'}';}
}
复制代码

1.2按顺序插入

代码实现

//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
public void addByOrder(HeroNode heroNode){//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置//因为单链表,因此我们找的temp是位于添加位置的前一个节点,否则插入不了HeroNode temp = head;boolean flag = false; //flag标志添加的编号是否存在,默认为falsewhile(true){if (temp.next == null) break;  //说明temp已经在链表最后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{//插入到链表中,temp后面heroNode.next = temp.next;temp.next = heroNode;}
}
复制代码
     heroNode.next = temp.next;temp.next = heroNode;
复制代码

这里我们把hero.next=temp.next操作是因为,我们要插入一个节点,那么就需要让索引后移一维,即插入位置的后一个节点指向临时变量节点指向的后一个节点,临时变量指向的节点等于要插入的节点

我们按照1423的顺序插入

singleLinkedList.addByOrder(hero1);
singleLinkedList.addByOrder(hero4);
singleLinkedList.addByOrder(hero2);
singleLinkedList.addByOrder(hero3);
复制代码

1.3修改节点信息

代码实现

//修改节点信息  根据no编号来修改,即no编号不能改,
//说明
//1. 根据根据newHeroNode的no来修改即可
public void update(HeroNode newHeroNode){//判断是否为空if (head.next==null){System.out.println("链表为空");return;}//找到需要修改的节点,根据no编号//定义一个铺助变量HeroNode 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的节点",newHeroNode.no);}
}
复制代码

1.4删除节点

思路

  • 我们先找到需要删除的这个节点的前一个节点temp
  • temp.next =temp.next.next
  • 被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收

代码实现

//删除节点
public void del(int no){HeroNode temp = head;boolean flag = false; //标志是否找到待删除节点while(true){if (temp.next == null) break;//已经到链表的最后if (temp.next.no == no){//找到了待删除的节点的前一节点tempflag = true;break;}temp = temp.next;   //temp后移,遍历}//判断flagif (flag){//找到//可以删除temp.next = temp.next.next;}else {System.out.printf("要删除的%d节点不存在",no);}
}
复制代码

2.双向链表

2.1单链表的缺点

  • 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
  • 单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp的下一个节点来删除的

2.2双向链表的操作思路

  • 遍历方式和单链表一样,只是可以向前查找.也可以向后查找
  • 添加(默认添加到双线链表的最后)
    • 先找到双线链表的最后这个节点
    • temp.next = newHeroNode
    • newHeroNode.pre=temp
  • 修改思路和原来的单向链表一样
  • 删除
    • 因为是双向链表,因此,我么可以实现自我删除某个节点
    • 直接找到要删除的这个节点,比如temp
    • temp.pre.next = temp.next
    • temp.next.pre = temp.pre

2.3代码实现

public class DoubleLinkedListDemo {public static void main(String[] args) {System.out.println("双向链表的测试");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.add(hero1);doubleLinkedList.add(hero2);doubleLinkedList.add(hero3);doubleLinkedList.add(hero4);doubleLinkedList.list();//修改HeroNode2 newHeroNode = new HeroNode2(4,"公孙胜","入云龙");doubleLinkedList.update(newHeroNode);System.out.println("修改后的链表情况");doubleLinkedList.list();//删除doubleLinkedList.del(3);System.out.println("删除后的链表情况");doubleLinkedList.list();}
}
//创建一个双线链表的类
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 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的节点",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节点不存在",no);}}
}//定义一个HeroNode2,每个HeroNode对象就是一个节点
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;}//为了显示方法,我们重新toString@Overridepublic String toString() {return "HeroNode{" +"no=" + no +", name='" + name + '\'' +", nickName='" + nickName + '\'' +'}';}
复制代码

3.环形链表和约瑟夫问题

3.1引入

Josephus(约瑟夫、约瑟夫环)问题

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

提示:

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

3.2思路

构建一个单向的环形链表思路

  • 先创建第一个节点,让first指向该节点,并形成环形
  • 后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可

遍历环形链表

  • 先让一个辅助指针(变量)curBoy,指向first节点
  • 然后通过一个while循环遍历该环形链表即可curBoy.next=first结束

3.3代码实现(构建环形链表和遍历)

public class Josephus {public static void main(String[] args) {//测试一把看看构建环形链表,和遍历是否QkCircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();circleSingleLinkedList.addBoy(5);circleSingleLinkedList.showBoy();}
}//创建一个环形单向链表
class CircleSingleLinkedList{//创建一个first节点,当前没有编号的private Boy first = null;//添加小孩的节点,构建成一个环形链表public void addBoy(int nums){//nums 做一个数据校验if (nums<1){System.out.println("nums的值不正确");return;}Boy curBoy = null;//辅助指针,帮助构建环形链表//使用for循环来创建环形链表for (int i = 1; i <= nums; i++) {//根据编号,创建小孩节点Boy boy = new Boy(i);//如果是第一个小孩if (i==1){first=boy;first.setNext(first); //构成环curBoy = first;//让curBoy指向第一个小孩}else {curBoy.setNext(boy);boy.setNext(first);curBoy=boy;}}}//遍历当前环形链表public void showBoy(){//判断链表是否为空if (first==null){System.out.println("链表为空,没有任何小孩");return;}//因为first不能动,因此我们仍然使用一个辅助指针完成遍历Boy curBoy = first;while (true){System.out.printf("小孩的编号%d\n",curBoy.getNo());if (curBoy.getNext()==first){//说明已经遍历完毕break;}curBoy = curBoy.getNext();  //curBoy后移}}}//创建一个Boy类,表示一个节点
class Boy{private int no;//编号private Boy next;//指向下一个节点,默认nullpublic 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;}
}
复制代码

3.4代码实现(出圈操作)

1.需求创建一个辅助指针(变量)helper,事先应该指向环形链表的最后这个节点 补充:小孩报数前,先让first和helper移动k-1次 2.当小孩报数时,让first和helper指针同时的移动m-1次 3.这时就可以将first指向的小孩节点出圈 first = first.next helper.next=first 原来fist指向的节点就没有任何引用,就会被回收

    //根据用户的输入,计算小孩出圈的顺序/*** @param startNo  表示从第几个小孩开始数数* @param countNum 表示数几下* @param nums     表示最初有多少小孩在圈中*/public void countBoy(int startNo, int countNum, int nums) {//先对数据进行校验if (first == null || startNo < 1 || startNo > nums) {System.out.println("参数输入有误,请重新输入");return;}//创建辅助指针,帮助小孩 出圈Boy helper = first;//需求创建一个辅助指针(变量)helper,事先应该指向环形链表的最后这个节点while (true) {if (helper.getNext() == first) {//说明helper指向最后小孩节点break;}helper = helper.getNext();}//小孩报数前,先让first和helper移动k-1次for (int i = 0; i < startNo-1; i++) {first = first.getNext();helper = helper.getNext();}//当小孩报数时,让first和helper指针同时的移动m-1次,然后出圈//这里是一个循环操作,知道圈中只有一个节点while(true){if (helper==first){//说明圈中只有一个节点break;}//让first和helper指针同时移动countNum-1for (int i = 0; i < countNum-1; i++) {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());}
}
复制代码

需求引入

计算式:[7*2*2-5+1-5+3-3]

请问:计算机底层是如何运算得到结果的?注意不是简单的把算式列出运算,因为我们看这个算式7*2*2-5,但是计算机怎么理解这个算式的对计算机而言,它接收到的就是一个字符串),我们讨论的是这个问题。=>栈

1.介绍

  • 栈的英文为(stack)

  • 栈是一个先入后出(FILO-First In LastOut))的有序列表。

  • 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另端为固定的一端,称为栈底(Bottom)。

  • 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除

  • 出栈和入栈的概念(如图所示)

2.应用场景

  • 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
  • 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
  • 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
  • 二叉树的遍历。
  • 图形的深度优先(depth一first)搜索法:

3.思路分析

  • 使用数组来模拟栈
  • 定义一个top来表示栈顶,初始化为1
  • 入栈的操作,当有数据加入到栈时,top++;stack[top]=data;
  • 出栈的操作,int value=stack[top];top--,return value;

4.代码实现

public class ArrayStackDemo {public static void main(String[] args) {//测试一下ArrayStack//先创建一个Arraystack对象->表示栈ArrayStack stack = new ArrayStack(4);String key="";boolean loop = true;//控制是否退出菜单Scanner scanner = new Scanner(System.in);while(loop){System.out.println("show:表示显示栈");System.out.println("exit:退出程序");System.out.println("push:添加数据到栈(入栈)");System.out.println("show:从栈取出数据(出栈)");System.out.println("请输入你的选择");key = scanner.next();switch (key){case "show":stack.list();break;case "push":System.out.println("请输入一个数字");int val = scanner.nextInt();stack.push(val);break;case "pop":try{int res = stack.pop();System.out.printf("出栈的数据是%d\n",res);}catch (Exception e){System.out.println(e.getMessage());}break;case "exit":scanner.close();loop = false;break;default:break;}}System.out.println("程序退出了");}
}
//定义一个ArrayStack 表示栈
class ArrayStack{private int maxSize;//栈的大小private int[] stack;//数组.数组模拟栈.数据就放在该数组中private int top = -1;//top表示栈顶,初始化位-1public ArrayStack(int maxSize) {this.maxSize = maxSize;stack = new int[this.maxSize];}//栈满public boolean isFull(){return top==maxSize-1;}//栈空public boolean isEmpty(){return top==-1;}//入栈-pushpublic void push(int value){//先判断栈是都满if (isFull()){System.out.println("栈满");return;}top++;stack[top]=value;}//出栈-pop,将栈顶的数据返回public int pop(){//先判断栈是否空if (isEmpty()){//抛出异常throw new RuntimeException("栈空,没有数据");}int value = stack[top];top--;return value;}//遍历栈的情况 遍历时,需要从栈顶开始显示数据public void list(){if (isEmpty()){System.out.println("栈空,没有数据");return;}for (int i = top; i >=0 ; i--) {System.out.printf("stack[%d]=%d\n",i,stack[i]);}}}
复制代码

Qz学算法-数据结构篇(链表、栈)相关推荐

  1. Qz学算法-数据结构篇(表达式、递归)

    前缀.中缀.后缀表达式->(逆波兰表达式) 1.前缀表达式(波兰表达式) 前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前 举例说明:(3+4)×5-6对应的前缀表达式就是-×+3456 ...

  2. Qz学算法-数据结构篇(引入)

    其实自己一直想写算法有关的系列,但出于时间的关系一直没有开展有关算法的学习,现在正好有这个活动,那就加油写下去吧.那么这个系列就以数据结构为开篇吧 一.引入 1.经典算法面试题 字符串匹配问题 1)有 ...

  3. Qz学算法-数据结构篇(冒泡、选择)

    冒泡排序 1.基本介绍 冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部, ...

  4. Qz学算法-数据结构篇(稀疏数组、队列)

    目录 1.稀疏(sparse array)数组 需求引入 分析问题 2.队列(queue) 1.数组模拟队列 需求引入 1.1介绍 1.2数组模拟队列 1.3思路分析 1.4代码实现 2.数组模拟环形 ...

  5. Qz学算法-数据结构篇(插入、希尔)

    插入排序 1.基本介绍 插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的. 2.基本思想 插入排序(Insertion Sorting).的基本思想是:把 ...

  6. Qz学算法-数据结构篇(排序)

    排序算法 排序的概念 排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程 分类 排序的分类: 内部排序: 指将需要处理的所有数据都加载到内部存储器中进行排序 ...

  7. 双向链表删除节点时间复杂度_「十分钟学算法」删除链表的倒数第N个节点

    给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点. 示例: 给定一个链表: 1->2->3->4->5, 和 n = 2.当删除了倒数第二个节点后,链表变为 1 ...

  8. 伍六七带你学算法 入门篇-链表的中间节点

    力扣-876链表的中间节点 难度-简单 给定一个带有头结点 head 的非空单链表,返回链表的中间结点. 如果有两个中间结点,则返回第二个中间结点. 示例 1: 输入:[1,2,3,4,5] 输出:此 ...

  9. c语言数据结构篇之栈(线性栈与链式栈)

    线性栈 1.判空条件:S.top==-1; 2.满栈条件:S.top=maxn-1 #include<cstdio> #include<stdlib.h>#define max ...

最新文章

  1. 三阶魔方还原步骤图_(六)最简单的三阶魔方入门教程——顶面还原
  2. 重磅 | MIT启动IQ计划:研究人类智能,让全世界的机构共同合作
  3. 10年磨一剑,软件编程走火入魔之:把简单的功能做个彻彻底底、把劳动成果重复利用...
  4. 石家庄地铁线路查询系统
  5. 使用临时表解决union和order by不能同时使用的问题
  6. 如何配置java环境变量
  7. ASP.NET Web开发框架之七 开发流程与模式
  8. 刘强东卸任京东集团CEO!接任人是他...
  9. 开课吧Java面试题:虚引用与软引用和弱引用的区别
  10. 对称矩阵、Hermite矩阵、正交矩阵、酉矩阵、奇异矩阵、正规矩阵、幂等矩阵、合同矩阵、正定矩阵...
  11. python3 模板库 好用_关于3个Python模板库的比较
  12. [Git] Squash all of my commits into a single one and merge into master
  13. 迅雷修改tracker服务器,qBittorrent 添加自定义 Tracker 的方法
  14. 瞄准千亿工业物联网市场,有人物联网为2万企业级用户提供完整可靠方案
  15. USB3.0高清视频和音频采集处理芯片——MS2131
  16. excel 无法求和
  17. ICT的圣杯(一):人形电脑天使心
  18. VS+Qt手动生成moc文件
  19. 自定义ListVIew添加上下更多项
  20. 学会自我学习(自律性)

热门文章

  1. 国际财务报告准则(IFRS)
  2. 必备技能16:三星355V4C笔记本电脑U盘启动方法
  3. 石破天惊 耶稣和颛顼是同一个人?
  4. 用友U8非标准的生产订单的材料出库单“成本对象”带不出来?
  5. AEGN复现细节、环境配置以及bug解决方案
  6. 年轻人不讲武德,TDengine边缘侧数据存储方案挑战SQLite
  7. 【你不知道的JavaScript上卷】——作用域与闭包
  8. 史上最强 Tomcat8 性能优化实战!
  9. 关于GCSTA by zrx
  10. AT24C512驱动调试中碰到的坑