一、什么是线性表?
线性表是由n(n≥0)个属性相同的数据元素a1,a2…an组成的一个有限序列。
可以表示为 A= (a1, a2, a3, … ai … an)
二、线性表的特征:
1、有且只有一个表头元素a1,它无前驱;
2、有且只有一个表尾元素an,它无后继;
3、表头元素与表尾元素外,其它所有元素有且只有一个前驱,也有且只有一个后继。
4、线性表中结点的个数n称为线性表的长度。当n=0时,称为空表。
线性表 A= (a1, a2, a3, … ai … an) ,以线性表A为例,a2的前驱是a1,a2的后继是a3。a1无前驱,an无后继。
三、线性表的存储
把逻辑上的线性表存储到计算机的内存中,有两种存储方式
顺序存储(使用数组) 顺序表
链式存储(使用指针) 链表
四、线性表的顺序存储结构
顺序表的定义:
用一组地址连续的存储单元依次存储线性表的每个数据元素,这种存储结构称为线性表的顺序存储结构,用这种结构表示的线性表称为顺序表。
顺序表的特点:
用数据元素在计算机内物理位置相邻来表示线性表中数据元素之间的逻辑关系。
顺序表中第 i 个数据元素 ai 的存储位置:
ai = a1 + ( i - 1) × m
a1为顺序表的首地址,m为顺序表中的一个数据元素占用的存储空间。
顺序表为随机存储结构(读取数据元素所需要的时间与其所在的位置无关)。
数组长度与线性表长度区别
数组长度是存放线性表的存储空间的长度,存储分配后这个量一般是不变的(静态分配),存储空间的长度变化的是动态分配,不过这会带来性能上的损耗。线性表长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的。在任意时刻,线性表的长度应该小于等于数组的长度。
如下图就比较通俗易懂一点:
顺序表的语言描述(C++)
#define MaxSize 100
typedef int ElemType; //声明顺序表的元素类型(方便修改顺序表的数据类型)
typedef struct {
ElemType data[MaxSize]; //存放顺序表数据,MaxSize代表顺序表能存最多数据个数
int length; //顺序表的长度,代表当前有多少个数据(元素)
} SqList; //SqList为用户定义的顺序表类型
五、顺序表的代码实现
线性表的基本运算(操作)
1、初始化线性表
初始化是指为顺序表分配一段预定义大小的连续空间,elem记录基地址,顺序表长度为0。
2、整体建立顺序表
3、判线性表是否为空表
4、求线性表的长度
5、输出线性表
6、求线性表L中指定位置的某个数据元素
7、定位查找
8、插入一个数据元素
在顺序表中第i个位置之前插入一个元素e,需要从最后一个元素开始,后移一位,直到把第i个元素也后移一位,然后把e放入第i个位置。
步骤:
1、判断顺序表是否满或插入的位置是否合法,否则会产生错误
2、将顺序表最后一个(第length个)元素到第pos(要插入的位置)个元素之间的所有元素依次逐个向后移动一个位置,为新元素留出插入位置。
3、将新元素插入于留出的位置上,顺序表长度增一。
9、删除数据元素
在顺序表中删除第i个元素,需要把该元素暂存到变量e,然后从i+1个元素开始前移,直到把第n个元素也前移一位,即可完成删除操作。
步骤:
1、判断顺序表是否满或插入的位置是否合法,否则会产生错误
2、将顺序表的第pos(要删除的位置)+1个元素到第length个元素之间的所有元素依次逐个向前移动一个位置。
3、顺序表长度减一。
六、基本操作的源码
.h文件中的:
#include <iostream>
using namespace std;
#define LISTSIZE 100
typedef int DataType;//声明DataType类型
typedef struct SqList
{
DataType items[LISTSIZE]; //存放线性表数据
int length;
}SqList;// SqList为用户定义的线性表类型
//初始化空线性表
void InitList(SqList &L);
//整体建立顺序表
void CreateList(SqList &L, DataType a[], int n);
//判断线性表是否为空
int ListEmpty(SqList &L);
//求出线性表长度
int ListLength(SqList &L);
//遍历输出线性表
int TraverseList(SqList &L);
//获取顺序表中指定位置上的数据元素
//pos为指定位置,e用于返回找到的数据元素
int GetElem(SqList &L, int pos, DataType &e);
//从线性表中查找元素,返回第一个与指定值匹配元素位置
//e为待查找的数据元素
int Find(SqList &L, DataType e);
//向线性表指定位置插入一个新元素
//pos为插入的(逻辑)位置, e为待插入的数据元素
int ListInsert(SqList &L, int pos, DataType e);
//从线性表中删除第pos个元素
//pos为删除的(逻辑)位置, 用e返回被删元素
int ListDelete(SqList &L, int pos, DataType &e);
.cpp文件中的:
#include "linearList.h"
//初始化空线性表
void InitList(SqList &L)
{
L.length = 0;
}
//整体建立顺序表
void CreateList(SqList &L, DataType a[], int n)
{
int i;
for (i = 0; i < n; i++)
{
L.items[i] = a[i];
}
L.length = n;
}
//判断线性表是否为空
int ListEmpty(SqList &L)
{
if (L.length <= 0)
{
return 1;
}
else
{
return 0;
}
}
//求出线性表长度
int ListLength(SqList &L)
{
return L.length;
}
//遍历输出线性表
int TraverseList(SqList &L)
{
int i;
for (i = 0; i < L.length; i++)
{
//从头到尾输出线性表的每一个元素
cout << L.items[i] << " ";
}
cout << endl;
return 1;
}
//获取顺序表中指定位置上的数据元素
int GetElem(SqList &L, int pos, DataType &e)
{
if (pos <= 0 || pos>L.length)
{
cout << "位置无效" << endl;
return 0;
}
e = L.items[pos - 1]; //把元素的值通过引用传递给外部
return 1;
}
//从线性表中查找元素,返回第一个与指定值匹配元素位置
int Find(SqList &L, DataType e)
{
int i = 0; // i表示当前查找的位置,从头开始
if (ListEmpty(L))
{
cout << "顺序表为空表,无法查找!" << endl;
return 0;
}
//从头到尾比较线性表中的元素,当未超出线性表的末尾,且未找到时,i向后移
while (i < L.length && L.items[i] != e)
{
i++;
}
if (i < L.length) //如果未超出线性表的末尾,说明找到
{
return i + 1; //返回逻辑位置
}
else
{
return 0; //超出线性表的末尾,则说明找不到
}
}
//向线性表指定位置插入一个新元素
int ListInsert(SqList &L, int pos, DataType e)
{
int i;
if (L.length >= LISTSIZE){ //判断顺序表是否满
cout << "顺序表满,无法插入!" << endl;
return 0;
}
if (pos <= 0 || pos>L.length + 1){ //判断位置是否有效
cout << "插入位置无效!" << endl;
return 0;
}
for (i = L.length - 1; i >= pos - 1; i--)
{
L.items[i + 1] = L.items[i]; //向后移动元素
}
L.items[pos - 1] = e; //插入元素e
L.length++; //表长加一
return 1;
}
//从线性表中删除第一个与指定值匹配的元素
int ListDelete(SqList &L, int pos, DataType &e)
{
int i;
if (ListEmpty(L)){ //判断顺序表是否为空
cout << "顺序表为空表,无法删除!" << endl;
return 0;
}
if (pos<1 || pos>L.length){ //判断位置是否有效
cout << "删除位置无效!" << endl;
return 0;
}
e = L.items[pos - 1]; //删除元素前,把元素的值通过指针传递给外部,备用
for (i = pos; i < L.length; i++)
{
L.items[i - 1] = L.items[i]; //向前移动元素
}
L.length--; //表长减一
return 1;
}
main.cpp 测试代码
#include "linearList.h"
int main(void)
{
//定义线标表SqList类型的变量
SqList my_List1, my_List2;
DataType Array[] = {3, 7, 9, 2, 8, 6};
//初始化线性表
InitList(my_List1);
InitList(my_List2);
//向线性表的指定位置插入数据
ListInsert(my_List1, 1, 8);
ListInsert(my_List1, 1, 7);
ListInsert(my_List1, 3, 6);
ListInsert(my_List1, 2, 2);
ListInsert(my_List1, 2, 9);
ListInsert(my_List1, 1, 3);
for (int i = 1; i <= 6; i++)
{
ListInsert(my_List2, i, Array[i - 1]);
}
//输出线性表元素
cout << "my_list1:";
TraverseList(my_List1);
cout << "my_list2:";
TraverseList(my_List2);
//输出表的长度
cout << ListLength(my_List1) << endl;
cout << ListLength(my_List2) << endl;
//查找元素的位置
cout << Find(my_List1, 3) << endl;
cout << Find(my_List2, 6) << endl;
system("pause");
return 0;
}
运行结果如下:
七、顺序存储的动态分配
动态分配与静态分配相比,动态分配的数组长度的大小可以灵活分配
#define InitSize 10 //设置默认最大长度
typedef struct
{
int *data; //动态分配数组的指针
int MaxSize; //数组的最大容量
int length; //当前的长度
}SqList;
基本操作与顺序存储的静态分配差不多,但是还是有一定的区别,如下:
动态分配要初始化:
void Initlist(Sqlist &L)
{
//用malloc函数分配一段连续的内存空间
L.data = (int *)malloc((sizeof(int)*InitSize);
L.length=0;
L.MaxSize=InitSize;
}
动态增加数组的长度:
//动态增加数组长度
void IncreaseSize(SqList &L, int len)
{
int *p = L.data;
L.data = (int*)malloc((L.MaxSize + len) * sizeof(int));
for (int i = 0; i < L.length; i++)
{
L.data[i] = p[i]; //数据复制到新区域
}
L.MaxSize = L.MaxSize + len; //顺序表最大长度增加len
free(p); //释放原来内存空间
}
动态分配增加了销毁顺序表:
void DestroyList(SqList &L)
{
free(L.data); //销毁顺序表,销毁数据,没有修改指针指向
L.data=NULL; //销毁数据后,让指针指向空,不然会出现野指针
L.length=0;
L.listsize=0;
}
其它操作(插入、删除、查找等)与静态分配差不多。
动态存储的物理存储是连续的,顺序的。
八、顺序存储的优缺点:
顺序存储结构的优点:
逻辑相邻,物理相邻;
可随机存取任一元素;
存储空间使用紧凑。
顺序存储结构的缺点:
插入、删除操作需要移动大量的元素;
预先分配空间需按最大空间分配,利用不充分;
表容量难以扩充。