大话数据结构-线性表

news2024/11/26 14:03:46

1 定义

  线性表是零个或多个数据元素的有限序列。

2 抽象数据类型

ADT 线性表(List)
    Data:线性表的数据对象集合为{al,a2,a3,....an},每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个后继元素。数据元素之间的关系是一对一的关系。
    Operation:
        init(int maxSize):初始化操作,建立一个空的线性表;
        isEmpty():若线性表为空,返回true,否则返回false;
        clear():清空线性表;
        getElement(int position):获取线性表中第position个元素值;
        locateElement(DataType e):在线性表中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号,否则返回0表示失败;
        insert(DataType e, int position):在线性表的第position个位置插入元素e;
        delete(int position):删除纯属表中第position个位置的元素;
        length():返回线性表的元素个数;
endADT

3 顺序存储结构

3.1 概述

image.png

  线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

  线性表的顺序存储结构的抽象数据类型在标准类型的基础上,新增了length和maxSize两个属性:

ADT 线性表(List)
    Data:线性表的数据对象集合为{al,a2,a3,....an},每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个后继元素。数据元素之间的关系是一对一的关系。
    length:线性表中实际元素个数,应小于等于maxSize。
    maxSize:线性表可以存储的元素个数。
    Operation:
        init(int maxSize):初始化操作,建立一个空的线性表;
        isEmpty():若线性表为空,返回true,否则返回false;
        clear():清空线性表;
        getElement(int position):获取线性表中第position个元素值;
        locateElement(DataType e):在线性表中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号,否则返回0表示失败;
        insert(DataType e, int position):在线性表的第position个位置插入元素e;
        delete(int position):删除纯属表中第position个位置的元素;
        length():返回线性表的元素个数;
endADT

3.2 初始化

  定义一个空的线性表,初始化maxSize和data数组:

/**
 * 初始化线性表
 *
 * @param maxSize 线性表最大长度
 * @author Korbin
 * @date 2023-01-04 18:29:17
 **/
public void init(int maxSize) {
    this.maxSize = maxSize;
    data = new Object[maxSize];
}

3.3 判断是否为空

  判断length是否为0即可,为避免其他异常,使用小于等于,如下:

/**
 * 判断线性表是否为空
 *
 * @return 线性表为空返回true,非空返回false
 * @author Korbin
 * @date 2023-01-11 15:54:53
 **/
public boolean isEmpty() {
    return length <= 0;
}

3.4 清空线性表

  把length和data重新初始化即可(C语言则需要回收内存):

/**
 * 清空线性表
 *
 * @author Korbin
 * @date 2023-01-11 15:58:15
 **/
public void clear() {
    this.length = 0;
    this.data = new Object[this.maxSize];
}

3.5 获取指定位置元素

  线性表长度为0或要获取的位置小于1或大于线性表长度时抛出异常,实际位置是索引加1:

/**
 * 获取线性表中的第position个元素
 *
 * @param position 位置
 * @return 获取到的元素
 * @throws RuntimeException 线性表长度为0或要获取的位置小于1或大于线性表长度时抛出异常
 * @author Korbin
 * @date 2023-01-04 18:32:10
 **/
@SuppressWarnings("unchecked")
public T getElement(int position) {

    if (position < 1 || position > length) {
        throw new RuntimeException("error position");
    } else {
        return (T) (data[position - 1]);
    }
}

  时间复杂度为O(1)。

3.6 在线性表中查找与给定值e相等的元素

  使用equals判断是否相等,因此DataType需要实现equals方法:

/**
 * 查找element在线性表中的位置
 *
 * @param element 待查找的元素
 * @return 元素在线性表中的位置
 * @author Korbin
 * @date 2023-01-11 15:59:23
 **/
@SuppressWarnings("unchecked")
public int locateElement(T element) {

    int position = -1;
    for (int i = 0; i < length; i++) {
        T e = (T) (data[i]);
        if (e.equals(element)) {
            position = i;
            break;
        }
    }
    return position + 1;
}

  时间复杂度为O(n)。

3.7 插入操作

image.png
  线性表已满,或位置异常时抛出异常,插入时,其后的所有元素需要往后移,实际元素个数要加1:

/**
 * 在线性表中的指定位置position插入一个元素
 *
 * @param position 指定位置
 * @param element  要插入的元素
 * @throws RuntimeException 线性表已满,或位置异常时抛出异常
 * @author Korbin
 * @date 2023-01-04 19:22:34
 **/
public void insert(int position, T element) {
    if (length == maxSize) {
        throw new RuntimeException("already full");
    }

    if (position < 1 || position > length + 1) {
        throw new RuntimeException("error position");
    }

    if (position <= length) {
        for (int k = length - 1; k >= position - 1; k--) {
            data[k + 1] = data[k];
        }
    }
    data[position - 1] = element;
    length++;
}

  时间复杂度为O(n)。

3.8 删除操作

image.png
  线性表为空或位置不对时抛出异常,实际元素个数要减1:

/**
 * 删除线性表中第position位的元素
 *
 * @param position 位置
 * @throws RuntimeException 线性表为空或位置不对时抛出异常
 * @author Korbin
 * @date 2023-01-05 09:46:58
 **/
public void delete(int position) {
    if (length == 0) {
        throw new RuntimeException("empty list");
    }

    if (position < 1 || position > length) {
        throw new RuntimeException("error position");
    }

    if (position < length) {
        for (int k = position; k < length; k++) {
            data[k - 1] = data[k];
        }
    }
    // 如果要删除的位置就是线性表的长度所在的位置的话,那直接把长度减1就行了
    length--;
}

  时间复杂度为O(n)。

3.9 返回实际元素个数

/**
 * 获取线性表真实长度
 *
 * @return 线性表长度
 * @author Korbin
 * @date 2023-01-05 10:15:41
 **/
public int length() {
    return length;
}

3.10 完整代码

/**
 * 顺序存储线性表
 *
 * @author Korbin
 * @date 2023-01-04 18:19:38
 **/
public class SequentialList<T> {

