小题考频:5
大题考频:12
难度:☆☆☆
顺序表——用顺序存储的方式实现线性表
顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
各个元素数据类型相同,所占的内存空间一样大。
顺序表中各元素在物理内存上连续存放。
第二个元素存放的位置 - 顺序表起始地址+数据元素的大小
第三个元素存放的位置 - 顺序表起始地址+2*数据元素的大小
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
//尝试“违规”打印整个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; //顺序表的类型定义(动态分配方式)
顺序表的容量可变
动态申请
和释放
内存空间malloc
、free
函数(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];
静态分配、动态分配都一样
②存储密度高,每个节点只存储数据元素
链式存储中,还需要一部分空间存放指针
③拓展容量不方便(即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
④插入、删除操作不方便,需要移动大量元素
用存储位置的相邻来体现数据元素之间的逻辑关系。
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)
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)
对不上
由于顺序表的各个数据元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第i个元素——“随机存取”特性
时间复杂度:O(1)O(1)O(1)
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
//在顺序表L中查找第一个元素值等于e的元素,并返回其位序
int LocateElem(SeqList L,ElemType e){for(int i=0;i
Eg.
typedef struct{int *data; //指示动态分配数组的指针int MaxSize; //顺序表的最大容量int length; //顺序表的当前长度
}SeqList;
//在顺序表L中查找第一个元素值等于e的元素,并返回其位序
int LocateElem(SeqList L,int e){for(int i=0;i
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)
上一篇:Matlab论文插图绘制模板第82期—箭头图(quiver)
下一篇: 歌颂长征的诗歌