1

定义:

线性表全名线性存储结构是最基本、最简单、也是最常用的一种数据结构,专门用于存储逻辑关系为"一对一"的数据。

特性:

  1. 数据类型一致
  2. 数据的逻辑关系都具有一对一关系
  3. 数据元素有限(专业术语为:存在唯一一个被称为“第一个”的数据元素,存在唯一一个被称为“最后一个”的数据元素)
  4. 是有序序列(专业术语为:除第一个元素外,每个元素都有唯一的一个”直接前驱“。除最后一个元素外,每一个元素都有唯一的”直接后继“)

存储方式:

线性表,基于数据在实际物理空间中的存储状态,又可细分为**顺序表(顺序存储结构)**和**链表(链式存储结构)**。

顺序表(顺序存储结构):

定义:

顺序表,一般使用数组实现。也就是在内存中找个初始地址,然后数据元素依次排队,数组大小有两种方式指定——静态分配和是动态扩展。

特点:

  1. 随机访问:可通过首地址和元素序号在单位时间O(1)内找到指定的元素。
  2. 存储密度高:存储密度高是因为每个结点存储空间指用来存储数据元素,没有别的额外开销。
  3. 物理位置相邻:物理位置和逻辑位置一样,保持相邻,因此插入和删除元素需要移动大量元素,比较耗时。这是物理结构相邻的所有数据结构的通病,虽然访问快,但是如果有频繁的增删移动操作,就会效率很低。

    优点:

  4. 空间利用率高(理由:连续存放)
  5. 存取速度高效,通过下标直接进行存储和读取。(想想排队哈)
  6. 顺序存储,随机读取(不算是优点,算是特点)

缺点:

  1. 插入和删除比较慢。(想想插队,后面的全部需要往后移一个位)
  2. 顺序表开始时就要定义一个长度,因此是存在空间限制的。当需要存储的元素个数可能多于顺序表元素时,可能出现“溢出”问题。

链式存储结构:

定义:

用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的),包括数据域和指针域,数据域存数据,指针域指示其后继的信息。

特点:

随机存储、顺序读取:物理位置和逻辑位置不同,物理位置随机存储,但都通过指针域连成线,可以按逻辑顺序读取

优点:

  1. 插入和删除的速度快,保留原有的物理顺序(只改变下指针。想想上述的插队问题就明白了)。
  2. 没有空间限制,基本只与内存空间大小有关。(满天飞)
  3. 随机存储,顺序读取(算是特点)

缺点:

  1. 查找元素比较慢(不能进行索引访问,只能从头结点开始顺序查找)
  2. 占用额外的空间用来存储指针(不是连续存放的(满天飞了解下),空间碎片多)
    造成空间浪费。

实操

创建线性表接口类

/**
 * 线性表接口
 * @author gxw
 * @date 2021/11/1 晚上21:50
 */
public interface LinearList {

    /**
     * 查找方法
     */

    //查找,是否包含g元素
    boolean contains(Object g);

    //查找,返回 g 元素所在位置
    int indexOf(Object g);

    //查找,返回第i位置的元素
    Object getByIndex(int i);

    /**
     * 添加方法
     */
    //将元素插入到第i个位置
    void add(int i, Object g) throws Exception ;

    //将元素插入到最后
    void add(Object g) throws Exception ;

    //将g元素插入到x元素之前
    boolean addBefore(Object x, Object g) throws Exception;

    //将g元素插入到x元素之后
    boolean addAfter(Object x, Object g) throws Exception;


    /**
     * 删除方法
     */

    //删除第i个元素
    void remove(int i) throws Exception ;

    //删除第一个为g的元素
    void remove(Object g) throws Exception ;

    /**
     * 修改方法
     */

    //将第i个元素替换为g
    boolean replace(int i, Object g);

    //将x元素全部替换为g
    boolean replaceAll(Object x, Object g);

    /**
     * 返回线性表大小
     */
    int getSize();

    /**
     * 判断线性表是否为空
     */
    boolean isEmpty();

    /**
     * 输出线性表
     */
    void display();

}

顺序表:

第一步:创建顺序表实现类

