顺序表的操作与使用
作者:竹影
一、线性表ADT
ADT List{
数据对象:
D = {a[i] | a[i]∈ ElemSet, i = 1,2,...,n,n >= 0}
数据关系:
S = {<a[i-1],a[i]> | a[i-1],a[i]∈ D,i= 1,2,...n}
基本操作:
(1)InitList(&L) //初始化表格
(2)DestoryList(&L) //销毁表格
(3)ClearList(&L) //清空表格
(4)ListEmpty(L) //判空表格
(5)ListLength(L) //求表格长度
(6)GetElem(L,i,&e) //取某一位的元素
(7)LocateElem(L,e,compare())
//查找元素定位,compare是数据元素判定函数,若找到,则返回元素第一次出现的位序,若不存在则返回0
(8)PriorElem(L,cur_e,&pre_e) //找前驱
(9)NextElem(L,cur_e,&next_e) //找后继
(10)ListInsert(&L,i,e) //插入元素
(11)ListDelete(&L,i,&e) //删除元素
(12)ListTraverse(L,visit()) //遍历线性表
对表中的每个元素都做visit操作
} ADT List
以上就是我们线性表的基本图纸,现在我们来看看线性表的基本操作
二、线性表的基本操作
1.前置部分(搞定ADT中DS的部分)
#include <stdio.h>
#include <stdlib.h> //动态内存分配malloc函数的调用
#define LIST_SIZE 100 //线性表存储空间的初始分配量,单位为元素个数
#define LISTINCREMENT 10 //线性表存储空间的分配增量
/*特殊的判断符*/
#define OK 1
#define ERROR 0
typedef int ElemType; //并非多此一举哦,如果之后发现表格存储元素需要改成别的数据类型,在这里可以快速改动
typedef int Status; //与OK和ERROR对应,为函数类型
typedef struct{
ElemType *elem; //存储空间基址
int length; //当前长度
int listsize; //当前分配的存储容量(以sizeof(ElemType)为单位 , 即也表示元素储存的最大个数)
} SqList;
2.顺序表基本操作
(1)顺序表初始化
//1.顺序表初始化
Status InitList_Sq(SqList &L)
{
L.elem = (ElemType*)malloc(LIST_SIZE*sizeof(ElemType)); //动态分配内存,方便后需更改
if(!L.elem) //判断L.elem是否为NULL空指针,即内存是否分配失败
return ERROR;
L.length = 0; //此时表为空表
L.listsize = LIST_SIZE; //初始内存大小记录
return OK;
}
(2)销毁顺序表
//2.销毁顺序表
Status DestoryList_Sq(SqList &L)
{
if(L.elem)
free(L.elem); //释放动态开辟的内存空间
L.elem = NULL; //重置指针
L.length = 0;
L.listsize = 0;
return OK;
}
(3)清空顺序表
//3.清空顺序表
Status ClearList_Sq(SqList &L)
{
L.length = 0; //从逻辑上将表变成空表;
return OK;
}
值得一提的是:顺序表的清空并没有对L.elem的内存空间进行清空,而是对长度进行清零,即:保留物理内存,只进行逻辑上的清空。这样,之后对线性表重新规划的时候是直接在原来的内存上进行覆盖,简化了代码逻辑
(4)顺序表判空
//4.顺序表判空
Status ListEmpty_Sq(SqList L)
{
return(L.length == 0);
}
这里使用了极简表达,L.length == 0为逻辑语句,当其为真时值为非零,其为假时值为0,即当表为空表的时候返回0,等价于ERROR,反之等价于返回OK
(5)求顺序表长度
//5.求顺序表长度
int ListLength_Sq(SqList L)
{
return (L.length);
}
没什么好说的,注意一下这里的类型使用的是int
而非Status
,这是因为返回的长度的数据类型为int
,Status
是我们转为函数返回逻辑OK
和ERROR
特设的数据类型
(6)取每一位置的元素
//6.取元素
Status GetElem(SqList L,int i ,ElemType &e)
{
if(i < 1 || i > L.length)
return ERROR; //i的值不合法
e = L.elem[i-1];
return OK;
}
e = L.elem[i-1]
也可以写 e = *(L.elem+i)
, 效果一样,但是更强调L.elem
的指针特性,因为从C语言数组和指针的特点角度来讲,二者等价,不做详细说明,不懂的话补一下C语言指针与数组的基础
(7)找元素位置
//7.找元素位置
Status compare(ElemType e1,ElemType e2)
{
return(e1 == e2);
}
int LocateElem_Sq(SqList L, ElemType e , Status compare(ElemType,ElemType))
{
int i = 0;
ElemType *p = L.elem;
while((i < L.length) && !(*compare)(*p++,e))
{
i++;
}
if(i < L.length)
return i+1;
else
return 0;
}
唯一值得补充的是,函数的指针调用:
函数指针作为函数参数
函数指针可以作为参数传递给其他函数,这在回调函数等场景中非常有用。
首先函数的指针是这样定义的:
返回类型 (*指针变量名)(参数列表)
(8)找元素的前驱
//8.找元素的前驱
Status PriorElem_Sq(SqList L,ElemType cur_e,ElemType &pre_e,Status (*compare)(ElemType,ElemType))
{
for(int i = 0 ; i < L.length ; i++)
{
if((*compare)(L.elem[i],cur_e) && (i!=0)) //注意i != 0,因第一个元素没有前驱
{
pre_e = L.elem[i-1];
return OK;
}
}
return ERROR;
}
(9)找元素的后继
//9.找元素的后继
Status NextElem_Sq(SqList L,ElemType cur_e,ElemType &next_e,Status (*compare)(ElemType,ElemType))
{
int i = 0;
ElemType *p = L.elem;
while((i < L.length-1) && (*compare)(*p++,cur_e))
{
i++;
if((i >= 0 ) && (i < L.length-1))
{
next_e = L.elem[i+1];
return OK;
}
else
return ERROR;
}
}
本来格式可以完全照着前面一个写的,但是这里用指针的思想重新写一个,两种任选一个喜欢的即可
(10)顺序表的插入
//10.顺序表的插入
Status ListInsert_Sq(SqList &L, int i , ElemType e)
{
if(i < 1 || i > L.length+1) //保证i值合法
return ERROR;
if(L.length >= L.listsize) //当前储存空间已满
{
ElemType *newbase = (ElemType*)realloc(L.elem,(L.listsize + LISTINCREMENT)*sizeof(ElemType));//重新分配内存空间
if(!newbase)
return ERROR;
L.elem = newbase; //刷新
L.listsize += LISTINCREMENT; //增加内存容量
}
}
注意一个很有意思的新函数:realloc(原内存指针,新内存指针分配的总大小)
,它用于在原来的基础上增加内存或减少内存,返回值和malloc
一样也为指针
(11)顺序表的删除元素
//11.顺序表的删除
Status ListDelete_Sq(SqList &L, int i , ElemType &e)
{
if(i < 1 || (i > L.length))
return ERROR;
ElemType *p = L.elem+i-1;
e = *p;
ElemType *q = L.elem+L.length-1;
while(p < q)
{
*p = *(p+1);
p++;
}
L.length--;
return OK;
}
可以试着计算一下他的时间复杂度
(12)顺序表的遍历
//12.顺序表的遍历
Status visit(ElemType e)
{
printf("%d ",e);
//or what else
}
Status ListTraverse(SqList L,Status(*visit)(ElemType))
{
for(int i = 0 ; i < L.length ; i++)
{
if(!visit(L.elem[i-1]))
return ERROR;
}
}
总结:由于顺序表和数组的高度逻辑相似性,指针和数组之间的切换特性仍然可用,但是要注意动态内存分配和释放
并且,对于要对列表本体进行修改就要使用引用格式&L
,单纯的查找和遍历就不需要