    /**
     * 线性表数据
     **/
    private Object[] data;

    /**
     * 线性表当前长度
     **/
    private int length;

    /**
     * 线性表最大长度
     */
    private int maxSize = 20;

    /**
     * 清空线性表
     *
     * @author Korbin
     * @date 2023-01-11 15:58:15
     **/
    public void clear() {
        this.length = 0;
        this.data = new Object[this.maxSize];
    }

    /**
     * 删除线性表中第position位的元素
     *
     * @param position 位置
     * @throws RuntimeException 线性表为空或位置不对时抛出异常
     * @author Korbin
     * @date 2023-01-05 09:46:58
     **/
    public void delete(int position) {
        if (length == 0) {
            throw new RuntimeException("empty list");
        }

        if (position < 1 || position > length) {
            throw new RuntimeException("error position");
        }

        if (position < length) {
            for (int k = position; k < length; k++) {
                data[k - 1] = data[k];
            }
        }
        // 如果要删除的位置就是线性表的长度所在的位置的话,那直接把长度减1就行了
        length--;
    }

    /**
     * 获取线性表中的第position个元素
     *
     * @param position 位置
     * @return 获取到的元素
     * @throws RuntimeException 线性表长度为0或要获取的位置小于1或大于线性表长度时抛出异常
     * @author Korbin
     * @date 2023-01-04 18:32:10
     **/
    @SuppressWarnings("unchecked")
    public T getElement(int position) {

        if (position < 1 || position > length) {
            throw new RuntimeException("error position");
        } else {
            return (T) (data[position - 1]);
        }
    }

    /**
     * 初始化线性表
     *
     * @param maxSize 线性表最大长度
     * @author Korbin
     * @date 2023-01-04 18:29:17
     **/
    public void init(int maxSize) {
        this.maxSize = maxSize;
        data = new Object[maxSize];
    }

    /**
     * 在线性表中的指定位置position插入一个元素
     *
     * @param position 指定位置
     * @param element  要插入的元素
     * @throws RuntimeException 线性表已满,或位置异常时抛出异常
     * @author Korbin
     * @date 2023-01-04 19:22:34
     **/
    public void insert(int position, T element) {
        if (length == maxSize) {
            throw new RuntimeException("already full");
        }

        if (position < 1 || position > length + 1) {
            throw new RuntimeException("error position");
        }

        if (position <= length) {
            for (int k = length - 1; k >= position - 1; k--) {
                data[k + 1] = data[k];
            }
        }
        data[position - 1] = element;
        length++;
    }

    /**
     * 判断线性表是否为空
     *
     * @return 线性表为空返回true,非空返回false
     * @author Korbin
     * @date 2023-01-11 15:54:53
     **/
    public boolean isEmpty() {
        return length <= 0;
    }

    /**
     * 获取线性表真实长度
     *
     * @return 线性表长度
     * @author Korbin
     * @date 2023-01-05 10:15:41
     **/
    public int length() {
        return length;
    }

    /**
     * 查找element在线性表中的位置
     *
     * @param element 待查找的元素
     * @return 元素在线性表中的位置
     * @author Korbin
     * @date 2023-01-11 15:59:23
     **/
    @SuppressWarnings("unchecked")
    public int locateElement(T element) {

        int position = -1;
        for (int i = 0; i < length; i++) {
            T e = (T) (data[i]);
            if (e.equals(element)) {
                position = i;
                break;
            }
        }
        return position + 1;
    }

    /**
     * 打印线性表
     *
     * @throws RuntimeException 线性表为空时抛出异常
     * @author Korbin
     * @date 2023-01-05 09:55:55
     **/
    @Override
    public String toString() {
        if (length == 0) {
            throw new RuntimeException("empty list");
        }
        StringBuilder result = new StringBuilder("[");
        for (int i = 0; i < length; i++) {
            result.append(i + 1).append(":").append(data[i]).append(",");
        }
        result.append("]");
        return result.toString();
    }

}

3.11 优缺点

image.png

4 链式存储结构

4.1 概述

  用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的,只是通过数据元素中的后继元素地址来指向后继元素的存储地址,即称为链式存储。

  在链式存储结构中,每个数据元素除了存储其本身的信息外,还需存储一个指示其直接后继的信息,即直接后继的存储位置,存储数据元素信息的域称为数据域,存储直接后继位置的域称为指针域,指针域中存储的信息称做指针或链。数据域和指针域组成数据元素的存储映像,称为结点。

  因为每个结点中只包含一个指针域,因此这种链表也被称为单链表。

image.png

  单链表的第一个结点称为头结点,头结点不存储数据,只存储链表中第一个实际元素的存储位置,称为头指针。

  单链表的最后一个结点没有后继元素,因此其指针域的值是Null。

image.png

  单链表的抽象存储结构与线性表的顺序存储结构的抽象存储结构略有区别:

ADT 线性表(List)
    Data:线性表的数据对象集合为{al,a2,a3,....an},每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个后继元素。数据元素之间的关系是一对一的关系。单链表中只存储头结点对象即可。
    length:线性表中实际元素个数。
    Operation:
        init:初始化操作,建立一个空的线性表;
        isEmpty:若线性表为空,返回true,否则返回false;
        clear:清空线性表;
        getElement(int position):获取线性表中第position个元素值;
        locateElement(DataType e):在线性表中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号,否则返回0表示失败;
        insert(DataType e, int position):在线性表的第position个位置插入元素e;
        delete(int position):删除纯属表中第position个位置的元素;
        length():返回线性表的元素个数;
endADT

4.2 结点结构

DataType需要进行特殊定义,必须包含后继元素指针,如下:

import lombok.Data;

import java.util.UUID;

/**
 * 单链表
 *
 * @author Korbin
 * @date 2023-01-05 10:58:05
 **/
@Data
public class LinkListNode<T> {

    /**
     * 数据
     **/
    private T data;

    /**
     * 设置一个ID,已进行equals判断
     **/
    private String id = UUID.randomUUID().toString();

