zoukankan      html  css  js  c++  java
  • Java数据结构与算法 day02 链表

    第三章 链表

    单链表介绍和内存布局

    链表是有序的列表,但是它在内存中是实际存储结构如下:

    在这里插入图片描述

    小结:
    1.链表是以节点的方式来存储,是链式存储(即各个节点之间并不一定是连续存储的,而是相互指向的);
    2.每个节点包含 data 域:存放数据的域, next 域:指向下一个节点;
    3.如图:发现链表的各个节点不一定是连续存储;
    4.链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定.
    

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

    在这里插入图片描述

    单链表创建和遍历的分析实现

    • 先看一个例子

    使用带head头的单向链表实现 –水浒英雄排行榜管理

    1)完成对英雄人物的增删改查操作, 注: 删除和修改,查找可以考虑学员独立完成,也可带学员完成

    2)第一种方法在添加英雄时,直接添加到链表的尾部

    3)第二种方式在添加英雄时,根据排名将英雄插入到指定位置
    (如果有这个排名,则添加失败,并给出提示)

    • 单链表的创建示意图(添加), 显示单向链表的分析
    class HeroNode {
        int no;
        String name;
        String nickName;
        HeroNode next;
    }
    

    添加(创建)过程

    • 先创建一个head 头节点, 作用就是表示单链表的头;

    在这里插入图片描述

    • 之后每添加一个节点,就直接加入到—》链表的最后。

    在这里插入图片描述
    在这里插入图片描述

    遍历过程

    • 通过一个辅助变量(临时变量temp)遍历,帮助遍历整个链表。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    代码实现

    /*
     * 此代码添加数据有问题,待优化
     */
    public class SingleLinkedListTest {
    
    	public static void main(String[] args) {
    		//测试一下
    		//1.创建节点
    		HeroNode hero = new HeroNode(1,"宋江","及时雨");
    		HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟");
    		HeroNode hero3 = new HeroNode(3,"吴用","智多星");
    		HeroNode hero4 = new HeroNode(4,"公孙胜","入云龙");
    		HeroNode hero5 = new HeroNode(5,"关胜","大刀");
    		
    		//创建一个链表
    		SingleLinkedList singk = new SingleLinkedList();
    		
    		//添加数据到链表
    		singk.add(hero);
    		singk.add(hero2);
    		singk.add(hero3);
    		singk.add(hero4);
    		singk.add(hero5); 
    		
    		//显示链表
    		singk.list();
    	}
    
    }
    
    //定义一个SingleLinkedList类,来管理我们的英雄
    class SingleLinkedList{
    	//先初始化一个头节点,头节点不要动(防止后期找不到此链表),不存放具体数据
    	private HeroNode head = new HeroNode(0, "", "");
    	
    	//添加节点到单向链表
    	//思路,当不考虑编号的顺序时
    	//1.找到当前链表的最后节点
    	//2.将最后这个节点的next域指向这个新的节点
    	public void add(HeroNode heroNode){
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		//遍历链表,找到最后
    		while(true){
    			//找到链表的最后
    			if(temp.next == null){	//判定找到了的条件
    				break;
    			}
    			//如果没有找到,将temp后移
    			temp = temp.next;
    		}
    		//当退出循环时,temp就指向了链表的最后
    		//将最后这个节点的next --指向--》 新的节点
    		temp.next = heroNode;
    	}
    	
    	//显示链表[遍历]
    	public void list(){
    		//判断链表是否为空
    		if(head.next == null){
    			System.out.println("链表为空!!!");
    			return;
    		}
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		while(true){
    			//判断是否到链表的最后
    			if(temp == null){
    				break;
    			}
    			//如果不为空,输出节点的信息
    			System.out.println(temp);
    			//注意!!!!将next后移(因为不向后移动,会造成死循环)
    			temp = temp.next;
    		}
    	}
    }
    
    //定义一个HeroNode,每个HeroNode对象就是一个节点
    class HeroNode{
    	public int id;
    	public String name;
    	public String nickName;	//别名,昵称
    	public HeroNode next;	//指向下一个节点
    	
    	//构造器
    	public HeroNode(int id, String name, String nickName) {
    		super();
    		this.id = id;
    		this.name = name;
    		this.nickName = nickName;
    	}
    
    	//为了显示方便,重写toString方法
    	@Override
    	public String toString() {
    		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
    	}
    }
    

    单链表按顺序插入节点

    • 需要按照编号的顺序添加
    • 首先找到新添加的节点的位置, 是通过辅助变量(指针), 通过遍历来搞定

    在这里插入图片描述

    • 新的节点.next = temp.next

    在这里插入图片描述

    • 将temp.next = 新的节点

    在这里插入图片描述

    • 代码实现
    /*
     * 此代码已优化
     */
    public class SingleLinkedListTest {
    
    	public static void main(String[] args) {
    		//测试一下
    		//1.创建节点
    		HeroNode hero = new HeroNode(1,"宋江","及时雨");
    		HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟");
    		HeroNode hero3 = new HeroNode(3,"吴用","智多星");
    		HeroNode hero4 = new HeroNode(4,"公孙胜","入云龙");
    		HeroNode hero5 = new HeroNode(5,"关胜","大刀");
    		
    		//创建一个链表
    		SingleLinkedList singk = new SingleLinkedList();
    		
    		//添加数据到链表
    //		singk.add(hero);
    //		singk.add(hero2);
    //		singk.add(hero4);
    //		singk.add(hero3);
    //		singk.add(hero5); 
    		
    		//添加按照编号的顺序
    		singk.addByOrder(hero);
    		singk.addByOrder(hero4);
    		singk.addByOrder(hero2);
    		singk.addByOrder(hero5); 
    		singk.addByOrder(hero3);
    //		singk.addByOrder(hero3);
    		
    		//显示链表
    		singk.list();
    	}
    
    }
    
    //定义一个SingleLinkedList类,来管理我们的英雄
    class SingleLinkedList{
    	//先初始化一个头节点,头节点不要动(防止后期找不到此链表),不存放具体数据
    	private HeroNode head = new HeroNode(0, "", "");
    	
    	//第一种方式:第一种方法在添加英雄时,直接添加到链表的尾部
    	//添加节点到单向链表,思路,当不考虑编号的顺序时
    	//1.找到当前链表的最后节点
    	//2.将最后这个节点的next域指向这个新的节点
    	public void add(HeroNode heroNode){
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		//遍历链表,找到最后
    		while(true){
    			//找到链表的最后
    			if(temp.next == null){	//判定找到了的条件
    				break;
    			}
    			//如果没有找到,将temp后移
    			temp = temp.next;
    		}
    		//当退出循环时,temp就指向了链表的最后
    		//将最后这个节点的next --指向--》 新的节点
    		temp.next = heroNode;
    	}
    	
    	//显示链表[遍历]
    	public void list(){
    		//判断链表是否为空
    		if(head.next == null){
    			System.out.println("链表为空!!!");
    			return;
    		}
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		while(true){
    			//判断是否到链表的最后
    			if(temp == null){
    				break;
    			}
    			//如果不为空,输出节点的信息
    			System.out.println(temp);
    			//注意!!!!将next后移(因为不向后移动,会造成死循环)
    			temp = temp.next;
    		}
    	}
    	
    	//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
    	//(如果有这个排名,则添加失败,并给出提示)
    	public void addByOrder(HeroNode heroNode){
    		//由于头节点head不能动,所以需要一个辅助变量 temp,找到添加的位置
    		//由于是单链表,而找到temp是位于 添加位置的前一个节点,否则插入不了
    		HeroNode temp = head;
    		boolean flag = false;	//标识添加的编号是否存在,默认为false
    		while(true){
    			if(temp.next == null){	//说明已经在链表的最后
    				break;	
    			}
    			if(temp.next.id > heroNode.id){	//位置找到了,就在temp的后面插入
    				break;
    			}else if(temp.next.id == heroNode.id){	//说明希望添加的heroNode的编号已然存在
    				flag = true;	//说明编号存在
    				break;
    			}
    			temp = temp.next;	//后移,遍历当前链表
    		}
    		//判断flag的值
    		if(flag){	//不能添加,说明编号存在
    			System.out.printf("准备插入的英雄编号%d已经存在了。无法加入
    ",heroNode.id);
    		}else{
    			//插入到链表中,temp的后面
    			heroNode.next = temp.next;
    			temp.next = heroNode;
    		}
    	}
    
    }
    
    //定义一个HeroNode,每个HeroNode对象就是一个节点
    class HeroNode{
    	public int id;
    	public String name;
    	public String nickName;	//别名,昵称
    	public HeroNode next;	//指向下一个节点
    	
    	//构造器
    	public HeroNode(int id, String name, String nickName) {
    		super();
    		this.id = id;
    		this.name = name;
    		this.nickName = nickName;
    	}
    
    	//为了显示方便,重写toString方法
    	@Override
    	public String toString() {
    		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
    	}
    }
    

    单链表节点的修改

    public class SingleLinkedListTest {
    
    	public static void main(String[] args) {
    		//测试一下
    		//1.创建节点
    		HeroNode hero = new HeroNode(1,"宋江","及时雨");
    		HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟");
    		HeroNode hero3 = new HeroNode(3,"吴用","智多星");
    		HeroNode hero4 = new HeroNode(4,"公孙胜","入云龙");
    		HeroNode hero5 = new HeroNode(5,"关胜","大刀");
    		
    		//创建一个链表
    		SingleLinkedList singk = new SingleLinkedList();
    		
    		//添加按照编号的顺序
    		singk.addByOrder(hero);
    		singk.addByOrder(hero4);
    		singk.addByOrder(hero2);
    		singk.addByOrder(hero5); 
    		singk.addByOrder(hero3);
    		
    		//显示链表
    		singk.list();
    		
    		//测试修改节点的代码
    		HeroNode hero6 = new HeroNode(5,"渣渣辉","一刀999");
    		singk.update(hero6);
    		
    		//显示链表
    		System.out.println("修改后的链表情况:");
    		singk.list();
    	}
    
    }
    
    //定义一个SingleLinkedList类,来管理我们的英雄
    class SingleLinkedList{
    	//先初始化一个头节点,头节点不要动(防止后期找不到此链表),不存放具体数据
    	private HeroNode head = new HeroNode(0, "", "");
    	
    	//修改节点的信息,根据id编号来修改,即id编号不改
    	//说明
    	//1.根据newHeroNode 的 id 来修改即可
    	public void update(HeroNode newHeroNode){
    		//判断是否为空
    		if(head.next == null){	//链表为空
    			System.out.println("链表为空!!!");
    			return;
    		}
    		//找到需要修改的节点,根据 id 编号
    		//定义一个临时变量
    		HeroNode temp = head.next;
    		boolean flag = false;	//表示是否找到该节点
    		while(true){
    			if(temp == null){
    				break;	//已经遍历完链表
    			}
    			if(temp.id == newHeroNode.id){
    				//找到了
    				flag = true;
    				break;
    			}
    			temp = temp.next;
    		}
    		//根据flag 判断是否找到要修改的节点
    		if(flag){
    			temp.name = newHeroNode.name;
    			temp.nickName = newHeroNode.nickName;
    		}else{
    			System.out.printf("没有找到编号为 %d 的节点.
    ",newHeroNode.id);
    		}
    	}
    	
    	//显示链表[遍历]
    	public void list(){
    		//判断链表是否为空
    		if(head.next == null){
    			System.out.println("链表为空!!!");
    			return;
    		}
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		while(true){
    			//判断是否到链表的最后
    			if(temp == null){
    				break;
    			}
    			//如果不为空,输出节点的信息
    			System.out.println(temp);
    			//注意!!!!将next后移(因为不向后移动,会造成死循环)
    			temp = temp.next;
    		}
    	}
    	
    	//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
    	//(如果有这个排名,则添加失败,并给出提示)
    	public void addByOrder(HeroNode heroNode){
    		//由于头节点head不能动,所以需要一个辅助变量 temp,找到添加的位置
    		//由于是单链表,而找到temp是位于 添加位置的前一个节点,否则插入不了
    		HeroNode temp = head;
    		boolean flag = false;	//标识添加的编号是否存在,默认为false
    		while(true){
    			if(temp.next == null){	//说明已经在链表的最后
    				break;	
    			}
    			if(temp.next.id > heroNode.id){	//位置找到了,就在temp的后面插入
    				break;
    			}else if(temp.next.id == heroNode.id){	//说明希望添加的heroNode的编号已然存在
    				flag = true;	//说明编号存在
    				break;
    			}
    			temp = temp.next;	//后移,遍历当前链表
    		}
    		//判断flag的值
    		if(flag){	//不能添加,说明编号存在
    			System.out.printf("准备插入的英雄编号%d已经存在了。无法加入
    ",heroNode.id);
    		}else{
    			//插入到链表中,temp的后面
    			heroNode.next = temp.next;
    			temp.next = heroNode;
    		}
    	}
    
    }
    
    //定义一个HeroNode,每个HeroNode对象就是一个节点
    class HeroNode{
    	public int id;
    	public String name;
    	public String nickName;	//别名,昵称
    	public HeroNode next;	//指向下一个节点
    	
    	//构造器
    	public HeroNode(int id, String name, String nickName) {
    		super();
    		this.id = id;
    		this.name = name;
    		this.nickName = nickName;
    	}
    
    	//为了显示方便,重写toString方法
    	@Override
    	public String toString() {
    		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
    	}
    }
    

    单链表节点的删除和小结

    从单链表中删除一个节点的思路图解

    在这里插入图片描述

    1. 我们先找到 需要删除的这个节点的前一个节点 temp

    在这里插入图片描述

    1. temp.next = temp.next.next

    在这里插入图片描述

    1. 被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收
    public class SingleLinkedListTest {
    
    	public static void main(String[] args) {
    		//测试一下
    		//1.创建节点
    		HeroNode hero = new HeroNode(1,"宋江","及时雨");
    		HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟");
    		HeroNode hero3 = new HeroNode(3,"吴用","智多星");
    		HeroNode hero4 = new HeroNode(4,"公孙胜","入云龙");
    		HeroNode hero5 = new HeroNode(5,"关胜","大刀");
    		
    		//创建一个链表
    		SingleLinkedList singk = new SingleLinkedList();
    		
    		//添加按照编号的顺序
    		singk.addByOrder(hero);
    		singk.addByOrder(hero4);
    		singk.addByOrder(hero2);
    		singk.addByOrder(hero5); 
    		singk.addByOrder(hero3);
    		
    		//显示链表
    		singk.list();
    		
    		//删除一个节点
    		singk.del(2);
    		singk.del(1);
    		singk.del(3);
    		singk.del(4);
    		singk.del(5);
    		System.out.println("删除后的情况:");
    		singk.list();	//显示链表
    	}
    
    }
    
    //定义一个SingleLinkedList类,来管理我们的英雄
    class SingleLinkedList{
    	//先初始化一个头节点,头节点不要动(防止后期找不到此链表),不存放具体数据
    	private HeroNode head = new HeroNode(0, "", "");
    	
    	//删除节点
    	//思路
    	//1.由于头节点head不能动,所以需要一个辅助变量 temp来找到待删除节点的前一个节点
    	//2.说明我们在比较时,是temp.next.id 和  需要删除的节点的id比较
    	public void del(int id){
    		HeroNode temp = head;
    		boolean flag = false;	//标志是否找到带删除的节点
    		while(true){
    			if(temp.next == null){	//已经到链表的最后
    				break;
    			}
    			if(temp.next.id == id){	//找到了带删除的节点的前一个节点
    				flag = true;
    				break;
    			}
    			temp = temp.next;	//temp后移,遍历
    		}
    		//判断flag
    		if(flag){	//说明找到了
    			//可以删除
    			temp.next = temp.next.next;
    		}else{
    			System.out.printf("要删除的节点 %d 不存在。
    ",id);
    		}
    	}
    	
    	
    	//显示链表[遍历]
    	public void list(){
    		//判断链表是否为空
    		if(head.next == null){
    			System.out.println("链表为空!!!");
    			return;
    		}
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		while(true){
    			//判断是否到链表的最后
    			if(temp == null){
    				break;
    			}
    			//如果不为空,输出节点的信息
    			System.out.println(temp);
    			//注意!!!!将next后移(因为不向后移动,会造成死循环)
    			temp = temp.next;
    		}
    	}
    	
    	//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
    	//(如果有这个排名,则添加失败,并给出提示)
    	public void addByOrder(HeroNode heroNode){
    		//由于头节点head不能动,所以需要一个辅助变量 temp,找到添加的位置
    		//由于是单链表,而找到temp是位于 添加位置的前一个节点,否则插入不了
    		HeroNode temp = head;
    		boolean flag = false;	//标识添加的编号是否存在,默认为false
    		while(true){
    			if(temp.next == null){	//说明已经在链表的最后
    				break;	
    			}
    			if(temp.next.id > heroNode.id){	//位置找到了,就在temp的后面插入
    				break;
    			}else if(temp.next.id == heroNode.id){	//说明希望添加的heroNode的编号已然存在
    				flag = true;	//说明编号存在
    				break;
    			}
    			temp = temp.next;	//后移,遍历当前链表
    		}
    		//判断flag的值
    		if(flag){	//不能添加,说明编号存在
    			System.out.printf("准备插入的英雄编号%d已经存在了。无法加入
    ",heroNode.id);
    		}else{
    			//插入到链表中,temp的后面
    			heroNode.next = temp.next;
    			temp.next = heroNode;
    		}
    	}
    
    }
    
    //定义一个HeroNode,每个HeroNode对象就是一个节点
    class HeroNode{
    	public int id;
    	public String name;
    	public String nickName;	//别名,昵称
    	public HeroNode next;	//指向下一个节点
    	
    	//构造器
    	public HeroNode(int id, String name, String nickName) {
    		super();
    		this.id = id;
    		this.name = name;
    		this.nickName = nickName;
    	}
    
    	//为了显示方便,重写toString方法
    	@Override
    	public String toString() {
    		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
    	}
    }
    

    单链表面试题

    单链表的常见面试题有如下:

    1)求单链表中有效节点的个数

    2)查找单链表中的倒数第k个结点 【新浪面试题】

    3)单链表的反转【腾讯面试题,有点难度】

    4)从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】

    5)合并两个有序的单链表,合并之后的链表依然有序【课后练习.】

    • 求单链表中有效节点的个数
    public class SingleLinkedListTest {
    
    	public static void main(String[] args) {
    		//测试一下
    		//1.创建节点
    		HeroNode hero = new HeroNode(1,"宋江","及时雨");
    		HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟");
    		HeroNode hero3 = new HeroNode(3,"吴用","智多星");
    		HeroNode hero4 = new HeroNode(4,"公孙胜","入云龙");
    		HeroNode hero5 = new HeroNode(5,"关胜","大刀");
    		
    		//创建一个链表
    		SingleLinkedList singk = new SingleLinkedList();
    		
    		//添加按照编号的顺序
    		singk.addByOrder(hero);
    		singk.addByOrder(hero4);
    		singk.addByOrder(hero2);
    		singk.addByOrder(hero5); 
    		singk.addByOrder(hero3);
    		
    		//显示链表
    		singk.list();
    		
    		//测试一下:求单链表中有效节点的个数
    		System.out.println("单链表有效的节点个数 = " + getLength(singk.getHead()));//5
    	}
    	
    	//获取单链表的节点的个数(如果是带头节点的链表,需求不统计头结点)
    	/**
    	  * 
    	  * @Description 
    	  * @author subei
    	  * @date 2020年5月22日下午5:09:46
    	  * @param head 链表的头节点
    	  * @return 返回的是有效的节点的个数
    	 */
    	public static int getLength(HeroNode head){
    		if(head.next == null){
    			return 0;
    		}
    		int length = 0;
    		//定义一个临时的变量,未统计头节点
    		HeroNode str = head.next;
    		while(str != null){
    			length++;
    			str = str.next;	//即遍历
    		}
    		return length;
    	}
    
    }
    
    //定义一个SingleLinkedList类,来管理我们的英雄
    class SingleLinkedList{
    	//先初始化一个头节点,头节点不要动(防止后期找不到此链表),不存放具体数据
    	private HeroNode head = new HeroNode(0, "", "");
    	
    	//返回头节点
    	public HeroNode getHead() {
    		return head;
    	}
    
    	//第一种方式:第一种方法在添加英雄时,直接添加到链表的尾部
    	//添加节点到单向链表,思路,当不考虑编号的顺序时
    	//1.找到当前链表的最后节点
    	//2.将最后这个节点的next域指向这个新的节点
    	public void add(HeroNode heroNode){
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		//遍历链表,找到最后
    		while(true){
    			//找到链表的最后
    			if(temp.next == null){	//判定找到了的条件
    				break;
    			}
    			//如果没有找到,将temp后移
    			temp = temp.next;
    		}
    		//当退出循环时,temp就指向了链表的最后
    		//将最后这个节点的next --指向--》 新的节点
    		temp.next = heroNode;
    	}
    	
    	//修改节点的信息,根据id编号来修改,即id编号不改
    	//说明
    	//1.根据newHeroNode 的 id 来修改即可
    	public void update(HeroNode newHeroNode){
    		//判断是否为空
    		if(head.next == null){	//链表为空
    			System.out.println("链表为空!!!");
    			return;
    		}
    		//找到需要修改的节点,根据 id 编号
    		//定义一个临时变量
    		HeroNode temp = head.next;
    		boolean flag = false;	//表示是否找到该节点
    		while(true){
    			if(temp == null){
    				break;	//已经遍历完链表
    			}
    			if(temp.id == newHeroNode.id){
    				//找到了
    				flag = true;
    				break;
    			}
    			temp = temp.next;
    		}
    		//根据flag 判断是否找到要修改的节点
    		if(flag){
    			temp.name = newHeroNode.name;
    			temp.nickName = newHeroNode.nickName;
    		}else{
    			System.out.printf("没有找到编号为 %d 的节点.
    ",newHeroNode.id);
    		}
    	}
    	
    	//删除节点
    	//思路
    	//1.由于头节点head不能动,所以需要一个辅助变量 temp来找到待删除节点的前一个节点
    	//2.说明我们在比较时,是temp.next.id 和  需要删除的节点的id比较
    	public void del(int id){
    		HeroNode temp = head;
    		boolean flag = false;	//标志是否找到带删除的节点
    		while(true){
    			if(temp.next == null){	//已经到链表的最后
    				break;
    			}
    			if(temp.next.id == id){	//找到了带删除的节点的前一个节点
    				flag = true;
    				break;
    			}
    			temp = temp.next;	//temp后移,遍历
    		}
    		//判断flag
    		if(flag){	//说明找到了
    			//可以删除
    			temp.next = temp.next.next;
    		}else{
    			System.out.printf("要删除的节点 %d 不存在。
    ",id);
    		}
    	}
    	
    	
    	//显示链表[遍历]
    	public void list(){
    		//判断链表是否为空
    		if(head.next == null){
    			System.out.println("链表为空!!!");
    			return;
    		}
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		while(true){
    			//判断是否到链表的最后
    			if(temp == null){
    				break;
    			}
    			//如果不为空,输出节点的信息
    			System.out.println(temp);
    			//注意!!!!将next后移(因为不向后移动,会造成死循环)
    			temp = temp.next;
    		}
    	}
    	
    	//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
    	//(如果有这个排名,则添加失败,并给出提示)
    	public void addByOrder(HeroNode heroNode){
    		//由于头节点head不能动,所以需要一个辅助变量 temp,找到添加的位置
    		//由于是单链表,而找到temp是位于 添加位置的前一个节点,否则插入不了
    		HeroNode temp = head;
    		boolean flag = false;	//标识添加的编号是否存在,默认为false
    		while(true){
    			if(temp.next == null){	//说明已经在链表的最后
    				break;	
    			}
    			if(temp.next.id > heroNode.id){	//位置找到了,就在temp的后面插入
    				break;
    			}else if(temp.next.id == heroNode.id){	//说明希望添加的heroNode的编号已然存在
    				flag = true;	//说明编号存在
    				break;
    			}
    			temp = temp.next;	//后移,遍历当前链表
    		}
    		//判断flag的值
    		if(flag){	//不能添加,说明编号存在
    			System.out.printf("准备插入的英雄编号%d已经存在了。无法加入
    ",heroNode.id);
    		}else{
    			//插入到链表中,temp的后面
    			heroNode.next = temp.next;
    			temp.next = heroNode;
    		}
    	}
    
    }
    
    //定义一个HeroNode,每个HeroNode对象就是一个节点
    class HeroNode{
    	public int id;
    	public String name;
    	public String nickName;	//别名,昵称
    	public HeroNode next;	//指向下一个节点
    	
    	//构造器
    	public HeroNode(int id, String name, String nickName) {
    		super();
    		this.id = id;
    		this.name = name;
    		this.nickName = nickName;
    	}
    
    	//为了显示方便,重写toString方法
    	@Override
    	public String toString() {
    		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
    	}
    }
    

    新浪面试题

    在这里插入图片描述

    public class SingleLinkedListTest {
    
    	public static void main(String[] args) {
    		//测试一下
    		//1.创建节点
    		HeroNode hero = new HeroNode(1,"宋江","及时雨");
    		HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟");
    		HeroNode hero3 = new HeroNode(3,"吴用","智多星");
    		HeroNode hero4 = new HeroNode(4,"公孙胜","入云龙");
    		HeroNode hero5 = new HeroNode(5,"关胜","大刀");
    		
    		//创建一个链表
    		SingleLinkedList singk = new SingleLinkedList();
    		
    		//添加按照编号的顺序
    		singk.addByOrder(hero);
    		singk.addByOrder(hero4);
    		singk.addByOrder(hero2);
    		singk.addByOrder(hero5); 
    		singk.addByOrder(hero3);
    		
    		//显示链表
    		singk.list();
    		
    		//测试一下:求单链表中有效节点的个数
    		System.out.println("单链表有效的节点个数 = " + getLength(singk.getHead()));//5
    		
    		//测试一下:查找单链表中的倒数第k个结点 
    		HeroNode res = findLastIndexNode(singk.getHead(), 4);
    		System.out.println("res = "+ res);
    	}
    	
    	//查找单链表中的倒数第k个结点 
    	//思路:
    	//1.编写一个方法,接收head节点,同时接收一个index 
    	//2.index 表示是倒数第index个节点
    	//3.先把链表从头到尾遍历,得到链表的总的长度 getLength
    	//4.得到size 后,我们从链表的第一个开始遍历 (size-index)个,就可以得到
    	//5.如果找到了,则返回该节点,否则返回null
    	public static HeroNode findLastIndexNode(HeroNode head, int index) {
    		//判断如果链表为空,返回null
    		if(head.next == null) {
    			return null;//没有找到
    		}
    		//第一个遍历得到链表的长度(即节点个数)
    		int size = getLength(head);
    		//第二次遍历  size-index 位置,即倒数的第K个节点
    		//先做一个index的校验
    		if(index <= 0 || index > size) {
    			return null; 
    		}
    		//第一一个临时变量,for 循环定位到倒数的index
    		HeroNode str = head.next; //3 // 3 - 1 = 2
    		for(int i =0; i< size - index; i++) {
    			str = str.next;
    		}
    		return str;
    	}
    	
    	//获取单链表的节点的个数(如果是带头节点的链表,需求不统计头结点)
    	/**
    	  * 
    	  * @Description 
    	  * @author subei
    	  * @date 2020年5月22日下午5:09:46
    	  * @param head 链表的头节点
    	  * @return 返回的是有效的节点的个数
    	 */
    	public static int getLength(HeroNode head){
    		if(head.next == null){
    			return 0;
    		}
    		int length = 0;
    		//定义一个临时的变量,未统计头节点
    		HeroNode str = head.next;
    		while(str != null){
    			length++;
    			str = str.next;	//即遍历
    		}
    		return length;
    	}
    
    }
    
    //定义一个SingleLinkedList类,来管理我们的英雄
    class SingleLinkedList{
    	//先初始化一个头节点,头节点不要动(防止后期找不到此链表),不存放具体数据
    	private HeroNode head = new HeroNode(0, "", "");
    	
    	//返回头节点
    	public HeroNode getHead() {
    		return head;
    	}
    	
    	//显示链表[遍历]
    	public void list(){
    		//判断链表是否为空
    		if(head.next == null){
    			System.out.println("链表为空!!!");
    			return;
    		}
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		while(true){
    			//判断是否到链表的最后
    			if(temp == null){
    				break;
    			}
    			//如果不为空,输出节点的信息
    			System.out.println(temp);
    			//注意!!!!将next后移(因为不向后移动,会造成死循环)
    			temp = temp.next;
    		}
    	}
    	
    	//第二种方式在添加英雄时,根据排名将英雄插入到指定位置
    	//(如果有这个排名,则添加失败,并给出提示)
    	public void addByOrder(HeroNode heroNode){
    		//由于头节点head不能动,所以需要一个辅助变量 temp,找到添加的位置
    		//由于是单链表,而找到temp是位于 添加位置的前一个节点,否则插入不了
    		HeroNode temp = head;
    		boolean flag = false;	//标识添加的编号是否存在,默认为false
    		while(true){
    			if(temp.next == null){	//说明已经在链表的最后
    				break;	
    			}
    			if(temp.next.id > heroNode.id){	//位置找到了,就在temp的后面插入
    				break;
    			}else if(temp.next.id == heroNode.id){	//说明希望添加的heroNode的编号已然存在
    				flag = true;	//说明编号存在
    				break;
    			}
    			temp = temp.next;	//后移,遍历当前链表
    		}
    		//判断flag的值
    		if(flag){	//不能添加,说明编号存在
    			System.out.printf("准备插入的英雄编号%d已经存在了。无法加入
    ",heroNode.id);
    		}else{
    			//插入到链表中,temp的后面
    			heroNode.next = temp.next;
    			temp.next = heroNode;
    		}
    	}
    
    }
    
    //定义一个HeroNode,每个HeroNode对象就是一个节点
    class HeroNode{
    	public int id;
    	public String name;
    	public String nickName;	//别名,昵称
    	public HeroNode next;	//指向下一个节点
    	
    	//构造器
    	public HeroNode(int id, String name, String nickName) {
    		super();
    		this.id = id;
    		this.name = name;
    		this.nickName = nickName;
    	}
    
    	//为了显示方便,重写toString方法
    	@Override
    	public String toString() {
    		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
    	}
    }
    

    腾讯面试题

    单链表的反转,效果图如下:

    在这里插入图片描述

    思路:

    1. 先定义一个节点 reverseHead = new HeroNode();

    2. 从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead 的最前端.

    3. 原来的链表的head.next = reverseHead.next

    具体如下图:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    public class SingleLinkedListTest {
    
    	public static void main(String[] args) {
    		//测试一下
    		//1.创建节点
    		HeroNode hero = new HeroNode(1,"宋江","及时雨");
    		HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟");
    		HeroNode hero3 = new HeroNode(3,"吴用","智多星");
    		HeroNode hero4 = new HeroNode(4,"公孙胜","入云龙");
    		HeroNode hero5 = new HeroNode(5,"关胜","大刀");
    		
    		//创建一个链表
    		SingleLinkedList singk = new SingleLinkedList();
    		
    		//添加按照编号的顺序
    		singk.add(hero);
    		singk.add(hero4);
    		singk.add(hero2);
    		singk.add(hero5); 
    		singk.add(hero3);
    		
    		
    		//测试一下:单链表的反转功能
    		System.out.println("原来链表的情况:");
    		singk.list();	//显示链表
    		
    		System.out.println("反转的单链表:");
    		reversetList(singk.getHead());
    		singk.list();	
    	}
    	
    	//单链表反转
    	public static void reversetList(HeroNode head) {
    		//如果当前链表为空,或者只有一个节点,无需反转,直接返回
    		if(head.next == null || head.next.next == null) {
    			return;
    		}
    		
    		//1.定义一个辅助的指针(变量),帮助我们遍历原来的链表
    		HeroNode str = head.next;
    		HeroNode next = null;//指向当前节点[str]的下一个节点
    		HeroNode reverseHead = new HeroNode(0, "", "");
    		//2.遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
    		while(str != null) { 
    			//先暂时保存当前节点的下一个节点,因为后面需要使用
    			next = str.next;
    			//将str的下一个节点指向新的链表的最前端
    			str.next = reverseHead.next;
    			reverseHead.next = str; //将str连接到新的链表上
    			str = next;	//让str后移
    		}
    		//3.将head.next 指向 reverseHead.next, 实现单链表的反转
    		head.next = reverseHead.next;
    	}
    	
    }
    
    //定义一个SingleLinkedList类,来管理我们的英雄
    class SingleLinkedList{
    	//先初始化一个头节点,头节点不要动(防止后期找不到此链表),不存放具体数据
    	private HeroNode head = new HeroNode(0, "", "");
    	
    	//返回头节点
    	public HeroNode getHead() {
    		return head;
    	}
    
    	//第一种方式:第一种方法在添加英雄时,直接添加到链表的尾部
    	//添加节点到单向链表,思路,当不考虑编号的顺序时
    	//1.找到当前链表的最后节点
    	//2.将最后这个节点的next域指向这个新的节点
    	public void add(HeroNode heroNode){
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		//遍历链表,找到最后
    		while(true){
    			//找到链表的最后
    			if(temp.next == null){	//判定找到了的条件
    				break;
    			}
    			//如果没有找到,将temp后移
    			temp = temp.next;
    		}
    		//当退出循环时,temp就指向了链表的最后
    		//将最后这个节点的next --指向--》 新的节点
    		temp.next = heroNode;
    	}
    	
    	//显示链表[遍历]
    	public void list(){
    		//判断链表是否为空
    		if(head.next == null){
    			System.out.println("链表为空!!!");
    			return;
    		}
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		while(true){
    			//判断是否到链表的最后
    			if(temp == null){
    				break;
    			}
    			//如果不为空,输出节点的信息
    			System.out.println(temp);
    			//注意!!!!将next后移(因为不向后移动,会造成死循环)
    			temp = temp.next;
    		}
    	}
    	
    }
    
    //定义一个HeroNode,每个HeroNode对象就是一个节点
    class HeroNode{
    	public int id;
    	public String name;
    	public String nickName;	//别名,昵称
    	public HeroNode next;	//指向下一个节点
    	
    	//构造器
    	public HeroNode(int id, String name, String nickName) {
    		super();
    		this.id = id;
    		this.name = name;
    		this.nickName = nickName;
    	}
    
    	//为了显示方便,重写toString方法
    	@Override
    	public String toString() {
    		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
    	}
    }
    

    百度面试题

    从尾到头打印单链表

    思路:
    1. 上面的题的要求就是逆序打印单链表.
    2. 方式1: 先将单链表进行反转操作,然后再遍历即可,这样的做的问题是会破坏原来的单链表的结构,不建议
    3. 方式2:可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果.
        
    举例演示栈的使用 Stack 
    

    在这里插入图片描述

    • 入栈出栈的代码演示
    import java.util.Stack;
    
    //演示栈Stack的基本使用
    public class TestStack {
    	public static void main(String[] args) {
    		Stack<String> stack = new Stack();
    		//入栈
    		stack.add("jack");
    		stack.add("Tom");
    		stack.add("smith");
    		
    		//出栈
    		//出栈顺序:smith-->Tom-->jack
    		while(stack.size() > 0){
    			System.out.println(stack.pop());	//pop()就是将栈顶的数据取出
    		}
    	}
    }
    
    • 面试题代码实现
    import java.util.Stack;
    
    public class SingleLinkedListTest {
    
    	public static void main(String[] args) {
    		//测试一下
    		//1.创建节点
    		HeroNode hero = new HeroNode(1,"宋江","及时雨");
    		HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟");
    		HeroNode hero3 = new HeroNode(3,"吴用","智多星");
    		HeroNode hero4 = new HeroNode(4,"公孙胜","入云龙");
    		HeroNode hero5 = new HeroNode(5,"关胜","大刀");
    		
    		//创建一个链表
    		SingleLinkedList singk = new SingleLinkedList();
    		
    		//添加按照编号的顺序
    		singk.add(hero);
    		singk.add(hero4);
    		singk.add(hero2);
    		singk.add(hero5); 
    		singk.add(hero3);
    
    		System.out.println("原来链表的情况:");
    		singk.list();	//显示链表
    		
    		System.out.println("测试逆序打印的单链表,未改变链表的本身结构:");
    		reversePrint(singk.getHead());	
    	}
    	
    	//方式2:
    	//可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果
    	public static void reversePrint(HeroNode head) {
    		if(head.next == null){
    			return;	//空链表,无法打印
    		}
    		//先创建一个栈,将各个节点压入栈中
    		Stack<HeroNode> stack = new Stack<HeroNode>();
    		HeroNode str = head.next;
    		//将链表的所有节点压入栈
    		while(str != null){
    			stack.push(str);	
    			str = str.next;	//str后移,这样就可以压入下一个节点
    		}
    		//将栈中的节点进行打印,pop()出栈
    		while(stack.size() > 0){
    			System.out.println(stack.pop());	//stack的特点是先进后出
    		}
    	}
    	
    }
    
    //定义一个SingleLinkedList类,来管理我们的英雄
    class SingleLinkedList{
    	//先初始化一个头节点,头节点不要动(防止后期找不到此链表),不存放具体数据
    	private HeroNode head = new HeroNode(0, "", "");
    	
    	//返回头节点
    	public HeroNode getHead() {
    		return head;
    	}
    
    	//第一种方式:第一种方法在添加英雄时,直接添加到链表的尾部
    	//添加节点到单向链表,思路,当不考虑编号的顺序时
    	//1.找到当前链表的最后节点
    	//2.将最后这个节点的next域指向这个新的节点
    	public void add(HeroNode heroNode){
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		//遍历链表,找到最后
    		while(true){
    			//找到链表的最后
    			if(temp.next == null){	//判定找到了的条件
    				break;
    			}
    			//如果没有找到,将temp后移
    			temp = temp.next;
    		}
    		//当退出循环时,temp就指向了链表的最后
    		//将最后这个节点的next --指向--》 新的节点
    		temp.next = heroNode;
    	}
    	
    	//显示链表[遍历]
    	public void list(){
    		//判断链表是否为空
    		if(head.next == null){
    			System.out.println("链表为空!!!");
    			return;
    		}
    		//由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		while(true){
    			//判断是否到链表的最后
    			if(temp == null){
    				break;
    			}
    			//如果不为空,输出节点的信息
    			System.out.println(temp);
    			//注意!!!!将next后移(因为不向后移动,会造成死循环)
    			temp = temp.next;
    		}
    	}
    	
    }
    
    //定义一个HeroNode,每个HeroNode对象就是一个节点
    class HeroNode{
    	public int id;
    	public String name;
    	public String nickName;	//别名,昵称
    	public HeroNode next;	//指向下一个节点
    	
    	//构造器
    	public HeroNode(int id, String name, String nickName) {
    		super();
    		this.id = id;
    		this.name = name;
    		this.nickName = nickName;
    	}
    
    	//为了显示方便,重写toString方法
    	@Override
    	public String toString() {
    		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
    	}
    }
    

    课后练习

    合并两个有序的单链表,合并之后的链表依然有序。

    public class SingleLinkedListTest {
    
    	public static void main(String[] args) {
    		// 测试一下
    		// 1.创建节点
    		HeroNode hero = new HeroNode(1, "宋江", "及时雨");
    		HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
    		HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
    		HeroNode hero4 = new HeroNode(4, "公孙胜", "入云龙");
    		HeroNode hero5 = new HeroNode(5, "关胜", "大刀");
    		HeroNode hero6 = new HeroNode(6, "林冲", "豹子头");
    		HeroNode hero7 = new HeroNode(7, "秦明", "霹雳火");
    
    		// 创建一个链表
    		SingleLinkedList singk = new SingleLinkedList();
    
    		// 添加按照编号的顺序
    		singk.addByOrder(hero);
    		singk.addByOrder(hero7);
    		singk.addByOrder(hero5);
    
    		// 创建另一个链表
    		SingleLinkedList singk2 = new SingleLinkedList();
    
    		// 添加按照编号的顺序
    		singk2.addByOrder(hero3);
    		singk2.addByOrder(hero6);
    		singk2.addByOrder(hero2);
    		singk2.addByOrder(hero4);
    
    		// 显示链表
    		singk.list();
    		System.out.println("------------------");
    		singk2.list();
    
    		//合并后的
    		System.out.println("合并后的:");
    		SingleLinkedList singk3 = singk.merge(singk, singk2);
            singk3.list();
    	}
    
    }
    
    // 定义一个SingleLinkedList类,来管理我们的英雄
    class SingleLinkedList {
    	// 先初始化一个头节点,头节点不要动(防止后期找不到此链表),不存放具体数据
    	private HeroNode head = new HeroNode(0, "", "");
    
    	public SingleLinkedList(HeroNode head) {
    		this.head = head;
    	}
    	
    	public SingleLinkedList() {
            head = new HeroNode();
        }
    
    	// 显示链表[遍历]
    	public void list() {
    		// 判断链表是否为空
    		if (head.next == null) {
    			System.out.println("链表为空!!!");
    			return;
    		}
    		// 由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode temp = head;
    		while (true) {
    			// 判断是否到链表的最后
    			if (temp == null) {
    				break;
    			}
    			// 如果不为空,输出节点的信息
    			System.out.println(temp);
    			// 注意!!!!将next后移(因为不向后移动,会造成死循环)
    			temp = temp.next;
    		}
    	}
    
    	// 第二种方式在添加英雄时,根据排名将英雄插入到指定位置
    	// (如果有这个排名,则添加失败,并给出提示)
    	public void addByOrder(HeroNode heroNode) {
    		// 由于头节点head不能动,所以需要一个辅助变量 temp,找到添加的位置
    		// 由于是单链表,而找到temp是位于 添加位置的前一个节点,否则插入不了
    		HeroNode temp = head;
    		boolean flag = false; // 标识添加的编号是否存在,默认为false
    		while (true) {
    			if (temp.next == null) { // 说明已经在链表的最后
    				break;
    			}
    			if (temp.next.id > heroNode.id) { // 位置找到了,就在temp的后面插入
    				break;
    			} else if (temp.next.id == heroNode.id) { // 说明希望添加的heroNode的编号已然存在
    				flag = true; // 说明编号存在
    				break;
    			}
    			temp = temp.next; // 后移,遍历当前链表
    		}
    		// 判断flag的值
    		if (flag) { // 不能添加,说明编号存在
    			System.out.printf("准备插入的英雄编号%d已经存在了。无法加入
    ", heroNode.id);
    		} else {
    			// 插入到链表中,temp的后面
    			heroNode.next = temp.next;
    			temp.next = heroNode;
    		}
    	}
    	
    	//合并两个有序的单链表,合并之后的链表依然有序
        public SingleLinkedList merge(SingleLinkedList list1, SingleLinkedList list2) {
            if (list1.head.next == null) {
                return list2;
            } else if (list2.head.next == null) {
                return list1;
            }
    
            HeroNode newNode = new HeroNode();
            HeroNode n1 = newNode;
            HeroNode l1 = list1.head.next;
            HeroNode l2 = list2.head.next;
    
            while (l1 != null && l2 != null) {
                if (l1.id < l2.id) {
                    n1.next = l1;
                    l1 = l1.next;
                    n1 = n1.next;
                } else {
                    n1.next = l2;
                    l2 = l2.next;
                    n1 = n1.next;
                }
            }
    
            if (l1 == null) {
                n1.next = l2;
            }
            if (l2 == null) {
                n1.next = l1;
            }
            return new SingleLinkedList(newNode);
        }
    
    }
    
    // 定义一个HeroNode,每个HeroNode对象就是一个节点
    class HeroNode {
    	public int id;
    	public String name;
    	public String nickName; // 别名,昵称
    	public HeroNode next; // 指向下一个节点
    
    	// 构造器
    	public HeroNode(int id, String name, String nickName) {
    		super();
    		this.id = id;
    		this.name = name;
    		this.nickName = nickName;
    	}
    
    	public HeroNode() {
    	}
    
    	// 为了显示方便,重写toString方法
    	@Override
    	public String toString() {
    		return "HeroNode [id=" + id + ", name=" + name + ", nickName=" + nickName + "]";
    	}
    }
    

    双向链表增删改查分析图解及实现

    使用带head头的双向链表实现 –水浒英雄排行榜管理单向链表的缺点分析:

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

    2)单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp,temp是待删除节点的前一个节点.

    示意图如下:

    在这里插入图片描述

    分析 双向链表的遍历,添加,修改,删除的操作思路:

    1. 遍历 方式和 单链表一样,只是可以向前查找,也可以向后查找

    2. 添加 (默认添加到双向链表的最后)

      (1) 先找到双向链表的最后这个节点

      (2) temp.next = newHeroNode

      (3) newHeroNode.pre = temp

    3. 修改 思路和 原来的单向链表的思路一样.

    4. 删除

      (1) 因为是双向链表,因此,我们可以实现自我删除某个节点

      (2) 直接找到要删除的这个节点,比如temp

      (3) temp.pre.next = temp.next

      (4) temp.next.pre = temp.pre;

    删除的图示如下:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    • 代码实现:
    public class DoubleLinkedListTest {
    
    	public static void main(String[] args) {
    		// 测试一下
    		// 1.创建节点
    		HeroNode2 hero = new HeroNode2(1, "宋江", "及时雨");
    		HeroNode2 hero2 = new HeroNode2(2, "卢俊义", "玉麒麟");
    		HeroNode2 hero3 = new HeroNode2(3, "吴用", "智多星");
    		HeroNode2 hero4 = new HeroNode2(4, "公孙胜", "入云龙");
    		HeroNode2 hero5 = new HeroNode2(5, "关胜", "大刀");
    
    		// 创建一个双向链表
    		DoubleLinkedList singk = new DoubleLinkedList();
    
    		// 添加按照编号的顺序
    		singk.add(hero);
    		singk.add(hero4);
    		singk.add(hero2);
    		singk.add(hero5);
    		singk.add(hero3);
    
    		System.out.println("原来链表的情况:");
    		singk.list(); // 显示链表
    
    		// 修改
    		HeroNode2 newHeroNode = new HeroNode2(4, "公孙胜", "--");
    		singk.update(newHeroNode);
    		System.out.println("修改后的链表情况");
    		singk.list();
    
    		// 删除
    		singk.del(3);
    		System.out.println("删除后的链表情况~~");
    		singk.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;
    		}
    		// 由于头节点head不能动,所以需要一个辅助变量 temp,找到添加的位置
    		HeroNode2 temp = head.next;
    		while (true) {
    			if (temp == null) { // 判断是否到链表最后
    				break;
    			}
    			// 输出节点的信息
    			System.out.println(temp);
    			temp = temp.next; // 将temp后移
    		}
    	}
    
    	// 添加一个节点到双向链表的最后.
    	public void add(HeroNode2 heroNode) {
    		// 由于头节点head不能动,所以需要一个辅助变量 temp
    		HeroNode2 temp = head;
    		// 遍历链表,找到最后
    		while (true) {
    			// 找到链表的最后
    			if (temp.next == null) { // 判定找到了的条件
    				break;
    			}
    			// 如果没有找到,将temp后移
    			temp = temp.next;
    		}
    		// 当退出循环时,temp就指向了链表的最后
    		// 将最后这个节点的next --指向--》 新的节点
    		temp.next = heroNode;
    	}
    
    	// 修改节点的信息,根据id编号来修改,即id编号不改
    	// 说明
    	// 1.根据newHeroNode 的 id 来修改即可
    	public void update(HeroNode2 newHeroNode) {
    		// 判断是否空
    		if(head.next == null){	//链表为空
    			System.out.println("链表为空!!!");
    			return;
    		}
    		//找到需要修改的节点,根据 id 编号
    		//定义一个临时变量
    		HeroNode2 temp = head.next;
    		boolean flag = false;	//表示是否找到该节点
    		while(true){
    			if(temp == null){
    				break;	//已经遍历完链表
    			}
    			if(temp.id == newHeroNode.id){
    				//找到了
    				flag = true;
    				break;
    			}
    			temp = temp.next;
    		}
    		// 根据flag 判断是否找到要修改的节点
    		if (flag) {
    			temp.name = newHeroNode.name;
    			temp.nickname = newHeroNode.nickname;
    		} else { // 没有找到
    			System.out.printf("没有找到编号为 %d 的节点,不能修改。
    ", newHeroNode.id);
    		}
    	}
    
    	// 删除节点
    	// 思路
    	// 1.由于头节点head不能动,所以需要一个辅助变量 temp来找到待删除节点的前一个节点
    	// 2.说明我们在比较时,是temp.next.id 和 需要删除的节点的id比较
    	public void del(int id) {
    		HeroNode2 temp = head;
    		boolean flag = false; // 标志是否找到带删除的节点
    		while (true) {
    			if (temp.next == null) { // 已经到链表的最后
    				break;
    			}
    			if (temp.next.id == id) { // 找到了带删除的节点的前一个节点
    				flag = true;
    				break;
    			}
    			temp = temp.next; // temp后移,遍历
    		}
    		// 判断flag
    		if (flag) { // 说明找到了
    			// 可以删除
    			temp.next = temp.next.next;
    		} else {
    			System.out.printf("要删除的节点 %d 不存在。
    ", id);
    		}
    	}
    }
    
    // 定义HeroNode2 , 每个HeroNode 对象就是一个节点
    class HeroNode2 {
    	public String nickName;
    	public int id;
    	public String name;
    	public String nickname;
    	public HeroNode2 next; // 指向下一个节点, 默认为null
    	public HeroNode2 pre; // 指向前一个节点, 默认为null
    
    	// 构造器
    	public HeroNode2(int id, String name, String nickname) {
    		super();
    		this.id = id;
    		this.name = name;
    		this.nickname = nickname;
    	}
    
    	// 为了显示方便,重写toString方法
    	@Override
    	public String toString() {
    		return "HeroNode2 [id=" + id + ", name=" + name + ", nickname=" + nickname + "]";
    	}
    
    }
    

    环形链表介绍和约瑟夫问题

    在这里插入图片描述
    在这里插入图片描述

    • Josephu 问题

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

    • 提示

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

    • 示意图说明如下

    在这里插入图片描述

    约瑟夫问题分析图解和实现

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

    1. 先创建第一个节点, 让 first 指向该节点,并形成环形

    在这里插入图片描述

    1. 后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可.

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    遍历环形链表

    1. 先让一个辅助指针(变量) curBoy,指向first节点

    2. 然后通过一个while循环遍历 该环形链表即可 curBoy.next == first 结束

    在这里插入图片描述

    • 代码实现
    public class Josepfu {
    
    	public static void main(String[] args) {
    		//测试构建环形链表和遍历是否正确
    		CircleLinkList cingk = new CircleLinkList();
    		cingk.addBoy(5);	//加入五个节点
    		cingk.shoeBoy();	//显示一下
    	}
    
    }
    //创建一个单项环形链表
    class CircleLinkList{
    	//创建一个first节点,当前没有编号
    	private Boy first = null;
    	//添加新节点,构成一个环形的链表
    	public void addBoy(int nums){
    		//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){
    				first = boy;
    				first.setNext(first); //构成环状
    				curBoy = first;	//让curBoy指向第一个节点
    			}else{
    				curBoy.setNext(boy);
    				boy.setNext(first);
    				curBoy = boy;
    			}
    		}
    	}
    	
    	//遍历当前环形链表
    	public void shoeBoy(){
    		//判断链表是否为空
    		if(first == null){
    			System.out.println("链表为空");
    			return;
    		}
    		//因为first头节点不能动,因此创建一个辅助指针完成遍历
    		Boy curBoy = first;
    		while(true){
    			System.out.printf("当前节点的编号 %d
    ",curBoy.getId());
    			if(curBoy.getNext() == first){	//遍历完成
    				break;
    			}
    			curBoy = curBoy.getNext();	//让curBoy后移 
    		}
    	}
    }
    
    //创建一个Boy类,表示一个节点
    class Boy{
    	private int id;	//编号
    	private Boy next;	//指向下一个节点,默认为null
    	
    	public Boy(int id){
    		this.id = id;
    	}
    
    	public int getId() {
    		return id;
    	}
    
    	public void setId(int id) {
    		this.id = id;
    	}
    
    	public Boy getNext() {
    		return next;
    	}
    
    	public void setNext(Boy next) {
    		this.next = next;
    	}
    	
    }
    

    根据用户的输入,生成一个人物出圈的顺序

    n = 5, 即有5个人

    k = 1, 从第一个人开始报数

    m = 2, 数2下

    在这里插入图片描述

    1. 需求创建一个辅助指针(变量) helper , 事先应该指向环形链表的最后这个节点.

    补充: 人物报数前,先让 first 和 helper 移动 k - 1次

    在这里插入图片描述

    1. 当人物报数时,让first 和 helper 指针同时 的移动 m - 1 次

    在这里插入图片描述

    1. 这时就可以将first 指向的人物节点 出圈

    first = first .next

    helper.next = first

    原来first 指向的节点就没有任何引用,就会被回收

    在这里插入图片描述
    在这里插入图片描述

    综上,出圈的顺序:2->4->1->5->3

    • 代码实现
    public class Josepfu {
    
    	public static void main(String[] args) {
    		//测试构建环形链表和遍历是否正确
    		CircleLinkList cingk = new CircleLinkList();
    		cingk.addBoy(5);	//加入五个节点
    		cingk.shoeBoy();	//显示一下
    		
    		//测试一下:判断人物出圈是否正确
    		cingk.countBoy(1, 42, 5); // 2->4->1->5->3
    	}
    
    }
    //创建一个单项环形链表
    class CircleLinkList{
    	//创建一个first节点,当前没有编号
    	private Boy first = null;
    	//添加新节点,构成一个环形的链表
    	public void addBoy(int nums){
    		//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){
    				first = boy;
    				first.setNext(first); //构成环状
    				curBoy = first;	//让curBoy指向第一个节点
    			}else{
    				curBoy.setNext(boy);
    				boy.setNext(first);
    				curBoy = boy;
    			}
    		}
    	}
    	
    	//遍历当前环形链表
    	public void shoeBoy(){
    		//判断链表是否为空
    		if(first == null){
    			System.out.println("链表为空");
    			return;
    		}
    		//因为first头节点不能动,因此创建一个辅助指针完成遍历
    		Boy curBoy = first;
    		while(true){
    			System.out.printf("当前节点的编号 %d
    ",curBoy.getId());
    			if(curBoy.getNext() == first){	//遍历完成
    				break;
    			}
    			curBoy = curBoy.getNext();	//让curBoy后移 
    		}
    	}
    	
    	//根据用户的输入,计算人物出圈的顺序
    	/**
    	  * 
    	  * @Description 
    	  * @author subei
    	  * @date 2020年5月23日下午4:45:48
    	  * @param startId  表示从第几个人物开始数数
    	  * @param countNum 表示数几下
    	  * @param nums  表示最初有几个人物在圈中
    	 */
    	public void countBoy(int startId,int countNum,int nums){
    		//先对数据节点进行校验
    		if(first == null || startId < 1 || startId > 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 j = 0; j < startId - 1; j++) {
    			first = first.getNext();
    			helper = helper.getNext();
    		}		
    		//当人物报数时,让first 和 helper 指针同时 的移动 m - 1 次
    		//通过循环,知道圈中只有一个节点
    		while(true) {
    			if(helper == first) { //说明圈中只有一个人物节点
    				break;
    			}
    			//让 first 和 helper 指针同时 的移动 countNum - 1
    			for(int j = 0; j < countNum - 1; j++) {
    				first = first.getNext();
    				helper = helper.getNext();
    			}
    			//此处first指向的节点,就是要出圈的人物节点
    			System.out.printf("人物%d出圈
    ", first.getId());
    			//此处将first指向的人物节点出圈
    			first = first.getNext();
    			helper.setNext(first);
    		}
    		System.out.printf("最后留在圈中的人物的编号:%d 
    ", first.getId());
    	}
    }
    
    //创建一个Boy类,表示一个节点
    class Boy{
    	private int id;	//编号
    	private Boy next;	//指向下一个节点,默认为null
    	
    	public Boy(int id){
    		this.id = id;
    	}
    
    	public int getId() {
    		return id;
    	}
    
    	public void setId(int id) {
    		this.id = id;
    	}
    
    	public Boy getNext() {
    		return next;
    	}
    
    	public void setNext(Boy next) {
    		this.next = next;
    	}
    	
    }
    

    本章导图总结

    在这里插入图片描述

    欢迎查阅
  • 相关阅读:
    Best Time to Buy and Sell Stock
    Permutations II
    数组中最大和的子数组
    基于Socket.IO的Client封装
    Java中的ThreadLocal功能演示
    基于WebSocket的client封装
    Socket接口开发和测试实践
    自动化测试用例的原子性
    [CF1477C] Nezzar and Nice Beatmap
    [CF1477B] Nezzar and Binary String
  • 原文地址:https://www.cnblogs.com/gh110/p/12943719.html
Copyright © 2011-2022 走看看