Java数据结构(4) 链表——单向链表

Java数据结构(4) 链表——单向链表

1.链表的简介

链表是有序的列表,但以节点的方式存储数据,是链式存储。

一个节点包括:数据域(data):存放数据,链接域(next):指向下一个节点。各节点不一定是连续存储

用代码表示就如下所示:

Node节点类:

/**
 * 代码实现链表——节点类
 */
class Node {
    private Object data;//数据域
    //...
    private Node next;//节点

    //节点的构造方法:初始化数据域,将节点指向空
    public Node(Object data) {
        this.data = data;
        this.next = null;
    }
}

LinkedList链表类:

/**
 * 代码实现链表——链表类
 */
public class LinkedList {
    private Node first;
    private Node last;
    //链表的方法:判空,打印链表,在尾部添加节点,删除节点等
    //...
}

2.单向链表代码实现

节点类:数据域举例为 整型的书籍序列号 ,字符型的书籍名称。同上述简介中节点类一致,在构造方法中初始化数据域中的数据,并将next节点指向空。

单向链表类只实现了:判空,打印链表,在尾部添加节点三个基本方法。

/**
 * 代码实现单向链表——节点类
 */
class Node {
    int bookId;//书籍序列号
    String bookName;//书籍名称
    Node next;//节点

    //节点的构造方法:初始化数据域,将节点指向空
    public Node(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
        this.next = null;
    }
}

/**
 * 代码实现单向链表(判空,打印链表,在尾部添加节点)
 */
public class LinkedList {
    private Node first;
    private Node last;

    //判空
    public boolean isEmpty() {
        return first == null;//布尔表达式,头节点空返回true,反之返回false
    }

    //打印链表(从链表头打印到链表尾)
    public void printLinkedList() {
        Node current = first;//定义一个暂时的节点,赋值为头节点
        while (current != null) {
            System.out.println(current.bookId + current.bookName);
            current = current.next;
        }
    }

    //在尾部添加节点
    public void addNode(Node newNode) {
        if (this.isEmpty()) {//链表为空时,插入节点(头尾节点均为新节点)
            first = newNode;
            last = newNode;
        } else {//链表不为空时,以下两个语句不可颠倒,否则之前的尾节点会被覆盖。
            last.next = newNode;//新节点赋值为 当前尾节点的下一节点
            last = newNode;//此时链表的尾节点 为刚插入的新节点
        }
    }

    //单向链表测试
    public static void main(String[] args) {
        LinkedList linkedList=new LinkedList();
        linkedList.addNode(new Node(12086,"《数据结构》"));
        linkedList.addNode(new Node(18002,"《计算机网络》"));
        System.out.println("单向链表为空?"+linkedList.isEmpty());
        linkedList.printLinkedList();
    }
}

测试结果如下:

单向链表为空?false
12086《数据结构》
18002《计算机网络》

3.单向链表的节点删除与插入

只有判空,打印链表,在尾部添加节点三个基本方法是不够的,还需要节点删除与插入。

这里将节点删除和节点插入两个方法单独提出来,是因为这两个方法是最重要的

删除节点分为三种情况:删除的节点为头节点,删除的节点为尾节点,删除的为中间的某个节点。

方法概述:

  • 删头节点:新头节点为原头节点的下一节点
  • 删尾节点:新尾节点为原尾节点的前一节点
  • 删中间节点:需删除节点的前一节点指向需删除节点的后一节点

具体代码如下(更多细节在注释中有所标注):

 //删除节点 分为三种情况:删除的节点为头节点,删除的节点为尾节点,删除的为中间的某个节点。
    public void delete(Node needDeleteNode) {
        Node newNode;
        Node tempNode;
        //1.删除的节点为头节点
        if (first.bookId == needDeleteNode.bookId) {
            first = first.next;//新头节点赋值为原始头结点的下一个节点
        } else if (last.bookId == needDeleteNode.bookId) {
            //2.删除的节点为尾节点//需要先遍历节点至 尾节点的前一个节点
            newNode = first;
            while (newNode.next != last) newNode = newNode.next;//直至newNode为尾节点的前一个节点
            newNode.next=last.next;//原尾节点的前一个节点 的next节点置空(其实就是原尾节点失去指向)
            last=newNode;//新尾节点为 原尾节点的前一个节点
        }else {//3.删除的为中间的某个节点。需要知道需删除节点的前一节点
            newNode=first;
            tempNode=first;
            while (newNode.bookId!=needDeleteNode.bookId){//遍历链表至newNode为needDeleteNode
                tempNode=newNode;//tempNode暂存newNode
                newNode=newNode.next;
                //循环结束时tempNode为newNode前一个节点
            }
            //循环结束:
            // newNode为needDeleteNode
            // tempNode为newNode的前一节点,即needDeleteNode的迁移节点
            //删除节点即:需删除节点的前一节点指向需删除节点的后一节点
            tempNode.next=needDeleteNode.next;
        }
    }

插入节点分为三种情况:插入的节点为头节点,插入的节点为尾节点,插入的为中间的某个节点。

