【数据结构】顺序表

顺序表知识点梳理

一、基础概念与结构特性

1. 逻辑与物理结构

  • 逻辑结构 :顺序表属于线性表,元素呈线性排列(如 [1,2,3,4],元素间存在 "一对一" 相邻关系)。

  • 物理结构 :采用顺序存储,即逻辑上相邻的元素,在物理内存中地址也连续(Java 中通过数组实现,数组元素占用连续内存空间)。

  • 对比链表:链表采用离散存储,逻辑相邻元素物理地址可不连续,需通过指针 / 引用关联。

2. 核心特性:随机存取

  • 定义:可通过 "起始地址 + 元素索引" 直接定位任意元素,无需遍历。

  • Java 映射 :数组的索引访问(如 arr[i]),本质就是顺序表的随机存取。

    • 地址公式(逻辑映射):若数组首元素地址为 LOC(arr[0]),元素大小为 size,则第 i 个元素地址为 LOC(arr[i]) = LOC(arr[0]) + i * size
  • 时间复杂度O(1)(直接定位,无循环 / 递归开销),远高于链表的顺序访问(O(n))。

二、声明与初始化方式(Java 实现)

Java 中无 "静态 / 动态数组" 的严格语法区分,但可通过固定长度数组 (模拟静态)和动态扩容数组 (模拟动态,如 ArrayList)实现两种模式。

1. 静态数组实现(固定长度)

特点
  • 初始化时确定数组大小,后续不可扩展;

  • 内存由 JVM 自动分配(基本类型数组在栈 / 堆,对象数组在堆),无需手动释放。

Java 示例代码
java 复制代码
public class StaticSequenceList {
    // 存储元素的数组
    private int[] data;
    // 当前元素个数(注:区别于数组长度,数组长度是"最大容量")
    private int length;

    // 构造
    public StaticSequenceList(int maxSize) {
        this.data = new int[maxSize];
        this.length = 0; 
    }

    /** 获取当前元素个数 */
    public int getLength() { 
        return this.length;
    }

    /** 获取最大容量 */
    public int getMaxSize() {
        return this.data.length;
    }
    public boolean add(int value) {
        if (this.length == this.data.length) {
            System.out.println("静态顺序表已满,无法添加元素:" + value);
            return false;
        }
        this.data[this.length++] = value; // 末尾赋值,元素个数+1
        return true;
    }
    
 
    public static void main(String[] args) {
        // 创建容量为5的静态顺序表
        StaticSequenceList list = new StaticSequenceList(5);
        
        // 初始状态
        System.out.println("初始状态:");
        System.out.println("元素个数: " + list.getLength());
        System.out.println("最大容量: " + list.getMaxSize());
        
        // 添加元素
        System.out.println("\n添加元素测试:");
        list.add(10);
        list.add(20);
        list.add(30);
        System.out.println("添加3个元素后,元素个数: " + list.getLength());
        
        // 再次添加
        list.add(40);
        list.add(50);
        System.out.println("添加5个元素后,元素个数: " + list.getLength());
        
        // 继续添加
        System.out.println("\n测试超出容量:");
        boolean result = list.add(60);
        System.out.println("添加第6个元素结果: " + result);
        System.out.println("最终元素个数: " + list.getLength());
    }
}

2. 动态数组实现(可扩容)

特点
  • 基于数组实现,但支持自动扩容(容量不足时扩大数组大小);

  • 底层逻辑与 Java ArrayList 一致,需手动实现扩容、元素复制等核心逻辑;

  • 内存由 JVM 自动回收(无需 free(),GC 会处理无用数组)。

Java 示例代码(模拟 ArrayList 核心逻辑)
java 复制代码
public class DynamicSequenceList {
    private int[] data;
    private int length;
    private int maxSize;

    /** 无参构造 */
    public DynamicSequenceList() {
        this.maxSize = 10;
        this.data = new int[this.maxSize];
        this.length = 0;
    }

    /** 有参构造 */
    public DynamicSequenceList(int initialCapacity) {
        if (initialCapacity < 1) {
            throw new IllegalArgumentException("初始容量非法:" + initialCapacity);
        }
        this.maxSize = initialCapacity;
        this.data = new int[this.maxSize];
        this.length = 0;
    }

    //get方法
    public int getLength() { 
        return this.length;
    }
    public int getMaxSize() {
        return this.maxSize;
    }


