【数据结构与算法】数据结构与算法之线性表Linear table(Java版)

如果觉得有帮助,麻烦动动手指点赞加关注💗💗💗 非常感谢!!!

有想看源码的小伙伴请移步这里👉https://gitee.com/fearless123/demo/tree/master/src/main/java/com/ma

一、绪论


1. 概述

在这里插入图片描述
数据之间的相互关系成为逻辑结构。通常分为四类基本结构:

  • 集合:结构中的数据元素除了同属于一种类型外,别无其它关系。(数组)
  • 线性结构:结构中的数据元素之间存在一对一的关系。(线性表)
  • 树形结构:结构中的数据元素之间存在一对多的关系。(树)
  • 图状结构或网状结构:结构中的数据元素之间存在多对多的关系。(图,多对多的意思就是,同一个结点,可能有多个上一元素,也可以有多个下一元素,即从不同的结点出发,可以到达同一个结点,从同一结点出发,又可以到达不同的结点,这就是图的数据元素关系是多对多)

数据结构在计算机中有两种不同的存储方法:

  • 顺序存储结构:用数据元素在存储器中的相对位置来表示数据元素之间的逻辑关系
  • 链式存储结构:在每一个数据元素中增加一个存放地址的指针,用此指针表示数据元素之间的逻辑关系。

2. 时间复杂度

一个算法花费的时间与算法中语句执行的次数成正比列,哪个算法中语句执行次数多,它花费时间就多。算法中的语句执行次数称为语句频度或时间频度,记为T(n)。n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。但有时我们想知道它变化呈现什么规律。为此,我们引入时间复杂度的概念。

一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)T(n)同数量级函数。记作T(n)=O(n),称O(f(n))为算法的渐进时间复杂度,简称时间复杂度

对于求其基本操作执行的次数,就是求函数f(n)。求出以后就可以取出f(n)中随n增大而增长最快的项,然后其系数变为1,作为时间复杂度的度量,记作T(n)=O(f(n)中增长最快的项/此项的系数),例如,f(n)=2n3 + 4n² + 100,则其时间复杂度为T(n)=O(2n3/2) =O(n3)。                                                                                                                   ——摘自20版数据结构高分笔记

有时候,算法中的基本操作重复执行的次数还随问题的输入数据集不同而不同,如在冒泡排序中,输入数据有序而无序,其结果是不一样的。此时,我们计算平均值。

常见的算法的时间复杂度之间的关系为:

O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(2^n) < O(n!) < O(n^n)

总结步骤:

1)找出基本操作(多数情况下取最深层循环内的语句所描述的操作作为基本操作),确定规模n

2)计算出n的函数f(n)

实例1:

    public void fun(int n){
        int i=1,j=100;
        while(i<n){
            ++j;
            i+=2;
        }
    }

分析:显然,基本操作步骤是 ++j,依靠 in 控制循环结束。i 的初值为1,每次自增2,假设 i 自增m次后循环结束,则i最后的值为1+2m,因此有1+2m+K=n(其中K为一个常数,因为在循环结束时 i 的值稍大于 n,为方便计表述和进一步计算,用K将1+2m修正成 n,因为K为常数,所以这样做不会影响最终时间复杂度的计算),解得m=(n-1-K)/2,因此时间复杂度T(n) = O(n)

实例2:

    public void fun(int n){
        int i,j,x=0;
        for (i=0;i<n;++i){
            for (j=i+1;j<n;++j){
                ++x;
            }
        }
    }

分析:++x 处于最内层循环,因此取 ++x 为基本操作。显然 n 为规模,可以算出 ++x 的执行次数为f(n)=n(n-1)/2(可以使用递推法),变化最快的项为n2/2,因此时间复杂度是T(n)= O(n²)

实例3:

    public void fun(int n){
        int i=0,s=0;
        while(s<n){
            ++i;
            s=s+i;
        }
    }				

分析:显然n为规模,基本操作为++i 和 s=s+i,i 与 s 都从0开始,假设循环执行m次结束,则有s1=1,s2=1+2=3,s3=1+2+3=6,… ,sm=m(m+1)/2(其中sm为执行到第m次的时候s的值),则有m/(m+1)/2+K=n(K为修正作用的常数),由求根公式得,

