数据结构之(四)链表

1. 链表

在这里插入图片描述
小结
(1)链表是节点的方式存储的
(2)每个节点包含data域,next域(指向下一个节点)
(3)如图:发现链表的各个节点不一定连续
(4)链表分带头结点的链表和不带头结点的链表,根据实际需求来定
逻辑结构
在这里插入图片描述

单链表的应用实例

使用带 head 头的单向链表实现 –水浒英雄排行榜管理完成对英雄人物的增删改查操作。
在这里插入图片描述

1.1无顺序插入

package com.datastruct.demo.structure.linked;

/**
 * 定义Hero,每个Hero对象就是一个节点
 * @author Administrator
 */
public class HeroNode {
    private 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;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname + "}" ;
    }
}

package com.datastruct.demo.structure.linked;

/**
 * 定义SingleLinkedList 用于管理英雄
 */
public class SingleLinkedList {
    // 先初始化一个头结点,头结点不要动,不存放具体的数据
    private HeroNode head = new HeroNode(1,"","");

    /** 将节点添加到链表最后
     * 不考虑编号顺序时
     * 1.找到当前链表的最后节点
     * 2.将这个最后节点的next域指向新的节点
     * @param node
     */
    public void add(HeroNode node){
        // 因为head节点不能动,因此我们需要一个辅助遍历temp
        HeroNode temp = this.head;
        // 循环找最后的节点
        while (true){
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
        }
        temp.next = node;
    }

    /**
     * 显示链表
     */
    public void show(){
        if (head.next == null){
            System.out.println("链表为空");
        }
        // 头结点不能动,只能借助辅助变量
        HeroNode temp = this.head;
        while (temp.next != null){
            System.out.println(temp.next.toString());
            temp = temp.next;
        }
    }
}

1.2按顺序插入

在这里插入图片描述


    /** )第二种方式在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
     * 考虑编号顺序时
     * 1.遍历
     * 2.新的节点的next = temp.next
     * 3.temp.next = 新的节点
     * @param node
     */
    public void addByOrder(HeroNode node){
/*         因为head节点不能动,因此我们需要一个辅助遍历temp,来找到插入的位置
        因为是单链表,所以temp是插入位置的前一个节点*/
        HeroNode temp = this.head;

        // 标志添加的编号是否存在,默认为False;
        boolean flag = false;
        // 循环找最后的节点
        while (true){
            if (temp.next == null) {
                break;
            }
            // 如果当前的下一个节点的编号比要插入的编号大,则插入在当前编号后边,(因为是单链表,顺序遍历)
            if (temp.next.no > node.no){
                // 返回要插入的上一个位置
                break;
            }else {
                if (temp.next.no == node.no){
                    // 说明链表中存在相同编号的节点
                    flag = true;
                    break;
                }
            }
            temp = temp.next;
        }
        if (false){
            System.out.printf("插入的新节点的编号%d已经存在,不能加入",node.no);
        }else {
            // 先节火车尾,再拼火车头
            node.next = temp.next;
            temp.next = node;
        }
    }

1.3修改功能节点

在这里插入图片描述

 /** 修改节点的信息,根据no来修改,即no编号不能改
     * 1.根据node的no来修改
     * @param node
     */
    public void updata(HeroNode node){
        // 判断是否为空
        if (head.next == null){
            System.out.println("链表为空~");
            return;
        }
        // 遍历根据no找修改的节点
        HeroNode temp = this.head;
        // 标志是否找到节点
        boolean flag = false;
        while (true){
            if (temp == null){
                // 遍历结束(注意这里并不是temp.next==null)
                break;
            }
            if (temp.no == node.no){
                // 找到节点
                flag = true;
                break;
            }
            temp = temp.next;
        }
        // 根据flag,是否要修改节点信息
        if (flag){
            temp.name = node.name;
            temp.nickname = node.nickname;
        } else {
            System.out.println("没有找到该节点");
        }
    }

1.4删除节点

在这里插入图片描述

2.大厂面试题

(1)单链表查询有效节点


    /**
     * 获取单链表节点的个数(忽略头结点)
     * @return 返回单链表的具体个数
     */
    public int getLength(){
        int length = 0;
        if (head.next == null){
            return length;
        }
        // 遍历
        HeroNode cur = this.head.next;
        while (cur != null){
            length ++;
            cur = cur.next;
        }
        return length;
    }