    /**
     * 后继结点
     **/
    private LinkListNode<T> next;

    /**
     * 构造子
     *
     * @author Korbin
     * @date 2023-01-05 12:38:08
     **/
    public LinkListNode() {

    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean equals(Object obj) {
        if (obj instanceof LinkListNode) {
            try {
                LinkListNode<T> node = (LinkListNode<T>) obj;
                return data.equals(node.getData()) && id.equals(node.getId());
            } catch (ClassCastException e) {
                return false;
            }
        } else {
            return false;
        }

    }

    @Override
    public String toString() {
        return "{id=" + id + ", data=" + data.toString() + "}";
    }

}

  构造子、equals和toString方法可以按需,添加一个id的主要目的是用于equals,如有其他判断相等的方式,也可以不加id。

4.3 初始化和清空

  初始化和清除在Java中因为垃圾回收机制,因此实现方式相同。

  单链表定义的Data,不需要是一个数组,只需要放置headNode,即头结点即可,因此初始化和清空时只需要将length置为0,将headNode的值和后继结点置为空即可。

/**
 * 清空
 * <p>
 * 直接调用初始化方法即可
 *
 * @author Korbin
 * @date 2023-01-11 15:58:15
 **/
public void clear() {
    init();
}
/**
 * 初始化
 *
 * @author Korbin
 * @date 2023-01-04 18:29:17
 **/
public void init() {
    headNode = new LinkListNode<>();
    headNode.setData(null);
    headNode.setNext(null);
    length = 0;
}

4.4 获取指定位置的元素

  从头结点开始,迭代后继结点:

/**
 * 获取单链表中第position位的元素的值
 *
 * @param position 要获取的结点所在的位置
 * @return 指定位置结点的值
 * @throws RuntimeException 未获取到结点时抛出
 * @author Korbin
 * @date 2023-01-05 11:06:01
 **/
public LinkListNode<T> getElement(int position) {

    if (position == 0) {
        return headNode;
    }

    LinkListNode<T> result = headNode.getNext();

    int j = 1;

    while (null != result && j < position) {
        result = result.getNext();
        j++;
    }
    if (null == result || j > position) {
        throw new RuntimeException("node not exists");
    }

    return result;
}

  时间复杂度为O(n)。

4.5 插入操作

image.png
  先获取待插入位置的前驱结点,然后让前驱结点指向新结点,新结点指向原结点即可:

/**
 * 在单链表的指定位置插入一个结点
 *
 * @param position 要插入的位置
 * @param value    要插入的结点的值
 * @throws RuntimeException 要插入的位置不存在结点时抛出
 * @author Korbin
 * @date 2023-01-05 11:12:05
 **/
public void insert(int position, T value) {
    LinkListNode<T> positionNode = getElement(position - 1);

    LinkListNode<T> newNode = new LinkListNode<>();
    newNode.setData(value);
    newNode.setNext(positionNode.getNext());

    positionNode.setNext(newNode);

    length++;

}

  时间复杂度为O(n)。

4.6 删除操作

image.png
  先获取待删除结点的前驱结点,然后让前驱结点指向其后继的后继即可:

/**
 * 删除单链表中指定位置的元素
 *
 * @param position 要删除结点所在位置
 * @throws RuntimeException 要删除的位置不存在结点时抛出异常
 * @author Korbin
 * @date 2023-01-05 11:15:21
 **/
public void delete(int position) {
    LinkListNode<T> lastNode = getElement(position - 1);
    lastNode.setNext(lastNode.getNext().getNext());
    length--;
}

  删除操作的时间复杂度为O(n)。

4.7 查找与给定值e相等的元素

  从头结点开始迭代后继结点,直到没有后续结点或者找到的结点等于给定的元素:

/**
 * 查找element在单链表中的位置
 *
 * @param element 待查找的元素
 * @return 元素在单链表中的位置
 * @author Korbin
 * @date 2023-01-11 15:59:23
 **/
public int locateElement(LinkListNode<T> element) {

    LinkListNode<T> result = headNode.getNext();

    int j = 1;

    while (null != result) {
        if (result.equals(element)) {
            break;
        }
        result = result.getNext();
        j++;
    }
    if (null == result) {
        throw new RuntimeException("node not exists");
    }
    return j;
}

4.8 完整代码

/**
 * 单链表
 *
 * @author Korbin
 * @date 2023-01-05 16:19:13
 **/
public class LinkList<T> {

    /**
     * 头结点
     **/
    private LinkListNode<T> headNode;

    /**
     * 结点数量
     **/
    private int length;

    /**
     * 清空
     * <p>
     * 直接调用初始化方法即可
     *
     * @author Korbin
     * @date 2023-01-11 15:58:15
     **/
    public void clear() {
        init();
    }

    /**
     * 删除单链表中指定位置的元素
     *
     * @param position 要删除结点所在位置
     * @throws RuntimeException 要删除的位置不存在结点时抛出异常
     * @author Korbin
     * @date 2023-01-05 11:15:21
     **/
    public void delete(int position) {
        LinkListNode<T> lastNode = getElement(position - 1);
        lastNode.setNext(lastNode.getNext().getNext());
        length--;

    }

    /**
     * 获取单链表中第position位的元素的值
     *
     * @param position 要获取的结点所在的位置
     * @return 指定位置结点的值
     * @throws RuntimeException 未获取到结点时抛出
     * @author Korbin
     * @date 2023-01-05 11:06:01
     **/
    public LinkListNode<T> getElement(int position) {

        if (position == 0) {
            return headNode;
        }

        LinkListNode<T> result = headNode.getNext();

        int j = 1;

        while (null != result && j < position) {
            result = result.getNext();
            j++;
        }
        if (null == result || j > position) {
            throw new RuntimeException("node not exists");
        }

        return result;
    }

    /**
     * 初始化
     *
     * @author Korbin
     * @date 2023-01-04 18:29:17
     **/
    public void init() {
        headNode = new LinkListNode<>();
        headNode.setData(null);
        headNode.setNext(null);
        length = 0;
    }