/**
 * 线性表之顺序表(顺序存储结构)
 * @author gxw
 * @date 2021/11/1 晚上21:51
 */
public class SequenceList implements LinearList {
    //声明大小为maxSize的顺序表
    private Object[] myList;//顺序表的存储空间
    private int size;//顺序表当前的长度
    private int maxSize;//顺序表最大的长度

    //构造方法来初始化顺序表
    public SequenceList(int maxSizeParam){
        //设置顺序表的初始长度为0
        size = 0;
        //设置顺序表的最大长度
        maxSize = maxSizeParam;
        //实例化顺序表
        myList = new Object[maxSize];
    }

    //是否包含g元素
    @Override
    public boolean contains(Object g) {//时间复杂度O(n)
        for (Object x : myList) {
            if(x.equals(g))
                return true;
        }
        return false;
    }

    //获取g元素在第几个位置
    @Override
    public int indexOf(Object g) {//时间复杂度O(n)
        for(int i = 0;i < size;i++){
            if(myList[i].equals(g))
                return i;
        }
        return -1;
    }

    //获取第i位置的元素
    @Override
    public Object getByIndex(int i) {//时间复杂度O(1)
        //判断是否符合现有长度
        if(i < 0 || i > size)
            return null;
        return myList[i];
    }

    @Override
    public void add(int i, Object g) throws Exception {//时间复杂度O(n)
        //判断顺序表是否已经满员
        if(size == maxSize)
            throw new Exception("顺序表已满员,不可插入!");//手动抛出异常

        //判断i是否在正确范围内
        if(i < 0 || i > size)
            throw new Exception("插入位置不合理,不可插入!");//手动抛出异常

        //准备执行插入,在插入之前,将要插入位置的元素往后移
        for(int j = size;j > i;j--){
            myList[j] = myList[j - 1];
        }
        //执行插入
        myList[i] = g;
        //现有顺序表长度增加1
        size++;
    }

    @Override
    public void add(Object g) throws Exception {//时间复杂度O(1)
        //判断顺序表是否已经满员
        if(size == maxSize)
            throw new Exception("顺序表已满员,不可插入!");//手动抛出异常
        //执行插入,插入到最后
        myList[size] = g;
        //现有长度加一
        size++;
    }

    @Override
    public boolean addBefore(Object x, Object g) throws Exception {//时间复杂度O(n)
        //判断顺序表是否已经满员
        if(size == maxSize)
            return false;

        //先获取x元素所在位置
        int index = indexOf(x);

        if(index < 0)
            return false;

        //将x元素以及后继元素往后移
        add(index, g);
        return true;
    }

    @Override
    public boolean addAfter(Object x, Object g) throws Exception {//时间复杂度O(n)
        //判断顺序表是否已经满员
        if(size == maxSize)
            return false;

        //先获取x元素所在位置
        int index = indexOf(x);

        if (index < 0)
            return false;
        //将x元素的后继元素往后移
        add(index + 1, g);
        return true;
    }

    @Override
    public void remove(int i) throws Exception {//时间复杂度O(n)
        //判断顺序表是否已经为空
        if(size == 0)
            throw new Exception("顺序表已空,无法删除!");//手动抛出异常

        //判断i元素是否在0-size范围内
        if(i < 0 || i > size)
            throw new Exception("角标超出范围,无法删除!");//手动抛出异常

        //将i元素之后后继元素往前移
        for(int j = i;j < size;j++){
            myList[j] = myList[j + 1];
        }
        //顺序表长度减一
        size--;
    }

    @Override
    public void remove(Object g) throws Exception {//时间复杂度O(n)
        //判断顺序表是否已经为空
        if(size == 0)
            throw new Exception("顺序表已空,无法删除!");//手动抛出异常
        //查找元素的位置
        int index = indexOf(g);

        if(index >= 0){
            //删除元素,并将后继元素往前移
            remove(index);
        }else{
            throw new Exception("顺序表无此元素,无法删除!");//手动抛出异常
        }

    }

    @Override
    public boolean replace(int i, Object g) {//时间复杂度O(1)
        //判断顺序表是否已经为空
        if(size == 0)
            return false;

        //判断i是否在0-size范围内
        if(i < 0 || i > size)
            return false;

        //执行替换,将i元素的值替换为g
        myList[i] = g;
        return true;
    }