(2)查找单链表中的倒数第K个节点
在这里插入图片描述

 /**
     * 查找倒数第几个节点
     * 1.获取链表的总长度
     * 2.得到长度后,我们从链表的第一个开始遍历(size-index)个,就可以得到
     * 因为导数,默认指向第一个,假如三个导数第一个这就是第一个节点加2,也就是size - index
     * 如果找到了就返回,否则为NUll
     * @param index 倒数第几个节点
     * @return
     */
    public HeroNode findLastIndexNode(int index){
        if (this.head.next == null){
            return null;
        }
        // 得到链表的长度
        int size = getLength();
        if (index <= 0 || index > size){
            return null;
        }
        // 定义给辅助变量,循环拿到定位在倒数的index
        HeroNode cur = this.head.next;
        for (int i = 0;i <size - index;i++){
            cur = cur.next;
        }
        return cur;
    }

(3)单链表的反转
在这里插入图片描述
在这里插入图片描述
(4)从尾到头打印单链表
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

    /**
     * 逆序打印
     * 可以利用栈这个数据结构,将各个节点压入栈中,然后利用栈的先进后出的特点,就实现了逆序打印
     */
    public void reversePrint(){
        if (this.head.next == null){
            return;
        }
        Stack<HeroNode> stack = new Stack<>();
        HeroNode cur = head.next;
        // 将链表的所有节点压入栈
        while (cur != null){
            stack.push(cur);
            cur = cur.next;
        }
        // 将栈中的节点进行打印pop出栈
        while (stack.size() > 0){
            System.out.println(stack.pop());
        }
    }

3.双向链表应用实例

单向列表存在以下问题

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

3.1 双向链表的操作分析与实现

在这里插入图片描述
在这里插入图片描述
注意节点类加上了一个pre指针,下面说明遍历、修改、添加、删除
(1)遍历:方向和单链表一样,不过可以向前、也可以向后
(2)添加:默认(添加到双向链表的最后)
1.先找到双向链表最后的节点
2.temp.next = newNode
3.newNode.pre = temp
(3)修改和原来的思路一样
(4)删除:因为是双向链表,因此可以实现自我删除某个节点
1.直接遍历找到要删除的节点temp
2.temp.pre.next = temp.next
3.temp.next.pre = temp.pre

package com.datastruct.demo.structure.linked.doublelinked;

/**
 * 定义Hero,每个Hero对象就是一个节点
 * @author Administrator
 */
public class HeroDoubleNode {
    public int no;
    public String name;
    public String nickname;
    /**
     * 指向下一个节点
     */
    public HeroDoubleNode next;

    /**
     * 指向上一个节点
     */
    public HeroDoubleNode pre;

    public HeroDoubleNode(int no, String name, String nickname){
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", nickname='" + nickname + "}" ;
    }
}
package com.datastruct.demo.structure.linked.doublelinked;

import com.datastruct.demo.structure.linked.singlelinked.HeroNode;
import com.fasterxml.jackson.databind.node.DoubleNode;

import java.util.Stack;

/** 双向列表
 * DoubleLinkedList 用于管理英雄
 */
public class DoubleLinkedList {
    // 先初始化一个头结点,头结点不要动,不存放具体的数据
    private HeroDoubleNode head = new HeroDoubleNode(0,"","");

    /** 将节点添加到双向链表最后
     * 		1.先找到双向链表最后的节点
     * 		2.temp.next = newNode
     * 		3.newNode.pre = temp
     * @param node
     */
    public void add(HeroDoubleNode node){
        // 因为head节点不能动,因此我们需要一个辅助遍历temp
        HeroDoubleNode temp = this.head;
        // 循环找最后的节点
        while (true){
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
        }
        //
        temp.next = node;
        node.pre = temp;
    }

    /** )第二种方式在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
     * 考虑编号顺序时
     * 1.遍历
     * 2.新的节点的next = temp.next
     * 3.temp.next = 新的节点
     * @param node
     */
    public void addByOrder(HeroDoubleNode node){
//         因为head节点不能动,因此我们需要一个辅助遍历temp,来找到插入的位置
        HeroDoubleNode temp = this.head;

        // 标志添加的编号是否存在,默认为False;
        boolean flag = false;
        // 循环找最后的节点
        while (true){
            if (temp.next == null) {
                break;
            }
            // 如果当前的下一个节点的编号比要插入的编号大,则插入在当前编号后边,(因为是链表,顺序遍历)
            if (temp.next.no > node.no){
                // 返回要插入的上一个位置
                break;
            }else {
                if (temp.next.no == node.no){
                    // 说明链表中存在相同编号的节点
                    flag = true;
                    break;
                }
            }
            temp = temp.next;
        }
        if (flag){
            System.out.printf("插入的新节点的编号%d已经存在,不能加入\n",node.no);
        }else {
            node.next = temp.next;
            temp.next = node;
            node.pre = temp;
            // 避免插入后的节点是最后一个造成空指针
            if (temp.next != null){
                temp.next.pre = node;
            }
        }
    }

