【数据结构】线性表(一)—— 顺序表(C语言版)
一、线性表的定义
线性表定义:由零个或多个数据元素组成的有限序列。
关键点:
(1)它是一个序列,也就是说元素之间是有先来后到的。
(2)若元素存在多个,则第一个元素无前驱,而最后一个元素无后继,其他元素有且只有一个前驱和一个后继。
(3)线性表强调是有限的,无论计算机发展到多强大,它所处理的元素是有限的。
1. 数学语言定义
如果用数学语言来定义,可如下定义:
若将线性表记为(a1,…,ai-1,ai,ai+1,… , an), 则表中ai-1领先于ai, ai领先于ai+1, 称ai-1是ai的直接前驱元素,ai+1是ai的直接后继元素。
线性表元素的个数 n (n>=0)定义为线性表的长度,当n=0时,称为空表。
2. 线性表的抽象数据类型定义:
111
ADT List{
数据对象: D={ai | ai E ElemSet, i = l, 2, …,n, n \geq }
数据关系: R=(<a<font sub>i-1</sub>,ai>I a 仁1,ai ED, i=2, …, n}
基本操作:
InitList (&L)
操作结果:构造一个空的线性表L。
DestroyList(&L)
初始条件:线性表L已存在。
操作结果:销毁线性表L。
ClearList (&L)
初始条件:线性表L已存在。
操作结果:将L重置为空表。
ListEmpty(L)
初始条件:线性表L已存在。
操作结果:若L为空表,则返回true, 否则返回false。
ListLength(L)
初始条件:线性表L已存在。
操作结果:返回L中数据元素个数。
GetElem(L,i,&e)
初始条件:线性表L巳存在,且1:,s;i:os;ListLength(L)。
操作结果:用e返回L中第1个数据元素的值。
LocateElem(L,e)
初始条件:线性表L已存在。
操作结果:返回L中第1个 值与e相同的元素在L中的位置。若这样的数据元素不存在,则返回值为0。
PriorElem(r,,cur_e,&pre_e)
初始条件:线性表L已存在。
操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回其前驱,否则操作失败,pre_e无定义。
NextElem(L,cur_e,&next_e)
初始条件:线性表L已存在。
操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回其后继,否则操作失败,next_e无定义。
Listinsert(&L,i,e)
初始条件:线性表L已存在,且1:,s;i:os;ListLength (L) +l。
操作结果:在L中第1个位置之前插入新的数据元素 e, L的长度加1。 ListDelete(&L,i)
初始条件:线性表L已存在且非空,且l:os;i:os;ListLength(L)。
操作结果:删除L的第1个数据元素,L的长度减1。
TraverseList(L)
初始条件:线性表L已存在。
操作结果:对线性表L进行遍历,在遍历过程中对L的每个结点访问一次。}ADT List
(1)抽象数据类型仅是一个模袒的定义,并不涉及模型的具体实现,因此这里描述 中所涉及的参数不必考虑具体数据类型。在实际应用中,数据元素可能有多种类型,到时可根据具体需要选择使用不同的数据类型。
(2) 上述抽象数据类型中给出的操作只是基本操作,由这些基本操作可以构成其他较复杂的操作。
二、线性表的存储结构
线性表有两种存储结构:顺序存储结构和链式存储结构。
注意:涉及到malloc和realloc要加头文件 #include<stdlib.h>
1. 顺序存储结构
1.1 顺序存储结构的存储方式
线性表的顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构或顺序映像。通常,称这种存储结构的线性表为顺序表(Sequenital List)。
特点:逻辑上相邻的数据元素,其物理次序也是相邻的。
1.2 顺序存储结构的地址计算
假设线性表的每个元素需占用c个存储单元,并以所占的第一个单元的存储地址作为数据元素的存储起始位置。则线性表中第 i+ 1 个数据元素的存储位置LOC a( i+ 1)和 第 i 个数据元素的存储位置 LOC a(i) 之间满足下列关系:
一般来说, 线性表的第 i 个数据元素 ai 的存储位置可以由 首元素推导出来
LOC(a i ) = LOC(a1) + (i−1) ∗ c
只要确定了存储线性表的起始位置, 线性表中任一数据元素都可随机存取, 所以线性表的顺序存储结构是一种随机存取的存储结构。
2. 顺序表的代码实现
2.1 数据元素结构体定义
定义如下:
typedef struct{
char no[20];
char name[50];
float price;
}Book;
typedef Book ElemType;
2.2 顺序表静态存储结构
typedef struct{
ElemType data[MAXSIZE]; // 利用data数组存取元素
int length;
}SeqList; // sequence 顺序
顺序表静态存储结构的三个属性:
(1) 存储空间的起始位置:数组data,它的存储位置就是存储空间的存储位置;
(2) 线性表的最大存储容量:数组长度MaxSize;
(3)线性表的当前长度:length;
位置序号与数组下标的区别
length表示顺序表中当前数据元素的个数。因为C语言数组的下标是从 0 开始 的,而位置序号是从 1 开始的,所以要注意区分元素的位置序号和该元素在数组中的 下标位置 之间的对应关系,
数据元素a,、a2、…、an依次存放在数组eel[m]O、eel[m]l、 …、 eel[melngth-1]中。 用顺序表存储案例2.2的稀疏多项式数据时,其顺序存储分配情况如图2.3所示。多项式的
注:采用上述顺序结构定义时,相当于创建了一个顺序表;另有一种顺序存储结构定义方式见下文。
2.3 顺序表静态存储结构的插入:
(1) 如果插入位置不合理,抛出异常 ( [ 1 , Length + 1 ] )
(2) 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量
(3) 判断是否插入在表尾,若不是,从最后一个元素开始向前遍历到第 i 个位置,分别将它们都向后移动一个位置;
(4) 将要插入元素填入位置i处(即数组下标为 i - 1 处)
(5) 表长加1
// 插入元素//在顺序表L 中第i个位置之前插入新的元素e, i值的合法范围是 1..;i..;L.length+l
Status InsertList (SeqList *L, int i, ElemType elem)
{
int k;
if ( i<1 || i> L->length+1)
{
printf("i is error");
return ERROR;
}
else if (L->length == MAXSIZE)
{
printf(" List is full");
return ERROR;
}
else if (i <= L->length)// 插入的户数元素不在表尾
{
for (k = L->length; k >= i; k--)
{
L->data[k] = L->data[k-1]; // i是指元素对应位置数,从1开始算;对应下标减一
}
}
L->data[i - 1] = elem;
L->length +=1;
return OK;
}
注:插入操作可以参照现实中排队过程中,某人插队,需要从最后一个开始到被插队位置的人,逐个向后退一位;
2.4 顺序表静态存储结构的删除:
(1)如果删除位置不合理,抛出异常 ( i 必须为 [ 1, Length])
(2)取出删除元素
(3)判断删除元素是否在表尾,若不是,从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
(4) 表长度减1
// 删除线性表中位置为 i 的元素,并将删除的元素赋给elem
Status DeleteList (SeqList *L, int i, ElemType *elem)
{
int k;
if (i<1 || i> L->length)
{
printf("error: i is wrong");
return ERROR;
}
*elem = L->data[i-1]; // 取删除元素
if (i < L->length) //如果删除的元素不在表尾
{
for (k = i+1; k<= L->length; k++)
{
L->data[k-2] = L->data[k-1];//第i+1元素的下标为 k-1;向前移动一个 为k-2
}
}
L->length --;
return OK;
}
2.5 数据元素的输入与输出
注:对于C语言结构体类型,推荐设计 赋值函数 对数据元素进行赋值
当然也可以设计进行输出
ElemType InputSData (ElemType *p)
{
printf("请输入数据元素:\n");
scanf("%s", &p->no);
scanf("%s", &p->name);
scanf("%f", &p->price);
return *p ;
}
创建,初始化,非空否,元素个数, 增,删,改,查,取
3. 优化:
顺序表强调元素在逻辑上紧密相邻,所以首先想到用数组存储。但是普通数组有着无法克服的容量限制,在不知道输入有多少的情况下,很难确定出一个合适的容量。对此,一个较好的解决方案就是使用动态数组。
首先用malloc申请一块拥有指定初始容量的内存,这块内存用作存储单链表元素,当录入的内容不断增加,以至于超出了初始容量时,就用 realloc 扩展内存容量,这样就做到了既不浪费内存,又可以让单链表容量随输入的增加而自适应大小。
3.1 顺序存储结构的动态内存分配
#define LIST_INIT_SIZE 4 // 线性表存储空间的初始分配量
#define LISTINCREMENT 2 // 线性表存储空间的分配增量
typedef struct
{
ElemType *elem; // 存储空间基址
int length; // 当前长度
int listSize; // 当前分配的存储容量(以sizeof(ElemType)为单位)
}SeqList;
3.2 动态内存分布的顺序表的创建
Status CreateList (SeqList *L)
{
L->elem = (ElemType*)malloc(sizeof(ElemType)*LIST_INIT_SIZE);
if (!L -> elem)
{
printf("分配内存失败");
return ERROR;
}
L->listSize = LIST_INIT_SIZE;
return OK;
}
或者:
SeqList CreateList (SeqList *L)
{
L->elem = (ElemType*)malloc(sizeof(ElemType)*MAXSIZE);
if (!L -> elem)
{
printf("分配内存失败");
return ERROR;
}
L->listSize = LIST_INIT_SIZE;
return *L;
}
3.3 动态内存分布的顺序表插入元素
(1)判断是否分配了空间;
(2) 如果插入位置不合理,抛出异常 ( [ 1 , Length + 1 ] )
(3) 如果线性表长度大于等于数组长度,动态增加容量
(4) 判断是否插入在表尾,若不是,从最后一个元素开始向前遍历到第 i 个位置,分别将它们都向后移动一个位置;
(5) 将要插入元素填入位置i处(即数组下标为 i - 1 处)
(6) 表长加1
Status InsertList(SeqList *L, int i, ElemType elem)
{
if (L->listSize == 0)
{
printf("未分配空间!");
return ERROR;
}
else if (i<1 || i > (L->length)+1)
{
printf("i is error");
return ERROR;
}
if (L->length >= L->listSize) //当前存储空间已满,增加分配
{
ElemType *newbase;
newbase=(ElemType *)realloc((*L).elem,((*L).listSize+LISTINCREMENT)*sizeof(ElemType));
(*L).elem=newbase; // 新基址
(*L).listSize+=LISTINCREMENT; // 增加存储容量
}
if ( L->length + 1 != i) //判断是否在表尾
{
int k;
for (k = L->length; k >= i; k--)
{
L->elem[k] = L->elem[k-1];
}
}
L->elem[i-1] = elem;
L->length++;
return OK;
}
注意: 增加分配内存空间 使用 realloc 而不是 malloc 。
原因:
3.4 动态内存分布的顺序表删除元素
(1)判断表是否分配了空间;
(2)如果删除位置不合理,抛出异常 ( i 必须为 [ 1, Length])
(3)取出删除元素
(4)判断删除元素是否在表尾,若不是,从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
(5) 表长度减1
Status DeleteList(SeqList *L, int i, ElemType *e)
{
if (L->listSize == 0)
{
printf("未分配空间!");
return ERROR;
}
else if (i<0 || i> (L->length))
{
printf("error: i is wrong");
return ERROR;
}
*e = L->elem[i-1];
if (i != L->length) // 判断是否在表尾
{
int k;
printf("删除元素不在表尾时,元素进行移动了\n");
for (k = i+1; k <= L->length; k++)
{
L->elem[k-2] = L->elem[k-1];
}
}
L->length --;
return OK;
}
3.5 动态内存分布的顺序表的销毁
Status DestroyList (SeqList *L)
{
free(L->elem);
L->elem = NULL;
L->length = 0;
L->listSize = 0;
return OK;
}
三、线性表顺序存储结构的优缺点:
在存、读数据时,不管是哪个位置,时间复杂度都是O(1)。
插入或删除时,平均移动次数和最中间的那个元素移动次数相等,为 (n-1)/2 ,时间复杂度为O(n)
· 这就说明,它比较适合元素个数比较稳定,不经常插入和删除元素,而更多的操作是存取数据的应用。
· 优点:
-无须为表示表中元素之间的逻辑关系而增加额外的存储空间。
-可以快速地存取表中任意位置的元素。
· 缺点:
-插入和删除操作需要移动大量元素。
-当线性表长度变化较大时,难以确定存储空间的容量。
-容易造成存储空间的“碎片”。
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define Status int
// 确定数据元素类型:BOOK
typedef struct{
char no[20];
char name[50];
float price;
}Book;
typedef Book ElemType;
// 创建顺序线性表
typedef struct{
ElemType data[MAXSIZE]; // 利用data数组存取元素
int length;
}SeqList; // sequence 顺序
// 初始化 线性表
void InitList(SeqList *L)
{
L->length = 0;
}
// 判断非空否
Status IsEmptyList(SeqList L)
{
if (L.length == 0)
return OK;
else
return ERROR;
}
// 表中数据元素个数
Status LenList(SeqList L)
{
//printf("L.length %d\n", L.length);
return L.length;
}
// 插入元素//在顺序表L 中第i个位置之前插入新的元素e, i值的合法范围是 1..;i..;L.length+l
Status InsertList (SeqList *L, int i, ElemType elem)
{
int k;
if ( i<1 || i> L->length+1)
{
printf("i is error");
return ERROR;
}
else if (L->length == MAXSIZE)
{
printf(" List is full");
return ERROR;
}
else if (i <= L->length)// 插入的户数元素不在表尾
{
for (k = L->length; k >= i; k--)
{
L->data[k] = L->data[k-1]; // i是指元素对应位置数,从1开始算;对应下标减一
}
printf("InsertList is ok %d", L->length);
}
L->data[i - 1] = elem;
L->length +=1;
return OK;
}
// 删除线性表中位置为 i 的元素,并将删除的元素赋给elem
Status DeleteList (SeqList *L, int i, ElemType *elem)
{
int k;
if (i<1 || i> L->length)
{
printf("error: i is wrong");
return ERROR;
}
*elem = L->data[i-1]; // 取删除元素
if (i < L->length) //如果删除的元素不在表尾
{
for (k = i+1; k<= L->length; k++)
{
L->data[k-2] = L->data[k-1];//第i+1元素的下标为 k-1;向前移动一个 为k-2
}
}
L->length --;
return OK;
}
//修改第i个元素
//修改的英语单词是modification
Status ModifyList (SeqList *L, int i, ElemType elem)
{
if (L->length == 0)
{
printf("error: L is empty");
return ERROR;
}
else if (i<1 || i> L->length)
{
printf("error: i is wrong");
return ERROR;
}
else
{
L->data[i-1] = elem;
return OK;
}
}
// 获取表中元素(将获取的 第i个位置的元素, 赋给 e)
Status GetElemt(SeqList L, int i, ElemType *e)
{
if ( i<1 && i> L.length)
{
printf("i is error");
return ERROR;
}
else if (L.length == 0)
{
printf("List is empty");
return ERROR;
}
else
{
*e = L.data[i-1];
return OK;
}
}
// 当数据元素为结构体类型时,可以设计输入输出函数,对结构体元素进行赋值
ElemType InputSData (ElemType *p)
{
printf("请输入数据元素:\n");
scanf("%s", &p->no);
scanf("%s", &p->name);
scanf("%f", &p->price);
return *p ;
}
void PrintSData (ElemType *p)
{
printf("%s\n", p->no);
printf("%s\n", p->name);
printf("%f\n", p->price);
}
int main()
{
ElemType elem1, elem2, p;
elem1 = InputSData(&elem1);
elem2 = InputSData(&elem2);
SeqList L;
InitList(&L);
printf("初始化后表长:%d\n", LenList(L));
InsertList(&L, 1, elem1);
InsertList(&L, 2, elem2);
printf("插入elem1 与 elem2 后线性表元素个数:%d\n", LenList(L));
DeleteList(&L, 1, &p);
printf("删除 第 1 个元素后,线性表元素个数:%d\n", LenList(L));
printf("删除的第 1 个元素的值:\n");
PrintSData(&p);
ElemType t1, t2, t3;
GetElemt(L, 1 , &t1);
printf("删除的第 1 个元素后,取线性表中第一个元素的值:\n");
PrintSData(&t1);
ModifyList(&L, 1, elem1);
GetElemt(L, 1 , &t2);
printf("修改的第 1 个元素后的值:\n");
PrintSData(&t2);
DeleteList(&L, 1, &t3);
printf("判断再删除一个元素后,线性表是否为空:%d\n", IsEmptyList(L));
return 0;
}