数据结构与算法系列四(单链表)

132 阅读5分钟

在上一篇【数据结构与算法系列三(数组)】中,我们知道了最基础的数据结构:数组。在这一篇中,我们再来看另外一种基础数据结构:链表。常用的链表有:单链表、双向链表、循环链表。

这一篇我们主要看:单链表

#考考你:
1.你能用自己的话描述链表吗?
2.你知道链表和数组的区别吗?
3.你知道都有哪些常用的链表吗?

案例

链表定义

链表与数组一样,都是常用的基础数据结构,它通过“指针”将一组零散的内存块串联起来使用。每一个零散的内存块称为:节点

为了将所有节点串联起来,每个链表节点除了存储数据,还需要存储链上下一个节点的地址,我们把存储下一个节点地址的指针,称为:后继指针

链表有两个特殊的节点:头节点、尾节点

头节点:第一个节点

尾节点:后继指针指向null的节点

如图:

image.png

链表与数组区别

内存空间

上一篇我们知道数组的一个特点:需要连续的内存空间。链表与数组刚好相反,链表不需要连续的内存空间,它是通过“指针”将一组零散的内存块串联起来使用。

如图:

image.png

操作:插入、删除

数组的插入、删除操作,需要向后,向前移动数据,时间复杂度是:O(n)

链表的插入、删除操作,只需要改变节点指针,不需要移动数据,时间复杂度是:O(1)

如图:

image.png

操作:查找

数组的内存空间是连续的,支持随机访问操作,根据下标索引访问,时间复杂度是:O(1)

链表的内存空间不连续,不支持随机访问操作,从头节点遍历访问,时间复杂度是:O(n)

单链表代码实现

节点封装

/**
     * 链表节点:Node<E>
     */
    class Node<E>{
        private E e;
        private Node<E> next;

        public E getE() {
            return e;
        }

        public void setE(E e) {
            this.e = e;
        }

        public Node<E> getNext() {
            return next;
        }

        public void setNext(Node<E> next) {
            this.next = next;
        }
    }

完整代码

package com.anan.struct.linetable;

/**
 * 单链表实现思路:
 *     1.空闲一个头节点,即头节点不存储数据
 *     2.这样有利于简化链表的实现
 */
public class SingleLinkedList<E> {

    // 链表大小
    private int size;

    public int getSize() {
        return size;
    }

    // 链表头节点
    private Node<E> head;
    // 链表尾节点
    private Node<E> tail;

    public SingleLinkedList(){
        head = new Node<E>();
        tail = head;

        size = 1;
    }

    /**
     * 在链表结尾插入元素
     */
    public boolean add(E e){
        // 创建节点
        Node<E> node = new Node<E>();
        node.setE(e);

        // 改变尾节点指针,指向新节点
        tail.next = node;
        // 设置新的尾节点
        tail = node;

        // 链表大小加1
        size ++;
        
        return true;
    }

    /**
     * 在指定索引位置,插入节点
     */
    public boolean insertPos(int pos,E e){

       // 判断索引位置有效性
        if(pos < 1 || pos > size ){
            return  false;
        }

        // 创建节点
        Node<E> node = new Node<E>();
        node.setE(e);

        // 获取插入位置节点
        Node<E> posNode = get(pos - 1);
        
        // 改变节点指针指向
        node.next = posNode.next;
        posNode.next = node;

        // 链表大小加1
        size ++;
        
        return true;
    }

    /**
     * 删除链表尾元素
     */
    public boolean remove(){

        // 获取链表倒数第二个元素
        Node<E> node = get(getSize() - 2);
        
        // 改变尾节点
        tail = node;
        node.next = null;

        // 链表大小减1
        size -- ;
        
        return true;
    }

    /**
     * 删除指定位置的元素(不能删除头节点)
     */
    public boolean delPos(int pos){

        // 判断索引位置有效性
        if(pos < 1 || pos > size){
            return false;
        }

        // 如果删除的是最后一个元素
        if((pos + 1) == size){
            remove();
        }else{
            // 获取删除元素节点
            Node<E> node = get(pos);
            // 获取删除元素的前一个节点
            Node<E> preNode = get(pos - 1);

            // 删除操作
            preNode.next = node.next;

            // 链表大小减1
            size --;
        }

        return true;
    }


