当前位置: 首页 > news >正文

【数据结构】线性表——顺序表

目录

  • 线性表
    • 线性表的定义
    • 顺序表
      • 顺序表的定义
      • 顺序表的初始化
        • 静态分配
        • 动态分配
      • 顺序表的插入
      • 顺序表的删除
      • 顺序表的查找
        • 按位查找
        • 按值查找
        • 代码实现
      • 总结

线性表

线性表的定义

定义线性表(Linear List) 是一种数据结构,它是 n 个具有相同类型的数据元素的有限序列

若把元素记为 a1,a2,…,ana_1,a_2,…,a_na1,a2,,an,满足:

  • 除第一个元素外,每个元素有且仅有一个直接前驱;
  • 除最后一个元素外,每个元素有且仅有一个直接后继;
  • 线性结构,逻辑次序是线性的。

典型基本操作(抽象接口,后面不同实现会给出具体代码):

  • InitList(&L):初始化表。构造一个空的线性表 L,分配内存空间
  • DestroyList(&L):销毁操作。销毁线性表,并释放线性表 L 所占用的内存空间
  • ListInsert(&L, i, e):插入操作。在表 L 中的第 i 个位置上插入指定元素 e
  • ListDelete(&L, i, &e):删除操作。删除表 L 中第 i 个位置的元素,并用 e 返回删除元素的值
  • LocateElem(L, e):按值查找操作。在表 L 中查找具有给定关键字值的元素
  • GetElem(&L, i):按位查找操作。获取表 L 中第 i 个位置的元素的值
  • ListLength(L):求表长。返回线性表 L 的长度,即 L 中数据元素的个数
  • PrintList(L):输出操作。按前后顺序输出线性表 L 中的所有元素值
  • ListEmpty(L):判空操作。若 L 为空表,则返回 true,否则返回 false

顺序表

顺序表的定义

定义:顺序表(Sequential List)是一种线性表的顺序存储结构,它使用一段连续的存储单元来依次存放线性表中的元素。

特点

  1. 逻辑上相邻的元素,在物理上也相邻。
  2. 支持随机存取,可以通过下标直接访问任意元素。
  3. 插入和删除操作需要移动元素,效率较低(平均要移动一半元素)。

适用场景:适合数据量相对固定、查找操作频繁的场景。

顺序表的存储结构

  1. 使用数组保存数据元素。
  2. 需要一个变量记录顺序表当前长度(元素个数)。
  3. 通常还需要一个变量记录顺序表的最大容量(数组大小)。

顺序表的初始化

静态分配
  1. 在内存中分配存储顺序表 L 的空间。包括 MAXSIZE*sizeof(ElemType) 和存储 length 的空间
  2. 将各个数据元素的值设为默认值(可省略,后面会讲述原因)
  3. length 的值设为 0

原理:在定义顺序表时,就用固定大小的数组分配存储空间。

特点

  1. 大小在编译时确定,运行期间不可改变。
  2. 空间利用率可能不高,要么浪费内存,要么不够用。
  3. 优点是实现简单,访问效率高。

C 语言演示代码如下:

#include <stdio.h>
#include <stdlib.h>#define MAXSIZE 10typedef struct {int data[MAXSIZE];  // 存放元素的静态数组int length;         // 当前表的长度
} SeqList;// 顺序表初始化实现
void InitList(SeqList *L) {for(int i=0; i<MAXSIZE; i++)L->data[i]=0;  // 将所有数据元素设置为默认初始值L->length = 0;  // 通过指针L访问顺序表的length成员,将其赋值为0。
}int main() {SeqList L;  // 声明一个顺序表InitList(&L);  // 初始化顺序表return 0;
}

输出一下结果:

printf("顺序表元素: \n");
for (int i = 0; i < MAXSIZE; i++) {printf("data[%d]=%d\n", i, L.data[i]);
}

结果如下:

在这里插入图片描述

假如在初始化操作中不将所有数据元素设置为默认初始值

void InitList(SeqList *L) {L->length = 0;
}

输出的结果则大不相同:

造成这样的原因是内存中存在脏数据,也就是说读取到了内存中未初始化的残留值,但是前面说过是可以省略初始化默认值的,这是因为打印数组如果直接用 i<MAXSIZE 是违规的,而且如果 MAXSIZE 的值很大,还会造成初始化时间过长,内存浪费。正确的方法应该是用 i<L.length 通过顺序表的长度来访问数据元素,这样才是合规的,才能避免读取到内存中的脏数据。

顺序表初始化的代码如下所示:

#include <stdio.h>
#include <stdlib.h>#define MAXSIZE 10typedef struct {int data[MAXSIZE];  // 存放元素的静态数组int length;         // 当前表的长度
} SeqList;// 顺序表初始化实现
void InitList(SeqList *L) {L->length = 0;  // 通过指针L访问顺序表的length成员,将其赋值为0。
}int main() {SeqList L;  // 声明一个顺序表InitList(&L);  // 初始化顺序表printf("顺序表元素: \n");for (int i = 0; i < L.length; i++) {printf("data[%d]=%d\n", i, L.data[i]);}return 0;
}

静态分配的局限性: 顺序表容量不可变,存满后无法扩展;若初始申请过大空间,可能浪费内存资源。

动态分配

原理:在运行时根据需要,动态申请存储空间(用 malloccalloc ),数组大小可以灵活设定。

  1. 传统方法:malloc + free

    • 定义动态顺序表
      // 定义动态顺序表结构体
      typedef struct {int *data;      // 动态分配数组的指针int MaxSize;    // 顺序表最大容量int length;     // 顺序表现有长度
      } SeqList;
      
    • malloc 申请一片连续的内存空间,并初始化
      // 初始化顺序表
      void InitList(SeqList *L) {// 分配内存:InitSize 个 int 大小的空间L->data = (int *)malloc(InitSize * sizeof(int));if (L->data == NULL) {  // 检查内存分配是否失败printf("内存分配失败!\n");exit(1);  // 直接终止程序(也可返回错误码处理)}L->length = 0;          // 初始化为空表L->MaxSize = InitSize;  // 设置初始最大容量
      }
      
    • 增加动态数组长度
      // 扩容函数:将最大容量增加 len
      void IncreaseSize(SeqList *L, int len) {int *p = L->data;  // 保存原数据的地址(用于释放和拷贝)// 分配新内存:原容量 + lenL->data = (int *)malloc((L->MaxSize + len) * sizeof(int));if (L->data == NULL) {printf("内存扩容失败!\n");exit(1);}// 拷贝原有数据到新内存for (int i = 0; i < L->length; i++) {L->data[i] = p[i];}L->MaxSize += len;  // 更新最大容量free(p);            // 释放原内存(避免内存泄漏)
      }
      
    • main 函数中执行方法
      int main() {SeqList L;          // 声明顺序表变量InitList(&L);       // 初始化(传地址,因为函数参数是指针)// 手动插入几个元素(演示用,实际可封装插入函数)for (int i = 0; i < 5; i++) {if (L.length < L.MaxSize) {  // 确保不超出容量L.data[L.length++] = i;  // 插入数据并更新长度}}printf("扩容前:MaxSize = %d, length = %d\n", L.MaxSize, L.length);// 扩容:最大容量增加 5IncreaseSize(&L, 5);printf("扩容后:MaxSize = %d, length = %d\n", L.MaxSize, L.length);// 释放动态分配的内存(必须!否则内存泄漏)free(L.data);return 0;
      }
      
    • 核心逻辑说明
      • 动态分配:通过 malloc 申请堆内存,避免栈内存大小固定的限制;
      • 扩容流程:
        1. 保存原数据指针 p
        2. 分配更大的新内存;
        3. 拷贝原数据到新内存;
        4. 更新容量并释放原内存;
      • 使用后释放:动态分配的内存需手动 free,否则程序结束前不会自动回收。

    完整代码如下:

    #include <stdio.h>
    #include <stdlib.h>  // 包含 malloc、free 等函数#define InitSize 10  // 默认最大长度// 定义动态顺序表结构体
    typedef struct {int *data;      // 动态分配数组的指针int MaxSize;    // 顺序表最大容量int length;     // 顺序表现有长度
    } SeqList;// 初始化顺序表
    void InitList(SeqList *L) {// 分配内存:InitSize 个 int 大小的空间L->data = (int *)malloc(InitSize * sizeof(int));if (L->data == NULL) {  // 检查内存分配是否失败printf("内存分配失败!\n");exit(1);  // 直接终止程序(也可返回错误码处理)}L->length = 0;          // 初始化为空表L->MaxSize = InitSize;  // 设置初始最大容量
    }// 扩容函数:将最大容量增加 len
    void IncreaseSize(SeqList *L, int len) {int *p = L->data;  // 保存原数据的地址(用于释放和拷贝)// 分配新内存:原容量 + lenL->data = (int *)malloc((L->MaxSize + len) * sizeof(int));if (L->data == NULL) {printf("内存扩容失败!\n");exit(1);}// 拷贝原有数据到新内存for (int i = 0; i < L->length; i++) {L->data[i] = p[i];}L->MaxSize += len;  // 更新最大容量free(p);            // 释放原内存(避免内存泄漏)
    }int main() {SeqList L;          // 声明顺序表变量InitList(&L);       // 初始化(传地址,因为函数参数是指针)// 【示例】手动插入几个元素(演示用,实际可封装插入函数)for (int i = 0; i < 5; i++) {if (L.length < L.MaxSize) {  // 确保不超出容量L.data[L.length++] = i;  // 插入数据并更新长度}}printf("扩容前:MaxSize = %d, length = %d\n", L.MaxSize, L.length);// 扩容:最大容量增加 5IncreaseSize(&L, 5);printf("扩容后:MaxSize = %d, length = %d\n", L.MaxSize, L.length);// 释放动态分配的内存(必须!否则内存泄漏)free(L.data);return 0;
    }
    

    程序运行结果如下:

    在这里插入图片描述

  2. 使用标准库专门用于 “调整已分配内存大小” 的函数 realloc

    • 顺序表结构体和初始化方法不变,扩容的方法为:
      // 扩容:容量翻倍(用 realloc)
      void Expand(SeqList *L) {int *new_data = realloc(L->data, 2 * L->MaxSize * sizeof(int));if (new_data == NULL) {printf("扩容失败!\n");return;}L->data = new_data;       // 更新指针L->MaxSize *= 2;          // 容量翻倍printf("扩容成功!新容量:%d\n", L->MaxSize);
      }
      
    • main 函数中执行方法
      int main() {SeqList L;          // 声明顺序表变量InitList(&L);       // 初始化(传地址,因为函数参数是指针)// 手动插入几个元素(演示用,实际可封装插入函数)for (int i = 0; i < 5; i++) {if (L.length >= L.MaxSize) {  // 确保不超出容量Expand(L);}L.data[L.length++] = i;}printf("扩容前:MaxSize = %d, length = %d\n", L.MaxSize, L.length);// 扩容:最大容量增加 5IncreaseSize(&L, 5);printf("扩容后:MaxSize = %d, length = %d\n", L.MaxSize, L.length);// 释放动态分配的内存(必须!否则内存泄漏)free(L.data);return 0;
      }
      

    完整代码如下:

    #include <stdio.h>
    #include <stdlib.h>typedef struct {int *data;      // 动态数组int MaxSize;    // 当前最大容量int length;     // 实际长度
    } SeqList;// 初始化:初始容量为10
    void InitList(SeqList *L) {L->data = (int*)malloc(10 * sizeof(int));L->MaxSize = 10;L->length = 0;
    }// 扩容:容量翻倍(用 realloc)
    void Expand(SeqList *L) {int *new_data = realloc(L->data, 2 * L->MaxSize * sizeof(int));if (new_data == NULL) {printf("扩容失败!\n");return;}L->data = new_data;       // 更新指针L->MaxSize *= 2;          // 容量翻倍printf("扩容成功!新容量:%d\n", L->MaxSize);
    }int main() {SeqList L;          // 声明顺序表变量InitList(&L);       // 初始化(传地址,因为函数参数是指针)printf("扩容前:MaxSize = %d, length = %d\n", L.MaxSize, L.length);// 手动插入几个元素(演示用,实际可封装插入函数)for (int i = 0; i < 15; i++) {if (L.length >= L.MaxSize) {  // 确保不超出容量Expand(&L);}L.data[L.length++] = i;}printf("扩容后:MaxSize = %d, length = %d\n", L.MaxSize, L.length);// 释放动态分配的内存(必须!否则内存泄漏)free(L.data);return 0;
    }
    

    程序运行结果如下:

    在这里插入图片描述

