[入门必看]数据结构2.2:线性表的顺序表示
创始人
2025-05-31 01:12:04
0

[入门必看]数据结构2.2:线性表的顺序表示

  • 第二章 线性表
  • 2.2 线性表的顺序表示
    • 知识总览
      • 2.2.1 顺序表的定义
      • 2.2.2_1 顺序表的插入删除
      • 2.2.2_2 顺序表的查找
    • 2.2.1 顺序表的定义
      • 顺序表的实现——静态分配
      • 顺序表的实现——动态分配
      • 顺序表的特点
    • 2.2.2_1 顺序表的插入和删除
      • 顺序表的插入
        • 插入操作的时间复杂度
      • 顺序表的删除
        • 删除操作的时间复杂度
    • 2.2.2_2 顺序表的查找
      • 按位查找
        • 按位查找的时间复杂度
      • 按值查找
        • 按值查找的时间复杂度
  • 知识回顾与重要考点
    • 2.2.1顺序表的定义
    • 2.2.2_1 顺序表的插入和删除
    • 2.2.2_2 顺序表的查找


第二章 线性表

小题考频:5
大题考频:12


2.2 线性表的顺序表示

难度:☆☆☆

知识总览

2.2.1 顺序表的定义

在这里插入图片描述

2.2.2_1 顺序表的插入删除

在这里插入图片描述

2.2.2_2 顺序表的查找

在这里插入图片描述


2.2.1 顺序表的定义

在这里插入图片描述
顺序表——用顺序存储的方式实现线性表
顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。

在这里插入图片描述
各个元素数据类型相同,所占的内存空间一样大。
顺序表中各元素在物理内存上连续存放。
第二个元素存放的位置 - 顺序表起始地址+数据元素的大小
第三个元素存放的位置 - 顺序表起始地址+2*数据元素的大小

  • Question:如何知道一个数据元素大小?
    ——C语言中:sizeof(ElemType)

Eg1.
sizeof(int) = 4B

Eg2.

typedef struct{
int num; //号数
int people; //人数
}Customer;

sizeof(Customer) = 8B

ElemType就是顺序表中存放的数据元素类型


顺序表的实现——静态分配

#define MaxSize 10 //定义最大长度
typedef struct{ElemType data[MaxSize]; //用静态的“数组”存放数据元素int length; //顺序表的当前长度
}SqList; //顺序表的类型定义(静态分配方式)

数组长度大小确定后不可以改变

给各个数据元素分配连续的存储空间,大小为MaxSize*sizeof(ElemType)

Sq:sequence —— 顺序,序列

代码实现:

#include 
#define MaxSize 10 //定义最大长度
typedef struct{int data[MaxSize]; //用静态的“数组”存放数据元素 - 数据元素类型(ElemType)是Intint length; //顺序表的当前长度
}SqList; //顺序表的类型定义//基本操作 - 初始化一个顺序表
void InitList(SqList &L){for(int i = 0; i < MaxSize; i++) //Step 2L.data[i] = 0; //将所有数据元素设置为默认值L.length = 0; //顺序表初始长度为0 - Step 3
}int main(){SqList L; //声明一个顺序表 - Step 1InitList(L); //初始化顺序表//……return 0;
}

Step1:在内存中分配存储顺序表L的空间。
包括:MaxSize*sizeof(ElemType)
和 存储length的空间

Step2:把各个数据元素的值设为默认值(可省略)

Step3:将Length的值设为0
在这里插入图片描述

  • 如果Step2中,不把各个数据元素的值设为默认值:
    ——内存中会有遗留的“脏数据”
    在这里插入图片描述
  • 以上这种打印方式为 - 打印整个data数组:
//尝试“违规”打印整个data数组
for(int i = 0; i < MaxSize; i++)printf("data[%d]=%d\n", i, L.data[i]);
return 0;

打印时,不该从第一个元素访问到最后一个元素。
—— i < MaxSize;
应该是访问到顺序表中实际存储的最后一个元素。
—— i < L.length;
所以此时大于顺序表实际长度的部分是不会被访问的 - Step2设置默认值可省略的原因。

更好的方式是使用基本操作来访问各个数据元素
GetElem(L,i)

  • Question1:Step3将Length的值设为0能否省略?
    ——不能省略,无法预知在length区域之前存的是什么数据。

  • Question2:如果“数组”存满了怎么办?
    ——放弃治疗,顺序表的表长刚开始确定后就无法更改(存储空间是静态的)