m = − 1 + 8 n + 1 − 8 K 2 m = \frac{-1 + \sqrt[]{8n + 1 -8K}}{2} m=21+8n+18K

f ( n ) = − 1 + 8 n + 1 − 8 K 2 f(n) = \frac{-1 + \sqrt[]{8n + 1 -8K}}{2} f(n)=21+8n+18K

由此可知时间复杂度为

T ( n ) = O ( n ) T(n) = O(\sqrt{n}) T(n)=O(n )


3. 空间复杂度

算法所需存储空间的度量,记作:

S(n) = O(f(n))

其中n为问题的规模。

一个算法在计算机存储器上所占用的存储空间,包括三个方面:

  • 存储算法本身算占的存储空间
  • 算法的输入输出数据所占用的存储空间
  • 算法在运算过程中所占用的存储空间

如果额外空间相对于输入数据量来说是个常数,则称此算法是原地工作

算法的输入输出数据所占用的存储空间是由要解决的问题决定的,是通过参数表由调用函数传递而来的,它不随本算法的不同而改变。存储算法本身所占用的存储空间与算法书写的长短成正比,要压缩这方面的存储空间,就必须编写出较短的算法。


二、顺序表及链表

1. 线性表概念

线性表就是在逻辑上呈线程关系排列的简单数据结构,其具备的特点如下:

  • 数据一旦用线程存储,就是有序的,各个元素之间的相对位置就固定了
  • 线程表第一个元素有且仅有一个直接后继,最后一个元素有且仅有一个直接前驱
  • 线程表允许零元素,即空表

按两种存储结构分为顺序表链表,后面依次讲解。

2. 顺序表

采用顺序存储结构生成的表,称为顺序表
在这里插入图片描述

如上图,同一顺序表的数据在物理内存地址中是具有连续性的,数组就是顺序表的一个应用。

Java实现顺序表💻

我这里的顺序表是一个不会自动扩容的顺序表,采用Java中的数组为底层结构。实现顺序表的初始化、查找、插入、修改、删除元素功能。

建议独立实现完后可结合ArrayList、LinkedList源码进行比较总结

感兴趣的小伙伴可以点击这里👉Java底层数据结构之ArrayList、LinkedList源码解读

package com.ma.structure;

import java.io.Serializable;
import java.util.Arrays;

/**
 * @author by fearless
 * @description SeqList 线性表
 * 实现功能如下:
 *     顺序表是否为空
 *     顺序表的长度
 *     查找顺序表指定索引位置的元素
 *     修改顺序表指定索引位置的元素
 *     顺序插入元素
 *     插入顺序表指定索引位置的元素
 *     删除顺序表指定索引位置的元素
 * @date 2022/1/19 10:57
 * @version v1.0

 */
public class SeqList<T> implements Serializable {

    private Object[] element;   // 数组
    private Integer len;        // 顺序表长度
    private Integer size;       // 顺序表容量

    public SeqList(Integer size) {
        this.element = new Object[size];
        this.len = 0;
        this.size = size;
    }

    /********************************查找*************************************/
    /**
     * 判断顺序表是否为空
     * @return
     */
    public boolean isEmpty(){
        return this.len == 0;
    }

    /**
     * 获取顺序表的长度
     * @return
     */
    public int length(){
        return this.len;
    }

    /**
     * 查找第i个元素,并返回,若不存在则抛异常
     * @param i
     * @return
     */
    @SuppressWarnings("unchecked")
    public T get(int i) throws Exception {
        rangeCheck(i);
        return (T) this.element[i];
    }
    /********************************更新*************************************/
    /**
     * 对i位置的数据进行更新
     * @param i
     * @param t
     * @return
     */
    public T set(int i, T t){
        rangeCheck(i);

        T old = elementData(i);
        element[i] = t;
        return old;
    }

    /********************************插入*************************************/
    /**
     * 顺序插入
     * @param t
     */
    public void insert(T t) throws Exception {
        if (t == null){
            throw new NullPointerException("插入数据不允许为null");
        }

        if (this.len < this.size){
            this.element[this.len++] = t;
        } else {
            throw new Exception("顺序表容量为" + this.size + " 当前顺序表长度为" + this.len
                    + " 无法继续添加元素" + t);
        }

    }