顺序表的特性:

  • 具有随机访问特性,可在常数级时间找到元素;
  • 存储密度高,仅存储数据元素本身;
  • 静态分配不可拓展容量,动态分配拓展容量时间开销高;
  • 插入删除操作不方便,需移动大量元素。

顺序表的插入

插入操作的基本概念:插入操作是往线性表的第 i 个位置(位序,从 1 开始)插入指定元素 e。以静态分配的顺序表为例,若要在第三个位置插入元素,需将第三个位置及之后的元素依次后移,再插入新元素,同时顺序表长度加 1。

// 插入元素:在位置i插入元素e (1-based)
int Insert(SeqList *L, int i, int e) {if (i < 1 || i > L->length + 1) return 0;  // 插入位置非法if (L->length >= MAXSIZE) return 0;        // 表已满for (int j = L->length; j >= i; j--) {L->data[j] = L->data[j-1];}L->data[i-1] = e;L->length++;return 1;
}

这段代码是顺序表的插入操作实现,功能是在顺序表的指定位置(1-based,即从 1 开始计数)插入一个新元素,下面逐行解释其逻辑:

  1. 函数定义
    int Insert(SeqList *L, int i, int e)
    
    • 作用:向顺序表中插入元素
    • 参数:
      • SeqList *L:指向顺序表的指针(需要修改原顺序表,所以传指针)
      • int i:插入位置(1-based,例如 i=1 表示插入到第一个位置)
      • int e:要插入的元素值
    • 返回值:1 表示插入成功,0 表示插入失败
  2. 插入位置合法性检查
    if (i < 1 || i > L->length + 1) return 0;
    
    • 顺序表的插入位置有严格限制:
      • 最小位置是 1(不能在第 0 个位置之前插入);
      • 最大位置是 L->length + 1(可以插入到当前表的末尾,即最后一个元素的后面)。
    • 例如:如果当前表长度为 3(有 3 个元素),合法的插入位置是 1、2、3、4(分别对应 “第 1 个元素前” 到 “第 3 个元素后”)。
    • i 不在这个范围,返回 0(插入失败)。
  3. 表满检查
    if (L->length >= MAXSIZE) return 0;
    
    • 顺序表的最大容量由 MAXSIZE 定义(静态顺序表的固定大小)。
    • 若当前表的长度(L->length)已经等于或超过 MAXSIZE,说明表已满,无法插入新元素,返回0(插入失败)。
  4. 元素后移(核心步骤)
    for (int j = L->length; j >= i; j--) {L->data[j] = L->data[j-1];
    }
    
    • 插入元素前,需要将插入位置及之后的元素向后移动一位,腾出插入位置。
    • 为什么从后往前移?
      假设从前往后移(j从i开始),会导致后面的元素被前面的元素覆盖(例如:data[i] 会先被 data[i-1] 覆盖,导致 data[i] 的原值丢失)。
    • 示例:
      原表:[10, 20, 30](长度 3,MAXSIZE=5),要在 i=2(第 2 个位置)插入 25。
      • 循环从 j=3(当前长度)开始,到 j=2(插入位置 i=2)结束:
        • j=3data[3] = data[2] → 表变为 [10, 20, 30, 30]
        • j=2data[2] = data[1] → 表变为 [10, 20, 20, 30]
      • 移动后,i=2 对应的索引 1(因为数组是 0-based)位置被腾出。
  5. 插入新元素
    L->data[i-1] = e;
    
    • 顺序表的存储数组 data 是 0-based(从 0 开始计数),而插入位置 i 是 1-based,因此需要转换为索引 i-1
    • 示例:上述例子中 i=2,对应索引 1,执行 data[1] = 25 → 表变为 [10, 25, 20, 30]
  6. 更新表长度
    L->length++;
    return 1;
    
    • 插入成功后,表的实际长度加 1(length 是当前元素个数)。
    • 返回 1 表示插入成功。

