Java数据结构与算法04——单向链表

标签 : Java 数据结构 算法

作者 : Maxchen

版本 : V1.0.0

日期 : 2020/4/1


1.单向链表——原理

假如我们现在要存放一些物品,但是没有足够大的空间将所有的物品一次性放下(电脑中使用链式存储不是因为内存不够先事先说明一下…,具体原因后续会说到),同时设定我们因为脑容量很小,为了节省空间,只能记住一件物品位置。此时我们很机智的找到了解决方案:存放物品时每放置一件物品就在物品上贴一个小纸条,标明下一件物品放在那里,只记住第一件物品的位置,寻找的时候从第一件物品开始寻找,通过小纸条我们可以找到所有的物品,这就是链式存储。链表实现的时候不再像线性表一样只存储数据即可,还有下一个数据元素的地址,因此先定义一个节点类(Node),记录物品信息和下一件物品的位置,我们把物品本身叫做数据域,存储下一件物品地址信息的小纸条称为引用域。链表结构示意图如下:1

image.png-26.5kB

提示: 寻找物品的时候发现了一个问题,我们从一件物品找下一件物品的时候很容易,但是如果要找上一件物品就得从头开始找,真的很麻烦。为了解决这个问题我们又机智了一把,模仿之前的做法,在存放物品的时候多放置一个小纸条记录上一件物品的位置,这样就可以很快的找到上一件物品了。我们把这种方式我们称为双向链表,前面只放置一张小纸条的方式称为单向链表

2.单向链表——代码实现

代码整体实现如下图所示:首先构建一个单链表,然后初始化头节点,随后往下面添加节点,最后打印整个链表的数据。

单链表.png-13.5kB

第一步: 对节点进行定义,我们这里以平常经常使用的几个大品牌电脑为例,包含以下字段:序号、品牌名称、品牌外号、指向下一个节点。

class ComputerNode {
    public int no; //序号
    public String name; //品牌名称
    public String nickname; //品牌外号
    public ComputerNode next; //指向下一个节点

    //构造器
    public ComputerNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }
    //为了显示方法,我们重新toString
    @Override
    public String toString() {
        return "ComputerNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
    }

}

第二步: 定义一个单链表并初始化头节点

class SingleLinkedList {

    //先初始化一个头节点,头不存放具体的数据
    private ComputerNode head = new ComputerNode(0, "", "");

}

第三步: 往这个单链表添加节点

class SingleLinkedList {

    ……

    //添加节点到单向链表
    public void add(ComputerNode computerNode) {

        //因为head节点不能动,因此我们需要一个辅助遍历 temp
        ComputerNode temp = head;
        //遍历链表,找到最后
        while(true) {
            //找到链表的最后
            if(temp.next == null) {//next字段为空,则表示为链表结尾
                break;
            }
            //如果没有找到最后,next字段不为空 将temp后移
            temp = temp.next;
        }
        //当退出while循环时,temp就指向了链表的最后
        //将最后这个节点的next指向新的节点
        temp.next = computerNode;
    }
}

第四步: 定以显示链表的方法

class SingleLinkedList {

    ……
    
        //显示链表[遍历]
    public void list() {
        //判断链表是否为空
        if(head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //因为头节点,不能动,因此我们需要一个辅助变量来遍历
        ComputerNode temp = head.next;
        while(true) {
            //判断是否到链表最后
            if(temp == null) {
                break;
            }
            //输出节点的信息
            System.out.println(temp);
            //将temp后移, 一定小心
            temp = temp.next;
        }
    }
}

第五步: 我们对以上方法进行一次整体的测试,可以看到一种现象,链表最后展示的结果是按照代码添加的顺序排列的

public class SingleLinkedListDemo {

    public static void main(String[] args) {

        //先创建节点
        ComputerNode computer1 = new ComputerNode(1, "联想", "美帝良心想");
        ComputerNode computer2 = new ComputerNode(2, "惠普", "铁板熊掌普");
        ComputerNode computer4 = new ComputerNode(4, "戴尔", "人傻钱多戴");
        ComputerNode computer3 = new ComputerNode(3, "华硕", "奸若磐石硕");

        //创建要给链表并将节点加入到链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        singleLinkedList.add(computer1);
        singleLinkedList.add(computer2);
        singleLinkedList.add(computer4);
        singleLinkedList.add(computer3);

        //显示链表,最后得到的结果如下,显然链表最后展示的结果是按照代码添加的顺序排列的
        // ComputerNode [no=1, name=联想, nickname=美帝良心想]
        // ComputerNode [no=2, name=惠普, nickname=铁板熊掌普]
        // ComputerNode [no=4, name=戴尔, nickname=人傻钱多戴]
        // ComputerNode [no=3, name=华硕, nickname=奸若磐石硕]
        singleLinkedList.list();

    }

}

image.png-31.3kB

第六步: 我们在前面基础上增加一个要求:按照编号顺序排序,并且重复编号的值不能添加

class SingleLinkedList {

    ……
    
    //第二种方式在添加电脑时,根据编号顺序将电脑插入到指定位置
    public void addByOrder(ComputerNode computerNode) {
        //头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
        //因为单链表,因为我们找的temp 是位于 添加位置的前一个节点,否则插入不了
        ComputerNode temp = head;
        boolean flag = false; // flag标志添加的编号是否存在,默认为false
        while(true) {
            if(temp.next == null) {//说明temp已经在链表的最后
                break;
            }
            if(temp.next.no > computerNode.no) { //位置找到,就在temp的后面插入
                break;
            } else if (temp.next.no == computerNode.no) {//说明希望添加的heroNode的编号已然存在

                flag = true; //说明编号存在
                break;
            }
            temp = temp.next; //后移,遍历当前链表
        }
        //判断flag 的值
        if(flag) { //不能添加,说明编号存在
            System.out.printf("准备插入的电脑的编号 %d 已经存在了, 不能加入\n", computerNode.no);
        } else {
            //插入到链表中, temp的后面
            computerNode.next = temp.next;
            temp.next = computerNode;
        }
    }

}

第七步:add方法改为addByOrder并测试,发现链表能按照编号顺序添加节点

public class SingleLinkedListDemo {