    /**
     * 在单链表的指定位置插入一个结点
     *
     * @param position 要插入的位置
     * @param value    要插入的结点的值
     * @throws RuntimeException 要插入的位置不存在结点时抛出
     * @author Korbin
     * @date 2023-01-05 11:12:05
     **/
    public void insert(int position, T value) {
        LinkListNode<T> positionNode = getElement(position - 1);

        LinkListNode<T> newNode = new LinkListNode<>();
        newNode.setData(value);
        newNode.setNext(positionNode.getNext());

        positionNode.setNext(newNode);

        length++;

    }

    /**
     * 判断是否为空
     *
     * @return 为空返回true,非空返回false
     * @author Korbin
     * @date 2023-01-11 15:54:53
     **/
    public boolean isEmpty() {
        return null == headNode.getNext() && length == 0;
    }

    /**
     * 获取真实长度
     *
     * @return 长度
     * @author Korbin
     * @date 2023-01-05 10:15:41
     **/
    public int length() {
        return length;
    }

    /**
     * 查找element在单链表中的位置
     *
     * @param element 待查找的元素
     * @return 元素在单链表中的位置
     * @author Korbin
     * @date 2023-01-11 15:59:23
     **/
    public int locateElement(LinkListNode<T> element) {

        LinkListNode<T> result = headNode.getNext();

        int j = 1;

        while (null != result) {
            if (result.equals(element)) {
                break;
            }
            result = result.getNext();
            j++;
        }
        if (null == result) {
            throw new RuntimeException("node not exists");
        }
        return j;
    }

    @Override
    public String toString() {

        LinkListNode<T> next = headNode.getNext();
        StringBuilder builder = new StringBuilder("[");
        while (null != next) {
            builder.append(next).append(",");
            next = next.getNext();
        }
        builder.append("length=").append(length);
        builder.append("]");

        return builder.toString();
    }

}

4.9 单链表结构与顺序存储结构优缺点

image.png

  因此,通常情况下:

  (1) 需要频繁查找,很少进行插入和删除时,建议使用顺序存储结构;

  (2) 需要频繁插入和删除时,建议使用单链表结构,实际上,如果需要批量插入或删除时,单链表的优势才能体现出来,即,如果需要插入/删除指定位置的10个元素时,单链表只需要迭代一次找到待插入/删除位置的元素,然后就可以直接对10个元素进行操作了,而顺序存储结构则需要将待插入/删除元素之后的元素移动;

  (3) 元素个数变化较大或根本不知道多大时,建议使用单链表,否则使用顺序存储结构;

5 静态链表

5.1 概述

  在一些没有指针也没有引用的语言中,用数组来代替指针,来描述的单链表,称为静态链表。

  在静态链表中,每一个结点由两个数据域组成,data用于存储数据,cursor用于存储后继结点的指针(数组的索引),使用cursor代替单链表中的后继结点。

  由于静态链表中存储的数据是一个数组,因此静态链表也会定义length和maxSize,分别用于表示结点的实际数量和最多可存放的结点数量。

  数组的所有元素并不都存储着数据(结点),未存储数据的元素被称为备用链表,而数组的第一个元素,即下标为0的元素存储的结点不包含数据,只包含cursor,cursor存放着备用链表的第一个结点的下标,即下一个可存储数据的数组下标。

  数组的最后一个元素存储的结点也不存储数据,只包含cursor,cursor存放着第一个结点的下标,即头结点的下标,因此数组的最后一个元素起着头指针的作用。

  因此,静态链表初始化状态会如下所示:

image.png

  下标当然是从0、1、2一直到999,而cursor比较特殊:

  (1) 假设数组为array,那么array[0]的cursor值指向下一个可存储结点的元素的下标,因此应为1;

  (2) array[1]还没有存储结点,那我们让它的cursor指向下一个结点,即array[2],因此array[1]的cursor为2,依此类推;

  (3) 最后一个节点,array[999],它应该指向第一个结点的下标,此时还没有任何一个结点,因此array[999]的cursor应为0;

  此时,假设静态链表中依次存储了甲、乙、丁、戊、己、庚,则其结构应如下所示:

image.png

  (1) array[0],不存储数据,存储的是下一个可存储数据的下标,如图,下一个可存储的下标是7,因此array[0]的cursor为7;

  (2) array[1],存储第1个结点甲,指向第二个结点乙,乙是array[2],因此array[1]的cursor为array[2]的下标,即2,依此类推;

  (3) array[6],此时静态链表中的最后一个结点,存储的数据是庚,因其没有后继结点,因此令其cursor为0;

  (4) array[999],作为数组的最后一个元素,其存储的是静态链表中第一个结点的下标,我们知道,静态链表中的第一个结点是甲,存储在array[1],下标是1,因此array[999]的cursor是1;

5.2 结点定义

  与单链表相同,添加一个属性id用于判断结点是否相等:

import lombok.Data;

import java.util.UUID;

/**
 * 静态链表节点
 *
 * @author Korbin
 * @date 2023-01-05 16:25:49
 **/
@Data
public class StaticLinkNode<T> {

    /**
     * 游标,指向下一个节点,为0时表示无指向
     **/
    private int cursor;

    /**
     * 节点值
     **/
    private T data;

    /**
     * 设置一个ID,已进行equals判断
     **/
    private String id = UUID.randomUUID().toString();

    @Override
    @SuppressWarnings("unchecked")
    public boolean equals(Object obj) {
        if (obj instanceof StaticLinkNode) {
            try {
                StaticLinkNode<T> node = (StaticLinkNode<T>) obj;
                return data.equals(node.getData()) && id.equals(node.getId());
            } catch (ClassCastException e) {
                return false;
            }
        } else {
            return false;
        }

    }

}

5.3 初始化和清除