完整的实现如下所示:

#include <stdio.h>
#include <stdlib.h>#define MAXSIZE 10typedef struct {int data[MAXSIZE];  // 存放元素的数组int length;         // 当前表的长度
} SeqList;// 初始化顺序表
void InitList(SeqList *L) {L->length = 0;
}// 插入元素:在位置i插入元素e (1-based)
int Insert(SeqList *L, int i, int e) {if (i < 1 || i > L->length + 1) return 0;  // 插入位置非法if (L->length >= MAXSIZE) return 0;        // 表已满for (int j = L->length; j >= i; j--) {L->data[j] = L->data[j-1];}L->data[i-1] = e;L->length++;return 1;
}int main() {SeqList L;InitList(&L);Insert(&L, 1, 10);Insert(&L, 2, 20);Insert(&L, 3, 30);printf("顺序表元素: ");for (int i = 0; i < L.length; i++) {printf("%d ", L.data[i]);}printf("\n");return 0;
}

程序运行结果如下:

在这里插入图片描述

插入操作的时间复杂度:

  • 最好情况:新元素插入到表尾,不需要移动元素 i=n+1i = n+1i=n+1,循环 0 次;最好时间复杂度 = O(1)O(1)O(1)

  • 最坏情况:新元素插入到表头,需要将原有的 n 个元素全都向后移动 i=1i = 1i=1,循环 n 次;最坏时间复杂度 = O(n)O(n)O(n)

  • 平均情况:

    假设新元素插入到任何一个位置的概率相同,即 i=1,2,3,…,length+1i = 1,2,3, \dots, \text{length+1}i=1,2,3,,length+1 的概率都是 p=1n+1p = \boldsymbol{\dfrac{1}{n+1}}p=n+11

    • i=1i = 1i=1,循环 nnn 次;
    • i=2i=2i=2 时,循环 n−1n-1n1 次;
    • i=3i=3i=3,循环 n−2n-2n2 次 ……
    • i=n+1i=n+1i=n+1 时,循环 0 次

    平均循环次数 = np+(n−1)p+(n−2)p+⋯+1⋅p=n(n+1)2⋅1n+1=n2⇒np + (n-1)p + (n-2)p + \dots + 1 \cdot p= \frac{n(n+1)}{2} \cdot \frac{1}{n+1} = \frac{n}{2}\boldsymbol{\Rightarrow}np+(n1)p+(n2)p++1p=2n(n+1)n+11=2n 平均时间复杂度 = O(n)O(n)O(n)

