数据结构与算法—链表(单向链表)

基本介绍

链表是有序的列表,只包含一个指针域、由n个结点链接形成的链表,就称为线型链表或者单向链表。如图:

通过上图,可以总结出链表的特点:
①. 链表是以结点的方式来存储的,是链式存储的。
②. 每个结点包含data域,next域:指向下一个结点。
③. 链表的各个结点不一定是连续的。
④. 链表有带头结点和不带头结点的。

链表结构示意图

代码实现

博主作为一个英雄联盟的老粉,从S4开始就征战峡谷之巅了,每每想起曾经和自己一起玩游戏的伙伴,现在都分散在各地,心里便不是滋味。所以下面,通过对英雄联盟中的英雄进行管理,增加,删除,修改,以及排序等。来实现对链表的操作。

首先创建一个英雄对象

public class HeroNode {

    /**
     * 编号
     */
    public final int number;

    /**
     * 英雄名称
     */
    public String name;

    /**
     * 定位
     */
    public String position;

    /**
     * 价钱
     */
    public final double price;

    /**
     * 下一个结点
     */
    public HeroNode next;

    public HeroNode(int number, String name, String position, double price) {
        this.number = number;
        this.name = name;
        this.position = position;
        this.price = price;
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "number=" + number +
                ", name='" + name + '\'' +
                ", position='" + position + '\'' +
                ", price=" + price +
                '}';
    }
}

然后创建一个操作英雄的商城(链表对象)。

public class SaleLinkList {

    /**
     * 链表的头
     */
    public final HeroNode headNode = new HeroNode(0, "", "", 0);

    /**
     * TODO 添加数据(不考虑英雄价钱和重复的情况)
     */
    public void add(HeroNode node) {
        // 将头结点存入临时变量
        HeroNode temp = headNode;
        // 遍历链表,找到链表最后一个结点
        while (temp.next != null) {
            // 如果没有找到,就将当前结点后移
            temp = temp.next;
        }
        // 在链表最后添加新的结点
        temp.next = node;
    }

    /**
     *TODO 显示链表
     */
    public void list() {
        if (headNode.next == null) {
            System.out.println("链表为空");
            return;
        }
        HeroNode temp = headNode.next;
        // 判断链表是否到最后
        while (temp != null) {
            System.out.println(temp);
            // 将temp后移
            temp = temp.next;
        }
    }
}

基本上,添加链表的操作就完成了,下面编写测试代码

    public static void main(String[] args) {
        HeroNode node1 = new HeroNode(1, "薇恩", "ADC", 6300);
        HeroNode node2 = new HeroNode(2, "冰凤凰", "法师", 4800);
        HeroNode node3 = new HeroNode(3, "阿卡丽", "刺客", 3150);
        HeroNode node4 = new HeroNode(4, "努努", "打野", 450);
        HeroNode node5 = new HeroNode(5, "小炮", "ADC", 1350);
        HeroNode node6 = new HeroNode(1, "薇恩", "ADC", 6300);

        // 创建链表(商城)
        SaleLinkList saleLinkList = new SaleLinkList();
        // 添加结点(添加英雄到商店)
        saleLinkList.add(node1);
        saleLinkList.add(node2);
        saleLinkList.add(node3);
        saleLinkList.add(node4);
        saleLinkList.add(node5);
        saleLinkList.add(node6);

        // 显示链表(显示所有英雄)
        saleLinkList.list();
    }

打印结果是:

HeroNode{number=1, name='薇恩', position='ADC', price=6300.0}
HeroNode{number=2, name='冰凤凰', position='法师', price=4800.0}
HeroNode{number=3, name='阿卡丽', position='刺客', price=3150.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=1, name='薇恩', position='ADC', price=6300.0}

可以看到输出结果是正常的。证明添加和显示操作正确,但是这里出现了重复数据,在英雄联盟商城中,可不会出现重复英雄的。而且还有一点:英雄联盟中默认按照英雄的价钱降序排列,所以这里需要进行优化。

针对上面代码进行改进。
添加操作(思路分析):

1. 首先找到新添加结点的位置,通过变量 temp(指针)
2. 新的结点的 next(newNode.next = temp.next)
3. 将 temp.next 指向新的结点(temp.next = newNode)

因为链表还涉及修改、删除操作,所以这里先分析,后面再用代码依次实现
修改操作(思路分析):

1. 这里不能对 编号 以及 价钱 修改,不然就相当于添加新的数据
2. 找到需要修改的这个结点,根据变量 temp 进行查找
3. 满足条件: temp.number = newNode.number
4. 将新的值赋值给以前的值(temp.name = newNode.name;temp.position = newNode.position)

删除操作(思路分析):

1. 首先找到需要删除的这个结点的前一个结点 temp
2. 直接将前一个结点的 next 指向下下个结点(temp.next = temp.next.next)
3. 被删除的结点,没有其他引用,就会被垃圾回收机制回收

思路分析完,下面就完成代码的编写。

public class SaleLinkList {

    /**
     * 链表的头
     */
    public final HeroNode headNode = new HeroNode(0, "", "", 0);

    /**
     * TODO 添加数据(不考虑英雄价钱和重复的情况)
     */
    public void add(HeroNode node) {
        // 将头结点存入临时变量
        HeroNode temp = headNode;
        // 遍历链表,找到链表最后一个节点
        while (temp.next != null) {
            // 如果没有找到,就将当前节点后移
            temp = temp.next;
        }
        // 在链表最后添加新的节点
        temp.next = node;
    }

