线性表概念
定义 | 由一个或者多个数据元素组成的有序序列 |
---|---|
注意点 | 1、他是一个序列,有先来后到 2、每个元素有且只有一个前驱和后续,但是第一个没前驱,最后一个没后继. 3、线性表是有限个的 4、线性表的元素> =0 |
类型 | 顺序存储结构和链式存储结构 |
顺序存储结构
#define MAXSIZE 20 //设置大小
typedef int ElemType; //这里把int重新定义元素类型
typedef struct
{
ElemType data[MAXSIZE]; //数组的长度
int length; //线性表长度
}Sqlist;
总结一下顺序存储结构的三个属性
- 存储空间的起始位置,数组data,它的存储位置就是线性表存储空间的存储位置。
- 线性表的最大存储容量:数组的长度MaxSize。
- 线性表的当前长度: length
注意点:在上面的定义中ElemType data[MAXSIZE]是数组的长度,而int length是线性表长度。数组长度是存放线性表的存储空间的总长度,一般初始化后不会表化,而线性表长度是指表中的元素个数会变化。
顺序存储结构线性表操作方式
注意:这里的顺序线性表都依照上文定义的Sqlist结构体,与元素类型为ElemType
获取元素
注意:要先做一下判断是不是非法输入。
//顺序线性表L,索引i,返回的元素放在e中
int GetElem(Sqlist L,int i,ElemType *e){
if(L.lengt == 0 || i<0 || i>L.lengt)
{
return -1; //表示失败
}else{
*e = L.data[i];
return 0; //表示成功
}
}
插入元素
插入一个元素,后面的的元素往后移动
int ListInsert(Sqlist *L,int i,ElemType e){
int tmp = 0;
if(L->lenght == MAXSIZE || i<0||i>L->lenght){
return -1;
}
if(i<=L->lenght)
{
for(tmp = L->lenght-1; tmp>=i ;tmp--)
{
L->data[tmp+1] = L->data[tmp];
}
}
L->data[i-1] = e;
L->lenght++;
return 0;
}
删除元素
删除一个元素,后面的的元素往前移动,这里ElemType *e保存被删除的值
int ListDelete(Sqlist *L,int i,ElemType *e){
int tmp = 0;
if( i<0||i>L->lenght){
return -1;
}
*e = L->data[i];
if(i<=L->lenght)
{
for(tmp = i; tmp<L->lenght-1 ;tmp++)
{
L->data[tmp] = L->data[tmp+1];
}
}
L->lenght--;
return 0;
}
顺序存储结构线性表总结
适用场合 | 适用于不经常插入和删除,更多的是存取元素的操作 |
---|---|
最坏情况 | 插入和删除都是最后一个元素,复杂度为O(1) |
最好情况 | 插入和删除都是第一个元素,复杂度为O(n) |
平均情况 | O((n-1)/2) =O(n) |
优点 | 不需要为了维持逻辑关系而额外增加存储空间,可以快速存取元素 |
缺点 | 插入和删除需要大规模移动,容易产生内存碎片(有的空间空着没元素) |
链式存储结构
定义 | 用容易的存储单元存储线性表的数据元素,这组存储单元可以在内存中未被占用的任意位置。 |
---|---|
结构概述 | 在链式存储结构中,除了要存储数据元素信息外,还要存储他的后续元素的存储地址。 这里存放数据的地方叫数据域,存放地址的地方叫指针域 |
单链表 | 只有一个指针域的称为单链表。一般头结点的数据域放置链表长度或者不放数据 |
typedef struct Node{
ElemType data; //数据域
struct Node* next; //指针域
}Node_T;
链式存储结构线性表操作方式
获取元素
获取的元素保存在e中
int GetElem(Node_T*L,int i,ElemType *e)
{
int tmp;
Node_T* p;
p = L->next;
tmp = 1;
while(p && tmp <i) //下个节点不为空,且还没遍历到
{
p = L->next;
++tmp;
}
if(!p||tmp>i){ //遍历完或者出错
return -1
}
*e = p->data;
return 0;
}
插入元素
在序号i处插入元素
//这里使用Node_T**是为保护头结点
int ListInsert(Node_T** L,int i,ElemType e)
{
int tmp;
Node_T* p,newNode;
p = *L;
tmp = 1;
while(p && tmp <i) //下个节点不为空,且还没遍历到
{
p = L->next;
++tmp;
}
if(!p||tmp>i){ //遍历完或者出错
return -1
}
newNode = (Node_t*)malloc(sizeof(Node_t));
newNode->data = e;
newNode->next = p->next;
p->next= newNode;
return 0;
}
删除元素
在序号i处删除元素,用e来保存被删除结点的数据域
//这里使用Node_T**是为保护头结点
int ListDelete(Node_T** L,int i,ElemType *e)
{
int tmp;
Node_T* p,nextNode;
p = *L;
tmp = 1;
while(p && tmp <i) //下个节点不为空,且还没遍历到
{
p = L->next;
++tmp;
}
if(!p||tmp>i){ //遍历完或者出错
return -1
}
nextNode = p->next;
p->next = nextNode->next;
//保存数据域,删除结点
*e = nextNode->data;
free(nextNode);
nextNode = NULL;
return 0;
}
头插法(不怎么常用)
头插法创建链表示例
void CreateListHead(Node_T** L,int n){
Node_T* p;
int i;
srand(time(0)); //初始化随机种子
*L = (Node_T*)malloc(sizeof(Node_T));
for(i = 0;i<n ;i++){
p = (Node_T*)malloc(sizeof(Node_T));
p = data = rand()%100+1;
p->next = (*L)->next;
(*L)->next = p;
}
}
尾插法(常用)
尾插法创建链表示例
void CreateListHead(Node_T** L,int n){
Node_T* p ,tmp;
tmp = *L;
int i;
srand(time(0)); //初始化随机种子
*L = (Node_T*)malloc(sizeof(Node_T));
for(i = 0;i<n ;i++){
p = (Node_T*)malloc(sizeof(Node_T));
p = data = rand()%100+1;
tmp->next = p;
tmp = p; //游标往后移动
}
}
销毁链表
int ClearList(Node_T** L){
Node_T* p ,tmp;
p = (*L)->next;
while(p){
tmp = p->next;
free(p);
p = tmp;
}
(*L)->next = NULL;
return 0;
}
链式存储结构线性表总结
适用场合 | 适用于插入、删除多个数据 |
---|---|
最坏情况 | 插入和删除都是最后一个元素,复杂度为O(n) |
最好情况 | 插入和删除都是第一个元素,复杂度为O(1) |
平均情况 | O((n-1)/2) =O(n) |
优点 | 插入和删除元素只要简单指针赋值,而且不会产生内存碎片,比较灵活应变 |
缺点 | 需要额外的空间来存储指针 |
简单看来链式存储的优势不是很大,但是如果要一次插入或删除多个元素就会快很多,因为插入元素的时间复杂度为O(n),但是我们获取了指针域后,接下来的元素时间复杂度都为O(1)。
链式存储结构线性表和顺序存储结构线性表对比
比较方式 | 单链表结构 | 顺序存储结构 |
---|---|---|
存储方式 | 用容易的存储单元存放线性表的元素 | 用一段连续的存储单元一次存储线性表的数据元素 |
时间性能 | 查找O(n) 插入和删除:计算位置后为O(1) | 查找O(1) 插入和删除:O(n) |
空间性能 | 不需要预先分配,元素个数不受限制 | 需要预先分配空间,容易出现溢出或者浪费 |
综上:需要常常需要读取的时候用顺序结构,常常要删除插入的情况使用链式结构