《数据结构系列_线性表_链式存储之单链表》

1.前言

在上一篇中我们了解了线性表的顺序存储方式,今天我们再来看下链式存储。首先思考一个问题,为什么要学习链式存储,相比于顺序存储有哪些优缺点。

我们都知道,在顺序存储时,如果要是删除或者添加一个元素的话,需要移动大量的元素,因为顺序存储结构的元素之间都是紧邻的关系,没有间隙,自然无法快速的插入或者删除元素。 所以我们需要学习另外一种存储方式,那就是链式存储

2.简介

看下图

也就是这样,单链表为例

注:该图取于网上

3.对应操作

先以线性表单链表为主(每个结点只有1个指针域)
1.线性表链式结构初始化

/**
 * 结点
 *
 * 结点分为两部分,一部分存储数据,称为数据域,一部分存储下一个结点的位置,称为指针域
 * n个结点连成一条链式结构
 * */
public class Node<T> {

    T data;
    Node<T> next;

    public Node(T data) {
        this.data = data;
    }
}

/**
 * 线性表链式存储
 * */
public class MyLinkedList<T> {

    // 链表的头结点
    private Node<T> head = null;

    // 当前结点
    private Node<T> currentNode;
    
    // 链表长度
    private int size;
}

2.读取

(思路)

  1. 判断插入的位置是否真实有效
  2. 遍历链条找寻元素
  3. 将结果返回

(代码)

   /**
     * 根据index获取结点元素
     * */
    private Node<T> getNodeByIndex(int index){

        // 判断插入的位置是否真实有效
        if(index < 0 || index >= size){

            throw new IndexOutOfBoundsException("插入的位置无效");
        }

        // 通过next遍历链条
        currentNode = head;
        for (int n = 0; n < index && currentNode.next != null; n++){

            currentNode = currentNode.next;
        }
        return currentNode;
    }

   /**
     * 根据index获取数据
     * */
    public T getDataByIndex(int index){

        getNodeByIndex(index);
        return currentNode == null ? null : currentNode.data;
    }

3.插入

插入操作分三种情况

3.1 在头部添加元素
(思路)

  1. 将需添加的内容封装成结点
  2. 将新添加结点的指针域指向原第1个结点
  3. 将新添加结点设置为第1个结点
  4. 链表长度+1

(代码)

   /**
     * 在头部添加结点
     * */
    public void addDataByFirst(T data){

        // 封装结点
        Node<T> node = new Node (data);

        // 将原头结点添加到封装结点的指针域上
        node.next = head;

        // 将封装的结点设置成头结点
        head = node;

        // 链表长度+1
        size++;
    }

3.2 在尾部添加元素
(思路)

  1. 将数据封装成我们需要的结点
  2. 先判断链表是否为空,如果为空,添加到首结点
  3. 链表不为空,找寻到尾结点,将新建的结点,添加到尾结点的指针域上
  4. 链表长度+1

(代码)

   /**
     * 在尾部添加结点
     * */
    public void addDataByEnd(T data){

        // 封装结点
        Node<T> node = new Node<>(data);

        // 如果链表为空,则设置成头结点
        if(head == null){
            addDataByFirst(data);
            return;
        }

        // 如果链表不为空,则找寻尾结点,将封装的结点设置到尾结点的指针域上
        Node temp = head;
        while (temp.next != null){

            temp = temp.next;
        }
        temp.next = node;

        // 链表长度+1
        size++;
    }

3.3 在指定位置添加元素

(思路)

  1. 将要插入的元素封装成结点
  2. 判断要插入的位置是否有效
  3. 如果列表元素为空,则直接设置成头结点
  4. 找寻到要插入的位置,进行插入

(代码)

   /**
     * 在指定位置添加元素
     * */
    public void addDataByIndex(int index, T data){

        // 如果头结点是空
        if(head == null){

            addDataByFirst(data);
            return;
        }

        // 封装我们的结点
        Node<T> node = new Node<>(data);

        // 如果头结点不为空,则添加到指定位置
        getNodeByIndex(index);

        node.next = currentNode.next;
        currentNode.next = node;

        // 链表数量+1
        size++;
    }

4.删除

4.1 从头部删除元素

(思路)

  1. 判断链表是否还有值
  2. 将头结点的下一个元素指向当前位置
  3. 链表-1

(链表)

   /**
     * 删除
     * */
    public void deleteDataByFirst(){

        // 判断链表是否还有值
        if(head == null){

            throw new IndexOutOfBoundsException("链表为空");
        }

        // 将头结点的下一个元素指向当前位置
        head = head.next;

        // 链表-1
        size--;
    }

4.2 在指定位置删除元素

(思路)

  1. 检查当前链表是否为空
  2. 把当前指针(currentNode)定位到 需删除结点(index)的前1个结点
  3. 获取被删除结点的数据
  4. 将需删除结点(index)的前1个结点 的下1个结点 设置为 需删除结点(index)的下1个结点
  5. 链表长度-1

(代码)

   /**
     * 在指定位置删除结点
     * */
    public T deleteDataByIndex(int index){

        // 判断链表是否还有值
        if(head == null){

            throw new IndexOutOfBoundsException("链表为空");
        }

        // 把当前指针(currentNode)定位到 需删除结点(index)的前1个结点
        getNodeByIndex(index - 1);

        // 获取被删除结点的数据
        Node<T> deleteNode = currentNode.next;

        // 将需删除结点(index)的前1个结点 的下1个结点 设置为 需删除结点(index)的下1个结点
        currentNode.next = deleteNode.next;

        return deleteNode.data;
    }

4.性能比较

基本上单链表的用法都在上面了,接下来我们再分析下它和顺序存储的优缺点,我们从时间性能,存储方式,空间性能三点做比较

5.总结

资源代码下载

发布了30 篇原创文章 · 获赞 78 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/u010302765/article/details/90053412