    /** 修改节点的信息,根据no来修改,即no编号不能改
     * 1.根据node的no来修改
     * @param node
     */
    public void updata(HeroDoubleNode node){
        // 判断是否为空
        if (head.next == null){
            System.out.println("链表为空~");
            return;
        }
        // 遍历根据no找修改的节点
        HeroDoubleNode temp = this.head;
        // 标志是否找到节点
        boolean flag = false;
        while (true){
            if (temp == null){
                // 遍历结束(注意这里并不是temp.next==null)
                break;
            }
            if (temp.no == node.no){
                // 找到节点
                flag = true;
                break;
            }
            temp = temp.next;
        }
        // 根据flag,是否要修改节点信息
        if (flag){
            temp.name = node.name;
            temp.nickname = node.nickname;
        } else {
            System.out.println("没有找到该节点");
        }
    }

    /**
     * 删除节点
     1.直接遍历找到要删除的节点temp
     2.temp.pre.next = temp.next
     3.temp.next.pre = temp.pre
     *
     */
    public void delete(int no){
        // 删除标志
        boolean flag = false;
        HeroDoubleNode temp = this.head.next;
       if (temp == null){
           System.out.println("链表为空,不能删除");
           return;
       }
       while (true){
           if (temp == null){
               break;
           }
           if (temp.no == no){
               flag = true;
               break;
           }
           // 后移
           temp = temp.next;
       }
       // 判断flag
        if (flag){
            // 找到
            temp.pre.next = temp.next;
            // 注意这里为了避免删除最后一个节点,不需要修pre指针,不然会空指针
            if (temp.next != null){
                temp.next.pre = temp.pre;
            }
        }else {
            System.out.println("找不到该节点");
        }

    }

    /**
     * 获取单链表节点的个数(忽略头结点)
     * @return 返回单链表的具体个数
     */
    public int getLength(){
        int length = 0;
        if (head.next == null){
            return length;
        }
        // 遍历
        HeroDoubleNode cur = this.head.next;
        while (cur != null){
            length ++;
            cur = cur.next;
        }
        return length;
    }



    /**
     * 显示双链表
     */
    public void show(){
        if (head.next == null){
            System.out.println("链表为空");
        }
        // 头结点不能动,只能借助辅助变量
        HeroDoubleNode temp = this.head;
        while (temp.next != null){
            System.out.println(temp.next.toString());
            temp = temp.next;
        }
    }


}
package com.datastruct.demo.structure.linked.doublelinked;

import com.datastruct.demo.structure.linked.singlelinked.HeroNode;
import com.datastruct.demo.structure.linked.singlelinked.SingleLinkedList;

/** 测试类
 * SingleLinkedListDemo
 */
public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        HeroDoubleNode heroNode1 = new HeroDoubleNode(1,"zlx","zlx");
        HeroDoubleNode heroNode2 = new HeroDoubleNode(2,"zlx1","zlx");
        HeroDoubleNode heroNode3 = new HeroDoubleNode(3,"zlx2","zlx");
        HeroDoubleNode heroNode4 = new HeroDoubleNode(4,"zlx3","zlx");
        // 创建链表
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        doubleLinkedList.addByOrder(heroNode1);
        doubleLinkedList.addByOrder(heroNode2);
        doubleLinkedList.addByOrder(heroNode3);
        doubleLinkedList.addByOrder(heroNode4);
        doubleLinkedList.show();
    }
}

4. 单向环形链表

在这里插入图片描述

4.1 简单案例

在这里插入图片描述

4.2 Josephu问题

在这里插入图片描述
在这里插入图片描述
提示
用一个不带头结点的循环链表来处理Josephu问题,先构成一个有n个节点的单向环形链表,然后由k节点遍历起由1开始计数,计数到m时,对应的节点从链表中删除,然后1又从下一个节点开始算,直到最后一个节点被删除
在这里插入图片描述
在这里插入图片描述

package com.datastruct.demo.structure.linked.josephu;

import lombok.Data;

/**
 * 环形链表的节点类
 */
@Data
public class JosephuNode {
    private int no;
    private JosephuNode next;
    public JosephuNode(int no){
        this.no = no;
    }
}

package com.datastruct.demo.structure.linked.josephu;

import com.datastruct.demo.structure.linked.singlelinked.HeroNode;

import java.util.Stack;

/**
 * 定义JosephuLinkedList 用于管理环形链表
 */
public class JosephuLinkedList {

    // 创建一个first节点,当前没有编号为null
    private JosephuNode first;

    // 创建一个当前节点,指向环形队列的最后节点
    private JosephuNode currentNode;


