数据结构——浅谈顺序存储线性表
一、线性表
线性表可以说是最常用最简单的一种数据结构,自然掌握线性表是数据结构最重要的一环了。
线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的(注意,这句话只适用大部分线性表,而不是全部。比如,循环链表逻辑层次上也是一种线性表(存储层次上属于链式存储),但是把最后一个数据元素的尾指针指向了首位结点)。
我们说"线性"和"非线性",只在逻辑层次上讨论,而不考虑存储层次,所以双向链表和循环链表依旧是线性表。
二、从顺序存储谈起
顺序表示指的是用一组地址连续的存储单元依次存储线性表的数据元素,称为线性表的顺序存储结构。它以"物理位置相邻"来表示线性表中数据元素间的逻辑关系,可随机存取表中任一元素。
存储位置 内存状态 数据元素在线性表中的位序
三、代码实现
对于顺序表示线性表,定义表结构时首先应该明白,需要找到表的位置那么在定义时就要有表的地址,所以在申请存储空间时就应该将存储空间基址赋予,而且还需要考虑到如果在插入时空间是否还足够,所以需要将当前表分配的存储容量表示,当然对于当前表的长度也是可以定义在其中的
//静态定义
#define maxsize 线性表可能达到的最大长度
typedef struct
{
ElemType elem[maxsize];
// 在这里采用记录线性表中最后一个元素在数组elem[ ]中的位置(下标值),空表置为-1
int last;//表的最后一位元素位置,也相当于表长
}SeqList; /*对于顺序表示线性表,我这样进行定义的话自然就直接可以略去创建一个空链表函数的操作了
但是自然也有弊端,这样是一个静态的,适合事先知道线性表的最大值时,由于使用频率还是统一以动态的进行分析*/
//动态定义
#define maxsize 线性表可能达到的最大长度
#define addsize 空间不足时需要增加的量
typedef struct
{
ElemType *elem;//存储空间基址
int length;//当前表长
int listsize;//当前分配的存储容量
}SeqList; //如果这样进行结构体的定义那么在进行操作前建立一个空的线性表
//建立空表
status InitList_Sq(SqList &L)
{
L.elem=(ElemType*)malloc(addsize*sizeof(ElemType));/*申请将addsize个ElemType数据需要的大小
并返回一个ElemType类型的指针,指向一段可用内存的起始地址赋给L.elem*/
if(!L.elem)
{
printf("空间分配失败");
return -1;
}
L.length=0;
L.listsize=maxsize;
return 0;
}
对于线性表需要达到的最基本操作大致有
- 查找操作
- 插入操作
- 删除操作
- 顺序表合并算法
- …
查找操作 :
对于顺序表示线性表的查找分为两种
- 按序号查找
- 按内容查找
顺序表示线性表按序号查找是很好理解的
要求查找线性表L中第i个数据元素,其结果是L.elem[i-1]或L->elem[i-1]
当然也可以麻烦点写成函数:
status GetData(SeqList L,int i)
{
printf("%d位置上的数是%d",i, L.elem[i - 1]);
}
如果要求查找线性表L中与给定值e相等的数据元素,即按内容查找:
status Locate(SeqList L,ElemType e)
{
int i = 0; //i为扫描计数器,初值为0,即从第一个元素开始比较
while ((i <= L.length) && (L.elem[i] != e)) i++;
//顺序扫描表,直到找到值为key的元素,或扫描到表尾而没找到
if (i <= L.length)
return(i); //若找到值为e的元素,则返回其序号
else
return(-1); //若没找到,则返回空序号
}
插入操作 :
线性表的插入运算是指在表的第i (1≤i≤n+1)个位置,插入一个新元素e,使长度为n的线性表 (e1,…,ei-1,
ei,…,en) 变成长度为n+1的线性表(e1,…,ei-1,e,ei,…,en)。
对于顺序表示线性表需要进行插入操作需要在结点的物理顺序必须和结点的逻辑顺序保持一致其实简单
的理解就是原表中位置n, n-1,…, i上的结点, 依次后移到位置n+1,n,…,i+1上,空出第i个位
置,然后在该位置上插入新结点e,当然当i=n+1时, 是指在线性表的末尾插入结点,所以无需移动结点,直
接将e插入表的末尾即可,不过既然是建立的一个动态表那么就必须考虑到当原来分配的空间不够用的情况,
在这个时候就需要在原来的空间上增加空间,可以利用realloc来实现。
status InsList(SeqList* L, int i, ElemType e)
{
int k;
if ((i < 1) || (i > L->length + 2))//首先判断插入位置是否合法
{
printf("插入位置i值不合法");
return -1;
}
if (L->length >= L->listsize)
{
L->elem = (ElemType*)realloc(L->elem,(L->listsize+addsize) * sizeof(ElemType));
/*从基址开始申请扩大addsize个ElemType数据需要的大小并返回一个ElemType类型的指针,
指向一段可用内存的起始地址重新赋给L.elem*/
if (!L->elem)
{
printf("空间分配失败");
return -1;
}
L->listsize += addsize;//将表的当前分配空间更新
}
for (k = L->length; k >= i - 1; k--) //为插入元素而移动位置
L->elem[k + 1] = L->elem[k];
L->elem[i - 1] = e; //C语言数组第i个元素下标为i-1
L->length++;
return 1;
}
删除操作
删除操作其实和插入思想相同也需要进行数据段的移动,其实也还是物理顺序
必须和结点的逻辑顺序保持一致只不过删除是从删除的位置起将后面的元素向前挪一位
算法大致是若i=L->last+1, 则移位语句L->elem[k-1]= L->elem[k]不执行,
因为循环变量的初值大于终值,此时不需要移动元素,仅将表长度减1即可。
status DelList(SeqList* L, int i, ElemType* e)
{
int k;
if ((i < 1) || (i > L->length + 1)) {
printf("删除位置不合法!");
return -1;
}
*e = L->elem[i - 1]; //将删除的元素存放到e所指向的变量中
for (k = i; i <= L->length; k++)
L->elem[k - 1] = L->elem[k];//后面的元素依次前移
L->length--;
return 1;
}
顺序表合并算法
将两个元素均为非递减有序排列的顺序表合并成一个非递减有序排列顺序表LC,可以根据下面的算法进行操作
- 设表LC是一个空表,为使LC也是非递减有序排列,可设两个指针i、j分别指向表LA和LB中的元素
- 若LA.elem[i]>LB.elem[j],则当前先将LB.elem[j]插入到表LC中
- 若LA.elem[i]≤LB.elem[j] ,当前先将LA.elem[i]插入到表LC中
- 如此循环,直到其中一个表被扫描完毕,然后再将未扫描完的表中剩余的所有元素放到表LC中。
代码实现如下:
void merge(SeqList* LA, SeqList* LB, SeqList* LC)
{
int i = 0,j = 0, k = 0;
while (i <= LA->length && j <= LB->length)
if (LA->elem[i] <= LB->elem[j])
{
LC->elem[k] = LA->elem[i]; i++; k++;
}
else
{
LC->elem[k] = LB->elem[j]; j++; k++;
}
while (i <= LA->length) //当LA长,则将表LA余下元素赋给表LC
{
LC->elem[k] = LA->elem[i]; i++; k++;
}
while (j <= LB->length) //当LB长,则将表LB余下的元素赋给表LC
{
LC->elem[k]= LB->elem[j];
j++;
k++;
}
LC->length = LA->length + LB->length;
}
四、顺序存储结构的优缺点
优点
无需为表示结点间的逻辑关系而增加额外的存储空间;
可方便地随机存取表中的任一元素。
缺点
插入或删除运算不方便,除表尾的位置外,在表的其它位置上进行插入或删除操作都必须移动大量的结点,其效率较低;
由于顺序表要求占用连续的存储空间,存储分配只能预先进行静态分配。因此当表长变化较大时,难以确定合适的存储规模。