    /**
     * 获取指定索引的链表节点
     */
    public Node<E> get(int index){
        // 判断索引有效性
        if(index < 0 || index > size - 1){
            return null;
        }

        // 从头节点开始遍历
        Node<E> node = head;
        for(int i=0; i< index; i++){
            node = node.next;
        }

        return node;
    }

    /**
     * 获取指定索引位置的数据
     */
    public E getValue(int index){
        // 获取节点
        Node<E> node = get(index);
        if(node == null){
            return  null;
        }

        return  node.e;
    }

    /**
     * 链表节点:Node<E>
     */
    class Node<E>{
        private E e;
        private Node<E> next;

        public E getE() {
            return e;
        }

        public void setE(E e) {
            this.e = e;
        }

        public Node<E> getNext() {
            return next;
        }

        public void setNext(Node<E> next) {
            this.next = next;
        }
    }
}

测试

package com.anan.struct.linetable;

/**
 * 测试单链表
 */
public class SingleLinkedListTest {

    public static void main(String[] args) {
        // 1.创建链表,添加元素
        SingleLinkedList<Integer> list = new SingleLinkedList<Integer>();
        for (int i = 0; i < 5; i++) {
            list.add(i);
        }

        System.out.println("1.创建链表,添加元素-----------------------------------------");
        list(list);


        // 2.指定位置插入元素
        System.out.println("2.指定位置【5】插入元素-----------------------------------------");
        list.insertPos(5,666);
        list(list);


        // 3.删除链表结尾元素
        System.out.println("3.删除链表结尾元素-----------------------------------------");
        list.remove();
        list(list);


        // 4.再次在链表结尾添加元素
        System.out.println("4.再次在链表结尾添加元素-----------------------------------------");
        list.add(888);
        list(list);

        // 5.删除指定位置元素
        System.out.println("5.删除指定位置【1】元素-----------------------------------------");
        list.delPos(1);
        list(list);

    }

    /**
     * 遍历输出链表
     * @param list
     */
    public static  void list(SingleLinkedList<Integer> list){
        System.out.println("当前链表大小,size:" + list.getSize());
        for (int i = 1; i < list.getSize(); i++) {
            System.out.println(list.getValue(i));
        }
    }
}

测试结果:

D:\02teach\01soft\jdk8\bin\java com.anan.struct.linetable.SingleLinkedListTest
1.创建链表,添加元素-----------------------------------------
当前链表大小,size:6
0
1
2
3
4
2.指定位置【5】插入元素-----------------------------------------
当前链表大小,size:7
0
1
2
3
666
4
3.删除链表结尾元素-----------------------------------------
当前链表大小,size:6
0
1
2
3
666
4.再次在链表结尾添加元素-----------------------------------------
当前链表大小,size:7
0
1
2
3
666
888
5.删除指定位置【1】元素-----------------------------------------
当前链表大小,size:6
1
2
3
666
888

Process finished with exit code 0

讨论分享

#考考你答案:
1.你能用自己的话描述链表吗?
  1.1.链表与数组一样,都是常用的基础数据结构
  1.2.链表通过“指针”将一组零散的内存块串联起来使用
  1.3.每一个零散的内存块称为:节点
  1.4.链表的每个节点,除了存储数据以外,还需要存储一个指向下一个节点的指针
  1.5.通常我们把指向下一个节点的指针,称为:后继指针
  
2.你知道链表和数组的区别吗?
  2.1.数组需要连续的内存空间,链表不需要
  2.2.插入、删除操作
    2.2.1.数组需要移动数据,时间复杂度是:O(n)
    2.2.2.链表不需要移动数据,时间复杂度是:O(1)
    
  2.3.查找操作
    2.3.1.数组支持随机访问操作,时间复杂度是:O(1)
    2.3.2.链表需要从头节点遍历,不支持随机访问操作,时间复杂度是:O(n)

3.你知道都有哪些常用的链表吗?
  3.1.单链表
  3.2.双向链表
  3.3.循环链表