链表的基本概念

简介: 链表的基本概念

链表的定义

2.png

链表就像每个节点串起来一样构成了一串,每个节点都是一个节点对象


其中我们对于每个节点会去划分域,分为data域跟next域两种.


data代表数值域,next代表引用,用于存取下一个节点的地址.


链表的结构

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:


单向、双向


带头、不带头


循环、非循环


单向组合起来一共有以下几种:


单向、带头、循环、

单向、不带头、非循环 (面试中经常考到的,也是我们重点掌握内容)

单向、带头、非循环

单向、不带头、循环


单向不带头非循环链表(重点掌握,面试常考)

2.png

在单向不带头非循环链表中,第一个节点我们称之为头节点,最后一个节点我们称之为尾节点


同时单向链表的尾节点的next域为null,因为尾结点不再指向任何一个结点


同时我们可以发现链表上的地址不同于顺序表的是,链表的地址并不是连续的,链表中节点的相连是通过next域来实现的


单向带头非循环链表

2.png

在单向有头非循环链表中,我们是用一个专门的节点来作为头节点的,这个节点没有data这个数值域,只有next指针域,这个指针域用来存储我们第一个数据节点的地址,且这个头节点是无法删除的,它就像一个哨兵,指向我们第一个数据节点。


单向带头循环链表

2.png


自己实现一个链表(一定自己动手实现五遍以上)

单向不带头非循环链表(重点)

接口(方法)

//头插法(优化和非优化方法)
public void addFirst(int data);
//尾插法(优化和非优化方法)
public void addLast(int data);
//任意位置插入,第一个数据节点为0号下标
public boolean addIndex(int index,int data);
//查找关键字key是否在单链表当中
public boolean contains(int key);
//删除第一次出现关键字为key的节点
public void remove(int key);
//得到单链表的长度
public int size();
//遍历输出链表
public void display();
//清空单链表
public void clear();
//找到链表的最后一个节点
public void findLastNode();
//找到链表的倒数第二个节点
public void findLastTwoNode();
//找到链表的第n个节点
public Node findNumber(int n);


代码示例

/**
 * @author SongBiao
 * @Date 2021/1/18
 * 自己实现一个单向无头非循环链表
 */
