通过第六篇文章的学习我们知道,线性表分为顺序存储和连式存储两种结构,他们各自有自己的存储特点,在使用上也不同。
今天先学习下线性表的顺序存储,即数据结构中的
线性表
顺序存储的原理
我们在前边将学习数据结构的物理结构时,学习到了线性表的顺序存储结构,应该对他的存储原理有所了解了,即顺序存储,就是在存储器中分配一段连续的存储空间,逻辑上相邻的数据元素,其物理存储地址也是相邻的,假如要用顺序表来存储4个字母,则需要在内存中分配4个连续的存储单元
如下图所示:
我们看到,要存储的4个字母被存储到一段连续存储空间中,由存储空间的地址可以看出,这4个存储单元是连续的,第一个存储单元由序号0来标记,接下来的存储单元依次递增标记序号,这样在查找元素时,只要找到对应的索引就能找到严肃,非常的方便。
线性表的这种存储方式成为顺序存储或者顺序映射,又由于在逻辑上相邻的两个元素在对应的顺序表中的存储位置也相邻,因此这种映射也成为直接映射。
在上方图中,分配了4个存储单元来存储这4个字符,表的长度是4,容量也为4。但要注意的是,表的长度未必与容量相同,表的长度要小于等于表的容量,因为可能申请了4个存储单元,但实际只存入3个元素。
顺序表的特点
在顺序存储中,只要确定了线性表在存储空间的起始位置,线性表中任何元素就都可以随机存取,所以线性表的顺序存储结构是一种随机存取的结构。在高级语言中,顺序存储使用数组来实现的。
在顺序存储中,系统不需要为表元素之间的逻辑关系增加额外的存储空间,而且在存取元素时,他可以根据给出的下表快速计算出元素的存储位置,从而达到随机读取的目的。
例如,在上方的图中,如果需要读取第三个元素,因为他的序号为2,即与首元素的间隔为2,由内存段的起始位置0x001和元素间的差值可以快读计算出这个元素的存储地址为0x003,计算出地址后,就可以方便的操作该元素了。
但是,如果在顺序表中插入或删除元素,效率会特别低。
对于插入来说,只限于在表的长度小于表的容量的顺序表,如果插入大量元素,很难保证控件是否充足。
而且一旦分配了存储控件,如果想要扩充容量,需要重新分配控件,然后将原来的数据复制到新的控件中,非常麻烦;
另一方面,即使空间充足,在插入位置后的元素也必须要向后移动,效率非常低。
同理,如果要删除大量元素,势必会造成空间上的浪费,而且删除元素后,后面的元素都要向前移动,效率也会非常低。
顺序表(顺序存储)的实现
这里我们用C语言完成一些顺序表的基本操作,如:
查找,插入,删除等操作。
1、创建顺序表
在创建顺序表时,需要先创建一个头结点l来存放顺序表的长度、大小和地址等信息,然后再创建顺序表,同事将顺序表的地址保存到头结点中,如下如示意图所示:
实现思路如下:
(1)定义一个结构体struct来保存顺序表的信息
(2)为头结点分配空间
(3)为顺序表分配空间,将顺序表空间地址保存在头结点中。
(4)将头结点地址返回给调用者。
ok,我们来看下代码
//第1步,创建结构体
typeof struct SeqList{ //头结点,记录表的信息
int capacity; //表容量
int length; //表长度
int *node; //node[capacity]即为指针数组
}TSeqList;
//第2步,创建得到顺序表的方法
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;//返回创建好的顺序表地址
}
注:memset(temp,0,sizeof(TSeqList));该代码的意思是:将temp所指向的一块内存区域中的前几个元素地址的内容都设置为第二个参数的值,这里即为把所有的元素的值都置为0。
顺序表的实现并不难,且实现方式也很多,只要思路清晰,代码就很简单了。
java当中的顺序表最简单的例子就是数组了,java由于是高级语言,所以就不会像c语言一样需要考虑地址了,这里为了方便大家理解地址相关的知识,所以采用c语言。
2、求顺序表容量
在实现顺序表时,一般将顺序表信息保存在头结点中,因此求顺序表容量时,只需要直接从头结点获取即可,代码如下
//求顺序表容量
int seqList_Capacity(SeqList * list){
TSeqList * temp=NULL;
if(list==NULL)
{
return;
}
temp=(TSeqList *)list;
return temp->capacity;
}
解释下:
1、该方法的参数是顺序表的地址
2、上方的判断list是否为空做的是代码的健壮性判断
3、求顺序表长度
和求顺序表容量一样,顺序表长度也存储在顺序表头结点中,所以可以直接获取,代码如下:
//获取顺序表的长度
int seqList_Length(SeqList * list){
TSeqList * temp=NULL;
if(list==NULL){
return;
}
temp=(TSeqList *)list;
return temp->length;
}
4、插入元素
增删改查是数据结构的核心操作,每种数据结构都要实现这几种最基本的操作。在顺序表中,如果要插入元素,则需要将插入位置后的所有元素向后移动,其示意图如下
我们看到,在插入元素时,需要将要插入元素位置的元素及其后边的元素都要往后移动一个单位。
不过,在插入过程中,需要考虑一些异常情况:
- 当顺序表已满时,表中的元素无法向后移动,需要作出特别处理,比如当表满时,就不在插入,或者是当表满时,新开辟一块更大的空间来存储这些元素。
-
当插入的位置在空闲区域时,需要作出相应的处理。例如下图情况:
表的容量为5,表中只有A 和X两个元素,当插入新元素Y时,指定的位置是角标为3的位置,这样就造成了元素不连续,不符合顺序表的存储规则。在这种情况下,可以做适当的修正,将插入位置改为角标2.
有了插入元素的思路后,接下来用代码来实现:
//插入元素
int seqList_Insert(SeqList * list,SeqListNode * node,int pos){
int i;
TSeqList * temp=NULL;
if(list==NULL){
return -1;
}
temp=(TSeqList *)list;
//如果顺序表已满
if(temp->length>=temp->capacity){
return -2;
}
//容错
//如果给出的pos在长度之后,即中间有空余,就修正到最后一个元素后边
if(pos>temp->length)
pos=temp->length;
//如果pos在元素之间,那么依次后移插入位置及之后的元素
for(i=temp->length;i>pos;i--) {
temp->node[i]=temp->node[i-1];
}
//插入元素,赋值,并增加表的长度
temp->node[i]=(int)node;
temp->length++;
return 0;
}
解释下参数分别为:书序表地址,要插入元素的地址,插入位置。
5、删除元素
从顺序表中删除某一个元素,则将元素删除后,需要将后边的元素依次向前移动来补齐空位,删除过程如下图所示:
删除过程相对来说还是没有插入元素那个复杂,不必考虑内存是否足够从而造成的“溢出问题”,但是因为要移动元素,效率也是比较低的,代码如下:
//删除元素
SeqList * seqList_Delete(SeqList * list,int pos){
int i;
TSeqList tlist=NULL;
SeqListNode * temp=NULL;
if(list==NULL){
return NULL;
}
tlist=(TSeqList *)list;
if(pos<0||pos>=tlist->capacity){
printf("SeqList_Delete() error\n")
}
temp=(SeqListNode *)tlist->node[pos];
for(i=pos+1;i<tlist->length;i++){
tlist->node[i-1]=tlist->node[i];
}
tlist->length--;
return temp;
}
6、查找某个位置上的元素
在顺序表中查找某个元素是非常方便的,因为顺序表在底层是以数组来实现的,每个存储单元都有索引标注,要查找某个位置上的元素,直接按照索引来查找即可。
代码如下:
SeqListNode * seqList_Get(SeqList * list,int pos){
TSeqList * tlist=NULL;
SeqListNode * temp=NULL;
if(list==NULL){
printf("list is empty\n");
return NULL;
}
tlist=(TSeqList *)list;
if(pos<0||pos>tlist->=capacity){
printf("seqList_Get() error\n");
return NULL;
}
temp=(SeqListNode *)tlist->Node[pos]
return temp;
}
7、清空表
清空顺序表是将表中的内容全部置为0,请看代码
//清空顺序表
void seqList_Clear(SeqList * list){
TSeqList * temp=NULL;
if(list==NULL){
return;
}
temp=(TSeqList *)list;
temp->length=0;
//将书序表全部归于0
memset(temp->node,0,(temp->capacity*sizeof(void *)));
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;
}
总结
至此,我们已经实现了一个顺序表最基本的操作,可以完成简单的增删改查的功能了,接下来就可以将上方的代码复制到vs中即可。
我们可以根据自己实际中遇到的问题,通过我们刚才学过的顺序表进行编程。
本篇文章就到这里,下篇文章我们一起学习链式表