    /**
     * 对指定索引i的位置 顺序插入
     * @param i
     * @param t
     */
    public void insert(int i, T t) {
        if (t == null){
            throw new NullPointerException("插入数据不允许为null");
        }
        rangeCheck(i);
        // 将i及以后的元素向后移动
        for (int j = this.len - 1; j >= i; --j){
            this.element[++j] = this.element[j];
        }
        // 元素存放到位置i 同时长度+1
        set(i, t);
        this.len++;
    }

    /********************************删除*************************************/

    /**
     * 删除指定索引的元素,若成功则返回删除的元素
     * @param i
     * @return
     */
    public T remove(int i){
        rangeCheck(i);
        T old = elementData(i);
        // 元素移除,将i及以后的元素向前移动
        for (int j = i + 1; j <= this.len - 1; ++j){
            this.element[--j] = this.element[j];
        }
        // 最后一个元素置空
        this.element[this.len - 1] = null;
        this.len--;
        return old;
    }


    private void rangeCheck(int index){
        if (index >= size){
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }

    private String outOfBoundsMsg(int index){
        return "Index: " + index + ", Size" + size;
    }

    @SuppressWarnings("unchecked")
    T elementData(int index){
        return (T) element[index];
    }


    /********************************测试*************************************/

    public static void main(String[] args) throws Exception {
        SeqList<Integer> list = new SeqList<>(5);
        list.insert(1);
        list.insert(2);
        list.insert(3);
        list.insert(4);
        list.insert(5);
        list.insert(6);
        System.out.println(Arrays.toString(list.element));

        SeqList<String> strList = new SeqList<String>(10);
        strList.insert("ab");
        strList.insert("ab");
        strList.insert("ab");
        strList.insert("ab");
        strList.insert(2,"2ab");

        System.out.println(Arrays.toString(strList.element));
    }
}

小结:
这里的基本思路是采用Java的数组为底层数据结构。分别实现一个顺序表的初始化、查找、插入、修改,删除元素等操作。相对来说比较简单。唯一需要考虑的问题是插入和删除指定元素时的数据移动问题。

3. 链表

链表就是相对于顺序表而言,采用非物理连续存储而是逻辑上的线性关系,即具备链式存储结构的线性表。

链表的组成部分如下:(包括指针域、数据域)

在这里插入图片描述

  • 本身的信息,称为“数据域
  • 指向前驱结点或后继结点的指针,称为“指针域

在这里插入图片描述

由数据域和指针域两部分信息组成元素的存储结构,称为链表的“结点”。

Java实现链表💻

链表的代码实现主要分成两部分,第一部分结点的构成,第二部分是单链表的构成

package com.ma.structure.list;

/**
 * @author mayujie@jyd.com.cn
 * @version v1.0
 * @description Node 链表中的结点
 * @date 2022/1/20 14:03
 */
public class Node<T> {

    /**
     * 数据域
     */
    public T data;

    /**
     * 指针域
     */
    public Node<T> next;

    public Node(T data, Node<T> next) {
        this.next = next;
        this.data = data;
    }

    public Node() {
        this(null,null);
    }

}

package com.ma.structure.list;

import org.apache.poi.hssf.record.formula.functions.T;


/**
 * @author mayujie@jyd.com.cn
 * @version v1.0
 * @description SingleLinkedList 带头结点的单链表
 * 实现功能如下:
 *      默认构建一个空链表
 *      通过元素序列进行构建链表(By 头插入和尾插入两种方式)
 *      单链表的深拷贝式的构造
 *      判断链表是否为空表
 *      返回链表的长度
 *      返回第i个结点的数据域
 *      查找链表中首个关键字的位置
 *      对某个结点进行数据域的更新
 *      在某个结点位置插入一个新结点
 *      在链尾插入一个结点
 *      删除指定结点
 *      单链表的比较函数
 * @date 2022/1/20 14:11
 */
public class SingleLinkedList<T> {