方法概述:

  • 插头节点:原头节点的前一节点赋值为新插入节点,头节点赋值为新插入节点,若原链表空则头尾节点为新插入节点
  • 插尾节点:原尾节点的下一节点赋值为新插入节点,尾节点赋值为新插入节点,若原链表空则头尾节点为新插入节点
  • 插中间节点:(原链表不为空)插到a节点后,即a节点的下一节点赋值为新插入节点,a节点原来的下一节点

(在上述代码中addNode()方法即在尾部添加节点)

具体代码如下:

    //删除节点 分为三种情况:删除的节点为头节点,删除的节点为尾节点,删除的为中间的某个节点。
    public void delete(Node needDeleteNode) {
        Node newNode;
        Node tempNode;
        //1.删除的节点为头节点
        if (first.bookId == needDeleteNode.bookId) {
            first = first.next;//新头节点赋值为原始头结点的下一个节点
        } else if (last.bookId == needDeleteNode.bookId) {
            //2.删除的节点为尾节点//需要先遍历节点至 尾节点的前一个节点
            newNode = first;
            while (newNode.next != last) newNode = newNode.next;//直至newNode为尾节点的前一个节点
            newNode.next = last.next;//原尾节点的前一个节点 的next节点置空(其实就是原尾节点失去指向)
            last = newNode;//新尾节点为 原尾节点的前一个节点
        } else {//3.删除的为中间的某个节点。需要知道需删除节点的前一节点
            newNode = first;
            tempNode = first;
            while (newNode.bookId != needDeleteNode.bookId) {//遍历链表至newNode为needDeleteNode
                tempNode = newNode;//tempNode暂存newNode
                newNode = newNode.next;
                //循环结束时tempNode为newNode前一个节点
            }
            //循环结束:
            // newNode为needDeleteNode
            // tempNode为newNode的前一节点,即needDeleteNode的迁移节点
            //删除节点即:需删除节点的前一节点指向需删除节点的后一节点
            tempNode.next = needDeleteNode.next;
        }
    }

    //插入节点:三种情况:插入的节点为头节点,插入的节点为尾节点,插入的为中间的某个节点。
    //传入的节点needInsertNode 数据域与节点域均已初始化 固可根据needInsertNode.next判断
    public void insertNode(Node needInsertNode) {
        Node tempNode;
        Node newNode;
        if (this.isEmpty()) {//原链表空,插头插尾都一样 头尾均为新节点
            first = needInsertNode;
            last = needInsertNode;
        } else {
            if (needInsertNode.next == first) {//1.原链表非空,插头
                first = needInsertNode;//新头节点为needInsertNode
            } else if (needInsertNode.next == null) {//2.原链表非空。插尾
                last.next = needInsertNode;//原尾节点的下一节点为新插入节点
                last = needInsertNode;//新尾节点为新插入节点
            } else {//3.插中间 需要得到待插入节点的前一节点
                // (后一节点已知:即传入的Node类型参数的next属性)
                // 即needInsertNode的next节点(已知) 的前一节点
                newNode = first;
                tempNode = first;
                while (needInsertNode.next != newNode.next) {//遍历链表至newNode.next为needInsertNode.next
                    tempNode = newNode;//temp暂存newNode
                    newNode = newNode.next;
                    //循环结束时tempNode为newNode前一个节点
                }
                //循环结束:
                // needInsertNode.next = newNode.next,即 newNode=needInsertNode
                // tempNode=newNode的前一节点,即needInsertNode的前一节点
                //所以得到了待插入节点的前一节点
                tempNode.next = needInsertNode;
                needInsertNode.next = newNode;
            }
        }
    }

测试代码:

LinkedList linkedList = new LinkedList();
        Node node1 = new Node(12086, "《数据结构》");
        Node node2 = new Node(18002, "《计算机网络》");
        Node node3 = new Node(19121, "《数据库概论》");
        linkedList.addNode(node1);
        linkedList.addNode(node2);
        linkedList.addNode(node3);
        System.out.println("单向链表为空?" + linkedList.isEmpty());
        linkedList.printLinkedList();

        //测试插入
        System.out.println("测试插入");
        Node node4 = new Node(20918, "《操作系统》");
        //需要初始化节点的next属性
        // node4.next=linkedList.first;//插头
        node4.next = node3;//插中间 例如插到node3之前
        // node4.next = null;//插尾
        linkedList.insertNode(node4);
        linkedList.printLinkedList();

        //测试删除
        System.out.println("测试删除");
        //linkedList.delete(linkedList.first);//删头
        //linkedList.delete(linkedList.last);//删尾
        linkedList.delete(node3);//删中间
        linkedList.printLinkedList();

测试结果:

单向链表为空?false
12086《数据结构》
18002《计算机网络》
19121《数据库概论》
测试插入
12086《数据结构》
20918《操作系统》
18002《计算机网络》
19121《数据库概论》
测试删除
12086《数据结构》
20918《操作系统》
18002《计算机网络》

关于单链表的两个常见问题:单向链表的反转与串联,将在下一篇博客提及

发布了67 篇原创文章 · 获赞 32 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_42391904/article/details/102691879