    public static void main(String[] args) {

        ……

        //创建要给链表并将节点加入到链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        /*singleLinkedList.add(computer1);
        singleLinkedList.add(computer2);
        singleLinkedList.add(computer4);
        singleLinkedList.add(computer3);*/

        singleLinkedList.addByOrder(computer1);
        singleLinkedList.addByOrder(computer2);
        singleLinkedList.addByOrder(computer4);
        singleLinkedList.addByOrder(computer3);

        ……

    }

}

image.png-40kB

第八步: 我们测试一下重复添加某个节点会出现何种情况

public class SingleLinkedListDemo {

    public static void main(String[] args) {

        ……

        //重复添加computer3
        singleLinkedList.addByOrder(computer1);
        singleLinkedList.addByOrder(computer2);
        singleLinkedList.addByOrder(computer3);
        singleLinkedList.addByOrder(computer3);

        ……

    }

}

image.png-31.8kB

3.单向链表——整体代码

下面附上这次单向链表的所有代码

package com.maxchen.demo.linkedlist;

/**
 * @ClassName: SingleLinkedListDemo
 * @Description: TODO
 * @Author Maxchen
 * @Date 2020/4/1 18:32
 * @Version V1.0.0
 */
public class SingleLinkedListDemo {

    public static void main(String[] args) {

        //先创建节点
        ComputerNode computer1 = new ComputerNode(1, "联想", "美帝良心想");
        ComputerNode computer2 = new ComputerNode(2, "惠普", "铁板熊掌普");
        ComputerNode computer3 = new ComputerNode(3, "华硕", "奸若磐石硕");
        ComputerNode computer4 = new ComputerNode(4, "戴尔", "人傻钱多戴");

        //创建要给链表并将节点加入到链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        /*singleLinkedList.add(computer1);
        singleLinkedList.add(computer2);
        singleLinkedList.add(computer4);
        singleLinkedList.add(computer3);*/

        singleLinkedList.addByOrder(computer1);
        singleLinkedList.addByOrder(computer2);
        singleLinkedList.addByOrder(computer3);
        singleLinkedList.addByOrder(computer3);

        //显示链表,最后得到的结果如下,显然链表最后展示的结果是按照代码添加的顺序排列的
        // ComputerNode [no=1, name=联想, nickname=美帝良心想]
        // ComputerNode [no=2, name=惠普, nickname=铁板熊掌普]
        // ComputerNode [no=4, name=戴尔, nickname=人傻钱多戴]
        // ComputerNode [no=3, name=华硕, nickname=奸若磐石硕]
        singleLinkedList.list();

    }

}


class SingleLinkedList {

    //先初始化一个头节点,头不存放具体的数据
    private ComputerNode head = new ComputerNode(0, "", "");

    //添加节点到单向链表
    public void add(ComputerNode computerNode) {

        //因为head节点不能动,因此我们需要一个辅助遍历 temp
        ComputerNode temp = head;
        //遍历链表,找到最后
        while(true) {
            //找到链表的最后
            if(temp.next == null) {//next字段为空,则表示为链表结尾
                break;
            }
            //如果没有找到最后,next字段不为空 将temp后移
            temp = temp.next;
        }
        //当退出while循环时,temp就指向了链表的最后
        //将最后这个节点的next指向新的节点
        temp.next = computerNode;
    }

    //第二种方式在添加电脑时,根据编号顺序将电脑插入到指定位置
    public void addByOrder(ComputerNode computerNode) {
        //头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
        //因为单链表,因为我们找的temp 是位于 添加位置的前一个节点,否则插入不了
        ComputerNode temp = head;
        boolean flag = false; // flag标志添加的编号是否存在,默认为false
        while(true) {
            if(temp.next == null) {//说明temp已经在链表的最后
                break;
            }
            if(temp.next.no > computerNode.no) { //位置找到,就在temp的后面插入
                break;
            } else if (temp.next.no == computerNode.no) {//说明希望添加的heroNode的编号已然存在

                flag = true; //说明编号存在
                break;
            }
            temp = temp.next; //后移,遍历当前链表
        }
        //判断flag 的值
        if(flag) { //不能添加,说明编号存在
            System.out.printf("准备插入的电脑的编号 %d 已经存在了, 不能加入\n", computerNode.no);
        } else {
            //插入到链表中, temp的后面
            computerNode.next = temp.next;
            temp.next = computerNode;
        }
    }

    //显示链表[遍历]
    public void list() {
        //判断链表是否为空
        if(head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //因为头节点,不能动,因此我们需要一个辅助变量来遍历
        ComputerNode temp = head.next;
        while(true) {
            //判断是否到链表最后
            if(temp == null) {
                break;
            }
            //输出节点的信息
            System.out.println(temp);
            //将temp后移, 一定小心
            temp = temp.next;
        }
    }

}

class ComputerNode {
    public int no; //序号
    public String name; //品牌名称
    public String nickname; //品牌外号
    public ComputerNode next; //指向下一个节点

    //构造器
    public ComputerNode(int no, String name, String nickname) {
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }
    //为了显示方法,我们重新toString
    @Override
    public String toString() {
        return "ComputerNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
    }

}

  1. 数据结构之线性结构和非线性结构 ↩︎

发布了16 篇原创文章 · 获赞 32 · 访问量 2420

猜你喜欢

转载自blog.csdn.net/u012420395/article/details/105266644