    public Node<T> head;    // 头结点,指向单链表的首位结点

    /*****************************初始化**********************************/
    /**
     * 默认构造方法构造空单链表,即空表
     * 数据域和指针域均为null,仅有头结点
     */
    public SingleLinkedList() {
        this.head = new Node<>();
    }

    /**
     * 以数据域数组构造单链表(头尾插入)
     * @param type
     * @param element
     */
    public SingleLinkedList(int type, T[] element){
        this(); // 构造空表

        if (type == 0){ // 头插法
            // 构造数组最后一个元素的结点
            Node<T> node = new Node<>(element[element.length - 1], null);
            for (int i = element.length - 2; i >= 0; --i) {
                // 创建前前驱结点的,同时next指向后继结点
                node = new Node<>(element[i], node);
            }
            // head结点指向第一个结点元素
            this.head.next = node;
        }

        if (type == 1){ // 尾插法
            Node<T> node = this.head;
            for (T elem : element){
                // 创建后继结点,同时将前驱结点的next指向该后继结点
                node.next = new Node<>(elem, null);
                node = node.next;
            }
        }
    }


    /**
     * 单链表的深拷贝式的构造函数
     * @param linkedList
     */
    public SingleLinkedList(SingleLinkedList<T> linkedList){
        this();
        // 获得源链表的首个结点
        Node<T> temp = linkedList.head.next;
        // 获得新链表的head结点
        Node<T> node = this.head;
        // 遍历新链表
        while (temp != null){
            // 根据原链表的结点数据域,构造新的结点
            node.next = new Node<>(temp.data, null);
            // 到达下一个结点
            temp = temp.next;
            // 到达下一个结点
            node = node.next;
        }
    }

    /*****************************查找**********************************/


    /**
     * 判断是否为空表
     * @return
     */
    public boolean isEmpty(){
        // 如果头结点的指向null,则为空表
        return this.head.next == null;
    }

    /**
     * 返回单链表的长度
     * @return
     */
    public int length(){
        int len = 0;
        Node<T> node = this.head.next;

        while (node != null){
            len++;
            // 如果结点不为null,那么node指向其后继结点
            node = node.next;
        }
        return len;
    }

    /**
     * 根据插入顺序存储,返回第i个元素
     * @param i
     * @return
     * @throws Exception
     */
    public T get(int i) throws Exception {
        checkException(i);

        Node<T> node = this.head.next;
        // 首个结点的位置0,头结点记作-1
        int index = 0;
        // 从
        while (index != i){
            index++;
            node = node.next;
        }
        return node.data;
    }

    /**
     * 返回首先输出的元素值在链表中的位置,如果无则返回-1
     * @param t
     * @return
     * @throws Exception
     */
    public int search(T t) throws Exception {
        checkException(t);

        Node<T> node = this.head.next;
        // 开始结点的位置为0,头结点记作-1
        int index = 0;
        while(!node.data.equals(t)){    // 当数据域不相等,继续循环比较
            node = node.next;
            index++;
            if (node.next == null){ // 如果到了末尾还不存在,则返回-1
                return -1;
            }
        }
        return index;   // 返回结点位置,开始结点计算,起始为0
    }

    /*****************************更新**********************************/

    /**
     * 对结点i的数据域进行更新
     * @param i
     * @param t
     * @throws Exception
     */
    public void set(int i, T t) throws Exception {
        checkException(i,t);

        Node<T> node = this.head.next;
        // 开始结点的位置为0,头结点记作-1
        int index = 0;
        while (index != i){
            index++;
            node = node.next;
        }
        node.data = t;
    }

    /*****************************插入**********************************/


    /**
     * 在结点i前插入一个新的元素
     * @param i
     * @param t
     * @throws Exception
     */
    public void insert(int i, T t) throws Exception {
        checkException(i, t);
        if (i == 0){
            // head->(新)结点->(原开始)结点,完成头插
            this.head.next = new Node<>(t, this.head.next);
        } else {
            // 拿到开始结点
            Node<T> node = this.head.next;
            // 记录初始下标
            int index = 0;
            while (index != i-1){   // i-1表示获得待插入结点的前驱结点位置
                index++;
                // node结点更新
                node = node.next;
            }
            // i-1位置插入新元素
            node.next = new Node<>(t, node.next);
        }
    }