    @Override
    public boolean replaceAll(Object x, Object g) {//时间复杂度O(n)
        //判断顺序表是否已经为空
        if(size == 0)
            return false;
        //暂存角标字符串
        String indexStr = "";
        //取出元素为x的所有角标
        for (int i = 0;i < size;i++){
            if(myList[i].equals(x)){
                indexStr += i + ",";
            }
        }
        indexStr = indexStr.substring(0, indexStr.length() - 1);
        String[] indexArr = indexStr.split(",");
        //将取出的所有角标全替换为g元素
        for (int i = 0;i < indexStr.split(",").length;i++){
            int index = Integer.parseInt(indexArr[i]);
            //设置值
            myList[index] = g;
        }
        return true;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void display() {
        for (int i = 0;i < size;i++){
            System.out.print(myList[i] + " ");
        }
        System.out.println("展示顺序表结束:一共长度为:" + size);
    }
}

第二步:测试

public static void main(String[] args) throws Exception {
        /**
         * 顺序表
         */
        SequenceList sequenceList = new SequenceList(10);
        //添加
        sequenceList.add("sequence ");
        sequenceList.add("hello ");
        sequenceList.add("world!");
        sequenceList.add("hello ");
        sequenceList.display();
        //指定角标添加
        sequenceList.add(1, "哈哈 ");
        sequenceList.display();
        //在指定元素之前添加
        sequenceList.addBefore("哈哈 ", "呼呼 ");
        sequenceList.display();
        //在指定元素之后添加
        sequenceList.addAfter("哈哈 ", "嘿嘿 ");
        sequenceList.display();
        //替换
        sequenceList.replace(1, "略略 ");
        sequenceList.display();
        //将指定元素值全都替换成设置元素值
        sequenceList.replaceAll("hello ", "HELLO ");
        sequenceList.display();
        //删除
        sequenceList.remove(1);
        sequenceList.display();
        //指定元素删除
        sequenceList.remove("HELLO ");
        sequenceList.display();
    }

结果:
image.png

链表:

第一步:因为单链表依赖于Node,所以创建Node类

/**
 * 单链表采用了结点,所以需要以下定义
 * 主要两个属性,一个是数据域(存放数据内容),一个是指针域(存放下一个的结点的地址)
 */
public class Node {
    //数据域
    private Object data;
    //指针域
    private Node next;

    public Node(){
        this.data = new Object();
        this.next = null;
    }

    public Node(Object data, Node next){
        this.data = data;
        this.next = next;
    }

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

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Node getNext() {
        return next;
    }

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

第二步:创建单链表实现类

/**
 * 线性表之链表(链式存储结构)
 * @author gxw
 * @date 2021/11/1 晚上21:53
 */
public class LinkedList implements LinearList {
    private Node headNode = new Node();//定义一个头空结点
    private int size;//定义一共有几个结点

    @Override
    public boolean contains(Object g) {
        Node node = headNode;//从头结点开始
        for(int i = 0;i < size;i++){
            if(node.getData().equals(g)){
                return true;
            }
            //访问下一个结点
            node = node.getNext();
        }
        return false;
    }

    @Override
    public int indexOf(Object g) {
        Node node = headNode;//从头结点开始
        //因为头结点不存数据,所以直接获取子结点数据
        node = node.getNext();
        for(int i = 0;i < size;i++){
            if(node.getData().equals(g)){
                return i;
            }
            //访问下一个结点
            node = node.getNext();
        }
        return -1;
    }

    @Override
    public Object getByIndex(int i) {
        Node node = headNode;//从头结点开始
        for(int j = 0;j < i;j++){
            //访问下一个结点,直到访问的第i个位置
            node = node.getNext();
        }
        return node.getData();
    }