顺序表的删除

删除操作的基本概念及代码实现:删除操作是删除线性表中第 i 个位置的元素,需将该元素后面的元素依次前移,同时顺序表长度减 1。代码中删除操作的参数 e 为引用型,用于返回被删除的元素,且顺序表参数也需加引用,确保操作影响原顺序表。

// 删除元素:删除位置i的元素
int Delete(SeqList *L, int i, int *e) {if (i < 1 || i > L->length) return 0;*e = L->data[i-1];for (int j = i; j < L->length; j++) {L->data[j-1] = L->data[j];}L->length--;return 1;
}

这段代码是顺序表的删除操作实现,功能是删除顺序表中指定位置(1-based,从 1 开始计数)的元素,并通过指针返回被删除的元素值。下面逐行解释其逻辑:

  1. 函数定义
    int Delete(SeqList *L, int i, int *e)
    
    • 作用:删除顺序表中指定位置的元素,并返回被删除的元素
    • 参数:
      • SeqList *L:指向顺序表的指针(需要修改原顺序表,因此传指针)
      • int i:要删除的位置(1-based,例如 i=2 表示删除第 2 个元素)
      • int *e:指针,用于存储被删除的元素值(通过指针 “传出” 结果)
    • 返回值:1 表示删除成功,0 表示删除失败
  2. 删除位置合法性检查
    if (i < 1 || i > L->length) return 0;
    
    • 顺序表的删除位置必须是
      已存在的元素位置:
      • 最小位置是 1(不能删除第 0 个位置,因为元素从 1 开始计数);
      • 最大位置是 L->length(不能删除超过当前表长的位置,例如表长为 3 时,只能删除 1、2、3 位置)。
    • i 不在这个范围(位置非法),返回 0(删除失败)。
  3. 保存被删除的元素
    *e = L->data[i-1];
    
    • 顺序表的存储数组 data 是 0-based(从 0 开始计数),而删除位置 i 是 1-based,因此需要转换为索引 i-1
    • 通过指针 e 将被删除的元素值 “传出” 函数(例如,调用时传入 &val,则 val 会被赋值为被删除的元素)。
  4. 元素前移(核心步骤)
    for (int j = i; j < L->length; j++) {L->data[j-1] = L->data[j];
    }
    
    • 删除元素后,需要将删除位置后面的所有元素向前移动一位,填补被删除元素的空位。
    • 为什么从前往后移?
      假设删除第 i 个元素(对应数组索引 i-1),后面的元素(i, i+1, …, length-1)需要依次前移:
      • ji(1-based)开始,对应数组索引 j-1(删除位置的下一个元素);
      • 每次将 data[j](当前元素)赋值给 data[j-1](前一个位置),直到移动完最后一个元素。
    • 示例:
      原表:[10, 25, 20, 30](长度 4),删除 i=2(第 2 个元素,值为 25)。
      • 循环从 j=2(1-based)开始,到 j<4(表长)结束:
        • j=2data[1] = data[2] → 表变为 [10, 20, 20, 30]
        • j=3data[2] = data[3] → 表变为 [10, 20, 30, 30]
      • 移动后,被删除元素的位置被后面的元素填补,最后一个元素(索引 3)变为冗余值(但不影响,因为后续会更新表长)。
  5. 更新表长度
    L->length--;
    return 1;
    
    • 删除成功后,表的实际长度减 1(length 是当前元素个数,删除后减少一个)。
    • 返回 1 表示删除成功。