    /**
     * 扩容方法:当元素个数达到最大容量时,扩容为原容量的1.5倍
     * 采用位运算(maxSize >> 1)计算半容量,比除法(maxSize / 2)更高效
     */
    private void expandCapacity() {
        int newMaxSize = this.maxSize + (this.maxSize >> 1);
        int[] newData = new int[newMaxSize];
        System.arraycopy(this.data, 0, newData, 0, this.length);
        this.data = newData;
        this.maxSize = newMaxSize;
        System.out.println("动态顺序表扩容成功,新容量:" + this.maxSize);
    }

    /** 向末尾添加元素(自动扩容) */
    public void add(int value) {
        if (this.length == this.maxSize) {
            expandCapacity();
        }
        this.data[this.length++] = value;
    }

    public Integer get(int index) {
        if (index < 0 || index >= this.length) {
            throw new IndexOutOfBoundsException("索引越界:" + index + ",当前长度:" + this.length);
        }
        return this.data[index];
    }

        
    public static void main(String[] args) {
    
        DynamicSequenceList list = new DynamicSequenceList(2);
    
        System.out.println("初始状态:");
        System.out.println("元素个数: " + list.getLength());
        System.out.println("最大容量: " + list.getMaxSize());
        
        System.out.println("\n添加元素测试:");
        for(int i = 0;i<12;i++){
            list.add(i);
        }
        System.out.println("添加12个元素后,元素个数: " + list.getLength());
        System.out.println("添加12个元素后,最大容量: " + list.getMaxSize());
        System.out.println("添加12个元素后,索引为3的元素: " + list.get(3));
    }
}
说明
  • >> 是右移运算符,属于二元操作符,需要两个操作数。原代码中的 this.maxSize >> 1 表示将maxSize的值右移1位,相当于整数除法除以2(向下取整);

  • 实际开发中无需自定义动态顺序表,直接使用 java.util.ArrayList 即可(如 List<Integer> list = new ArrayList<>()),其内部已封装扩容、增删改查等逻辑;

  • ArrayList 扩容倍数:默认初始容量为10,当元素数量达到容量时,扩容为原来的1.5倍

三、基本操作及复杂度(Java 实现)

以 "动态顺序表(DynamicSequenceList)" 为例,实现插入、删除、查找操作,并分析时间复杂度(核心衡量指标:操作与数据规模 n 的关系)。

1. 插入操作

操作逻辑
  1. 合法性校验 :判断插入索引是否在 [0, length] 范围内(0 可插首,length 可插尾);

  2. 扩容判断:若元素个数达到最大容量,触发扩容;

  3. 元素后移:从插入位置的 "下一个元素" 到 "最后一个元素",依次后移 1 位(空出插入位置);

  4. 插入元素:将新元素赋值到空出的位置,元素个数 + 1。

Java 示例代码
java 复制代码
public boolean insert(int index, int value) {
    if (index < 0 || index > this.length) {
        throw new IndexOutOfBoundsException("插入索引非法:" + index + ",当前长度:" + this.length);
    }
    // 2. 扩容(若需)
    if (this.length == this.maxSize) {
        expandCapacity();
    }
    // 3. 元素后移
    // 例:index=2,length=5 → 元素4→5、3→4、2→3,空出index=2
    for (int i = this.length; i > index; i--) {
        this.data[i] = this.data[i - 1];
    }
    // 4. 插入并更新长度
    this.data[index] = value;
    this.length++;
    return true;
}
时间复杂度分析
  • 最好情况 :插入到末尾(index = length),无需移动元素 → O(1)(如 ArrayList.add(value));

  • 最坏情况 :插入到首位(index = 0),需移动所有 n 个元素 → O(n)

  • 平均情况 :移动元素的平均次数为 n/2O(n)

2. 删除操作

操作逻辑
  1. 合法性校验 :判断删除索引是否在 [0, length-1] 范围内(无元素时无法删除);

  2. 记录待删元素:保存删除位置的元素值(用于返回);

  3. 元素前移:从删除位置的 "下一个元素" 到 "最后一个元素",依次前移 1 位(覆盖待删元素);

  4. 更新长度:元素个数 - 1(无需手动清空最后一个元素,后续添加会覆盖)。

Java 示例代码
java 复制代码
public Integer remove(int index) {
 if (index < 0 || index >= this.length) {
        throw new IndexOutOfBoundsException("删除索引非法:" + index + ",当前长度:" + this.length);
    }
    // 2. 记录待删元素
    int deletedValue = this.data[index];
    // 3. 元素前移(从前往后移,覆盖待删元素)
    // 例:index=2,length=5 → 元素3→2、4→3、5→4,覆盖index=2的元素
    for (int i = index; i < this.length - 1; i++) {
        this.data[i] = this.data[i + 1];
    }
    // 4. 更新长度(最后一个元素无需清空,后续添加会覆盖)
    this.length--;
    return deletedValue;
}
时间复杂度分析
  • 与插入操作逻辑一致:

    • 最好情况(删除末尾):O(1)

    • 最坏情况(删除首位):O(n)

    • 平均情况:O(n)