//Node类适用于创建每个单个的节点对象
class Node {
    //代表我们的数值域
    public int data;
    //next是用来存取下一个节点对象的地址的,所以是Node类型,说明其也是一个引用
    public Node next;
    public Node() {
    }
    public Node(int data) {
        this.data = data;
    }
}
public class MyLinkedList {
    //head表示单链表的头,默认值为null,因为此时head这个头指针不指向我们的头节点
    public Node head;
    //在进行头插法之前,首先要进行链表的创建
    //其次在创建的过程中,我们还要将获取到的头节点的地址赋值给head
    public void createLinkList() {
        //将第一个节点的地址赋给我们head
        this.head = new Node(10);
        Node node2 = new Node(20);
        Node node3 = new Node(30);
        Node node4 = new Node(40);
        Node node5 = new Node(50);
        this.head.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
    }
    //遍历输出链表
    public void display() {
        Node cur = this.head;
        while (cur != null) {
            System.out.println(cur.data);
            cur = cur.next;
        }
        System.out.println();
    }
    //通过遍历,找到链表最后一个节点
    //注意要考虑到链表为空的情况
    public Node findLastNode() {
        if (this.head == null) {
            System.out.println("链表是空的,根本没有最后一个节点");
            return null;
        }
        Node cur = this.head;
        while (cur.next != null) {
            cur = cur.next;
        }
        return cur;
    }
    //通过遍历,找到链表的倒数第二个节点
    //注意要考虑到只有一个节点和链表为空的情况
    public Node findLastTwoNode() {
        if (this.head.next == null) {
            System.out.println("只有一个节点");
            return null;
        }
        if (this.head == null) {
            System.out.println("一个节点都没有");
            return null;
        }
        Node cur = this.head;
        while (cur.next.next != null) {
            cur = cur.next;
        }
        return cur;
    }
    //得到单链表的长度
    public int size() {
        int count = 0;
        Node cur = this.head;
        if (cur == null) {
            return -1;
        }
        while (cur != null) {
            cur = cur.next;
            count++;
        }
        return count;
    }
    //找到链表的第n个节点
    //同样需要判断链表为空和所找的第n个节点是否超过了当前链表的长度
    public Node findNumber(int n) {
        if (this.head == null) {
            System.out.println("一个节点都没有");
            return null;
        }
        if (n <= 0) {
            System.out.println("根本不存在这个节点");
            return null;
        }
        if (n > size()) {
            System.out.println("所要找的节点超过了链表的长度");
            return null;
        }
        Node cur = this.head;
        int count = 1;
        while (count != n) {
            cur = cur.next;
            count++;
        }
        return cur;
    }
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        Node cur = this.head;
        while (cur != null) {
            if (cur.data == key) {
                return true;
            }
            cur = cur.next;
        }
        //找不到返回false
        return false;
    }
    //头插法非优化方法
    /*
    分为三步:
    第一步:首先的有一个节点
    第二步:判断链表是否为空
    第三步:如果不为空,进行插入
     */
    public void addFirst(int data) {
        //创建一个要插入的节点,其next域默认为null
        Node node = new Node(data);
        if (this.head == null) {
            this.head = node;
        } else {
            //注意在任何情况下往链表里面插入数据的时候,先绑定后面,
            node.next = this.head;
            this.head = node;
        }
    }
    //头插法的优化
    public void addFirst1(int data) {
        //创建一个要插入的节点,其next域默认为null
        Node node = new Node(data);
        //优化之后可以去掉判断空的情况
        node.next = this.head;
        this.head = node;
    }
    //尾插法非优化方法
   /*
    1:创造一个要插入的节点
    2:判断此时要插入的链表是否为空,如果为空,就直接插入
    3:非空的话就正常插入
   */
    public void addLast(int data) {
        Node node = new Node(data);
        if (this.head == null) {
            this.head = node;
        } else {
            //获取到当前非空链表的头节点
            Node cur = this.head;
            while (cur.next != null) {
                cur = cur.next;
            }
            cur.next = node;
        }
    }
    //该函数是为了找到index-1位置的节点的引用
    //index是下标的意思,第三个节点的index值为2
    //由于链表底部不是数组,没有下标,所以这个下标是我们自己定义的,方便我们自己使用
    //例如此时我要在第三个节点处插入数据,此时这个节点index=2
    //在第三个节点插入数据,就应该去寻找第二个节点是谁,而第二个节点的index=1
    //所以说moveIndex方法的目标是寻找到所插入节点的位置的前一节点
    public Node moveIndex(int index) {
        if (index <= 1) {
            System.out.println("寻找的位置出错");
            return null;
        }
        if (index > size() + 1) {
            System.out.println("所找的位置大了");
            return null;
        }
        Node cur = this.head;
        int count = 0;
        while (count != index - 1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }
    //任意位置插入,第一个数据节点为0号下标
    //注意插入的时候不管什么插入,一定是先考虑后面插入,在考虑前面插入
    public void addIndex(int index, int data) {
        if (index < 0 || index > size()) {
            System.out.println("所插入的位置不合法");
  /*
  return;这个语句一般出现在一个无返回值的函数里面,
  函数里面可以有很多行,有的时候需要程序运行到某一行就要停止退出本函数,就需要用到return;语句
  */
            return;
        }
        //index=0的时候为头插法
        if (index == 0) {
            addFirst1(data);
            return;
        }
        //index等于链表长度的时候为尾插法
        if (index == size()) {
            addLast(data);
            return;
        }
        //创建一个要插入的新的节点
        Node node = new Node(data);
        //查找到要插入的节点位置的前一节点
        Node cur = moveIndex(index);
        node.next = cur.next;
        cur.next = node;
    }
    //删除第一次出现关键字为key的节点
    /*
    1:首先需要判断所删除的节点是否存在于链表中,可根据所要删除节点的前驱节点是否存在来判断
    如果存在,说明在链表中,反之亦然。
    2: 还要判断链表是否为空,如果为空,那么直接执行return;语句
    3:如果存在于链表中,那么就去找所要删除节点的前驱
    4:假设我们要删除的节点为del,此节点的前驱为cur,那么删除del这个节点的核心语句为
       cur.next=del.next
    5:
    当删除的节点为首节点的时候,首节点是没有前驱的,cur不存在
    所以必须进行独立的判断:
    if(this.head.data==key){
        this.head=this.head.next;
    }
     */
    public void remove(int key) {
        //判断链表是否为空
        if (this.head == null) {
            return;
        }
        if (this.head.data == key) {
            this.head = this.head.next;
            return;
        }
        //找到所要删除的节点的前驱节点
        Node pre = searchPre(key);
        //如果找不到所要删除的节点的前驱节点,说明要删除的这个节点根本不存在
        if (pre == null) {
            System.out.println("根本没有这个节点的前驱");
        } else {
            //将所找到的key值对应的节点赋给cur这个引用
            Node cur = pre.next;
            pre.next = cur.next;
        }
    }
    //找到存储关键字key这个节点的前驱节点
    /*找到关键key的前驱,如果有返回前驱节点的引用
      如果没有返回null*/
    public Node searchPre(int key) {
        Node cur = this.head;
        //注意循环条件为cur.next,如果是cur,相当于遍历完成,但是尾结点不可能是其他节点的前驱,
        //所以此处应该是走到最后一个节点时循环就应该终止了。
        while (cur.next != null) {
            if (cur.next.data == key) {
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }
    //清空链表
    public void clear() {
        this.head = null;
    }
    public static void main(String[] args) {
    }
    public static void main1(String[] args) {
        //new Node()代表实例化了一个节点对象,其地址存储在node这个引用当中
        Node node = new Node();
        System.out.println(node.data);
        System.out.println(node.next);
        System.out.println("=========");
        Node node1 = new Node(10);
        System.out.println(node1.data);
        System.out.println(node1.next);
    }
}

顺序表和链表的区别

2.png

相关文章
|
6月前
|
存储 算法 C语言
【链表专题】深入探索链表:文章索引与知识架构(链表的概念、实现、应用、经典例题大合集)
【链表专题】深入探索链表:文章索引与知识架构(链表的概念、实现、应用、经典例题大合集)
|
6月前
|
存储 算法 Linux
【内核链表】数据结构——深入理解内核链表的概念和操作&笔记
【内核链表】数据结构——深入理解内核链表的概念和操作&笔记
|
7月前
|
存储
🚀链表理论:基础概念与实战技巧!
【2月更文挑战第8天】
161 43
|
7月前
|
存储 算法 索引
数据结构与算法④(第二章下)链表概念+单链表的实现
数据结构与算法④(第二章下)链表概念+单链表的实现
44 0
|
机器学习/深度学习 存储
链表oj题 && 链表与LinkedList && 栈的概念 && 队列的概念 && 树和二叉树
链表oj题 && 链表与LinkedList && 栈的概念 && 队列的概念 && 树和二叉树
158 38
|
7月前
|
存储 JavaScript 算法
TypeScript算法专题 - [双链表1] - 双链的概念及其实现
TypeScript算法专题 - [双链表1] - 双链的概念及其实现
52 0
|
7月前
|
存储 C语言 索引
C语言数据结构(链表概念讲解和插入操作)
C语言数据结构(链表概念讲解和插入操作)
115 0
|
存储 算法
【数据结构和算法】图的各类概念与图的存储结构(还有十字链表与邻接多重表的介绍)
【数据结构和算法】图的各类概念与图的存储结构(还有十字链表与邻接多重表的介绍)
250 0
【数据结构和算法】图的各类概念与图的存储结构(还有十字链表与邻接多重表的介绍)
|
存储 算法
单链表的基本概念、数据结构实现以及常见操作
单链表的基本概念、数据结构实现以及常见操作
245 0
|
存储
<数据结构> 链表 - 链表的概念及结构
<数据结构> 链表 - 链表的概念及结构
105 0