定义(逻辑结构)
线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表。若用L命名线性表,则其一般表示为L=(a****1 ,a2…,a****i ,ai+1…,a****n )
除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继。
基本操作(运算)
创销
InitList(&L):初始化表。构造一个空的线性表L,分配内存空间。
Destroylist(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间。
增删
Listlnsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e。
ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
查找
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
GetElem(L,i):按位查找操作。获取表L中第个位置的元素的值。
其他常用操作:
Length(L):求表长。返回线性表L的长度,即L中数据元素的个数。
PrintList(L):输出操作。按前后顺序输出线性表的所有元素值。
Empty(L):判空操作。若L为空表,则返回true,否则返回false。
&位取地址符号,在传参时,除了查,增删改这种真实传值都要加上。
数据元素大小:sizeof()函数
存储结构(物理结构)
主要分为顺序表和链表,顺序表要求物理上存储也相邻,链表物理上存储可以不相邻。
顺序表(顺序存储)
定义
顺序表—-用顺序存储的方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。
优点:
1、可以随机存取
2、存储密度高
缺点:
3、扩展容量不方便
4、增删数据不方便
静态分配定义
#define ListMaxSize 10 //定义最大长度
typedef struct {
int data[ListMaxSize]; //使用静态数组存放数据
int length; //顺序表当前长度
}SqList; //顺序表类型定义(静态分配)
//初始化顺序表
void InitList(SqList &L) {
for (int i = 0; i < ListMaxSize; i++)
{
L.data[i] = 0;
}
L.length = 0;
}
int main() {
//初始化顺序表
SqList L;
InitList(L);
//违规打印所有值
for (int i = 0; i < ListMaxSize; i++)
{
printf("%d", L.data[i]);
}
return 0;
}
动态分配定义
#define ListMaxSize 10 //定义初始长度
typedef struct {
int* data; //指示动态分配数组的指针
int MaxSize; //顺序表的最大容量
int length; //顺序表当前长度
}SqList; //顺序表类型定义(动态分配)
动态申请和释放空间malloc函数和free函数
#include<stdio.h>
#include<stdlib.h>
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* temp = L.data;
L.data = (int*)malloc(sizeof(int) * (InitSize + len));
for (int i = 0; i < L.length; i++)
{
L.data[i] = temp[i];
}
L.MaxSize = L.MaxSize + len;
free(temp);
}
int main() {
//初始化顺序表
SqList L;
InitList(L);
IncreaseSize(L, 3);
//违规打印所有值
for (int i = 0; i < L.MaxSize; i++)
{
printf("%d", L.data[i]);
}
return 0;
}
插入操作
//插入操作
bool insertElement(SqList& L, int index, int element) {
if (index<1 || index>L.length + 1) {
return false;
}
if (L.length == ListMaxSize) {
return false;
}
for (int j = L.length; index <= j; j--)
{
L.data[j] = L.data[j - 1];
}
L.data[index - 1] = element;
L.length++;
return true;
}
最好时间复杂度:O(1)
最坏时间复杂度:O(n)
平均时间复杂度:O(n)
删除操作
//删除操作
bool deleteElement(SqList& L, int index, int element) {
if (index<1 || index>L.length) {
return false;
}
if (L.length == 0) {
return false;
}
for (int j = index; j < L.length; j++)
{
L.data[j - 1] = L.data[j];
}
L.data[L.length - 1] = 0;//可以不写
L.length--;
return true;
}
最好时间复杂度:O(1)
最坏时间复杂度:O(n)
平均时间复杂度:O(n)
查找操作(按位置查找)
//按位查找
int GetElementByIndex(SqList L, int i) {
return L.data[i - 1];
}
最好时间复杂度:O(1)
最坏时间复杂度:O(1)
平均时间复杂度:O(1)
查找操作(按值查找)
//按值查找
int GetElementByData(SqList L, int element) {
for (int i = 0; i < L.length; i++){
if (L.data[i] == element){
return i + 1;
}
return 0;
}
}
最好时间复杂度:O(1)
最坏时间复杂度:O(n)
平均时间复杂度:O(n)
链表(链式存储)
优点:
可随机存取,存储密度高
不要求大片连续空间,改变容量方便
缺点:
要求大片连续空间,改变容量不方便
不可随机存取,要耗费一定空间存放指针
单链表
两种定义方式:
typedef struct LNode { //定义单链表节点类型
int data; //每个节点存放一个数据元素
struct LNode* next; //指针指向下一个节点
}LNode, *LinkList;
struct LNode {
int data; //每个节点存放一个数据元素
struct LNode* next; //指针指向下一个节点
};
typedef struct LNode LNode;
typedef struct LNode* LinkList;
//单链表后插入节点
p=GetElem(L,i-1);//查找插入位置的前驱结点
s->next=p->next;//令新节点s指向p的后继
p->next=s;//s成为p新的后继
//单链表删除节点
p=GetElem(L,i-1);//查找删除位置的前驱结点
q=p->next;//令q指向被删除结点
p->next=q->next;//将*g结点从链中“断开”
free(q);/释放结点的存储空间
双链表
typedef struct DNode {
int data;
struct DNode *prior, *next;
}DNode, *DLinkList;
新增需要修改4个指针,根据已知指针的节点的前后,优先修改将要被覆盖的地址,否则会发生地址丢失。
//双链表新增节点
s->next=p->next;
p->next->prior=s;
s->prior=p;
p->next=s;
//双链表删除节点
p->next=q->next;
q->next->prior=p;
free(q);
循环单链表
p->rear = L;//表尾节点的尾指针p指向头部形成环
判空条件:L->next = L
循环双链表
p->next = L;//表尾节点的尾指针p指向头部形成环
L->prior = p;//头节点的前驱指向表尾节点
判空条件:L->next = L && L->prior = L
静态链表
#define ListMaxSize 50
typedef struct {
int data[ListMaxSize]; //使用静态数组存放数据
int next;
}SqList[ListMaxSize];
顺序表和链表的比较
1.存取方式
顺序表可以顺序存取,也可以随机存取。
链表只能从表头顺序存取元素。
2.逻辑结构与物理结构
采用顺序存储时,逻辑上相邻的元素,其对应的物理存储位置也相邻。
采用链式存储时,逻辑上相邻的元素,其物理存储位置则不一定相邻,其对应的逻辑关系是通过指针链接来表示的。
3.查找、插入和删除操作
对于按值查找,当顺序表在无序的情况下,两者的时间复杂度均为O(n);而当顺序表有序时,可采用折半查找,此时时间复杂度为O(logzn)。
对于按序号查找,顺序表支持随机访问,时间复杂度仅为O(1),而链表的平均时间复杂度为o(n)。
顺序表的插入、删除操作,平均需要移动半个表长的元素。
链表的插入、删除操作,只需要修改相关结点的指针域即可。由于链表每个结点带有指针域,因而在存储空间上比顺序存储要付出较大的代价,存储密度不够大。
4.空间分配
顺序存储在静态存储分配情形下,一旦存储空间装满就不能扩充,如果再加入新元素将出现内存溢出,需要预先分配足够大的存储空间。预先分配过大,可能会导致顺序表后部大量闲置;预先分配过小,又会造成溢出。动态存储分配虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且若内存中没有更大块的连续存储空间将导致分配失败。
链式存储的结点空间只在需要的时候申请分配,只要内存有空间就可以分配,操作灵活、高效。
在实际中应该怎样选取存储结构呢?
1.基于存储的考虑
对线性表的长度或存储规模难以估计时,不宜采用顺序表;链表不用事先估计存储规模,但链表的存储密度较低,显然链式存储结构的存储密度是小于1的。
2.基于运算的考虑
在顺序表中按序号访问a的时间复杂度为O(1),而链表中按序号访问的时间复杂度为0(m),所以如果经常做的运算是按序号访问数据元素,显然顺序表优于链表。
在顺序表中做插入、删除操作时,平均移动表中一半的元素,当数据元素的信息量较大且表较长时,这一点是不应忽视的;在链表中做插入、删除操作时,虽然也要找插入位置,但操作主要是比较操作,从这个角度考虑显然后者优于前者。
3.基于环境的考虑
顺序表容易实现,任何高级语言中都有数组类型;链表的操作是基于指针的,相对来讲,前者实现较为简单,这也是用户考虑的一个因素。
总之,两种存储结构各有长短,选择哪一种由实际问题的主要因素决定。通常较稳定的线性表选择顺序存储,而频繁做插入、删除操作的线性表(即动态性较强)宜选择链式存储。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情