数据结构和算法-单向链表

数据结构和算法-单向链表

链表是线性表的链式存储方式,逻辑上相邻的数据在计算机内的存储位置不必须相邻,那么,怎么表示逻辑上的相邻关系呢?可以给每个元素附加一个指针域,指向下一个元素的存储位置。 如图:

 

 

 

从上图中我们也可以直观看出,每个结点包含两个域:数据域和指针域,指针域存储下一个结点的地址,因此指针指向的类型也是结点类型。

1. 链表的特点

  • 链表是一种非线性、非顺序的物理结构,是由若干个节点组成。
  • 链表采用的是“见缝插针”的存储方法,不要求内存连续,靠next指针关联起来。
  • 链表的物理存储方式为随机存储,访问方式为顺序访问。
  • 查找节点的时间复杂度为O(n),插入、删除节点的时间复杂度为O(1)。
  • 链表适用于写操作多,读操作少的场景。

2.链表的核心要素

  1. 每个节点由数据域和指针域组成
  2. 指针域指向下一个节点的内存地址

3.链表类型

单向链表

双向链表

循环链表

4.单向链表

链表中最简单的一种是单向链表,它包含两个域,一个信息域(data)和一个指针域(next)。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值(NULL)。所谓单链表无疑是链式存储方式,节点中的next指针指向下一个节点,存储空间不是连续的。

单向链表我们把他分为头节点和节点,头节点默认是链表的头部,不存储数据,节点则存储数据。

 

 

如图,头节点不存储数据,但是他的指针指向节点1的地址,所以头节点和节点1链接起来了。然后节点1的指针又指向了节点2的地址,使节点1和节点2也链接起来了…最后,节点i的指针指向节点n,最后一个节点,最后一个节点n的指针指向了NULL。至此,一条链条诞生了。

单向链表的操作

单向链表的操作方式主要分为增、删、改、查等操作

4.1 单向链表的建立,初始化

(1)若原链表为空,则将新建节点设置为头节点

(2)若原链表为非空,则将新建节点添加到表尾

4.2 单链表的插入操作

向一个链表中插入一个新节点时,首先要新建一个节点,并将新建节点的指针域初始化为空NULL,然后在链表中寻找适当的位置执行节点插入操作,此时需要考虑下面4种情况:

(1)若原链表为空,则将新建节点p作为头节点,让head指向新节点p

(2)若原链表为非空,折按新建节点的值的大小(假设原链表已按节点值升序排列)确定插入新节点的位置。若在头结点前插入新节点,则将新节点的指针域指向原链表的头结点,并且让head指向新节点p

(3)若在原链表中间插入新节点,则将新节点p的指针域指向下一节点,并且让前一节点的指针域指向新建节点p

(4)若在表尾插入新节点,则将尾节点的指针域指向新节点p

4.3 单向链表的删除操作

链表的删除操作就是将待删除的节点从链表中断开,那么待删除节点的上一个节点就成为尾节点。在删除节点时,我们要考虑一下4种情况:

(1)若原链表为空,则不执行任何操作,直接退出程序

(2)若待删除节点是头节点,则将head指向当前节点的下一个节点,再删除当前节点

(3)若待删除节点不是头节点,则将前一节点的指针域指向当前节点的下一节点,即可删除当前节点。当待删除节点是尾节点时,由于p->next=NULL,因此执行pr->next = p->next后,pr->next的值也变为了NULL,从而使pr所指向的节点由倒数第二个节点变成尾节点。

(4)若待删除的节点不存在,则退出程序

注意:节点被删除后,只表示将它从链表中断开而已,它仍占用着内存,必须要释放这个内存,否则会出现内存泄漏。

4.4 单向链表的修改

(1)若原链表为空,则不执行任何操作,直接退出程序

(2)若节点找到,对节点数据进行修改,否则直到节点未找到时,则退出程序

4.5 单向链表的查找

(1)若原链表为空,则不执行任何操作,直接退出程序

(2)若节点找到,则返回节点,否则直到节点未找到时,则退出程序

4.6 程序实现

package com.joshua317;

public class Main {

    public static void main(String[] args) {

        // write your code here
        Node node1 = new Node(1,"小松");
        Node node2 = new Node(2,"小明");
        Node node3 = new Node(3,"小红");
        SingleLinkedList singleLinkedList = new SingleLinkedList();

        singleLinkedList.add(node1);
        singleLinkedList.add(node2);
        singleLinkedList.add(node3);
        singleLinkedList.list();
        singleLinkedList.len();

        singleLinkedList.delete(4);

        System.out.println("删除节点后的列表为:");
        singleLinkedList.list();
    }
}

class SingleLinkedList {
    //先初始化一个头节点
    private Node head = new Node(0, "");

    /**
     * 添加节点到单向链表,不考虑排序,在链表尾部加入节点
     * 1.找到当前链表的最后节点
     * 2.将最后这个节点的next指向新的节点
     */
    public void add(Node node)
    {
        Node temp = head;
        //遍历链表,找到最后的节点
        while (true) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
        }
        //当退出while循环时,temp就指向了链表的最后
        //将最后temp节点的next指向新的节点
        temp.next = node;
    }
    //显示链表[遍历]
    public void list()
    {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //因为头节点不能动,所以需要一个辅助变量来遍历
        Node temp = head.next;
        while (true) {
            //判断节点是否为空
            if (temp == null) {
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }

    /**
     * 获取节点数据
     * @param no
     */
    public void get(int no)
    {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //因为头节点不能动,所以需要一个辅助变量来遍历
        Node temp = head.next;
        while (true) {
            if (temp == null) {
                break;
            }
            if (temp.no == no)  {
                System.out.println(temp);
                break;
            }
            temp = temp.next;
        }
    }

    public void delete(int no)
    {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return;
        }
        //因为头节点不能动,所以需要一个辅助变量来遍历
        Node temp = head;
        boolean flag = false;
        while (true) {
            if (temp.next == null) {
                break;
            }
            if (temp.next.no == no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.next = temp.next.next;
        } else {
            System.out.println("数据no:" + no + "不存在");
        }
    }
    public int len()
    {
        //判断链表是否为空
        if (head.next == null) {
            System.out.println("链表为空");
            return 0;
        }
        Node temp = head.next;
        int len = 0;
        while (true) {
            if (temp == null) {
                break;
            }
            len++;
            temp = temp.next;
        }
        System.out.println("链表的长度为:" + len);
        return len;
    }
}

//定义一个Node节点,每个Node对象是一个节点
class Node {
    public int no;
    public String name;
    public Node next;//指向下一个节点
    //构造函数
    public Node (int no, String name) {
        this.no = no;
        this.name = name;
    }

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

5. 链表的常见面试题(持续整理,附代码)

5.1 两数相加

5.2 判断链表中是否有环

5.3 删除链表的倒数第N个节点

5.4 反转链表(单链表)

5.5 回文链表

5.6 合并两个有序链表

 

posted @ 2021-09-02 10:51  joshua317  阅读(231)  评论(0编辑  收藏  举报