index 数据结构与算法
ADT(array data table)线性表
Data
线性表的数据对象集合为{\(a_1,a_2,a_3...a_n\)},每个元素的类型均为DataType。其中,除第一个元素\(a_1\)外,每一个元素有且只有一个直接前驱元素,除了最后一个元素\(a_n\)外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。
线性表-顺序存储结构(数组)
.h
InitList(*L); 初始化操作,建立一个空的线性表L。
ListEmpty(L); 若线性表为空,返回true,否则返回false。
ClearList(*L); 将线性表清空。
GetElem(L,i,*e); 将线性表L中的第i个位置元素值返回给e。
LocateElem(L,e); 在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回0表示失败。
ListInsert(*L,i,e); 在线性表L中的第i个位置插入新元素e。
ListDelete(*L,i,*e); 删除线性表L中第i个位置的元素,并用e返回其值。
ListLength(L); 返回线性表L的元素个数。
.c
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
//Status是函数的返回类型,其值是函数结果状态代码,如OK等
#define MAXSIZE 20 /*存储空间初始化分配量*/
typedef int ElemType; /*ElemType 类型根据实际情况而定,这里假设为int */
typedef struct
{
ElemType data[MAXSIZE]; /*数组存储元素,最大值为MAXSIZE*/
int length; /*线性表当前长度*/
} SqList;
//GetElem(L,i,*e) 获取元素接口实现
//初始条件:顺序线性表L已存在,1<=i <=ListLength(L)
//操作结果:用e返回L中第i个数据元素的值
Status GetElem(SqList L, int i, ElemType *e)
{
if(L.length==0 || i<1 || i>L.length)
return ERROR;
*e=L.data[i-1];
return OK;
}
//ListInsert(*L,i,e) 在线性表L中的第i个位置插入新元素e
//初始条件:顺序线性表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) /*当i不在范围内时*/
return ERROR;
if(i<=L->length) /*若插入的位置不在表尾*/
{
//重点1:数组后移的遍历方式
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;
}
//ListDelete(*L,i,*e) 删除线性表L中第i个位置的元素,并用e返回其值
//初始条件:顺序线性表L已存在,1<=i <=ListLength(L)
//操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
Status ListDelete(SqList *L,int i,ElemType *e)
{
int k;
if(L->length==0) /*线性表为空*/
return ERROR;
if(i<1 || i>L->length) /*删除位置不正确*/
return ERROR;
*e=L->data[i-1];
if(i<L->length) /*如果删除不是最后位置*/
{
//重点2:从头到后遍历的方式一定不能有这种思想
for(k=i; k<L->length;k++) /*将删除位置后继元素前移*/
L->data[k-1]=L->data[k];
}
L->length--;
return OK;
}
数组优缺点
优点
无须为表示表中元素之间的逻辑关系儿增加额外的存储关系
可以快速地获取表中任一位置的元素
缺点
插入和删除操作需要移动大量元素
当线性表长度变化较大时,难以确定存储空间的容量
造成存储空间的“碎片
线性表-链式存储结构(单链表)
.h
CreateListHead(LinkList *L, int n); //单链表整表创建-头插法
CreateListTail(LinkList *L, int n); //单链表整表创建-尾插法
GetElem(LinkList L,int i,ElemType *e)
ListInsert(LinkList *L, int i, ElemType e)
ListDelete(LinkList *L, int i, ElemType *e)
ClearList(LinkList *L)
.c
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
//Status是函数的返回类型,其值是函数结果状态代码,如OK等
//3.6 线性表的链式存储结构
typedef struct Node
{
ElemType data;
struct Node *next;
} Node;
typedef struct Node* LinkList;
//3.7单链表的读取
//GetElem(L,i,*e); 将线性表L中的第i个位置元素值返回给e
//初始条件:顺序线性表L已存在,1<=i <=ListLength(L)
//操作结果:用e返回L中第i个数据元素的值
Status GetElem(LinkList L,int i, ElemType *e)
{
int j;
LinkList p; //声明一结点p
p = L->next; //让p指向链表L的第一个结点
j=1; //j为计数器
while(p && j<i) //p不为空或者计数器j还没有等于1时,循环继续
{
p=p->next; //让p指向下一个结点
++j;
}
if(!p || j>i)
return ERROR; //第i个元素不存在
*e=p->data;
return OK;
}
//3.8单链表的插入与删除
//ListInsert(*L,i,e); 在线性表L中的第i个位置插入新元素e
//初始条件:顺序线性表L已存在,1<=i <=ListLength(L)
//操作结果:在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; //第i个元素不存在
s=(LinkList)malloc(sizeof(Node)); //生成新结点
s->data = e;
s->next=p->next; //将p的后继结点赋给s的后继结点
p->next=s; //将p的后继结点赋给s
return OK;
}
//ListDelete(*L, i, e) 单链表的删除算法
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:删除L的第i个结点,并用e返回其值,L的长度减1
Status ListDelete(LinkList *L, int i, ElemType *e)
{
int j;
LinkList p,q;
p = *L;
j = 1;
//遍历寻找第i-1个结点
while(p->next && j < i)
{
p=p->next;
++j;
}
//第i个结点不存在
if(!(p->next) || j > i)
return ERROR;
q=p->next;
//将q的后继扶植给p的后继
p->next=q->next;
//将q结点中的数据给e
*e=q->data;
//让系统回收此结点,释放内存
free(q);
return OK;
}
//3.9单链表的整表创建
//头插法,随机产生n个元素的值,建立带表头节点的单链线性表L
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));
//随机生成100以内的数字
p->data=rand() % 100 +1;
p->next=(*L)->next;
//插入到表头
(*L)->next=p;
}
}
//尾插法,随机产生n个元素的值,建立带表头结点的单链线性表L
void CreateListTail(LinkList *L, int n)
{
LinkList p,r;
int i;
//初始化随机种子
srand(time(0));
*L=(LinkList)malloc(sizeof(Node));
//r为指向尾部的结点
//注意L与r的关系,L是指整个单链表,而r是指指向尾结点的变量,r会随着循环不断地变化结点
//而L则是随着循环增长为一个多节点的链表。
r=*L;
for(i=0; i<n; i++)
{
//生成新结点
p=(Node *)malloc(sizeof(Node));
//随机生成100以内的数字
p->data=rand() % 100 + 1;
//将表尾终结点的指针指向新节点
r->next = p;
//将当前的新结点定义为表尾终端结点
r=p;
}
//表示当前链表结束
r->next=NULL;
}
//3.10单链表的整表删除
//初始条件:顺序线性表L已存在,操作结果将L重置为空表
Status ClearList(LinkList *L)
{
LinkList p,q;
//p指向第一个结点
p=(*L)->next;
//每到表尾
while(p)
{
q=p->next;
free(p);
p=q;
}
//头结点指针域为空
(*L)->next=NULL;
return OK;
}
单链表优缺点
存储分配方式
顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
时间性能
查找:顺序存储结构O(1)、单链表O(n)
增删:顺序存储结构需要平均移动表长一半的元素,时间为O(n)、单链表在找出某位置的指针后,插入和删除时间仅为O(1)
空间:顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生上溢、单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
线性表-双向链表(list)
我们在单链表中,有了next指针,这就是我们要查找下一结点的时间复杂度为O(1),可我们要查找上一借点的话,那最坏的时间复杂度就是O(n)了,因为我们每次都要从头开始遍历查找。为了克服单向性这一缺点,我们的老科学家们设计出可双向链表。双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。
typedef struct DulNode{
ElemType data;
struct DulNode *prior; //直接前驱指针
struct DulNode *next; //直接后继指针
} DulNode, *DuLinkList;