    @Override
    public void add(int i, Object g) throws Exception {
        //如果i不在0-size范围
        if(i < 0 || i > size)
            throw new Exception("角标超出正常范围 i:" + i);

        //获取到第i个前面的结点,只需要让前面的结点指针域指到新要添加的结点上,
        // 自己的指针域指到第i个后面的结点上,就可以实现添加了
        Node node = headNode;
        for(int j = 0;j < i;j++){
            node = node.getNext();//持续访问指针域,直到访问到第i个结点
        }
        //创建新要添加的结点
        Node newNode = new Node();
        //设置新结点的数据域
        newNode.setData(g);
        //设置新结点的指针域,将前面获取到的结点的指针域的值,赋值给新要添加的结点
        newNode.setNext(node.getNext());
        //然后让前面获得的结点的指针域指向新添加的指针,即可成功添加
        node.setNext(newNode);
        //结点数加一
        size++;
    }

    @Override
    public void add(Object g) throws Exception {
        //在最后添加结点
        add(size, g);
    }

    @Override
    public boolean addBefore(Object x, Object g) throws Exception {
        //获取结点所在位置
        int index = indexOf(x);

        //如果没有匹配的结点
        if(index < 0)
            return false;

        //在x结点前添加结点,相当于就是查找到结点位置当前位置
        add(index, g);
        return true;
    }

    @Override
    public boolean addAfter(Object x, Object g) throws Exception {
        //获取结点所在位置
        int index = indexOf(x);

        //如果没有匹配的结点
        if(index < 0)
            return false;

        //在x结点后添加结点
        add(index + 1, g);
        return true;
    }

    @Override
    public void remove(int i) throws Exception {
        //如果i不在0-size范围
        if(i < 0 || i > size)
            throw new Exception("角标超出正常范围 i:" + i);

        //删除结点,原理就是让此结点的前一个结点的指针域,指到此结点的下一个结点
        Node node = headNode;
        for (int j = 0;j < i;j++){
            node = node.getNext();
        }
        //将前一个结点的指针域,指到此结点的下一个结点
        node.setNext(node.getNext().getNext());
        //结点数减1
        size--;
    }

    @Override
    public void remove(Object g) throws Exception {
        //查找元素所在位置
        int index = indexOf(g);

        //执行删除
        remove(index);
    }

    @Override
    public boolean replace(int i, Object g) {
        //如果i不在0-size范围
        if(i < 0 || i > size)
            return false;

        //找到第i个结点
        Node node = headNode;
        for (int j = 0;j < i + 1;j++){ //因为头结点是空结点,所以需要略过,就i + 1
            node = node.getNext();
        }
        //然后将第i个结点的数据域更换为g
        node.setData(g);

        return true;
    }

    @Override
    public boolean replaceAll(Object x, Object g) {
        return false;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void display() {
        Node node = headNode;
        for (int i = 0;i < size;i++){
            node = node.getNext();
            System.out.print(node.getData() + " ");
        }
        System.out.println("展示完成,结点长度为:" + size);
    }
}

第三步:测试

public static void main(String[] args) throws Exception {
      
        /**
         * 单链表
         */
        LinkedList linkedList = new LinkedList();
        //添加
        linkedList.add("1");
        linkedList.add("2");
        linkedList.add("3");
        linkedList.display();

        //在2之前添加
        linkedList.addBefore("2", "4");
        linkedList.display();

        //在2之后添加
        linkedList.addAfter("2", "5");
        linkedList.display();

        //查找位置
        System.out.println(linkedList.indexOf("1"));
        System.out.println(linkedList.indexOf("2"));
        System.out.println(linkedList.indexOf("3"));

        //替换
        linkedList.replace(1, "22 ");
        linkedList.display();

        //删除
        linkedList.remove("3");
        linkedList.display();


    }

结果:
image.png

其它补充:

前驱:

数据结构中,一组数据中的每个个体被称为“数据元素”(简称“元素”)。

直接前驱:

某一元素的左侧相邻元素称为“直接前驱”

前驱元素:

位于此元素左侧的所有元素都统称为“前驱元素”

后继

直接后继:

某一元素的右侧相邻元素称为“直接后继”

后继元素:

位于此元素右侧的所有元素都统称为“后继元素”

image.png

————————————END—————————————
数据结构之线性表篇,在此结束,学习中的总结,希望可以帮助认真学习的你,如果总结有问题,也希望贵人忘请指正,谢谢!




稳之楠
130 声望25 粉丝

行之稳,为之楠!