线性表
顺序存储结构:数组
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE];
int length;//线性表当前长度
}SqList;
封装了一个结构,实际上就是对数组进行了封装,增加了个当前的长度变量。
总结:顺序存储结构封装需要的三个属性:
1、存储空间的起始位置,数组data,它的存储位置就是线性表存储空间的存储位置
2、线性表的最大存储容量,数组的长度MaxSize
3、线性表的当前长度:length(时刻改变的)
地址计算方法:
线性表的定义是从1开始的,假设ElemType占用的是c个存储单元,那么线性表中第i+1个数据元素和第i个数据元素的存储位置的关系是:LOC(ai+1) = LOC(ai) + c(存储单元的宽度);
LOC(ai) = LOC(a1) + (i-1)*c
获取元素的操作getElem
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20
typedef int Status;
//Status 是函数的类型,其值是函数结果状态代码,如OK等
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:用e返回L中第i个数据元素的值
//typedef int ElemType;
typedef struct
{
Status data[MAXSIZE];
int length;//线性表当前长度
}SqList;
Status GetElem(SqList L, int i, Status *e)
{
if (L.length == 0 || i < 1 || i>L.length)
{
return ERROR;
}
*e = L.data[i - 1];
return OK;
}
插入操作ListInsert(*L,i,e)
插入算法的思路:
如果插入的位置不合理,抛出异常;
如果线性表长度大于数组长度,则抛出异常或是动态增加数组容量
从最后一个元素开始向前遍历到第i 个位置,分别将他们都想后移动一个位置
将要插入的元素插入位置i 处,线性表的长度+1
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:在L中第i个位置之前插入新的数据元素e,L长度+1
Status ListInsert(SqList *L, int i, ElemType e)
{
int k;
if (L->length == MAXSIZE)
{
return ERROR;
}
if (i<1 || i>L->length+1 )
{
return ERROR;
}
if (i <= L->length)
{
for (k = L->length - 1; k >= i - 1; k--)
{
L->data[k + 1] = L->data[k];
}
}
L->data[i - 1] = e;
L->length++;
return OK;
}
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:在L中删除第i个位置的数据元素,并用e来返回删除元素的值L长度-1
Status ListDelete(SqList *L, int i, ElemType *e)
{
int k;
if (L->length == 0)//表长为0不能再删除了
{
return ERROR;
}
if (i<1 || i>L->length + 1)//删除的位置不合理
{
return ERROR;
}
*e = L->data[i - 1];
if (i < L->length)
{
for (k = i; k >= L->length; k++)
{
L->data[k - 1] = L->data[k];//从删除的位置开始,所有元素向前移动一位
}
}
L->length--;
return OK;
}
优缺点:线性表的顺序存储结构,在存、读数据时,不管是哪个位置,时间复杂度都是O(1)。而在插入或是删除时,时间复杂度都是O(n).
有点:无须为表示表中元素之间的逻辑关系而增加额外的存储空间;可以快速的存取表中任意位置的元素
缺点:插入和删除操作需要移动大量的元素;当线性表长度变化较大时,难以确定存储空间的容量;容易造成存储空间的碎片
链式存储结构:
单链表:头指针、最后一个结点指针为空
头指针、头结点
结构指针描述单链表
typedef struct Node
{
ElemType data;//数据域
struct Node* Next;//指针域,指向一个结点类型的指针
}Node;
typedef struct Node* LinkList;//LinkList 相当于Node*
单链表的读取
获得链表第i个数据的算法思路
1、声明一个结点p指向第一个结点,初始化j从1开始
2、当j
3、若到链表末尾p为空,则说明第i个元素不存在
4、否则查找成功,返回结点p的数据
Status GetElem(LinkList L, int i, ElemType *e)
{
int j;
LinkList p;
p = L->Next;//p指向L的第一个结点
j = 1;
while ( p && j < i)//p不能为空,p为空指向了链表的最后一个元素了,要获取的i位置不能无效
{
p = p->Next;
++j;
}
if (!p || j > i)
{
return ERROR;
}
*e = p->data;
return OK;
}
最坏情况的时间复杂度O(n),由于但量表的结构中没有定义表长,所以不能事先知道要循环多少次,也就不方便用for来控制循环。算法的核心思想“工作指针后移”
s->next = p->next;
p->next = s;
//单链表插入,在L中第i个位置之前插入新的数据元素e,L的长度加1
Status ListInsert(LinkList *L, int i, ElemType e)
{
int j;
LinkList p, s;
p = *L;
j = 1;
while (p&&j < i)//寻找第i个结点
{
p = p->Next;
j++;
}
if (!p || j > i)
{
return ERROR;
}
s = (LinkList)malloc(sizeof(Node));
s->data = e;
s->Next = p->Next;
p->Next = s;
return OK;
}
单链表的删除
假设元素a2的结点为q,要实现结点q删除单链表的操作,其实就是将他的前继结点的指针绕过指向后继结点
p->next = p->next->next;
或
q = p->next;
p->next =q->next;
Status ListDelete(LinkList *L, int i, ElemType *e)
{
int j;
LinkList p,q;
p = *L;
j = 1;
while (p->Next&&j < i)//寻找第i个结点
{
p = p->Next;
j++;
}
if (!p->Next || j > i)
{
return ERROR;
}
q = p->Next;
p->Next = q->Next;
*e = q->data;
q = NULL;//free(q);
return OK;
}
单链表的整表创建
1、声明一个结点p和计数器变量i
2、初始化一个空链表L
3、让L的头结点的指针指向NULL,,即建立一个带头结点的单链表
4、循环实现后继结点的插入
头插法建立单链表
void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
(*L)->Next = NULL;
for (i = 0; i < n; i++)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
p->Next = (*L)->Next;
(*L)->Next = p;
}
}
尾插法建立单链表
void CreateListTail(LinkList *L, int n)
{
LinkList p,r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L;//尾插法,有一个r指向尾部,开始的时候是一个空表
for (i = 0; i < n; i++)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
r->Next = p;//把p接在r之后
r = p;//p是临时结点,保存每一次要尾插的数据,这一句是让r每一次都指向链表的尾部
}
}
单链表的整表删除
Status ClearList(LinkList *L)
{
LinkList p, q;
p = (*L)->Next;
while (p)
{
q = p->Next;
free(p);
p = q;
}
(*L)->Next = NULL;
}
常见错误:q的存在,free会释放整个结点,包括其指针域
单链表结构与顺序存储结构的优缺点
时间性能:
查找
顺序存储结构O(1)
单链表O(n)
插入和删除
顺序存储结构需要平均移动表长一半的元素,时间为O(n)
单链表在计算出某位置的指针后,插入和删除时间仅为O(1)
静态链表
线性表的静态链表
#define MAXSIZE 1000
typedef struct
{
ElemType data;
int cur;
}Compinent,StatucLinkList[MAXSIZE];
备忘录:我们对数组的第一个和最后一个元素做特殊处理,他们的data不存放数据
通常把未使用的数组元素称为备用链表
数组的第一个元素,即下标为0的那个元素的cur就存放备用链表的第一个结点的下标
数组的最后一个元素,即下标为MAXSIZE-1的cur则存放第一个有数值的元素的下标,相当于单链表中头结点的作用。
为了辨明数组中那些分量未被使用,解决的方法是将所有未被使用过得及已被删除的分量用游标链成一个备用的链表。
静态链表的插入操作(两部分)
一、获取空闲分量的下标
int Malloc_SLL(StaticLinkList space)
{
int i = space[0].cur;
if (space[0].cur)
space[0].cur = space[i].cur;
return i;
}
二、在静态链表L中第i个元素之前插入新的数据元素
Status ListInsert(StaticLinkList L, int i, ElemType e)
{
int j, k, l;
k = MAXSIZE - 1;//指向数组最后一个元素
if (i<1 || i>ListLength(L) + 1)
{
return ERROR;
}
j = Malloc_SLL(L);
//获取空闲的第一个元素的下标,获取之后存放在j中
if (j)
{
L[j].data = e;
for (l = 1; l <= i - 1; l++)
{
k = L[k].cur;
}
L[j].cur = L[k].cur;
L[k].cur = j;
return OK;
}
return ERROR;
}