    /**
     * TODO 添加数据(根据英雄的价格进行排序,并对编号进行去重)
     */
    public void addByNumber(HeroNode node) {
        // 将 头结点 赋给 中间值
        HeroNode temp = headNode;
        // 判断 当前英雄的编号是否存在
        boolean flag = false;
        while (true) {
            // 说明 temp 已经是 链表的最后
            if (temp.next == null) {
                break;
            }
            // 找到满足条件(按照价格插入,并且编号不能相等),在 temp 之后插入
            if (temp.next.price < node.price && temp.next.number != node.number) {
                break;
                // 编号已经存在
            } else if (temp.next.number == node.number) {
                flag = true;
                break;
            }
            // 后移,即遍历链表
            temp = temp.next;
        }
        if (flag) {
            System.out.printf("添加失败,插入当前的英雄的编号 %d 已经存在。\n", node.number);
        } else {
            // 将之前 temp 中的 下一个指针 赋给 当前的 node的 next
            node.next = temp.next;
            // 新的结点 指向当前 temp 的 next
            temp.next = node;
        }
    }

    /**
     * TODO 修改结点信息,根据 number 来修改(编号和价钱不能修改)
     */
    public void update(HeroNode node) {
        if (headNode.next == null) {
            System.out.println("链表为空");
            return;
        }
        HeroNode temp = headNode.next;
        // 是否找到这个结点
        boolean flag = false;
        while (true) {
            if (temp == null) {
                break;
            }
            if (temp.number == node.number) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        // 如果找到 对应的 编号,进行修改
        if (flag) {
            temp.name = node.name;
            temp.position = node.position;
        } else {
            System.out.printf("修改失败,没有找到 编号 为 %d 的节点 \n", node.number);
        }
    }

    /**
     * TODO 需要找到待删除结点之前的结点, 比较temp.next.number = number(需要删除的结点)
     */
    public void delete(int number) {
        HeroNode temp = headNode;
        // 是否找到删除结点
        boolean flag = false;
        while (true) {
            // 判断是否为链表的尾部
            if (temp.next == null) {
                break;
            }
            if (temp.next.number == number) {
                flag = true;
                break;
            }
            // 链表后移
            temp = temp.next;
        }
        if (flag) {
            // 删除,改变 next 的指向
            temp.next = temp.next.next;
        }else {
            System.out.printf("删除失败,没有找到 编号 为 %d 的节点 \n", number);
        }
    }

    /**
     * 显示链表
     */
    public void list() {
        if (headNode.next == null) {
            System.out.println("链表为空");
            return;
        }
        HeroNode temp = headNode.next;
        // 判断链表是否到最后
        while (temp != null) {
            System.out.println(temp);
            // 将temp后移
            temp = temp.next;
        }
    }
}

编写测试类

public class Test {

    public static void main(String[] args) {
        HeroNode node1 = new HeroNode(1, "薇恩", "ADC", 6300);
        HeroNode node2 = new HeroNode(2, "冰凤凰", "法师", 4800);
        HeroNode node3 = new HeroNode(3, "阿卡丽", "刺客", 3150);
        HeroNode node4 = new HeroNode(4, "努努", "打野", 450);
        HeroNode node5 = new HeroNode(5, "小炮", "ADC", 1350);
        HeroNode node6 = new HeroNode(1, "薇恩", "ADC", 6300);


        SaleLinkList saleLinkList2 = new SaleLinkList();

        // 添加节点(添加英雄到商店:价格排序,并进行重复校验)
        saleLinkList2.addByNumber(node1);
        saleLinkList2.addByNumber(node2);
        saleLinkList2.addByNumber(node3);
        saleLinkList2.addByNumber(node4);
        saleLinkList2.addByNumber(node5);
        saleLinkList2.addByNumber(node6);

        System.out.println("修改之前:");
        saleLinkList2.list();
        saleLinkList2.update(new HeroNode(1, "大嘴", "ADC", 6300));
        System.out.println("修改之后:");
        saleLinkList2.list();

        // 删除
        saleLinkList2.delete(6);
        saleLinkList2.delete(1);
        System.out.println("删除后:");
        saleLinkList2.list();
    }
}

输出结果:

添加失败,插入当前的英雄的编号 1 已经存在。
修改之前:
HeroNode{number=1, name='薇恩', position='ADC', price=6300.0}
HeroNode{number=2, name='冰凤凰', position='法师', price=4800.0}
HeroNode{number=3, name='阿卡丽', position='刺客', price=3150.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}
修改之后:
HeroNode{number=1, name='大嘴', position='ADC', price=6300.0}
HeroNode{number=2, name='冰凤凰', position='法师', price=4800.0}
HeroNode{number=3, name='阿卡丽', position='刺客', price=3150.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}
删除失败,没有找到 编号 为 6 的节点 
删除后:
HeroNode{number=2, name='冰凤凰', position='法师', price=4800.0}
HeroNode{number=3, name='阿卡丽', position='刺客', price=3150.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}

Process finished with exit code 0

基本上单向链表(带头结点)的实现就完成了,数据结构学起来还是挺有意思的,就是有的时候自己理解的慢,就容易产生烦躁的情绪。因此,需要多多的学习,多多的练习,才能克服这种抵触情绪。

参考资料:
作者:韩顺平
课程:《Java数据结构与算法》

天气因你逆转,世界因你天晴。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值