线性表是线性结构,我们来研究它的逻辑关系,用ADT(抽象数据类型)来表示,ADT的描述可以从顺序结构表示和链式结构表示。
线性表的表示与实现-------顺序结构
关于顺序结构
顺序结构用顺序表来实现和描述。顺序表在C语言中通常会用一维数组来表示顺序存储结构。
顺序表结构特点:随机查找,删除插入麻烦,可变大小。
用一组地址连续的存储单元依次存放线性表中的数据元素,顺序表中的每个在逻辑上相邻的元素在物理存储位置上也是相邻的。如下图所示,a1,a2在逻辑上相邻,在物理表中的存储位置也是相邻的。
设每个元素占用C个存储单元 ,线性表中第i+1个元素的存储位置LOC(a i+1)和第i个元素存储位置之间的关系:
LOC(ai) = LOC(ai-1) + C
一个数据元素所占存储量↑
所有数据元素的存储位置均取决于第一个数据元素的存储位置
LOC(ai) = LOC(a1) + (i-1)×C
↑基地址
顺序映像的描述定义:
注意:在顺序结构中,由于顺序结构的特点(逻辑上相邻,物理上也相邻),在定义和实现顺序结构的时候,可以不使用指针,因为顺序结构每个元素的指向都是根据存储位置而固定好的,就是一个挨着一个。为了大家好理解,在顺序结构中,我们会用两种做法来描述顺序结构。一种是使用指针,一种是不使用指针。
- 不使用指针
- #define MAXSIZE 100 // 线性表存储空间的分配量,即数组长度
- typedef struct {
- ElemType elem[MAXSIZE]; 定义数组
- int length; // 当前长度 ,线性表的元素个数,并不是数组长度
- } SqList; // 俗称 顺序表
- 指针定义
- #define MAXSIZE 100 // 线性表存储空间的分配量,即数组长度
- #define LISTINCREMENT 10 //线性表存储空间的分配增量
- typedef struct {
- ElemType *elem;//存储空间基地址
- int length; // 当前长度 ,线性表的元素个数,并不是数组长度
- int listsize; //当前分配的存储容量
- } SqList; // 俗称 顺序表
在上述代码中,用两种方法定义了一个结构体的顺序表,用ElemType表示未知数据类型的总称。
SqList为表的名称。
注意:不使用指针的顺序定义是定义了一个长度不可变的空间,即空间的大小不能变,就是MAXSIZE(100)个元素。 而使用指针定义是定义了一个长度可变的空间,它是动态内存分配的一维数组,可以在原来分配空间大小的基础上再增加LISTINCREMENT(10)个空间大小。
线性表的顺序实现(初始化、查找、插入、删除、取元素)
线性表有两种实现,一种是顺序实现,一种是链式实现。上述中我们已经定义了顺序表SqList,那么我们现在先用顺序实现。
顺序实现的几个例子:注意:在上述定义顺序表SqList时,我们用了两种方法(非指针和指针),那么在实现顺序结构时,也用两种方法进行实现,分别对应两种定义。
初始化: 将表中的元素进行初始化。
- Status InitList_Sq( SqList& L )
- { // 构造一个空的线性表
- L.length = 0; //表长度为0,即元素个数设置为0
- return OK;
- } // InitList_Sq
- 指针实现
- Status InitList_Sq( SqList& L )
- { // 构造一个空的线性表
- //用动态内存分配表的空间
- L.elem=(ElemType *)malloc(MAXSIZE *sizeof(ElemType));
- If(!L.elem)exit(OVERFLOW); //分配失败
- L.length = 0; //表长度为0,即元素个数设置为0
- L.listsize=MAXSIZE ;//初始存储容量,也就上定义中的10
- return OK;
- } // InitList_Sq
L.elem=(ElemType *)malloc(MAXSIZE *sizeof(ElemType)); malloc用于分配指定内存空间的库函数,它的作用就是分配空间,一个元素所占空间大小为sizeof(ElemType),一共有MAXSIZE个元素,所以一共分配的空间大小为MAXSIZE 乘以sizeof(ElemType),分配完成后将分配的空间转换为(ElemType*)类型的指针,然后赋给指针L.elem.
查找(定位):在顺序表中找到指定元素的位置,返回其下标。
- int Locate (SqList L, ElemType x)
- {
- // 在顺序表中查询第一个等于x的数据元素,
- // 若存在,则返回它的位序,否则返回 0
- i = 1; // i 的初值为第 1 元素的位序 ,表示第几个元素
- //L.elem[i-1]为最后一个元素 ,L.elem[0]为第一个元素,[]内是下标
- while( L.elem[i-1] !=x ) //从第一个元素开始,如果不等于要查找元素x,就再下一个,一直找到为止
- ++i;
- if (i <= L.length) return i; //找到元素位置i后,如果i在表中就返回i
- else return 0;
- } // Locate
- 指针实现
- int Locate (SqList L, ElemType e,Status(*compare)(ElemType,ElemType))
- {
- // 在顺序表中查询第一个等于x的数据元素,
- // 若存在,则返回它的位序,否则返回 0
- i = 1; // i 的初值为第 1 元素的位序
- p=L.elem; //将第一个元素的地址赋给指针p
- while( i<=L.length && !(*compare)(*p++,e))//判断L中第1个与e 满足关系compare()的数据元素
- ++i;
- if (i <= L.length) return i;
- else return 0;
- } // Locate
while( i<=L.length && !(*compare)(*p++,e))中 compare是一个函数,*p++和e 都是这个函数的参数。这句代码理解为:先是p指针指向的值加1,得到的值和e变量作为实际参数传递给函数compare进行处理,然后根据compare函数处理完的结果进行真假判断(真为1,假为0),然后!取反。
插入: 在顺序表中指定位置插入一个已知元素
(a1, …, ai-1, ai, …, an) 改变为(a1, …, ai-1, e, ai, …, an)
- Status ListInsert(SqList &L, int i, ElemType e)
- { // 在顺序表L的第 i 个元素之前插入新的元素e,
- // i 的合法范围为 1≤i≤L.length+1
- if (i < 1 || i > L.length+1) return ERROR; // 插入位置不合法
- for ( j= L.length ; j>=i ; j - - ) //从后向前查找
- L.elem[j]=L.elem[j-1]; // 插入位置及之后的元素右移
- L.elem[i-1] = e ; // 插入e
- ++L.length; // 表长增1
- return OK;
- } // ListInsert_Sq
- 指针
- Status ListInsert(SqList &L, int i, ElemType e)
- { // 在顺序表L的第 i 个元素之前插入新的元素e,
- // i 的合法范围为 1≤i≤L.length+1
- if (i < 1 || i > L.length+1) return ERROR; // 插入位置不合法
- if(L.length>=L.listsize){
- //先释放原来L.elem所指内存区域,重新分配空间同时将原有数据从头到尾拷贝到新分配的内存区域
- newbase=(ElemType*)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType));
- if(!newbase)exit(OVERFLOW);
- L.elem=newbase;
- L.listsize+=LISTINCREMENT;
- }
- q=&(L.elem[i-1]);
- for ( p=&(L.elem[L.length-1]);p>=q;--p) * (p+1)=*p ; //插入位置及之后的元素右移
- *q=e;
- ++L.length; // 表长增1
- return OK;
- } // ListInsert_Sq
newbase=(ElemType*)realloc(L.elem,(L.listsize+MAXSIZE)*sizeof(ElemType));这句代码实际上是从新分配了一个空间,然后把原来空间复制到新空间,然后又增加了一个新的空间,这个新的空间大小为LISTINCREMENT.
删除:将表中指定位置的元素删除
(a1, …, ai-1, ai, ai+1, …, an) 改变为 (a1, …, ai-1, ai+1, …, an)
- Status ListDelete (SqList &L, int i, ElemType &e)
- { // 在顺序表L中删除第i个元素,用e返回删除的值
- // i 的合法范围为 1≤i≤L.length+1
- if (i < 1 || i > L.length) return ERROR; // 插入位置不合法
- e=L.elem[i-1]; 将被删除的元素赋给e
- for(j=i+1;j<=length;j++)
- L.elem[j-2]=L.elem[j-1];
- L.length--;
- return OK;
- } // ListDelete_Sq
- 指针
- Status ListDelete (SqList &L, int i, ElemType &e)
- { // 在顺序表L中删除第i个元素,用e返回删除的值
- // i 的合法范围为 1≤i≤L.length+1
- if (i < 1 || i > L.length) return ERROR; // 插入位置不合法
- p=&(L.elem[i-1]);
- e=*p;
- q=L.elem+L.length-1;
- for(++p;p<=q;++p)*(p-1)=*p;
- --L.length;
- return OK;
- } // ListDelete_Sq
取元素
- Status GetElem (SqList L, int i, ElemType &e) {
- e=L.elem[i-1];
- return OK;
- }//GetElem
notice:
1. 在算法中经常会遇到“&”该不该加的问题,比如:
在Status ListInsert(SqList &L, int i, ElemType e)中为什么L前面加了&,而e没有。
&是引用,是一个传递参数的问题。X如果在此函数中有值的变化,那么X前面就应该加上&----&X, 比如:在插入中,表的长度加了1,表L有变化,所有&L,在删除中,元素e 被删除了,e从有值到没值,发生了变化,所以在删除中是&e.
2. 注意位序和下标。在一个表中第i(位序)个元素的值是L.elem[i-1](下标),位序是1,2,3,4,。。N 下标是 从0开始的。
3. 表长为元素的个数:L.length
第一个元素为L.elem[0](第一个元素),最后一个元素为L.elem[length-1]
关于线性表的顺序结构实现,我们就一起学习以上的几种操作,下一篇文章一起学习线性表中的链式实现。