  上文有提到初始化过程,即初始化maxSize、data,并设置数组中每个元素的cursor默认值,而清除静态链表和初始化的逻辑是一致的:

/**
 * 判断是否为空
 *
 * @return 为空返回true,非空返回false
 * @author Korbin
 * @date 2023-01-11 15:54:53
 **/
public boolean isEmpty() {
    return length == 0;
}
/**
 * 初始化静态链表
 *
 * @param maxSize 链表的最大长度
 * @throws RuntimeException 最大长度为0时抛出
 * @author Korbin
 * @date 2023-01-05 16:30:25
 **/
@SuppressWarnings("unchecked")
public void init(int maxSize) {
    if (maxSize < 1) {
        throw new RuntimeException("error maxSize");
    }
    this.maxSize = maxSize;

    data = new StaticLinkNode[maxSize];

    for (int i = 0; i < maxSize - 1; i++) {
        StaticLinkNode<T> node = new StaticLinkNode<>();
        node.setCursor(i + 1);
        data[i] = node;
    }

    StaticLinkNode<T> node = new StaticLinkNode<>();
    node.setCursor(0);
    data[maxSize - 1] = node;

    length = 0;
}

5.4 插入

  插入过程:

  (1) 先从array[0]中获取到下一个可存放数据的数组元素下标,设为nextIndex;

  (2) 然后找到待插入位置的上一个结点:先从array[length-1]中获取第1个节点下标,然后批第2个节点下标,然后找第3个节点下标,直到找到待插入位置position - 1个节点的下标,设找到的值为lastIndex;

  (3) 我们知道,array[0]存储中下一个可存放数据的数组元素下标,也知道array[nextIndex]存储着下下个可存放数据的数组元素下标y,因此如果array[nextIndex]被使用了,那么array[0]应存放的cursor值则为y,因此代码中需要对array[0]的cursor值进行修改;

  (4) 然后设置array[nextIndex]的数据域为待插入的结点,cursor则为待插入位置上一个结点的cursor,再把待插入位置上一个结点的cursor设置为nextIndex,即插入的结点下标,那么插入操作就完成了;

  (5) 最后注意,结点数量需要加1;

/**
 * 下一个可插入的索引位
 *
 * @return 下一个可插入的索引位
 * @author Korbin
 * @date 2023-01-05 20:20:14
 **/
public int getNextIndex() {
    return data[0].getCursor();
}
/**
 * 在指定位置插入
 *
 * @param position 位置
 * @param value    要插入的元素
 * @throws RuntimeException 插入到数组的0位,或者跳跃进行插入,或者数组长度满后插入时抛出异常
 * @author Korbin
 * @date 2023-01-05 19:32:06
 **/
public void insert(int position, T value) {

    // 数组的第0个元素存储着数组下一个可存放节点的位置,获取到这个位置
    int nextIndex = getNextIndex();

    if (position < 1 || position > length + 1) {
        // 插入位置不能小于1,也不能大于链表的实际长度+1
        // 可以为length+1的原因,是因为允许在最后面插入
        throw new RuntimeException("error position");
    }

    if (length == maxSize - 2) {
        // 如果实际长度等于最大长度时,表示链表已满,不能再插入
        throw new RuntimeException("fulled");
    }

    // 默认数组的最后一个元素存储的是第一个节点的下标

    int lastIndex = maxSize - 1;
    // 获取到待插入的节点的上一个节点的游标
    // 首先通过最后一个节点的游标,可以找到第一个节点
    // 通过第一个节点的游标,可以找到第二个节点
    // 依此类推
    for (int i = 1; i <= position - 1; i++) {
        lastIndex = data[lastIndex].getCursor();
    }

    // 设置第0个元素的游标值为待插入节点的游标值,初始化时有设置
    data[0].setCursor(data[nextIndex].getCursor());

    // 设置这个节点的节点值为待插入值
    data[nextIndex].setData(value);

    // 设置这个节点的游标为上一个节点的游标值
    data[nextIndex].setCursor(data[lastIndex].getCursor());
    // 设置上一个节点的游标值为这个节点的位置
    data[lastIndex].setCursor(nextIndex);

    ++length;
}

  时间复杂度为O(position-1),即O(n)。

5.5 删除

  (1) 先找到待删除结点的上一个结点,寻找方法与插入时一致,此时找到了lastNode;

  (2) 然后找到待删除结点,即lastNode.getCursor()对应的结点,此时找到了toDeleteNode;

  (3) 令lastNode指向toDeleteNode的下一个结点,删除操作就完成了;

  (4) toDeleteNode被删除了,那么它所在的数组元素,就可以存放新的结点了,这时,令toDeleteNode的cursor指向array[0]的cursor,令array[0]的cursor指向toDeleteNode的下标,即:让toDeleteNode所在的位置成为下一个可存储数据的下标,即备用链表的第一位,同时,把备用链表的其他可存储结点的下标依次往后挪一位;

  (5) 最后,注意结点数量应减1;

/**
 * 删除指定位置的节点
 *
 * @param position 待删除节点位置
 * @throws RuntimeException 删除第0个节点或者要删除的位置超出节点实际数量时抛出异常
 * @author Korbin
 * @date 2023-01-05 19:34:18
 **/
public void delete(int position) {

    // 不得删除第0个元素
    // 不得删除超出实际节点数量的元素
    if (position == 0 || position > length()) {
        throw new RuntimeException("error position");
    }

    // 找到要删除的节点的上一个节点
    int lastIndex = data[maxSize - 1].getCursor();

    int lastIndex = maxSize - 1;
    // 获取到待插入的节点的上一个节点的游标
    // 首先通过最后一个节点的游标,可以找到第一个节点
    // 通过第一个节点的游标,可以找到第二个节点
    // 依此类推
    for (int i = 1; i <= position - 1; i++) {
        lastIndex = data[lastIndex].getCursor();
    }
    StaticLinkNode<T> lastNode = data[lastIndex];

    // 待删除的节点
    int toDeleteIndex = lastNode.getCursor();
    StaticLinkNode<T> toDeleteNode = data[toDeleteIndex];

    // 上一个节点的游标指向下一个节点
    lastNode.setCursor(toDeleteNode.getCursor());
    data[lastIndex] = lastNode;
    // 当前节点的值设置为空
    toDeleteNode.setData(null);
    // 当前节点的游标设置为0
    toDeleteNode.setCursor(0);
    data[toDeleteIndex] = toDeleteNode;

    // 把已删除的节点的索引位置作为下一次插入时的位置
    data[toDeleteIndex].setCursor(data[0].getCursor());
    data[0].setCursor(toDeleteIndex);
    length--;

}

