链表的基本概念

链表的定义

​​​在这里插入图片描述
链表就像每个节点串起来一样构成了一串,每个节点都是一个节点对象

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

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

链表的结构

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

单向、双向

带头、不带头

循环、非循环

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

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

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

在这里插入图片描述
在单向不带头非循环链表中,第一个节点我们称之为头节点,最后一个节点我们称之为尾节点

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

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

单向带头非循环链表

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

单向带头循环链表

在这里插入图片描述

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

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

接口(方法)

//头插法(优化和非优化方法)

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);
    }
}

Guess you like

Origin blog.csdn.net/qq_41972686/article/details/119279929