1. 线性表
1 线性表的基本概念
线性表是最基本和最常用的一类数据结构,它表示的是线性结构。在线性结构中,数据元素之间存在着一对一的关系,其特点是数据元素之间按某种规定存在一个顺序关系。
- 的数据类型相同。
- 位序 从 1 开始。
- 唯一前驱和唯一后继。
2 线性表的基本操作:
线性表定义为指针类型 SqListPtr,数据元素类型为 ElemType。
2.1 初始化和销毁操作
2.2 引用型操作
2.3 加工型操作
扫描二维码关注公众号,回复:
6220990 查看本文章
- 函数参数是指针类型的是希望将得到的结果存入该指针。
2.4 线性表的基本操作测试函数
3 基于线性表操作的简单应用
3.1 合并线性表
Status List_Union(SqListPtr La, SqListPtr Lb)
{
ElemType elem; //存放从 Lb 中取出的元素
Status status; //状态代码
int i,j;
int len = List_Size(Lb); //len 存放 Lb 的元素个数
for(i=1;i<=len;i++)
{
List_Retrieve(Lb,i,&elem); //取出 Lb 中第 i 个数据元素
status = List_Locate(La,elem,&j); //判断它是否在 La 中
if (status!=success) //如果不在
{
status = List_Insert(La,1,elem); //插入到第一个位置
if(status!=success)
break; //插入失败则退出
}
else List_Add(La,j,1); //La 的第 j 个数据加1
}
return status;
}
- m 和 n 同数量级,则时间复杂度为 。选元素个数小的作为 Lb。
3.2 合并2个有序表
- 。如果 n 和 m 同数量级,则时间复杂度为 。
Status List_Merge(SqListPtr La, SqListPtr Lb, SqListPtr Lc)
{
ElemType elem1, elem2;
status = List_Init(Lc);
int i = 1, j = 1, k = 1; //i,j,k分别用于指示 La,Lb,Lc中当前元素
int n = List_Size(La);
int m = List_Size(Lb);
while(i<=n && j<=m)//两个表都还未处理完
{
List_Retrieve(La,i,&elem1);
List_Retrieve(Lb,j,&elem2);
if (elem1 < elem2)
{
status = List_Insert(Lc,k,elem1);
i = i+1;
}
else
{
status = List_Insert(Lc,k,elem2);
j = j+1;
}
k = k+1;
}
while(i <= n)//表La还未处理完
{
List_Retrieve(La,i,&elem1);
status = List_Insert(Lc,k,elem1);
i = i+1;
k = k+1;
}
while(j <= m)//表Lb还未处理完
{
List_Retrieve(Lb,j,&elem2);
status = List_Insert(Lc,k,elem2);
j = j+1;
k = k+1;
}
return status;
}
4 线性表的存储结构
4.1 线性表的顺序存储结构
用一组地址连续的存储单元依次存放线性表中的数据元素。
- 线性表的起始地址 s 称作线性表的基地址
- 可以随机存取数据。如:数组
顺序存储结构的实现:
#define LIST_INIT_SIZE 100
#define LIST_INCREAMENT 10
enum Status
{
success,fail,fatal,range_error //状态值枚举
};
typedef int ElemType;
typedef struct SqList
{
ElemType *elem; //存储空间的首地址指针
int length; //线性表的长度
int list_size; //线性表空间大小
}SqList, *Ptr;
typedef Ptr SqlListPtr;
基本操作的实现:初始化、创建线性表 (时间复杂度:O(1))
Status LIST_INIT(SqlListPtr L)
{
Status s = success;
L->list_size = LIST_INIT_SIZE;
L->length = 0;
L->elem = (ElemType *)malloc(sizeof(ElemType)*L->list_size);
if(L->elem == NULL)
s = fatal;
return s;
}
基本操作的实现:查找
- 查找--按位置 (时间复杂度:O(1))
Status List_Retrieve(SqlListPtr L, int pos, ElemType *elem)
{
Status s = range_error;
if (L)
{
if ((pos-1)>=0 && (pos-1) < L->length)
{
*elem = L->elem[pos-1];
s = success;
}
}
else
s = fatal;
return s;
}
- 查找--按值 (时间复杂度:O(N))
Status List_Locate(SqlListPtr L, ElemType elem, int *pos)
{
Status s = range_error;
if (L)
{
for(int i=0;i<L->length;++i)
{
if (L->elem[i] == elem)
{
*pos = i+1; //存储的时候:0——length-1,但实际位置:1——length
s = success;
break;
}
}
}
else
s = fatal;
return s;
}
基本操作的实现:插入元素操作 (时间复杂度:O(N))
- 第 i 个数据元素之前插入新数据元素 x 。
- 需要从后往前赋值,如果从前往后赋值,则后一个值会被前一个值覆盖掉,导致后面的值都为 x 。
Status List_Insert(SqlListPtr L, int pos, ElemType elem)
{
Status s = range_error;
if ((pos-1)>=0 && (pos-1)<=L->length) //插入位置合法
{
if (L && L->length < L->list_size) //表未占满
{
for(int i=L->length-1;i>=pos-1;--i) //逻辑位置与实际存储位置的下标相差1
{
L->elem[i+1] = L->elem[i];
}
L->elem[pos-1] = elem;
L->length++;
s = success;
}
}
else
s = fail;
return s;
}
基本操作的实现:删除元素操作 (时间复杂度:O(N))
- 从前往后循环赋值。若从后往前赋值,前面的元素会被后面的元素覆盖掉。
Status List_delete(SqlListPtr L, int pos)
{
Status s = range_error;
if ((pos-1)>=0 && (pos-1)<L->length)
{
if(L && L->length>0)
{
for(int i=pos;i<L->length;++i)
{
L->elem[i-1] = L->elem[i];
}
L->length--;
s = success;
}
}
else
s = fail;
return s;
}
基本操作的实现:其它操作实现
void List_Destory(SqlListPtr L)
{
if(L)
{
if (L->elem)
{
free(L->elem);
//void free(void *ptr);
//释放ptr指向的存储空间。被释放的空间通常被送入可用存储区池,以后可在调用malloc函数来再分配。
L->elem = NULL;
L->length = 0;
}
}
}
void List_Clear(SqlListPtr L)
{
if (L)
{
L->length = 0;
}
}
bool List_Empty(SqlListPtr L)
{
return (L->length == 0);
}
int List_Size(SqlListPtr L)
{
return L->length;
}
Status List_Prior(SqlListPtr L, int pos, ElemType * elem)
{
Status s = range_error;
if (L)
{
if (pos-1 > 0 && pos-1 < L->length) //第一个位置没有前驱
{
*elem = L->elem[pos-2]; //pos位置的下标为pos-1,前驱的下标为pos-2
s = success;
}
}
return s;
}
Status List_Next(SqlListPtr L, int pos, ElemType *elem)
{
Status s = range_error;
if (L)
{
if (pos-1>=0&&pos-1<L->length-1)//最后一个位置没有后继
{
*elem = L->elem[pos]; //pos位置的下标为pos-1,后继的下标为pos
s = success;
}
}
return s;
}
顺序存储结构存在的问题:
- 无法知道到底预先分配多大空间合适。
- 插入、删除开销很大。
解决方案:地址不连续(链式存储结构)
- 一个数据一个空间,数据之间不连续,避免一次性大空间分配失败,或者使用过程中空间不足的问题。
- 每个空间需要两部分:一部分存储数据;一部分存储数据之间的关系。(用C语言中的结构实现)
4.2 线性表的链式存储结构
4.2.1 单链表
用一组地址任意的存储单元存放线性表中的数据元素。
数据域(数据元素)+ 指针域(指示后继元素存储位置)= 结点
以“结点的序列”表示线性表——称作链表。
- 线性表的顺序存储结构在访问元素时可以一步到位,而链式存储结构在访问元素时需要从头指针开始一次访问每个元素,时间复杂度为:O(N)。
单链表类型定义:
typedef struct Node
{
ElemType elem;
struct Node *next;
}Node,*Ptr;
typedef Ptr *SqListPtr;
单链表基本操作实现:查找--按位置查找
- 问题:在给定的带头结点的单链表L中,查找指定位置的数据元素,如果存在,则返回success,同时取回相应结点的数据。
- 方法:链表的操作只能通过从头指针出发,顺着链域 next 逐个结点比较,直到搜索到指定位置的结点为止。
Status List_Retrieve(SqListPtr L, int pos, ElemType *elem)
{
Status s = range_error;
Ptr p = (*L)->next; //带头结点,移动 p 指向第一个元素结点
int i = 1; //计数器。置初值
while(p && i<pos) //p 指向的结点存在,且未到达指定位置。条件1:防止pos>表长。条件2:控制取第pos个,防pos<1
{
i++;
p = p->next;
}
if (p && i==pos)
{
*elem = p->data;
s = success;
}
return s;
}
单链表基本操作实现:查找--按值查找 (时间复杂度:O(N))
Status List_Locate(SqListPtr L, ElemType elem, int *pos)
{
Status s = range_error;
Ptr p = (*L)->next; //带头结点
int i = 1;
while(p!=NULL)
{
if(p->data == elem)
break;
i++;
p = p->next;
}
/*while(p && p->data!=elem)
{
i++;
p = p->next;
}*/
if (p)
{
*pos = i;
s = success;
}
return s;
}
单链表基本操作实现:插入操作(时间复杂度:O(N)(花在定位上))
- 挂 S 结点在单链表指定位置的操作:
s->next = p->next;
p->next = s;
//若用下面的形式,将导致链表后面断开,无法连接。(所以先要定位到ai位置)
p->next = s;
s->next = p->next;
Status List_Insert(SqListPtr L, int pos, ElemType elem)
{
Status status;
Ptr p,s;
status = List_Retrieve(L,pos-1,&p);
if (status == success)
{
s = (LinkedPtr)malloc(sizeof(Node)); //分配一个新的结点
if (s)
{
s->data = elem;
s->next = p->next;
p->next = s;
status = success;
}
else
status = fatal;
}
else
status = range_error;
return status;
}
- 虽然时间复杂度都是O(N),但是顺序存储结构需要大量移动数据。而链式存储结构只需修改指针内容,不需移动数据元素,操作方便。
单链表基本操作实现:删除操作 (时间复杂度:O(N)(花在定位上))
Status List_delete(SqListPtr L, int pos)
{
Status status = fail;
Ptr s, p;
status = List_Retrieve(L, pos-1, &p); //定位到 pos-1 位置
if (status == success)
{
s = p->next; //将要删除的位置pos的内存空间赋值给 s 。
p->next = s->next; // 修改指针内容(i-1 的后继为 i+1 的地址)
free(s);
s = NULL; //释放 pos 位置的内存空间
status = success;
}
return status;
}
单链表基本操作实现:创建单链表(创建单链表时在头结点插入效率最高)(时间复杂度:O(N))
Status List_Create(SqListPtr L, ElemType data[], int len)
{
Status s;
Ptr p;
s = List_Init(L);
if (s == success)
{
for(int i=len-1;i>=0;--i)
{
p = (Ptr)malloc(sizeof(Node)); //分配内存空间,创建结点
if (p)
{
p->data = data[i]; //把数据元素放入结点 p 。
p->next = (*L)->next;
(*L)->next = p;
}
else
{
s = fail;
break;
}
}
}
return s;
}