数据结构 _CH03_线性表(顺序表)
1.1知识回顾
2.1线性表的定义和特点
2.1.1定义
线性表是相同特性元素的一个有限序列
a1 a2 ...ai-1 ai ai+1 ... an
线 线
性 性
起 终
点 点
ai-1是ai的直接前驱
ai+1是ai的直接后继
a1又称起始结点
an又称终端结点
i是序号,表示元素在线性表当中的位置
n表示表长
n = 0表示空表
数据元素ai只是一个抽象符号,在不同条件下,其具体含义不一样。
e.g.1
字母表是线性表
(A,B,C,D,E,...,X,Y,Z)
元素特性相同,并且关系线性。
e.g.2
2.1.2特点
1、非空线性表,其起始结点a1,有且只有一个直接后继,没有直接前趋。终端结点类似。
2、除此之外的内部结点,有且只有一个直接前趋和直接后继。
2.1.3案例引入
两个多项式相加的运算。
Pn(x)每一项的指数i隐含在其系数Pi当中。
可以利用<数组>来实现
但是如果出现稀疏多项式,我们就需要把每个项的指数变成变量:
2.2线性表的实现
ADT List
{
数据对象:D = {ai| ai ∈ Elemtype , i = 1,2...n,n>= 0}
数据关系:R = { <ai-1, ai>|ai-1,ai ∈ D , i = 2,3,...n}
基本操作:
InitList(*L) //初始化线性表
ListEmpty(*L) //返回线性表是否为空,空返回True,非空返回False
ClearList(*L) //清空线性表
GetElem(L , i , *e) //把表中第i个元素返回给e
LocateElem(L,e) //找表中有没有跟e一样的元素,没有则返回0,有则返回元素下标
ListInsert(*L , i , e) //在第i个位置插入元素e
ListDelete(*L , i , *e) //删除表中第i个位置的元素e,并返回e的值
ListLength(L) //返回表长度
PriorElem(L,cur_e,*pur_e) //求前驱
NextElem(L,cur_e,*next_e) //求后继
ListTraverse(*L , visited()) //遍历线性表(对每个元素作visited()操作)
ShowList(*L)//打印整个线性表
}ADT List
//以上运算都是逻辑结构上定义的运算,而实现细节,只有等到存储结构确定后再来具体地实现。
2.2.1线性表的顺序实现
顺序表是顺序存储结构实现的线性表,又称线性表的顺序存储结构或者顺序映像。即把逻辑上相邻的数据元素,在物理位置上也相邻存储。
第一个元素的存储位置又称顺序表的基地址或者起始地址
另外,存储地址中间不能出现空地址,如果出现空地址也是不行的。
若已知一个元素占据的存储单元为h,则
LOC(a i+1) = LOC(ai) + h
因此,存储结构上:顺序表只要知道第一个元素的位置,就可以计算每一个元素的位置:
LOC(ai) = LOC(a1) + ( i-1 )*h
又因为逻辑结构和存储结构一一对应,所以每一个元素的值也是可以求出来的。
计算的时间复杂度是O(1)
对于存储元素的要求:
地址连续
依次存放
随机存取
类型相同
我们可以发现,这与C语言当中所学的数组是一致的。因此我们用一维数组来表示顺序表。
但是,线性表的长度可变,数组的长度是不可以动态定义的。因此需要自己定义一个一个常量表达式。
数组的定义:
数据类型 arr[常量表达式]
这个常量表达式可以是常量,也可以是表示常量的符号,但就是不能是变量。
#define List_Init_Size 100 //定义数组初始定义时分配的空间
typedef struct {
Elemtype elem[List_Init_Size];
int length; //当前表长度
}SqList;
这个Elemtype表示元素类型,它可以是int char float等等。
typedef char Elemtype;
typedef int Elemtype;
typedef float Elemtype;
typedef stuct {
}Elemtype;//定义复杂类型
对于这一个多项式:
他的线性表实现为:
#define MAXSIZE 1000 //顺序表所能达到的最大长度
typedef struct {
float p; //系数
int e; //指数
}Polynomial;
typedef struct {
Polynomial *elem; //存储空间的基地址
int length; //多项式当前的项数
}SqList;
另外类似图书表的线性表定义:
#define MAXSIZE 1000
typedef struct {
char num[20]; //ISBN码
char name[50]; //书名
int price; //定价
}Book;
typedef struct {
Book *elem; //定义基地址
int length; //当前存储的图书个数
}SqList;
2.2.2线性表的动态分配与静态分配的区别
静态分配
#define MaxSize 1000
typedef struct {
Elemtype data[MaxSize];
int length;
}SqList;
定义的这个数组名data存放的是数组的基地址,数组的大小直接定义为MaxSize
动态分配
#define MaxSize 1000
typedef struct {
Elemtype *data;
int length;
}SqList;
//定义的是一个指针类型data,存放第一个元素的地址,至于数组到底有多大,要用动态内存分配的函数malloc来实现。
SqList L; //定义一个SqList类型的表 L
L.data = (Elemtype*)malloc(sizeof(Elemtype)* MaxSize) //动态分配内存
另外,使用malloc需要加上头文件<stdlib.h>。完整代码
#define MaxSize 1000
#include<stdio.h>
#include<stdlib.h>
typedef struct {
Elemtype *data;
int length;
}SqList;
int main()
{
SqList;
L.data = (Elemtype*)malloc(sizeof(Elemtype)* MaxSize);
}
malloc的用法:
malloc 函数是 C 语言标准库函数之一,用于动态地在堆(heap)上分配内存。这个函数定义在 <stdlib.h> 头文件中。使用 malloc 时,你需要指定希望分配的字节数,它会返回一个指向该内存块的指针。如果内存分配失败,则返回 NULL。
void* malloc(size_t size);
这里的MaxSize即元素个数,而sizeof(Elemtype)计算的是这一个元素类型有几个字节,那么总字节数就是sizeof(Elemtype)*MaxSize。Elemtype 决定了存储什么类型,而 * 则是因为L.data是一个指针类型,要与之匹配。
free§:释放指针p所指变量的存储空间,即彻底删除一个变量。
2.2.3顺序表的基本操作具体实现
需要注意的是,线性表的逻辑位序和物理位序相差1。
若定义了一个SqList类型的变量L:有两种方式
SqList L;
SqList* L;
则它们访问成员的操作分别是
L.data
L.length
L->data
L->length
2.2.3.1 初始化操作
动态分配的初始化操作
#define MaxSize 100
#include<stdio.h>
#include<stdlib.h>
typedef int Elemtype;//通过这里更改Elemtype的数据类型
//给出线性表的结构定义
typedef struct {
Elemtype* arr;
int n;//整体申请的容量
int length;//当前的元素个数
}SqList;
//初始化线性表
SqList* InitList(int n)//返回指针类型
{
SqList* L = (SqList*)malloc(sizeof(Elemtype)* n); //定义一个顺序表类型的变量,采用指针形式定义
if(!L) return NULL; //如果内存申请失败,即L为空指针,则返回NULL。这样可以提高代码的健壮性
L->length = 0;//长度初始为0
l->n = n;
return L;//返回指针,跟函数返回类型相符。
}
时间复杂度:O(1)
2.2.3.2插入
初始化操作之后,线性表里还没有任何内容,所以我们先插入
(在表第i元素的位置插入e)
#define _CRT_SECURE_NO_WARNINGS
#define MaxSize 100
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int Elemtype; // 定义Elemtype的数据类型
typedef struct {
Elemtype* arr; // 存储元素的数组
int n; // 整体申请的容量
int length; // 当前元素个数
} SqList;
// 初始化线性表
SqList* InitList(int n) {
SqList* L = (SqList*)malloc(sizeof(SqList)); // 为SqList结构体分配内存
if (!L) return NULL; // 如果内存申请失败,则返回NULL
L->arr = (Elemtype*)malloc(sizeof(Elemtype) * n); // 为Elemtype数组分配内存
if (!L->arr) {
free(L); // 如果数组内存申请失败,释放结构体内存
return NULL;
}
L->n = n;
L->length = 0; // 长度初始为0
return L; // 返回指针
}
// 插入元素
bool ListInsert(SqList* L, int i, Elemtype e) {
if (i < 0 || i > L->length) return false; // 插入位置i的判断
if (L->length == L->n) { // 如果表满了,扩容
Elemtype* tmp = (Elemtype*)realloc(L->arr, sizeof(Elemtype) * (L->n * 2));
if (!tmp) return false; // 申请空间失败则报错
L->arr = tmp;
L->n *= 2;
}
// 将i及之后的元素向后移动一位
for (int j = L->length; j >= i; j--) {
L->arr[j] = L->arr[j - 1];
}
L->arr[i] = e; // 在位置i插入元素e
L->length++;
return true;
}
// 释放线性表内存
void FreeList(SqList* L) {
free(L->arr);
free(L);
}
int main() {
SqList* P = InitList(100);
if (!P) {
printf("Memory allocation failed for the list.\n");
return 1;
}
for (int i = 0; i < 100; i++) {
if (!ListInsert(P, i, i)) {
printf("Insertion failed at position %d.\n", i);
FreeList(P); // 插入失败,释放内存
return 1;
}
}
// 打印线性表的内容,检查插入结果
printf("List content after insertion:\n");
for (int i = 0; i < P->length; i++) {
printf("%d ", P->arr[i]);
}
printf("\n");
// 释放线性表内存
FreeList(P);
return 0;
}
返回结果
List content after insertion:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
时间复杂度
2.2.3.3查找下标
查找第i个元素(1<=i<=n)
第i个元素的下标是i-1。
bool GetElem(SqList* L,Elemtype* e,int i)
{
//判误
if(i<0 || L->length < 0 || i>L->n)return false;
*e = L->arr[i-1]; //对指针e解引用,则访问了e的地址,把arr[i-1]给了这个地址,即把arr[i-1]找到的这个元素放在了e指向的地址里,说明能够找到。
return true;
}
合并之前的代码,我们可以看到找到的元素是4
#define _CRT_SECURE_NO_WARNINGS
#define MaxSize 100
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int Elemtype; // 定义Elemtype的数据类型
typedef struct {
Elemtype* arr; // 存储元素的数组
int n; // 整体申请的容量
int length; // 当前元素个数
} SqList;
// 初始化线性表
SqList* InitList(int n) {
SqList* L = (SqList*)malloc(sizeof(SqList)); // 为SqList结构体分配内存
if (!L) return NULL; // 如果内存申请失败,则返回NULL
L->arr = (Elemtype*)malloc(sizeof(Elemtype) * n); // 为Elemtype数组分配内存
if (!L->arr) {
free(L); // 如果数组内存申请失败,释放结构体内存
return NULL;
}
L->n = n;
L->length = 0; // 长度初始为0
return L; // 返回指针
}
// 插入元素
bool ListInsert(SqList* L, int i, Elemtype e) {
if (i < 0 || i > L->length) return false; // 插入位置i的判断
if (L->length == L->n) { // 如果表满了,扩容
Elemtype* tmp = (Elemtype*)realloc(L->arr, sizeof(Elemtype) * (L->n * 2));
if (!tmp) return false; // 申请空间失败则报错
L->arr = tmp;
L->n *= 2;
}
// 将i及之后的元素向后移动一位
for (int j = L->length; j >= i; j--) {
L->arr[j] = L->arr[j - 1];
}
L->arr[i] = e; // 在位置i插入元素e
L->length++;
return true;
}
//查找元素
bool GetElem(SqList* L,Elemtype* e,int i)
{
//判误
if(i<0 || L->length < 0 || i>L->n)return false;
*e = L->arr[i-1]; //对指针e解引用,则访问了e的地址,把arr[i-1]给了这个地址,即把arr[i-1]找到的这个元素放在了e指向的地址里,说明能够找到。
return true;
}
// 释放线性表内存
void FreeList(SqList* L) {
free(L->arr);
free(L);
}
int main() {
SqList* P = InitList(100);
if (!P) {
printf("Memory allocation failed for the list.\n");
return 1;
}
for (int i = 0; i < 100; i++) {
if (!ListInsert(P, i, i)) {
printf("Insertion failed at position %d.\n", i);
FreeList(P); // 插入失败,释放内存
return 1;
}
}
// 打印线性表的内容,检查插入结果
printf("List content after insertion:\n");
for (int i = 0; i < P->length; i++) {
printf("%d ", P->arr[i]);
}
printf("\n");
Elemtype element;
int indexToPrint = 5; // 假设你想打印索引为 5 的元素
// 调用 GetElem 函数来获取元素
if (GetElem(P, &element, indexToPrint)) {
// 如果 GetElem 返回 true,说明元素成功获取
printf("Element at index %d is: %d\n", indexToPrint, element);
} else {
// 如果 GetElem 返回 false,说明获取元素失败
printf("Failed to get element at index %d\n", indexToPrint);
}
// 释放线性表内存
FreeList(P);
return 0;
}
输出:
List content after insertion:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
Element at index 5 is: 4
时间复杂度:
2.2.3.4查找元素
查找第一个值为e的元素,返回其下标
int LocateElem(SqList* L,Elemtype e)
{
for(int i = 0; i<=L->length ; i++)
{
if(L->arr[i] == e)
{
return i;
}
}
return -1;
}
合并之前的代码
#define _CRT_SECURE_NO_WARNINGS
#define MaxSize 100
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int Elemtype; // 定义Elemtype的数据类型
typedef struct {
Elemtype* arr; // 存储元素的数组
int n; // 整体申请的容量
int length; // 当前元素个数
} SqList;
// 初始化线性表
SqList* InitList(int n) {
SqList* L = (SqList*)malloc(sizeof(SqList)); // 为SqList结构体分配内存
if (!L) return NULL; // 如果内存申请失败,则返回NULL
L->arr = (Elemtype*)malloc(sizeof(Elemtype) * n); // 为Elemtype数组分配内存
if (!L->arr) {
free(L); // 如果数组内存申请失败,释放结构体内存
return NULL;
}
L->n = n;
L->length = 0; // 长度初始为0
return L; // 返回指针
}
// 插入元素
bool ListInsert(SqList* L, int i, Elemtype e) {
if (i < 0 || i > L->length) return false; // 插入位置i的判断
if (L->length == L->n) { // 如果表满了,扩容
Elemtype* tmp = (Elemtype*)realloc(L->arr, sizeof(Elemtype) * (L->n * 2));
if (!tmp) return false; // 申请空间失败则报错
L->arr = tmp;
L->n *= 2;
}
// 将i及之后的元素向后移动一位
for (int j = L->length; j >= i; j--) {
L->arr[j] = L->arr[j - 1];
}
L->arr[i] = e; // 在位置i插入元素e
L->length++;
return true;
}
//查找下标
bool GetElem(SqList* L,Elemtype* e,int i)
{
//判误
if(i<0 || L->length < 0 || i>L->n)return false;
*e = L->arr[i-1]; //对指针e解引用,则访问了e的地址,把arr[i-1]给了这个地址,即把arr[i-1]找到的这个元素放在了e指向的地址里,说明能够找到。
return true;
}
//查找元素
int LocateElem(SqList* L,Elemtype e)
{
for(int i = 0; i<=L->length ; i++)
{
if(L->arr[i] == e)
{
return i;
}
}
return -1;
}
// 释放线性表内存
void FreeList(SqList* L) {
free(L->arr);
free(L);
}
int main() {
SqList* P = InitList(100);
if (!P) {
printf("Memory allocation failed for the list.\n");
return 1;
}
for (int i = 0; i < 100; i++) {
if (!ListInsert(P, i, i)) {
printf("Insertion failed at position %d.\n", i);
FreeList(P); // 插入失败,释放内存
return 1;
}
}
// 打印线性表的内容,检查插入结果
printf("List content after insertion:\n");
for (int i = 0; i < P->length; i++) {
printf("%d ", P->arr[i]);
}
printf("\n");
Elemtype element;
int indexToPrint = 5; // 假设你想打印索引为 5 的元素
// 调用 GetElem 函数来获取元素
if (GetElem(P, &element, indexToPrint)) {
// 如果 GetElem 返回 true,说明元素成功获取
printf("Element at index %d is: %d\n", indexToPrint, element);
} else {
// 如果 GetElem 返回 false,说明获取元素失败
printf("Failed to get element at index %d\n", indexToPrint);
}
//调用LocateElem查找元素
int index = LocateElem(P,9);
printf("%d\n",index);
// 释放线性表内存
FreeList(P);
return 0;
}
输出
List content after insertion:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
Element at index 5 is: 4
9
时间复杂度:
经过了一个循环,所以时间复杂度是O(n)
2.2.3.5删除
删除第i个元素(1<= i <= n),并将其放入指针e所指的空间,成功返回true,失败返回false
bool ListDelete(SqList* L , int i , Elemtype* e)
{
//判误,表空或者i值异常
if(!L->length||i<1||L->n)return false;
//把第i个元素【即下标为i-1】放入指针e内的空间
*e = L->arr[i-1];
//把第i个元素之后的元素全部向前移动一格
for(int j = i; j < L->length ; j++)
{
L->arr[j-1] = L->arr[j];
}
L->length--;
return true;
}
时间复杂度:
2.2.3.6清空
清空线性表所有元素,但是不是删除线性表。
利用free()函数释放内存,并且要检查是否为空
void ClearList(SqList* L)
{
if(L-> != NULL)
{
free(L->arr);
}
L->length = 0;
}
2.2.3.7输出整个线性表
之前我们在主函数用for循环将线性表的所有元素全部打印了,我们可以将输出整个线性表的操作封装一下。
void ShowList(SqList *L)
{
if(!L || ! L->length)return false;
for(int j = 0 ; j<L->length ; j++)
{
printf("这是第%d个元素: %d",j+1,L->arr[j]);
}
printf("\n");
}