    /**
     * 添加节点,构成一个环形的链表
     */
    public void addNode(JosephuNode node){
        // 如果是插入第一个节点
        if (first == null){
            // first指针指向新增节点
            first = node;
            // first作为第一个节点,它的next指针要指向自己,因为是环形的
            first.setNext(first);
            // 记录链表最后一个节点
            currentNode = first;
        }else {
            // 链表最后一个节点的next指针指向新增节点
            currentNode.setNext(node);
            // 新增节点的next指向first节点,因为是环形
            node.setNext(first);
            // 记录链表最后一个节点
            currentNode = node;
        }
    }

    /**
     * 实现josephu算法(约瑟夫)
     * 1.创建一个辅助指针指向链表的最后一个节点(方便 删除)
     * 2.节点报数之前,先让first和helper移动到开始位置的节点(startNo-1)
     * 3.helper和first节点同时移动countNum-1次(因为first算一次)
     * 4.此时first指针指向的节点就是出圈的节点
     * 5.删除first节点
     * (1)first = first.next;
     * (2)helper.next = first;
     * first会被垃圾回收器回收
     */
    public void countJosephu(int startNo,int countNum,int nums){
        // 先对数据进行检验
        if (first == null || startNo < 1 || startNo > nums){
            System.out.println("输入参数有误");
            return;
        }

        // 创建辅助指针指向环形链表的最后的节点
        JosephuNode helper = first;
        while (true){
            if (helper.getNext() == first){
                break;
            }
            helper = helper.getNext();
        }

        // 节点报数之前先让first和help移动startNo-1次(保证起始位置在于startNo中)
        for (int i = 0;i < startNo - 1;i++){
            first = first.getNext();
            helper = helper.getNext();
        }
        // 节点开始报数,让first和helper指针同时移动m-1次,然后first指向的节点就是要被删除的节点
        while (true){
            if (helper == first){
                break;
            }
            for (int j=0;j<countNum-1;j++){
                first = first.getNext();
                helper = helper.getNext();
            }
            // 打印出圈的节点
            System.out.printf("节点%d出圈\n",first.getNo());

            // 删除出圈节点
            first = first.getNext();
            helper.setNext(first);
        }
        System.out.printf("最后留在圈中的编号%d",first.getNo());
    }

    /**
     * 显示链表
     */
    public void show(){
        // 判断是否为空
        if (first == null){
            System.out.println("链表为空");
            return;
        }
        // 因为first指针不能动,因此我们需要一个辅助指针完成遍历
        JosephuNode temp = first;
        while (true){
            System.out.println("编号" + temp.getNo());
            // 说明已经遍历完了
            if (temp.getNext() == first){
                break;
            }
            temp = temp.getNext();
        }
    }


}

package com.datastruct.demo.structure.linked.josephu;

public class JosephuLinkedDemo {
    public static void main(String[] args) {
        JosephuNode josephuNode1 = new JosephuNode(1);
        JosephuNode josephuNode2 = new JosephuNode(2);
        JosephuNode josephuNode3 = new JosephuNode(3);
        JosephuNode josephuNode4 = new JosephuNode(4);
        JosephuNode josephuNode5 = new JosephuNode(5);
        JosephuLinkedList josephuLinkedList = new JosephuLinkedList();
        josephuLinkedList.addNode(josephuNode1);
        josephuLinkedList.addNode(josephuNode2);
        josephuLinkedList.addNode(josephuNode3);
        josephuLinkedList.addNode(josephuNode4);
        josephuLinkedList.addNode(josephuNode5);
        josephuLinkedList.show();
        josephuLinkedList.countJosephu(1,2,5);

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目:使用 JavaScript 编写的杀死幽灵游戏(附源代码) 杀死鬼魂游戏是使用 Vanilla JavaScript、CSS 和 HTML 画布开发的简单项目。这款游戏很有趣。玩家必须触摸/杀死游荡的鬼魂才能得分。您必须将鼠标悬停在鬼魂上 - 尽量得分。鬼魂在眨眼间不断从一个地方移动到另一个地方。您必须在 1 分钟内尽可能多地杀死鬼魂。 游戏制作 这个游戏项目只是用 HTML 画布、CSS 和 JavaScript 编写的。说到这个游戏的特点,用户必须触摸/杀死游荡的幽灵才能得分。游戏会根据你杀死的幽灵数量来记录你的总分。你必须将鼠标悬停在幽灵上——尽量得分。你必须在 1 分钟内尽可能多地杀死幽灵。游戏还会显示最高排名分数,如果你成功击败它,该分数会在游戏结束屏幕上更新。 该游戏包含大量的 javascript 以确保游戏正常运行。 如何运行该项目? 要运行此游戏,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox。要玩游戏,首先,单击 index.html 文件在浏览器中打开游戏。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值