    /**
     * 在链尾插入一个元素
     * @param t
     * @throws Exception
     */
    public void insert(T t) throws Exception {
        if (this.head.next == null){
            this.head.next = new Node<>(t, null);
        } else {
            // 拿到开始结点
            Node<T> node = this.head.next;
            // 记录初始位置0
            int index = 0;
            // 遍历循环链表,直至链尾
            while (node.next != null){
                index++;
                node = node.next;
            }
            // 元素插入
            node.next = new Node<>(t,null);
        }


    }

    /*****************************删除**********************************/

    /**
     * 删除指定结点i
     * @param i
     * @return
     * @throws Exception
     */
    public T remove(int i) throws Exception {
        checkException(i);

        if (i == 0){    // 删除 开始结点
            // 记录待删结点
            Node<T> tmp = this.head.next;
            // 构建链表关系
            this.head.next = this.head.next.next;
            // 返回待删结点的数据
            return tmp.data;
        } else {    // 当删除 非开始结点
            // 拿到开始结点
            Node<T> node = this.head.next;
            // 记录初始位置0
            int index = 0;

            // 遍历链表获得待删结点的前驱
            while (index != i-1){
                index++;
                node = node.next;
            }
            Node<T> temp = node.next;
            node.next = temp.next;
            return temp.data;
        }
    }

    /*****************************工具**********************************/
    /**
     * 单链表的比较
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj){
        if (obj == this){   // 如果指向的是同一地址,返回true
            return true;
        }
        if(!(obj instanceof SingleLinkedList)){ // 如果类型不是SingleLinkedList,返回false
            return false;
        }
        // 拿到当前链表的开始结点
        Node<T> node = this.head.next;
        // 拿到待比较链表的开始结点
        Node<T> temp =((SingleLinkedList<T>)obj).head.next;
        while (node != null && temp != null && node.data.equals(temp.data)){
            node = node.next;   // node和temp同时不为null,且对应结点的data也相等,就指向下一个结点
            temp = temp.next;
        }
        return node == null && temp == null;    // 俩链表同时为null 表示链表长度相等
    }

    /**
     * 返回单链表所有元素的描述字符串 格式[1->2->3->null]
     * @return
     */
    @Override
    public String toString(){
        StringBuffer sb = new StringBuffer();
        sb.append("[");
        Node<T> node = this.head.next;
        while (node != null){
            sb.append(node.data);
            sb.append(" -> ");
            node = node.next;
        }
        sb.append("null]");
        return sb.toString();
    }

    /**
     * 异常检查
     * @param i 结点位置
     * @param t 数据域
     * @throws Exception
     */
    public void checkException(int i, T t) throws Exception {
        if (isEmpty()){ // 链表中没有数据
            throw new Exception("这是一个空链表!!!");
        }
        if (i < 0 || i >= length() || i > Integer.MAX_VALUE){
            // 传入的结点位置i不允许越界
            throw new Exception("结点位置越界");
        }
        if (t == null){
            // 传入的元素不能为null
            throw new NullPointerException("元素不能为null");
        }
    }

    /**
     * 异常检查
     * @param t 数据域
     * @throws Exception
     */
    public void checkException(T t) throws Exception {
        if (isEmpty()) {
            throw new Exception("这是一个空表");
        }
        if (t == null) {
            throw new NullPointerException("元素不能为null");
        }
    }

    /**
     * 异常检查
     * @param i 结点位置
     * @throws Exception
     */
    public void checkException(int i) throws Exception {
        if (isEmpty()) {
            throw new Exception("这是一个空表");
        }
        if (i < 0 || i >= length() || i > Integer.MAX_VALUE) {
            throw new Exception("结点位置越界越界");
        }
    }