完整的实现如下所示:

#include <stdio.h>
#include <stdlib.h>#define MAXSIZE 100typedef struct {int data[MAXSIZE];  // 存放元素的数组int length;         // 当前表的长度
} SeqList;// 初始化顺序表
void InitList(SeqList *L) {L->length = 0;
}// 插入元素:在位置i插入元素e (1-based)
int Insert(SeqList *L, int i, int e) {if (i < 1 || i > L->length + 1) return 0;  // 插入位置非法if (L->length >= MAXSIZE) return 0;        // 表已满for (int j = L->length; j >= i; j--) {L->data[j] = L->data[j-1];}L->data[i-1] = e;L->length++;return 1;
}// 删除元素:删除位置i的元素
int Delete(SeqList *L, int i, int *e) {if (i < 1 || i > L->length) return 0;*e = L->data[i-1];for (int j = i; j < L->length; j++) {L->data[j-1] = L->data[j];}L->length--;return 1;
}int main() {SeqList L;InitList(&L);Insert(&L, 1, 10);Insert(&L, 2, 20);Insert(&L, 3, 30);printf("顺序表元素: ");for (int i = 0; i < L.length; i++) {printf("%d ", L.data[i]);}printf("\n");int e;Delete(&L, 2, &e);printf("删除的元素: %d\n", e);printf("删除后顺序表: ");for (int i = 0; i < L.length; i++) {printf("%d ", L.data[i]);}printf("\n");return 0;
}

程序运行结果如下:

在这里插入图片描述

删除操作的时间复杂度:

  • 最好情况:删除最后一个元素 i=lengthi = lengthi=length,无需移动元素 → O(1)O(1)O(1)
  • 最坏情况:删除第一个元素 i=1i = 1i=1,需要移动 length−1length-1length1 个元素 → O(n)O(n)O(n)
  • 平均情况:假设删除每个位置的概率相同,平均移动次数为 n−12→O(n)\frac{n-1}{2} → O(n)2n1O(n)