3. 查找操作

顺序表的查找分为 "按位序查找"(按索引)和 "按键值查找"(按元素内容),两者复杂度差异显著。

(1)按位序查找(按索引)
  • 逻辑:利用随机存取特性,直接通过索引定位元素。

  • Java 示例代码 (复用动态顺序表的 get 方法):

java 复制代码
public Integer getByIndex(int index) {
    if (index < 0 || index >= this.length) {
        System.out.println("查找索引越界:" + index);
        return null;
    }
    return this.data[index]; 
}
  • 时间复杂度O(1)(无循环,一步到位)。
(2)按键值查找(按元素内容)

需根据数组是否有序,选择不同查找方式:

数组类型 查找方式 核心逻辑 时间复杂度
无序数组 顺序查找 遍历所有元素,逐个比较 O(n)
有序数组 折半查找(二分查找) 利用有序性,每次缩小一半查找范围 O(log₂n)
① 无序数组:顺序查找(Java 示例)
java 复制代码
public int findByValueUnordered(int value) {
    for (int i = 0; i < this.length; i++) {
        if (this.data[i] == value) {
            return i; // 找到第一个匹配元素,返回索引
        }
    }
    return -1; // 遍历结束未找到
}
  • 平均比较次数(n+1)/2(最好 1 次,最坏 n 次)。
② 有序数组:折半查找(Java 示例)
java 复制代码
public int binarySearch(int value) {
    int left = 0; // 左边界
    int right = this.length - 1; // 右边界

    while (left <= right) {
        // 计算中间索引:用left + (right-left)/2 避免left+right溢出(同 (left+right)/2)
        int mid = left + (right - left) / 2;

        if (this.data[mid] == value) {
            return mid; // 找到目标值,返回索引
        } else if (this.data[mid] < value) {
            left = mid + 1; // 目标值在右半区,调整左边界
        } else {
            right = mid - 1; // 目标值在左半区,调整右边界
        }
    }
    return -1;
}

当 left 和 right 都接近 Integer.MAX_VALUE (2^31-1) 时,它们的和可能会超过 int 的表示范围,导致溢出,结果变成负数

  • 关键前提:数组必须有序(升序 / 降序,需与比较逻辑匹配);

  • 复杂度优势log₂n 增长极慢(如 n=1000 时,仅需 10 次比较)。

四、关联算法

1. 核心算法基础

顺序表是数组类算法的载体,常用核心算法包括排序和查找:

(1)排序算法(适用于顺序表)
算法名称 时间复杂度 Java 实现(工具类) 适用场景
快速排序 O(n log n)(平均) Arrays.sort(int[])(基本类型) 大规模数据,对效率要求高
归并排序 O(n log n)(稳定) Arrays.sort(Object[])(对象类型) 需稳定排序(如自定义对象)
插入排序 O(n²)(最坏) 手动实现(小数据量) 数据量小或接近有序
  • 说明 :Java Arrays.sort() 是优化后的排序方法,底层根据元素类型选择不同算法(基本类型用双轴快速排序,对象类型用 TimSort,一种归并排序变体),实际开发中优先使用。
(2)查找算法
  • 无序表:顺序查找(O(n));
  • 有序表:折半查找(O(log₂n)),或 Collections.binarySearch(List, T)(针对 List 集合)。

个人观点,仅供参考

如有错误还望指正,共同进步!

相关推荐
Boop_wu1 小时前
[Java EE] 计算机基础
java·服务器·前端
橘子海全栈攻城狮1 小时前
【源码+文档+调试讲解】基于Spring Boot的考务管理系统设计与实现 085
java·spring boot·后端·spring
神仙别闹2 小时前
基于QT(C++) 实现哈夫曼压缩(多线程)
java·c++·qt
Python私教2 小时前
Python 开发环境安装与配置全指南(2025版)
开发语言·python
百锦再2 小时前
第12章 测试编写
android·java·开发语言·python·rust·go·erlang
无敌最俊朗@2 小时前
C++ 并发与同步速查笔记(整理版)
开发语言·c++·算法
C2H5OH6662 小时前
Netty详解-02
java·websocket·网络协议·tcp/ip·tomcat·netty·nio
Elastic 中国社区官方博客3 小时前
Observability:适用于 PHP 的 OpenTelemetry:EDOT PHP 加入 OpenTelemetry 项目
大数据·开发语言·人工智能·elasticsearch·搜索引擎·全文检索·php