2.线性表
线性表,顾名思义是像线一样性质的表,是最常用且最简单的一种数据结构。线性表的存储结构有顺序存储结构和链式存储结构两种。
2.1 什么是线性表
线性表是具有相同特性的数据元素组成的一个有限序列。
线性表中元素的个数为线性表的长度,当元素个数为0时,称该线性表为空表,。
线性表中的元素可以是整数、字符等简单数据,也可以由数个数据项组成的。
每一种数据结构都有它自己的特征,线性表作为一种最简单的数据结构,有如下几个特征:
- 线性表中有且只有一个开始结点(头结点),这个开始结点没有前驱结点。
- 线性表中有且只有一个末尾结点(尾结点),这个末尾结点没有后继结点。
- 除去开始结点与末尾结点,其他结点都有一个前驱结点和一个后继结点。
线性表在存储结构上有顺序存储结构和链式存储结构两种方式,但不管那种存储方式,他们的结构都有如下特点:
- 均匀性。虽然不同数据表的数据元素可以是各种各样的,但对于同一个线性表来说,数据元素必须具有相同的数据类型和数据长度。
- 有序性。各数元素在线性表中的位置只取决于它们的序号,数据元素之间的相对位置是线性的,即存在唯一的“第一个”和“最后一个”数据元素。除了第一个和最后一个外,其他元素前面均只有一个数据元素(直接前驱),后面均只有一个数据元素(直接后继)。
线性表是一种比较灵活的数据结构,它的长度可根据需要增减,它也可以进行插入、删除等操作。可对线性表进行的基本操作如下:
- 创建——Create():创建一个新的线性表。
- 初始化——Init():初始化操作,将新创建的线性表初始化为空。
- 获取长度——GetLength():获取线性表的长度。
- 判断表是否为空——IsEmpty():判断线性表是否为空
- 获取元素——Get():获取线性表某一个位置的元素。
- 清空表——Clear():清空线性表,将线性表置为空。
当然,在设计线性表时,也可以有其他操作,例如,根据某个条件对线性表中的元素进行排序,将两个线性表合并成一个线性表,重新复制一个线性表等,上面只是描述了线性表一些最基本的操作。
2.2线性表的顺序存储
2.2.1 顺序存储的原理
所谓顺序存储,就是在存储器中分配一段连续的存储空间,逻辑上相邻的数据元素,其物理存储地址也是相邻的。
地址 | 序号 | |
---|---|---|
0x001 | d | 0 |
0x002 | b | 1 |
0x003 | w | 2 |
0x004 | t | 3 |
要存储的4个字母被存储在一段连续的存储空间中。由存储空间的地址可以看出,这4个存储单元是连续的,第一个存储单元由序号0来标记,接下来的存储单元依次递增标记序号,这样在查找元素时,只要找到相应的索引就能找到元素,非常方便。线性表的这种存储方式称为顺序存储或顺序映射,又由于逻辑上相邻的两个元素在对应的顺序表中的存储位置也相邻,因此这种映射也称为直接映射。
在顺序存储中,只要确定了线性表在存储空间里的起始位置,线性表中任意元素就都可随机存取,所以线性表的顺序存储结构是一种随机存取的结构。在高级语言中,顺序存储是用数组来实现的。
在顺序存储中,系统不需要为表元素之间的逻辑关系增加额外的存储空间,而且在存取元素时,它可以根据给出的下标快速计算出元素的存储地址,从而达到随机读取的目的。例如如果要读取第3个元素,因它的下标为2(与第1个元素之间有两个间隔),由内存段的起始地址0x001和元素相差个数可以快谏计算出第3个元素的存储地址0x003,计算出地址后,就可以方便地对数据进行操作。
但是,如果在顺序表中插入或删除元素,效率会特别低。对插入来说,只限于在表的长度小于表的容量的顺序表中,如果插入大量数据,很难保证空间是否充足。而且一旦分配了存储空间,如果想要扩充容量,需要重新分配空间,然后将原来的数据复制到新的空间中,非常麻烦。另一方面,即使空间充足,在插入位置之后的元素也必须都要向后移动,效率非常低。同理,如果要删除大量元素,那么势必会造成空间的浪费,而且删除元素后,后面的元素都要向前移动,效率也会非常低。
2.2.2顺序存储的实现
1.创建顺序表
在创建顺序表时,需要创建一个头结点来存放顺序表的长度、大小和地址等信息,然后再创建顺序表,同时将顺序表的地址保存在头结点中。
实现思路:
1. 定义一个struct 来保存顺序表信息
2. 为头结点分配空间
3. 为顺序表分配空间,将顺序表空间地址保存在头结点中。
4. 将头结点地址分会给调用者。
代码实现如下:
typedef struct _tag_SeqList //头结点,记录表的信息
{
int capacity; //表容量
int length; //长度
int *node; //node[capacity] ,为指针数组
}TSeqList;
SeqList *SeqList_Create(int capacity) //返回值为SeqList*类型的,即顺序表的地址
{
int ret;
TSeqList * temp = NULL;
temp = (TSeqList *)malloc(sizeof(TSeqList)) ; //为头结点分配空间
if(temp == NULL)
{
ret = 1;
printf("func SeqList_Create() error:%d\n",ret);
return NULL;
}
memset(temp,0,sizeof(TSeqList));
temp->capacity = capacity;
temp->length = 0;
temp->node = (int*)malloc(sizeof(void*)*capacity); //分配一个指针数组
if(temp->node == NULL)
{
ret = 2;
printf("func SeqList_Create() error:%d\n",ret);
return NULL;
}
return temp;
}
2.求顺序表容量
//求顺序表容量
int SeqList_Capacity(SeqList *list)
{
TSeqList * temp = NULL;
if(list == NULL)
{
retrun;
}
temp = (TSeqList *)list;
return temp->capacity;
}
3.求顺序表长度
//获取顺序表长度
int SeqList_Length(SeqList *list)
{
TSeqList *temp;
if(list == NULL)
{
return;
}
temp = (TSeqList *)list;
return temp->length;
}
4.插入元素
增删改查是数据结构的核心操作,每种数据结构都要实现这几种最基本的操作。
在顺序表中,如果要插入元素,则需要将插入位置后的所有元素向后移动。在插入过程中,需要考虑一些特殊情况:
当顺序表已满时,表中的元素无法向后移动,需要作出特别处理(例如不插入,或者新开辟一块更大的空间来存储这些元素)。
当插入的位置在空闲区域时,需要作出相应处理。例如插入元素导致顺序表的元素不连续,破坏了顺序表的存储规则,这时就要适当修改插入元素的位置。
//插入元素
int SeqList_Insert(SeqList *list,SeqListNode *node,int pos) //参数为顺序列表地址、要插入的元素地址、插入位置
{
int i;
TSeqList *temp = NULL;
if(list == NULL || node == NULL)
{
return -1;
}
temp = (TSeqList *)list;
if(temp->length >= temp->capacity) //如果长度大于容量,则顺序表已满
{
return -2;
}
//容错
if(pos > temp->length) //如果给出的pos在长度后,即中间有空余
pos = temp->length; //就修正到最后一个元素后面
for(i = temp->length; i > pos;i--) //将插入位置的元素依次往后移动
{
temp->node[i] =temp->node[i-1];
}
temp->node[i] = (int)node; //然后往腾出的位置插入新元素结点
temp->length ++; //插入成功后,长度加1
return 0;
}
5.删除元素
从顺序表中删除某一个元素,则将元素删除后,需要将后面的元素依次向前移动来补齐空位。
删除过程相对来说没有插入过程那么复杂,不必考虑内存不足的“溢出”问题但因为要移动元素,效率还是较低的。
//删除元素
SeqList * SeqList_Delete(SeqList * list,int pos)
{
int i;
//先作健壮性判断
TSeqList * tlist = NULL;
SeqListNode * temp = NULL;
tlist = (TSeqList *)list;
if(list ==NULL || pos < 0 || pos >= tlist->capacity)
{
printf("SeqList_Delete () error\n");
return NULL;
}
temp = (SeqListNode *)tlist->node[pos]; //要删除的元素
for(i = pos+1; i < tlist->length;i--) //将删除元素位置后的所有元素向前移动
{
tlist->node[i-1] = tlist->node[i];
}
tlist->length--; //删除元素后,长度要减1
return temp;
}
6.查找某个位置上的元素
在顺序表中查找某个元素是非常方便的,因为顺序表在底层是以数组来实现的,每个存储单元都有索引标注,要查找某个位置的上的元素,直接按索引来查找即可。
//查找元素
SeqList *SeqList_Get(SeqList *list,int pos)
{
//先作健壮性判断
TSeqList *tlist = NULL;
SeqListNode *temp = NULL;
tlist = (TSeqList *)list;
if(tlist == NULL || pos<0 || pos >= tlist->capacity)
{
printf("SeqList_Get() error\n");
return NULL;
}
temp = (SeqListNode *)tlist->node[pos]; //将表中的pos位置的结点指针赋给temp;
return temp;
}
7.清空表
清空顺序表是将表中的内容分全部置0。
//清空表
void SeqList_Clear(SeqList *list)
{
TSeqList *temp;
if(list == NULL)
{
return;
}
temp = (TSeqList *)list;
temp->length = 0;
memset(temp->node,0,(temp->capacity * sizeof(void *))); //将顺序表全部归0
return;
}
8.销毁表
//销毁表
void SeqList_Destory(SeqList *list)
{
TSeqList *temp = NULL;
if(list == NULL)
{
return;
}
temp = (TSeqList * )list;
if(temp->node != NULL)
{
free(temp->node); //先释放头结点中的指针数组
}
free(temp); //再释放头结点
return;
}