顺序表的查找

按位查找
  1. 静态分配
    • 存储结构
      静态顺序表用一个定长数组存放元素,例如:
      #define MAXSIZE 100
      typedef struct {int data[MAXSIZE];int length; // 当前长度
      } SqList;
      
    • 按位查找
      所谓“按位查找”,就是找顺序表中第 i 个位置上的元素(注意是逻辑位序,不是数组下标)。
      因为:
      • 数组下标从 0 开始
      • 顺序表位序从 1 开始
        所以数组下标 = i - 1
    • 实现示例
      int GetElem(SqList L, int i) {if (i < 1 || i > L.length) { // 判断 i 合法性printf("位置不合法\n");return -1;}return L.data[i-1]; // 返回第 i 个元素
      }
      
    • 特点
      • 随机存取:直接通过下标算出地址 → O(1) 时间。
      • 静态数组内存编译时已经确定。
  2. 动态分配
    • 存储结构
      动态分配时,元素存储空间不是固定的,而是运行时用 malloc 申请:
      typedef struct {int *data;   // 指针,指向一段连续存储空间int length;  int maxSize; 
      } SqList;void InitList(SqList *L, int maxSize) {L->data = (int*)malloc(maxSize * sizeof(int));L->length = 0;L->maxSize = maxSize;
      }
      
    • 按位查找
      即使 data 是指针,也能像数组一样下标访问,因为 C 语言规定:
      • data[i] 实际上是 *(data + i)
      int GetElem(SqList L, int i) {if (i < 1 || i > L.length) return -1;return L.data[i-1];
      }
      
    • 特点
      • 运行时决定表的容量,更灵活。
      • 底层同样是连续内存,所以查找操作也是 O(1)。
  3. 时间复杂度分析
    • 按位查找不需要循环和递归。
    • 只做 一次地址计算 + 一次访问 → O(1)。
    • 这就是“顺序表的随机存取特性”:内存连续,元素大小相同,所以可以通过公式:
      地址 = 基地址 + (i-1) * 元素大小
      
      直接定位。
按值查找
  1. 基本思路
    • 从头到尾依次扫描表元素,找到与给定值 e 相等的元素。
    • 若找到 → 返回其位序(数组下标 + 1)。
    • 若没找到 → 返回 0 或 -1 表示失败。
  2. 基本数据类型(int、char)
    • 直接用 == 比较。
    • 示例代码
      int LocateElem(SqList L, int e) {for (int i = 0; i < L.length; i++) {if (L.data[i] == e) {return i + 1; // 位序}}return 0; // 没找到
      }
      
  3. 结构体类型
    • 如果表中元素是结构体,不能直接 == 比较。
    • C语言中需要逐一比较成员
      typedef struct {int id;char name[20];
      } Student;bool equal(Student a, Student b) {return (a.id == b.id && strcmp(a.name, b.name) == 0);
      }
      
  4. 时间复杂度分析
    • 最好情况:目标元素就在第一个位置 → 只比较一次 → O(1)O(1)O(1)
    • 最坏情况:目标元素在最后,或者不存在 → 比较 nnn 次 → O(n)O(n)O(n)
    • 平均情况:假设目标元素等概率分布在各位置,平均比较次数为:
      1+2+⋯+nn=n+12{1+2+⋯+n \over n}={n+1 \over 2}n1+2++n=2n+1
      所以平均时间复杂度 = O(n)O(n)O(n)
代码实现
  1. 静态分配
    #include <stdio.h>
    #include <stdlib.h>#define MAXSIZE 100
    typedef struct {int data[MAXSIZE]; // 静态数组int length;        // 当前长度
    } SqList_Static;// 按位查找(静态)
    int GetElem_Static(SqList_Static L, int i) {if (i < 1 || i > L.length) {printf("位置不合法\n");return -1;}return L.data[i - 1];
    }// 按值查找(静态)
    int LocateElem_Static(SqList_Static L, int e) {for (int i = 0; i < L.length; i++) {if (L.data[i] == e) return i + 1; // 返回位序}return 0; // 未找到
    }int main() {SqList_Static sList;sList.length = 5;for (int i = 0; i < sList.length; i++) {sList.data[i] = (i + 1) * 10;  // [10, 20, 30, 40, 50]}printf("静态顺序表测试:\n");printf("第3个元素是:%d\n", GetElem_Static(sList, 3));printf("元素40的位置是:%d\n", LocateElem_Static(sList, 40));printf("元素99的位置是:%d\n\n", LocateElem_Static(sList, 99));return 0;
    }
    
    程序运行结果如下:
    在这里插入图片描述
  2. 动态分配
    #include <stdio.h>
    #include <stdlib.h>typedef struct {int *data;     // 动态数组int length;    // 当前长度int maxSize;   // 最大容量
    } SqList_Dynamic;// 初始化动态顺序表
    void InitList(SqList_Dynamic *L, int maxSize) {L->data = (int*)malloc(maxSize * sizeof(int));L->length = 0;L->maxSize = maxSize;
    }// 按位查找(动态)
    int GetElem_Dynamic(SqList_Dynamic L, int i) {if (i < 1 || i > L.length) {printf("位置不合法\n");return -1;}return L.data[i - 1];
    }// 按值查找(动态)
    int LocateElem_Dynamic(SqList_Dynamic L, int e) {for (int i = 0; i < L.length; i++) {if (L.data[i] == e) return i + 1;}return 0;
    }int main() {SqList_Dynamic dList;InitList(&dList, 10);dList.length = 5;for (int i = 0; i < dList.length; i++) {dList.data[i] = (i + 1) * 100;  // [100, 200, 300, 400, 500]}printf("动态顺序表测试:\n");printf("第4个元素是:%d\n", GetElem_Dynamic(dList, 4));printf("元素300的位置是:%d\n", LocateElem_Dynamic(dList, 300));printf("元素999的位置是:%d\n", LocateElem_Dynamic(dList, 999));// 释放动态分配的内存free(dList.data);return 0;
    }
    
    程序运行结果如下:
    在这里插入图片描述

总结

在这里插入图片描述

http://www.dtcms.com/a/341144.html

相关文章:

  • 循环神经网络(RNN, Recurrent Neural Network)
  • Effective C++ 条款52:写了placement new也要写placement delete
  • 使用acme.sh自动申请AC证书,并配置自动续期,而且解决华为云支持问题,永久免费自动续期!
  • Spring Boot 定时任务与 xxl-job 灵活切换方案
  • 层在init中只为创建线性层,forward的对线性层中间加非线性运算。且分层定义是为了把原本一长个代码的初始化和运算放到一个组合中。
  • B站 韩顺平 笔记 (Day 24)
  • C++ std::optional 深度解析与实践指南
  • 当 AI 开始 “理解” 情绪:情感计算如何重塑人机交互的边界
  • linux报permission denied问题
  • Advanced Math Math Analysis |01 Limits, Continuous
  • uniapp打包成h5,本地服务器运行,路径报错问题
  • PyTorch API 4
  • 使数组k递增的最少操作次数
  • 路由器的NAT类型
  • 确保测试环境一致性与稳定性 5大策略
  • AI 效应: GPT-6,“用户真正想要的是记忆”
  • 获取本地IP地址、MAC地址写法
  • SQL 中大于小于号的表示方法总结
  • Bitcoin有升值潜力吗
  • 《代码沙盒深度实战:iframe安全隔离与实时双向通信的架构设计与落地策略》
  • 在SQL中使用大模型时间预测模型TimesFM
  • Mybatis执行SQL流程(五)之MapperProxy与MapperMethod
  • zoho crm api 无法修改富文本字段的原因:api 版本太低
  • 23种设计模式——构建器模式(Builder Pattern)详解
  • Spring Boot Controller 使用 @RequestBody + @ModelAttribute 接收请求
  • 车联网(V2X)中万物的重新定义---联网汽车新时代
  • Dubbo 的 Java 项目间调用的完整示例
  • 分析NeRF模型中颜色计算公式中的参数
  • Paraformer实时语音识别中的碎碎念
  • RuntimeError: Dataset scripts are no longer supported, but found wikipedia.py