  同样,时间复杂度为O(n)。

5.6 获取指定位置的元素

  从第一个节点一直往后找即可:

/**
 * 获取指定位置的元素
 *
 * @param position 指定的位置
 * @return 指定位置的元素
 * @throws RuntimeException 当试图获取第0个或超出实际节点数量的元素时抛出异常
 * @author Korbin
 * @date 2023-01-05 19:59:26
 **/
public StaticLinkNode<T> getElement(int position) {
    // 不得获取第0个元素
    // 不得获取超出实际节点数量的元素
    if (position == 0 || position > length()) {
        throw new RuntimeException("error position");
    }

    // 依次往下迭代即可
    int positionIndex = data[maxSize - 1].getCursor();
    for (int i = 1; i < position; i++) {
        positionIndex = data[positionIndex].getCursor();
    }

    return data[positionIndex];
}

5.7 获取指定结点在静态链表中的位置

  同样,从第一个结点一直往后找:

/**
 * 查找element在单链表中的位置
 *
 * @param element 待查找的元素
 * @return 元素在单链表中的位置
 * @author Korbin
 * @date 2023-01-11 15:59:23
 **/
public int locateElement(StaticLinkNode<T> element) {

    // 依次往下迭代即可
    int index = data[maxSize - 1].getCursor();
    for (int i = 0; i < length; i++) {
        StaticLinkNode<T> node = data[index];
        if (node.equals(element)) {
            break;
        }
        index = data[index].getCursor();
    }
    return index;
}

5.8 完整代码

/**
 * 静态链表
 *
 * @author Korbin
 * @date 2023-01-05 16:27:11
 **/
public class StaticLinkList<T> {

    /**
     * 节点数据
     */
    private StaticLinkNode<T>[] data;

    /**
     * 实际节点数量
     **/
    private int length;

    /**
     * 链表的最大长度
     */
    private int maxSize;

    /**
     * 清空
     *
     * @author Korbin
     * @date 2023-01-11 15:58:15
     **/
    public void clear() {
        init(maxSize);
    }

    /**
     * 删除指定位置的节点
     *
     * @param position 待删除节点位置
     * @throws RuntimeException 删除第0个节点或者要删除的位置超出节点实际数量时抛出异常
     * @author Korbin
     * @date 2023-01-05 19:34:18
     **/
    public void delete(int position) {
    
        // 不得删除第0个元素
        // 不得删除超出实际节点数量的元素
        if (position == 0 || position > length()) {
            throw new RuntimeException("error position");
        }
    
        // 找到要删除的节点的上一个节点
        int lastIndex = data[maxSize - 1].getCursor();
    
        int lastIndex = maxSize - 1;
        // 获取到待插入的节点的上一个节点的游标
        // 首先通过最后一个节点的游标,可以找到第一个节点
        // 通过第一个节点的游标,可以找到第二个节点
        // 依此类推
        for (int i = 1; i <= position - 1; i++) {
            lastIndex = data[lastIndex].getCursor();
        }
        StaticLinkNode<T> lastNode = data[lastIndex];
    
        // 待删除的节点
        int toDeleteIndex = lastNode.getCursor();
        StaticLinkNode<T> toDeleteNode = data[toDeleteIndex];
    
        // 上一个节点的游标指向下一个节点
        lastNode.setCursor(toDeleteNode.getCursor());
        data[lastIndex] = lastNode;
        // 当前节点的值设置为空
        toDeleteNode.setData(null);
        // 当前节点的游标设置为0
        toDeleteNode.setCursor(0);
        data[toDeleteIndex] = toDeleteNode;
    
        // 把已删除的节点的索引位置作为下一次插入时的位置
        data[toDeleteIndex].setCursor(data[0].getCursor());
        data[0].setCursor(toDeleteIndex);
        length--;
    
    }

    /**
     * 返回节点信息
     **/
    public StaticLinkNode<T>[] getData() {
        return data;
    }

    /**
     * 获取指定位置的元素
     *
     * @param position 指定的位置
     * @return 指定位置的元素
     * @throws RuntimeException 当试图获取第0个或超出实际节点数量的元素时抛出异常
     * @author Korbin
     * @date 2023-01-05 19:59:26
     **/
    public StaticLinkNode<T> getElement(int position) {
        // 不得获取第0个元素
        // 不得获取超出实际节点数量的元素
        if (position == 0 || position > length()) {
            throw new RuntimeException("error position");
        }

        // 依次往下迭代即可
        int positionIndex = data[maxSize - 1].getCursor();
        for (int i = 1; i < position; i++) {
            positionIndex = data[positionIndex].getCursor();
        }

        return data[positionIndex];
    }

    /**
     * 获取首节点索引
     *
     * @return 首节点索引
     * @author Korbin
     * @date 2023-01-05 20:09:43
     **/
    public int getFirstNodeIndex() {
        return data[maxSize - 1].getCursor();
    }

    /**
     * 下一个可插入的索引位
     *
     * @return 下一个可插入的索引位
     * @author Korbin
     * @date 2023-01-05 20:20:14
     **/
    public int getNextIndex() {
        return data[0].getCursor();
    }

    /**
     * 初始化静态链表
     *
     * @param maxSize 链表的最大长度
     * @throws RuntimeException 最大长度为0时抛出
     * @author Korbin
     * @date 2023-01-05 16:30:25
     **/
    @SuppressWarnings("unchecked")
    public void init(int maxSize) {
        if (maxSize < 1) {
            throw new RuntimeException("error maxSize");
        }
        this.maxSize = maxSize;

        data = new StaticLinkNode[maxSize];

        for (int i = 0; i < maxSize - 1; i++) {
            StaticLinkNode<T> node = new StaticLinkNode<>();
            node.setCursor(i + 1);
            data[i] = node;
        }

        StaticLinkNode<T> node = new StaticLinkNode<>();
        node.setCursor(0);
        data[maxSize - 1] = node;

        length = 0;
    }