    public static void main(String[] args) throws Exception {
        Integer[] integers = {1,2,3,4};
        SingleLinkedList<Integer> list = new SingleLinkedList<>();
        list.insert(1);
        list.insert(2);
        list.insert(3);
        list.insert(4);
        list.insert(1, 9);
        list.set(2, 10);
        list.remove(4);


        SingleLinkedList<Integer> linkedList2 = new SingleLinkedList<Integer>();
        linkedList2.insert(1);
        linkedList2.insert(2);
        linkedList2.insert(3);
        linkedList2.insert(4);

        System.out.println(list.length());
        System.out.println(list.get(2));
        System.out.println(list.toString());
        System.out.println(list.search(10));

        SingleLinkedList<Integer> linkedList3 = new SingleLinkedList<Integer>(1,integers);
        System.out.println(linkedList3.toString());
    }

}

这里的单链表采用有头结点的方式。但在相关处理中,为了更好的显示一些特征的区别,所以我还是把首元结点和其他结点的插入、删除操作做了代码上的区别对待。


4. 顺序表和单链表的比较

顺序表的优缺点:

  • 顺序表使用物理连续的内存空间来存放数据元素,具有很大的逻辑性,便于理解。
  • 顺序表优点是其随机存取非常快速,比如要做定点修改和查找操作时(set/select)效率高,直接通过索引既可访问到。缺点是因为逻辑上相邻的元素物理上也相邻,所以插入删除需要移动插入(删除)点及其后面的所有元素

单链表的优缺点:

  • 链式存储的数据元素在物理结构没有限制,当内存空间中没有足够大的连续的内存空间供顺序表使用时,可能使用链表能解决问题。可以利用上一些内存碎片)
  • 链表的优点是插入删除操作非常高效,只需要记住改变位置的前后项,通过改变指针的指向即可,不需要移动插入或删除位置的后续元素,缺点随机访问效率低,必须一个结点一个结点的遍历过去,不像顺序表直接通过索引位置即可访问,(优缺点两者刚好相反,思考:有没有兼顾两者特点的数据结构呢???)

三、循环链表和静态链表


1. 循环链表

一般分为循环单链表及循环双链表,(队列相当于顺序存储结构的循环表实现)和普通链表的区别主要是可以最后一个结点和首个结点是否具备逻辑关系。
在这里插入图片描述

2. 静态链表

通过 “数组+游标” 的方式存储具有线性关系数据的存储结构就是静态链表。静态链表也是线性存储结构的一种,兼顾顺序表和链表的有点于一身,可以看做是顺序表和链表的升级。
在这里插入图片描述

  • 优点:在插入和删除操作时,只需要修改游标,避免了链式结构增删时移动元素的弊端;同时具备随机访问特性,根据数组下标及游标就可以访问其他元素。
  • 缺点:失去了链式存储结构动态分配存储空间的特性。(即所谓的“静态”)

Java实现静态链表💻

package com.ma.structure.list;

import lombok.Data;
import lombok.ToString;

import java.util.Arrays;

/**
 * @Description TODO
 * @Classname StaticLinkedList
 * @Created by Fearless
 * @Date 2022/2/25 14:09
 */
@Data
public class StaticLinkedList {

    private Element[] linkList = null;
    private int DEFAULT_SIZE = 4;
    private int currentFree = 0;
    private int size = 1;

    class Element{
        int data;
        int cur;

        @Override
        public String toString() {
            return "(" + data +
                    ", " + cur +
                    ')';
        }
    }

    @Override
    public String toString() {
        return "StaticLinkedList{" +
                "linkList=" + Arrays.toString(linkList) +
                ", DEFAULT_SIZE=" + DEFAULT_SIZE +
                ", currentFree=" + currentFree +
                ", size=" + size +
                '}';
    }

    /**
     * 静态链表的长度
     * @return
     */
    public int length(){
        return size - 1;
    }

    /**
     * 静态链表初始化
     */
    public StaticLinkedList() {
        linkList = new Element[DEFAULT_SIZE];
        for (int i = 0; i < linkList.length; i++) {
            linkList[i] = new Element();
            linkList[i].data = -1;
            linkList[i].cur = i + 1;
        }
        // 当前空闲节点从1开始,因为第0个结点设置成了头结点,
        // 设置为空,不存储数据
        currentFree = 1;
    }

