线性表,全名为线性存储结构。
线性表是n个数据特性相同的元素的组成有限序列,是最基本且常用的一种线性结构(线性表,栈,队列,串和数组都是线性结构),同时也是其他数据结构的基础。具有“一对一”逻辑关系的数据,最佳的存储方式是使用线性表。
数据结构中,一组数据中的每个个体被称为“数据元素”(简称“元素”)。
以下图数据中的元素 3 来说,它的直接前驱是 2 ,此元素的前驱元素有 2 个,分别是 1 和 2;同理,此元素的直接后继是 4 ,后继元素也有 2 个,分别是 4 和 5。
线性表存储结构分为顺序存储结构和链式存储结构,前者称为顺序表,后者称为链表。
顺序表就是把线性表中的所有元素按照某种逻辑顺序,依次存储到从指定位置开始的一块连续的存储空间。
逻辑上相邻的数据元素,物理次序也是相邻的。
只要确定好了存储线性表的起始位置,线性表中任一数据元素都可以随机存取,所以线性表的顺序存储结构是一种随机存取的储存结构,因为高级语言中的数组类型也是有随机存取的特性,所以通常我们都使用数组来描述数据结构中的顺序储存结构,用动态分配的一维数组表示线性表。
**数组长度和线性表的长度区别:**数组长度是存放线性表的存储空间的长度,存储分配后这个量一般是不变的,线性表的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的。
优点 | 缺点 |
---|---|
无需为表示表中元素之间的逻辑关系而增加额外的存储空间 | 插入和删除操作需要移动大量元素 |
可快速的存取表中的任一位置元素 | 当线性表长度变化比较大时,难以确定存储空间的容量 |
容易造成存储空间碎片 |
结构体定义
顺序表可以分为静态分配和动态分配两种:
#define MaxSize 50 / /顺序表的最大长度
typedef struct{ // typedef类型重命名
ElemType data[MaxSize]; // 顺序表的元素,ElemType是未明确的数据类型,使用时可以用typedef定义具体类型
int length; // 长度
}SqList;
静态分配方法需要预先分配一段固定大小的连续空间,但在运算过程中,进行插入,合并等操作时,容易超过预分配的空间长度,出现溢出问题。
#define InitSize 50 // 顺序表的初始长度
typedef struct{ // typedef类型重命名
ElemType *data; // 指向动态分配数组的指针,基地址,*取内容
int MaxSize; // 数组的最大容量
int length; // 数组的当前个数
}SeqList;
// 定义SqList类型的变量
SqList L;
L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize);
初始化
struct SqList{
int data[MaxSize]; // 存放顺序表的元素
int length; // 存放顺序表的长度
int last;
};
void Init(SqList *L){
L->data[0] = int(malloc(MaxSize * sizeof(int)));
int n, i = 0;
cin >> n;
L->length = n;
while (i < n)
{
cin >>(L->data[i++]);
}
L->last = i - 1;
}
插入
bool InsertList(SqList &L, int i, int e)
{
// 在顺序表中第i个位置(位序,不是下标)插入数值e
int j;
if (i<1 || i>L.length+1) // 是否越界
return false;
if (L.length == MaxSize)// 内存已满
return false;
L.length++; // 长度++
for (j = L.length; j >= i; j--) // 将第i个起的数据后移
L.data[j] = L.data[j - 1];
L.data[j] = e;
return true;
}
删除
bool DeleteList(SqList&L, int i)
{
// 删除顺序表中第i个元素
int j;
if (i<1 || i>L.length)
return false; // 判断越界
for (j = i; j < L.length; j++)
L.data[j - 1] = L.data[j];
L.length--;
return true;
}
查找
bool LocateElem(SqList L, int e)
{
int i;
for(i = 0; i < L.length; i++){
if(L.data[i] == e){
return i+1;
}
}
return 0;
}
又称为链表,用于存储逻辑关系为 “一对一” 的数据。
用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的),包括数据域和指针域,数据域存数据,指针域指示其后继的信息。
一个完整的链表需要由以下几部分构成:
**头指针:**一个普通的指针,它的特点是永远指向链表第一个节点的位置。很明显,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据;
节点:链表中的节点又细分为头节点、首元节点和其他节点:
链表中有头节点时,头指针指向头节点;反之,若链表中没有头节点,则头指针指向首元节点。
结构体定义
typedef struct Lnode{
ElemType data;
struct Lnode *next;
}LNode,*LinkList
初始化
单链表的初始化是指构建一个空表。先创建一个空结点,不存放数据,然后令其指针域为空;
bool InitList_L(LinkList &L){
L = new Lnode;
if(!L) return false;
L -> next = NULL;
return true;
}
创建
创建单链表分为头插法和尾插法两种。
void CreateList_H(LinkList &L){
int n;
LinkList s;
L = new LNode;
L -> next = NULL;
cin>>n;
while(n--){
s = new LNode;
cin>>s->data;
s->next = L->next;
L->next = s;
}
}
void CreatList_R(LinkList &L){
int n;
LinkList s, r;
L = new Lnode;
L -> next = NULL;
r = L;
cin>>n;
while(n--){
s = new LNode;
cin >> s->data;
s -> next = NULL;
r -> next = s;
r = s;
}
}
按序取值
从单链表的第一个结点出发,顺指针逐个往下搜索,知道找到第i个结点为止,否则返回最后一个结点的指针NULL。
LinkList GetElem(LinkList L, int i){
int j = 1;
LinkList p = L -> next;
if(i == 0) return L;
if(i < 1) return NULL;
while(p && j<i){
p = p->next;
j++;
}
return p;
}
插入结点
p = GetElem(L, i-1);
s -> next = p -> next;
p -> next = s;
删除结点
p = GetElem(L, i-1);
q = p -> next;
p -> next = q -> next;
静态链表,兼顾了顺序表和链表的优点于一身。
使用静态链表存储数据,数据全部存储在数组中(和顺序表一样),但存储位置是随机的,数据之间"一对一"的逻辑关系通过一个整形变量(称为"游标",和指针功能类似)维持(和链表类似)。
在单链表的基础上,再在每个结点中设置一个指向其前驱结点的指针域,这样一个结点既可以指向它的前面又可以指向它的下一个,我们把这种链表称为双向链表。
将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。
本篇完