    /**
     * 在指定位置插入
     *
     * @param position 位置
     * @param value    要插入的元素
     * @throws RuntimeException 插入到数组的0位,或者跳跃进行插入,或者数组长度满后插入时抛出异常
     * @author Korbin
     * @date 2023-01-05 19:32:06
     **/
    public void insert(int position, T value) {

        // 数组的第0个元素存储着数组下一个可存放节点的位置,获取到这个位置
        int nextIndex = getNextIndex();

        if (position < 1 || position > length + 1) {
            // 插入位置不能小于1,也不能大于链表的实际长度+1
            // 可以为length+1的原因,是因为允许在最后面插入
            throw new RuntimeException("error position");
        }

        if (length == maxSize - 2) {
            // 如果实际长度等于最大长度时,表示链表已满,不能再插入
            throw new RuntimeException("fulled");
        }

        // 默认数组的最后一个元素存储的是第一个节点的下标

        int lastIndex = maxSize - 1;
        // 获取到待插入的节点的上一个节点的游标
        // 首先通过最后一个节点的游标,可以找到第一个节点
        // 通过第一个节点的游标,可以找到第二个节点
        // 依此类推
        for (int i = 1; i <= position - 1; i++) {
            lastIndex = data[lastIndex].getCursor();
        }

        // 设置第0个元素的游标值为待插入节点的游标值,初始化时有设置
        data[0].setCursor(data[nextIndex].getCursor());

        // 设置这个节点的节点值为待插入值
        data[nextIndex].setData(value);

        // 设置这个节点的游标为上一个节点的游标值
        data[nextIndex].setCursor(data[lastIndex].getCursor());
        // 设置上一个节点的游标值为这个节点的位置
        data[lastIndex].setCursor(nextIndex);

        ++length;
    }

    /**
     * 判断是否为空
     *
     * @return 为空返回true,非空返回false
     * @author Korbin
     * @date 2023-01-11 15:54:53
     **/
    public boolean isEmpty() {
        return length == 0;
    }

    /**
     * 获取真实长度
     *
     * @return 长度
     * @author Korbin
     * @date 2023-01-05 10:15:41
     **/
    public int length() {
        return length;
    }

    /**
     * 查找element在单链表中的位置
     *
     * @param element 待查找的元素
     * @return 元素在单链表中的位置
     * @author Korbin
     * @date 2023-01-11 15:59:23
     **/
    public int locateElement(StaticLinkNode<T> element) {

        // 依次往下迭代即可
        int index = data[maxSize - 1].getCursor();
        for (int i = 0; i < length; i++) {
            StaticLinkNode<T> node = data[index];
            if (node.equals(element)) {
                break;
            }
            index = data[index].getCursor();
        }
        return index;
    }

    @Override
    public String toString() {

        int length = length();
        int index = data[maxSize - 1].getCursor();

        StringBuilder builder = new StringBuilder("[");
        for (int i = 0; i < length; i++) {
            StaticLinkNode<T> node = data[index];
            builder.append(i + 1).append(":").append(node.getData()).append(", ");
            index = node.getCursor();
            if (index == 0) {
                break;
            }
        }
        builder.append("]");

        return builder.toString();
    }

}

5.9 静态链表的优缺点

image.png

  总的来说,静态链表其实是为了给没有指针的高级语言设计的一种实现单链表能力的方法。

6 其他线性表及总结

  单链表还可以继续扩展:

  (1) 将单链表的终端结点的指针端由空指针改为指向头结点,就使单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表,通过循环链表,可以从任何一个结点出发,访问到链表中的所有结点;

  (2) 在单链表的每个结点中,再设置一个指针指向其前驱结点的指针域的链表,称为双向链表,双向链表方便了从某一个结点,获取其前、后结点;