    /**
     * 给链表添加数据,每当链表满了就给链表添加额外的空间
     * @param data
     */
    public void add(int data){
        if (size < linkList.length){
            linkList[currentFree].data = data;
            currentFree = linkList[currentFree].cur;
            size++;
        }else { // 链表已满,给链表添加空间
            addLinkSpace();
            linkList[currentFree].data = data;
            currentFree = linkList[currentFree].cur;
            size++;

        }
    }

    /**
     * 得到索引指定的数据
     * @param index
     * @return
     */
    public int get(int index){
        if (index > size - 1 && index < 0){
           throw new IndexOutOfBoundsException("数据越界,索引不合法");
        } else {
            // 这里index + 1也是因为多了一个空的头结点
            return linkList[index++].data;
        }
    }

    /**
     * 删除指定未知的结点
     * @param index
     */
    public void delete(int index){
        index = index + 1;
        if (index < 1 || index >= size){
            throw new IndexOutOfBoundsException("超出链表长度");
        }else if (index == size - 1){
            size--;
            linkList = (Element[]) getTrueIndex(linkList, size);
        }else {
            int i = 0;
            while (index != linkList[i].cur){
                i++;
            }
            int j = 0;
            while(currentFree != linkList[j].cur){
                j++;
            }
            linkList[i].cur = linkList[index].cur;
            linkList[j].cur = index;
            linkList[index].cur = currentFree;
            currentFree = index;
            size--;
            linkList = (Element[]) getTrueIndex(linkList, size);
        }
    }

    /**
     * 增加链表空间
     */
    private void addLinkSpace() {
        DEFAULT_SIZE+=8;
        Element[] link = linkList;
        linkList = new Element[DEFAULT_SIZE];
        System.arraycopy(link, 0, linkList, 0, link.length);
        for (int i = link.length; i < DEFAULT_SIZE; i++){
            linkList[i] = new Element();
            linkList[i].data = -1;
            linkList[i].cur = i + 1;
        }
        currentFree = link.length;
    }


    /**
     * 根据指定的位置插入数据
     * @param index
     * @param data
     */
    public void insert(int index, int data){
        // 这里加1的原因是因为链表的第0位是空结点,这里设置的头结点为空
        index++;
        if (size < linkList.length){
            if (index > size && index < 0){
                System.out.println("数据越界,超出数据长度");
            } else if (index == size){
                linkList[currentFree].data = data;
                currentFree = linkList[currentFree].cur;
                size++;
            } else {
                // 未按路基顺序排序而插入数据的写法,因为未排序,
                // 则当前缩印的上个结点的索引不一定是当前索引减1
                int i = 0;
                while (index != linkList[i].cur){
                    i++;
                }
                int j = 0;
                while(currentFree != linkList[j].cur){
                    j++;
                }
                linkList[i].cur = currentFree;
                linkList[j].cur = linkList[currentFree].cur;
                linkList[currentFree].data = data;
                linkList[currentFree].cur = index;
                currentFree = linkList[j].cur;
                size++;
                // 每次插入后将链表按逻辑顺序重新排序,是为了方便输出查看
                linkList = (Element[]) getTrueIndex(linkList, size);
            }
        } else {
            addLinkSpace();
            insert(index, data);
        }
    }


    /**
     * 按照逻辑顺序重新排序
     * @param link
     * @param size
     * @return
     */
    public Object getTrueIndex(Element[] link, int size){
        Element[] linkList1 = new Element[linkList.length];
        int k = 0;
        for (int i = 0; i < linkList.length; ++i){
            linkList1[i] = new Element();
            linkList1[i].data = link[k].data;
            k = link[k].cur;
            linkList1[i].cur = i+1;
        }
        // 插入时,currentFree肯定是最后一个了,但删除后,currentFree就不一定是最后一位了
        currentFree = size;
        return linkList1;

    }

    public static void main(String[] args) {
        StaticLinkedList list = new StaticLinkedList();
        int la[] = {
                2,3,4,8,9,6,7
        };
        for (int i = 0; i < la.length; i++) {
            list.add(la[i]);
        }
        list.delete(6);
        list.delete(0);
        list.insert(3, 88);
        list.insert(3,78);
        System.out.println(list.toString());
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值