思考:如果刚开始就声明一个很大的内存空间呢?存在什么问题?
——浪费!


顺序表的实现——动态分配

#define InitSize 10 //顺序表的初始长度
typedef struct{ElemType *data; //指示动态分配数组的指针int MaxSize; //顺序表的最大容量int length; //顺序表的当前长度
}SeqList; //顺序表的类型定义(动态分配方式)

顺序表的容量可变

  • Key:动态申请释放内存空间
    —— C语言:mallocfree函数
    ——C++:new、delete关键字
    L.data = (ElemType*) malloc (sizeof(ElemType)* InitSize);

(ElemType*) :malloc函数返回一个指针, 空间需要强制转型为你定义的数据元素类型指针
 
malloc(Δ):malloc函数的参数Δ,指明要分配多大的连续内存空间

在这里插入图片描述
代码实现:

#include  //malloc、free函数的头文件#define InitSize 10 //默认的最大长度
typedef struct{int *data; //指示动态分配数组的指针int MaxSize; //顺序表的最大容量int length; //顺序表的当前长度
}SeqList;//初始化顺序表
void InitList(SeqList &L){//用malloc函数申请一片连续的存储空间L.data = (int*)malloc(InitList*sizeof(int));//增加动态数组的长度
void Increasesize(SeqList &L, int len){int *p = L.data;L.data=(int*)malloc((L.MaxSize + len) * sizeof(int));for(int i = 0; i < L.length; i++){L.data[i]=p[i]; //将数据复制到新区域 - 时间开销大}L.MaxSize = L.MaxSize + len; //顺序表最大长度增加lenfree(p); //释放原来的内存空间
}int main(){SeqList L; //声明一个顺序表InitList(L); //初始化顺序表//…往顺序表中随便插入几个元素…IncreaseSize(L,5);return 0;
}

在这里插入图片描述


顺序表的特点

顺序表的特点:
①随机访问,即可以在O(1)时间内找到第i个元素。

代码实现:data[i-1];
静态分配、动态分配都一样

②存储密度高,每个节点只存储数据元素

链式存储中,还需要一部分空间存放指针

③拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
④插入、删除操作不方便,需要移动大量元素


2.2.2_1 顺序表的插入和删除

在这里插入图片描述
用存储位置的相邻来体现数据元素之间的逻辑关系。

顺序表的插入

ListInsert(&L,i,e):插入操作。在表L中的第i个位置【位序】上插入指定元素e。
在这里插入图片描述

注:本节代码建立在顺序表的“静态分配”实现方式之上,“动态分配”也雷同。

在这里插入图片描述
逻辑结构:
在这里插入图片描述
代码实现:

#define MaxSize 10 //定义最大长度
typedef struct{int data[MaxSize]; //用静态的“数组”存放数据元素int length; //顺序表的当前长度
}SqList; //顺序表的类型定义//插入一个元素
void ListInsert(SqList &L, int i, int e){ //基本操作:在L的位序i处插入元素efor(int j = L.length; j >= i; j--) //将第i个元素及之后的元素后移L.data[j] = L.data[j - 1]; //注意位序、数组下标的关系,并从后面的元素依次移动L.data[i - 1] = e; //在位置i处放入eL.length++; //长度加1
}int main(){SqList L; //声明一个顺序表InitList(L); //初始化顺序表//…插入几个元素ListInsert(L,3,3);return 0;
}

在这里插入图片描述

基操——让自己实现的数据结构可以让别人很方便地使用

检查顺序表是否已满;插入的位置是否合法
反馈操作结果!

修改后代码实现:

#define MaxSize 10 //定义最大长度
typedef struct{int data[MaxSize]; //用静态的“数组”存放数据元素int length; //顺序表的当前长度
}SqList; //顺序表的类型定义//插入一个元素 - 返回bool型变量
bool ListInsert(SqList &L, int i, int e){ if(i < 1||i > L.length + 1) //判断i的范围是否有效return false;if(L.length >= MaxSize) //当前存储空间已满,不能插入return false;for(int j = L.length; j >= i; j--) //将第i个元素及之后的元素后移L.data[j] = L.data[j - 1]; L.data[i - 1] = e; //在位置i处放入eL.length++; //长度加1return ture;
}int main(){SqList L; //声明一个顺序表InitList(L); //初始化顺序表//…插入几个元素ListInsert(L,3,3);return 0;
}

算法中针对异常情况给出返回值。
——return true; return false;

好的算法,应该具有“健壮性”。
能处理异常情况,并给使用者反馈。


插入操作的时间复杂度

bool ListInsert(SqList &L, int i, int e){ if(i < 1||i > L.length + 1) //判断i的范围是否有效return false;if(L.length >= MaxSize) //当前存储空间已满,不能插入return false;for(int j = L.length; j >= i; j--) //将第i个元素及之后的元素后移L.data[j] = L.data[j - 1]; L.data[i - 1] = e; //在位置i处放入eL.length++; //长度加1return ture;
}

关注最深层循环语句执行次数与问题规模n的关系
L.data[j] = L.data[j - 1];

问题规模n = L.length(表长)

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

最坏情况:新元素插入到表头,需要后移所有n个元素
——i = 1,循环n次;最坏时间复杂度 = O(n)

平均情况:假设新元素插入到任何一个位置的概率相同,即i=1,2,3,…,length+1i = 1,2,3, … , length+1i=1,2,3,…,length+1的概率都是p=1n+1p=\frac{1}{n+1}p=n+11​
i = 1,循环n次;i = 2时,循环n-1次;i =3 ,循环n-2次……i = n+1时,循环0次。

平均循环次数:np+(n−1)p+(n−2)p+……+1⋅p=n(n+1)21n+1=n2np+\left( n-1 \right) p+\left( n-2 \right) p+……+1\cdot p=\frac{n\left( n+1 \right)}{2}\frac{1}{n+1}=\frac{n}{2}np+(n−1)p+(n−2)p+……+1⋅p=2n(n+1)​n+11​=2n​

平均时间复杂度= O(n)O(n)O(n)


顺序表的删除

ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
在这里插入图片描述
逻辑结构:
在这里插入图片描述
代码实现:

//删除
bool ListDelete(SqList &L, int i, int &e){if(i < 1||i > L.Length) //判断i的范围是否有效return false;e = L.data[i-1]; //将被删除的元素赋值给efor(int j = i; j < L.length; j++) //将第i个位置后的元素前移L.data[j-1] = L.data[j]; //注意位序、数组下标的关系,并从前面元素依次移动L.length--; //线性表长度减1·return true;
}int main(){SqList L; //声明一个顺序表InitList(L); //初始化顺序表//…插入元素int e = -1; //用变量e把删除的元素“带回来”if(ListDelete(L, 3, e))printf("Delete successfully, element = %d\n", e);elseprintf("Delete failed\n");return 0;

在这里插入图片描述
Question:如果参数没有加引用符号,会怎样?
——子函数和主函数的变量e在内存中不是同一个东西,返回主函数后e的值不会改变!


删除操作的时间复杂度

bool ListDelete(SqList &L, int i, int &e){if(i < 1||i > L.Length) //判断i的范围是否有效return false;e = L.data[i-1]; //将被删除的元素赋值给efor(int j = i; j < L.length; j++) //将第i个位置后的元素前移L.data[j-1] = L.data[j]; L.length--; //线性表长度减1·return true;
}

关注最深层循环语句的执行次数与问题规模n的关系
L.data[j-1] = L.data[j];

问题规模n = L.length(表长)

最好情况:删除表尾元素,不需要移动其他元素
—— i = n,循环0次;最好时间复杂度= O(1)

最坏情况:删除表头元素,需要将后续的n-1个元素全都向前移动
—— i = 1,循环n-1次;最坏时间复杂度= O(n)

平均情况:假设删除任何一个元素的概率相同,即i=1,2,3,…,lengthi = 1,2,3, … , lengthi=1,2,3,…,length的概率都是p=1np=\frac{1}{n}p=n1​
i = 1,循环n-1次;i = 2时,循环n-2次;i = 3,循环n-3次……i = n时,循环0次。

平均循环次数:(n−1)p+(n−2)p+……+1⋅p=n(n−1)21n=n−12(n-1)p+\left( n-2 \right) p+……+1\cdot p=\frac{n\left( n-1 \right)}{2}\frac{1}{n}=\frac{n-1}{2}(n−1)p+(n−2)p+……+1⋅p=2n(n−1)​n1​=2n−1​

平均时间复杂度= O(n)O(n)O(n)


2.2.2_2 顺序表的查找

按位查找

在这里插入图片描述
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。
在这里插入图片描述
静态分配顺序表
GetElem实现:

ElemType GetElem(SqList L, int i){ //返回值和数据元素类型相同return L.data[i-1]//健壮性 - 判断i是否合法
}

在这里插入图片描述
动态分配顺序表
GetElem实现:

ElemType GetElem(SeqList L, int i){return L.data[i-1]; //指针
}
//和访问普通数组的方法一样

ElemType *data 【指针】

指针指向malloc函数分配的一整片连续空间的起始地址。

Eg.1
假设指针data指向的地址为2000;
一个ElemType占6B,即sizeof(ElemType) == 6
ElemType *data
在这里插入图片描述
data[0] - 从2000开始数6B的内容 - 第一个数据元素
data[1] - 从2000+6开始数6B的内容 - 第二个数据元素

Eg.2
换一个类型的指针,指向同一个地址
int *p (注:一个int占4B)
在这里插入图片描述
对不上

  • 再次理解malloc:为何malloc函数返回的存储空间起始地址要转换为与数据元素的数据类型相对应的指针
    ——虽然指针指向同一地址,但是指针所指的数据类型定义错了,那么访问数据元素时也会出现问题。

按位查找的时间复杂度

由于顺序表的各个数据元素在内存中连续存放,因此可以根据起始地址数据元素大小立即找到第i个元素——“随机存取”特性

时间复杂度:O(1)O(1)O(1)


按值查找

LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
在这里插入图片描述

//在顺序表L中查找第一个元素值等于e的元素,并返回其位序
int LocateElem(SeqList L,ElemType e){for(int i=0;i
  • 数组下标为i的元素值等于e,返回其位序i+1

Eg.

typedef struct{int *data; //指示动态分配数组的指针int MaxSize; //顺序表的最大容量int length; //顺序表的当前长度
}SeqList;
//在顺序表L中查找第一个元素值等于e的元素,并返回其位序
int LocateElem(SeqList L,int e){for(int i=0;i

在这里插入图片描述

  • 基本数据类型:int、char、double、float等可以直接用运算符“==”比较
    ——Question:两个结构类型的数据元素,是否可以用运算符“==”比较?
    ——不能!

Answer实现:

typedef struct{int num;int people;
}Customer;void test(){Customer a;a.num = 1;a.people = 1;Customer b;b.num = 1;b.people = 1;if(a == b){ //Invalid operands to binary expression ('Customer'and'Customer') - 报错!printf("Equal");}else{printf("Unequal");}
}

注意:C语言中,结构体的比较不能直接用“==”
——需要依次对比各个分量来判断两个结构体是否相等,如下:

if(a.num == b.num && a.people == b.people){printf("Equal");
}else{printf("Unequal");
}
  • 更好的办法:定义一个函数!
bool isCustomerEqual(Customer a, Customer b){if(a.num == b.num && a.people == b.people)return true;elsereturn false;
}

Tips:
《数据结构》考研初试中,手写代码可以直接用“==”,无论ElemType是基本数据类型还是结构类型
 
手写代码主要考察学生是否能理解算法思想,不会严格要求代码完全可运行


按值查找的时间复杂度

//在顺序表L中查找第一个元素值等于e的元素,并返回其位序
int LocateElem(SeqList L,ElemType e){for(int i=0;i

关注最深层循环语句的执行次数与问题规模n的关系
if(L.data[i]==e)

问题规模n = L.length(表长)

最好情况:目标元素在表头
—— 循环1次;最好时间复杂度= O(1)

最坏情况:目标元素在表尾
—— 循环n次;最坏时间复杂度= O(n)

平均情况:假设目标元素出现在任何一个位置的概率相同,都是1n\frac{1}{n}n1​
目标元素在第1位,循环1次;在第2位,循环2次;……;在第n位,循环n次

平均循环次数:1⋅1n+2⋅1n+3⋅1n+……+n⋅1n=n(n+1)21n=n+121\cdot \frac{1}{n}+2\cdot \frac{1}{n}+3\cdot \frac{1}{n}+……+n\cdot \frac{1}{n}=\frac{n\left( n+1 \right)}{2}\frac{1}{n}=\frac{n+1}{2}1⋅n1​+2⋅n1​+3⋅n1​+……+n⋅n1​=2n(n+1)​n1​=2n+1​

平均时间复杂度= O(n)O(n)O(n)


知识回顾与重要考点

2.2.1顺序表的定义

在这里插入图片描述

  • 存储结构:逻辑上相邻的数据元素物理上也相邻
  • malloc申请一片更大的空间;free释放原区域

2.2.2_1 顺序表的插入和删除

在这里插入图片描述

  • 增删时,更改length值
  • 注意位序i和数组下标的区别
  • 健壮性 - 对必要的条件进行判断
  • 参数加“&”引用 - 被调用函数中处理的参数和主函数中的一样。

2.2.2_2 顺序表的查找

在这里插入图片描述

  • 位序与下标的区别
  • 判断两个结构体相等 - 依次对比各个分量

相关内容

热门资讯

高中短篇小说精选   下面是yjbys小编为大家分享的高中短篇小说精选,供大家参考借鉴,欢迎浏览!  高中短篇小说精选...
蜗角美文欣赏 蜗角美文欣赏  我爸常说:“能跟你段叔叔学篆刻,算你上辈子的造化!”  我十岁开始学书法,启蒙老师是...
成功人士的人生格言摘抄 成功人士的人生格言摘抄  导语:如果你想在这个世界上获得成功,当你进入某个沙龙时,你必须让你的虚荣心...
荒草萋萋叶适墓散文 荒草萋萋叶适墓散文  在日常的学习、工作、生活中,说起散文,大家肯定都不陌生吧?散文的特点是通过对现...
二十七八岁的年纪美文 二十七八岁的年纪美文  哭嚷着要糖吃仿佛还是昨天的记忆,刹那间已经二十七八岁了。一个既尴尬又荣耀的年...
描写桂花的优美段落 描写桂花的优美段落精选5篇描写桂花的优美段落1  1 、在秋雨纷纷的日子里,门前的桂花树没一点动静。...
蒙田的名言阅读欣赏 蒙田的名言阅读欣赏  在生活、工作和学习中,大家都知道一些经典的名言吧,名言主要用来激励和告诉当事人...
中秋经典美文 中秋经典美文中秋经典美文1  也许是我出生在秋高气爽,丹桂飘香的时节,格外觉得桂花的馨香沁人心脾……...
劳动的名人名言 关于劳动的名人名言  在生活、工作和学习中,大家总免不了要接触或使用名言吧,在议论文中,引用名言,不...
乐于助人的名言 关于乐于助人的名言  1、助人要从日常小事做起,不因善小而不为。——佚名  2、助人为乐是一种美德。...
青春的经典英语美文 关于青春的经典英语美文集锦  1、Whether 60 or 16, there is in eve...
人生婚姻格言大全   婚姻是两个人的事情,想要婚姻美满必须要共同作出努力!下面是应届毕业生小编为大家收集的关于,希望能...
美德的名言 关于美德的名言  美就是美的事物;德,古称之为得;合起来解释就是,美的事物可以吸引和得到社会中的一切...
经典名言名句 经典名言名句(通用160句)  无论在学习、工作或是生活中,大家都不可避免地会接触并使用名言吧,名言...
认识自我的名人名言 认识自我的名人名言1、谦固美名,过谦者,宜防其诈。 —— 朱熹2、如果你指挥不了自己,也就指挥不了别...
齐文化名言 齐文化名言七篇  一、政治篇  上之随天,其次随人。 《管子.白心》  威不两错,政不二门。 《管子...
自强的名人名言 关于自强的名人名言大全  勇气是智慧和一定程度教养的必然结果。下面我们来看看关于自强的名人名言大全,...
奉献精神的名言 关于奉献精神的名言  无论是身处学校还是步入社会,大家都经常接触到名言吧,名言主要用来激励和告诉当事...
中学教师人生格言 中学教师人生格言  教师,是播撒知识的种子,传递文明火炬的使者,中学教师人生格言。下面是应届毕业生小...
学习的名言座右铭 有关学习的名言座右铭大全  在我们平凡的日常里,大家常常会遇到需要使用名言的情形吧,名言主要用来激励...