  总之,比较常见的线性表包括以下:

image.png
  注:本文为程 杰老师《大话数据结构》的读书笔记,其中一些示例和代码是笔者阅读后自行编制的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/384572.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

锁屏面试题百日百刷-Hive篇(八)

锁屏面试题百日百刷&#xff0c;每个工作日坚持更新面试题。锁屏面试题app、小程序现已上线&#xff0c;官网地址&#xff1a;https://www.demosoftware.cn。已收录了每日更新的面试题的所有内容&#xff0c;还包含特色的解锁屏幕复习面试题、每日编程题目邮件推送等功能。让你…

gitblit 安装使用

1 安装服务端 简而言之&#xff1a;需要安装 java&#xff0c;gitblit&#xff0c; git 三个软件 Windows 10环境使用Gitblit搭建局域网Git服务器 前言 安装Java并配置环境安装gitblit并配置启动gitblit为windows服务使用gitblit创建repository并管理用户 1.1 安装Java并配…

[Java·算法·中等]LeetCode215. 数组中的第K个最大元素

每天一题&#xff0c;防止痴呆题目示例分析思路1题解1分析思路2题解2分析思路3题解3&#x1f449;️ 力扣原文 题目 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不…

REFL: 联邦学习中智能的设备选择方法

原创 齐天宇 隐私计算研习社 收录于合集#联邦学习54个现有的FL方案使用随机的参与者选择来提高选择过程的公平性&#xff0c;但是这会导致资源的低效利用和较低的训练质量。本文系统地解决了FL中资源效率低效的问题&#xff0c;展示了智能参与者选择和合并来自落后参与者的更新…

React-高阶组件

认识高级组件 高阶函数的维基百科定义:至少满足以下条件之一 1、接受一个或多个函数作为输入; 2、输出一个函数; JavaScript中比较常见的 filter、map、reduce 都是高阶函数 那么说明是高阶组件呢? 高阶组件的英文是 Higher-Order Components&#xff0c;简称为 HOC;官方的…

2023年如何通过软考初级程序员?

初级的考试难度不大&#xff0c;稍微有点编程基础&#xff0c;认真备考应该没什么大问题。 先清楚大纲&#xff1a; 高效备考&#xff01;理清考点&#xff0c;针对性复习 科目一&#xff1a;综合知识 75道单项选择题&#xff0c;1题1分&#xff0c;时长150分钟&#xff1b;…

Qt 防止程序退出

文章目录摘要QWidgetQML方法 1方法 2关键字&#xff1a; Qt、 eventFilter、 Close、 键盘、 任务管理器摘要 今天要聊得内容还是怎么防止别人关闭我的程序&#xff0c;之前都是在win下面&#xff0c;一般都是用过钩子连捕获键盘事件&#xff0c;完了吧对应的事件忽略&#x…

面了 6 家大厂,并拿下 5 家 offer,进大厂好像也没有那么困难吧....

前言 二月份的时候因为换工作的缘故&#xff0c;陆续参加了华为、蚂蚁、字节跳动、PDD、百度、Paypal 的社招面试&#xff0c;除了字节跳动流程较长&#xff0c;我主动结束面试以外&#xff0c;其他的都顺利拿到了 Offer。 最近时间稍微宽裕点了&#xff0c;写个面经&#xf…

1.测试用例

一、测试用例怎么写 用例编号&#xff0c;用例标题&#xff0c;模块/项目&#xff0c;前置条件&#xff0c;优先级&#xff0c;测试步骤&#xff0c;测试数据&#xff0c;预期结果&#xff0c;实际结果。 案例&#xff1a;微信登陆测试点 1.登录成功 2.密码错误&#xff0c;登…

安卓性能测试+结果可视化

使用到的技术&#xff1a;mobileperfpyecharts或mobileperfgrafana 性能测试 GitHub - alibaba/mobileperf: Android performance testAndroid performance test. Contribute to alibaba/mobileperf development by creating an account on GitHub.https://github.com/alibab…

Linux: ARM GIC仅中断CPU 0问题分析

文章目录1. 前言2. 分析背景3. 问题4. 分析4.1 ARM GIC 中断芯片简介4.1.1 中断类型和分布4.1.2 拓扑结构4.2 问题根因4.2.1 设置GIC SPI中断的CPU亲和性4.2.2 GIC初始化&#xff1a;缺省的CPU亲和性4.2.2.1 boot CPU亲和性初始化流程4.2.2.1 其它非 boot CPU亲和性初始化流程5…

KT404C语音芯片串口发数据没反应或者报错的处理总结

一、问题简介 KT404C我焊接到PCB板上面&#xff0c;直接使用串口调试助手发指令没有任何返回&#xff0c;请问是什么意思呢&#xff1f; 很确定&#xff0c;串口也没连错&#xff0c;使用的是CH340G的USB转TTL &#xff0c;【TX连接KT404C的7脚RX】 【RX连接KT404C的8脚TX】 二…

Python常用标准库-os库一文详解(二):文件操作和路径操作

目录 前言 文件操作 一、读写文件 1.读文件 2.写文件 二、创建文件 三、删除文件 四、重命名文件 五、文件判断 路径操作 1.拼接 2. 分离路径 3.获取路径中的文件名 4.获取路径中的路径名 5.获取绝对路径 6.分离文件拓展名 点关注&#xff0c;防走丢&#xff…

CEC2020:能量谷优化算法(Energy valley optimizer,EVO)求解CEC2020(提供MATLAB代码)

一、能量谷优化算法 能量谷优化算法&#xff08;Energy valley optimizer&#xff0c;EVO&#xff09;是MahdiAzizi等人于2023年提出的一种新颖的元启发式算法&#xff0c;其灵感来自关于稳定性和不同粒子衰变模式的物理原理。 物理反应是指两个粒子或外部亚原子粒子碰撞产生新…

【matplotlib】可视化解决方案——如何向画布添加交叉直线

概述 在 matplotlib 中&#xff0c;如果想要在画布上添加一组横纵较差的直线&#xff0c;需要使用到 Cursor 类&#xff0c;该类实现了图形化界面中任何位置的数值定位可视化某种意义上来讲&#xff0c;这种横纵交叉线类似数值放大镜&#xff0c;可以清楚地显示任何位置的坐标…

以图搜图服务快速搭建

以图搜图服务快速搭建 电商公司&#xff0c;管理的商品少则几千&#xff0c;多则上百万。如何帮助用户从多如牛毛的商品中找到类似的商品就成了问题。 以图搜图就可以很好的帮助解决这个问题&#xff0c;通过 Towhee&#xff08;resnet50 模型&#xff09; Milvus 如何实现本…

Linux常用命令——lsusb命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) lsusb 显示本机的USB设备列表信息 补充说明 lsusb命令用于显示本机的USB设备列表&#xff0c;以及USB设备的详细信息。 lsusb命令是一个学习USB驱动开发&#xff0c;认识USB设备的助手&#xff0c;推荐大家使用…

深信服面经---云计算方向(附问题知识点解析)

深信服面经---云计算高级开发一、一面问题概览二、实操相关三、复盘对问题答案进行整理&#xff08;查漏补缺&#xff09;3.1、go语言简单了解3.2、项目中成就感最大或挑战最大的地方3.3、项目问题---协议头引入之后&#xff0c;包的大小增加了多少3.4、如何建立缓存3.5、cache…

STM32定时器的配置,解析预分频系数和重装载值与时钟频率的关系

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都在这儿哦&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 - 蓝…

“一键转换图片:学习如何使用Python调整大小、增强和转换图片!“

目录 简介&#xff1a; 源代码&#xff1a; 代码说明&#xff1a; 效果如图所示&#xff1a; 有关其中用到的Pillow模块&#xff1a; 简介&#xff1a; 在这个世界上&#xff0c;图片处理已经成为了必须掌握的技能之一&#xff0c;无论是为了更好地展示产品&#xff0c;还是…