线性表
1.线性表定义:(List)零个或多个数据元素的有限序列。
2 线性表的抽象数据类型:
3 线性表的顺序存储结构:
线性表的长度应该小于等于数组的长度。
顺序存储结构的插入与删除
- 获得元素操作GetElem
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
Status GetElem (SqList L,int i,ElemType *e)
{
if(L.lenth==0||i<1||i>L.length)
return ERROR;
*e=L.data(i-1);
return OK;
}
2.插入元素
Status ListInsert(SqList *L,int i,ElemType e)
{
int k;
If(L->Lenth==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;
}
3.删除元素
Status ListDelete(SqList *L,int i,ElemType e)
{
int k;
If(L->Length==MAXSIZE)
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;
}
线性表的链式存储结构
数据域、指针域
指针域中存储的信息称做指针或链 这两部分信息组成数据 元素ai的存储映像,称为节点(Node)
单链表
链表中第一个结点的存储位置叫做头指针。
为了方便操作单链表的第一个结点附近设一个结点,头节点
空链表
单链表中,C语言用结构指针描述
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;
- 单链表的读取
Status GetElem(LinkList L,int i,ElemType *e)
{
int j;
LinkList p;
p=L->next;
j=1;
while(p && j<i) //p不为空,j不小于i
{
p=p->next;
++j;
}
if(!p||j>i) return ERROR;
*e=p->data;
return OK;
}
2.单链表的插入与删除
Status ListInsert(LinkList *L,int i,ElemType e)
{
int j;
LinkList p,s;
p=*L;
j=1;
while(p&&j<i)
{
p=p->next;
++j;
}
if(!p||j>i) return ERROR;
s=(LinkList)malloc(sizeof(Node));
s->next=p->next;
p-next=s;
return OK;
}
Status ListDelete(LinkList *L,int i,ElemType e)
{
int j;
LinkList p,q;
p=*L;
j=1;
while(p&&j<i)
{
p=p->next;
++j;
}
if(!(p->next)||j>i) return ERROR;
q=p->next;
p->next=q->next;
*e=q->data;
free(q);
return OK;
}
3.单链表的整表创建
单链表整表创建的算法思路:
1.声明一结点p 和计数器变量i ;
2 . 初始化一空链表L;
3. 让L 的头结点的指针指向NULL ,即建立一个带头结点的单链表i
4 . 循环:
• 生成一新结点赋值给p;
• 随机生成一数字赋值给p 的数据域p->data;
• 将p 插入到头结点与前一新结点之间。
/* 随机产生n 个元素的值,建立带表头结点的单链线性表L (头插法)*/
void CreateListTail (LinkList *L, int n)
{
LinkList p;
int i;
strand(time(1));
*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;
}
}
/* 随机产生n 个元素的值,建立带表头结点的单链线性表L (尾插法)*/
void CreateListTail (LinkList *L, int n)
{
LinkList p ,r;
int i;
srand (time (0) );//初始化随机数种子
*L= (LinkList) malloc(size of (Node));
r=* L ;
for (i=O ; i<n; i++)
{
p = ( Node * ) malloc ( sizeof ( Node) ) ;//生成新节点
p->data=rand()%100+1;
r->next=p;
r=p;
}
r->next = NULL;//表示当前链表结束
}
4.单链表的整表删除
单链表整表删除的算法思路如下:
1. 声明一结点p 和q ;
2. 将第一个结点赋值给p;
3 . 循环:
• 将下一结点赋值给q;
• 释放p;
• 将q 赋值给p 。
Status Clear(LinkList *L)
{
LinkList p, q;
p = (*L)->next;
while (p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return Ok;
}
- 单链表结构与顺序结构优缺点
若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。若需要频繁插入和删除时,宜采用单链裴结构。比如说游戏开发中,对于用户注册的个人信息,除了注册时插入数据外,绝大多数情况都是读取,所以应该考虑用顺序存储结构。而游戏中的玩家的武器或者装备列表,随着玩家的游戏过程中,可能会随时增加或删除,此时再用顺序存储就不大合适了,主事链表结构就可以大展拳脚。
当线性表中的元素个数变化较大或者根本不知道有多大肘,最好用单链表结构, 这样可以不需要考虑存储空间的大小问题。而如果事先知道线性表的大致长度,比如一年12 个月,一周就是星期一至星期日共七天,这种用顺序存储结构效率会高很多。
静态链表
用数组描述的链表叫做静态链袭,这种描述方法还有起名叫做游标实
现法。
让数组的元素都是由两个数据域组成, 也钮和cur。也就是说,数组的每个下标都对应一个也ta 和一个cur o 数据域也ta ,用来存放数据元素, 也就是通常我们要处理的数据;而游标cur 相当于单链表中的next 指针,存放该元素的后继在数组中的下标。
/*线性表的静态链表存储结构*/
#define MAXSIZE 1000
typedef struct
{
ElemType data;
int cur; /*游标Cursor,为0时表示无指向*/
}Conponent,StaticLinlList[MAXSIZE];
另外我们对数组第一个和最后一个元素作为特殊元素处理,不存数据。我们通常把未被使用的数组元素称为备用链表。而数组第一个元素,即下标为0 的元素的cur就存放备用链袤的第一个结点的下标i 而数组的最后一个元素的cur 则存放第一个有数值的元素的下标,相当于单链表中的头结点作用,当整个链表为空时,则为02 。
/*初始化: 另外我们对数组第一个和最后一个元素作为特殊元素处理,不存数据*/
Status InitList(StaticLinkLisr space)
{
int i;
for (i = 0; i < MAXSIZE - 1; i++)
space[i].cur = i + 1;
space[MAXSIZE - 1].cur = 0;
return OK;
}
插队过程:
动态链表中结点的申请释放分别借用malloc()和free()两个函数来实现。在静态链表中操作的是数组,需要我们自己实现这两个函数,才可以做插入删除操作。
/*插队*/
Status InitInsert(StaticLinkList L,int i,ElemType e)
{
int j,k,l;
k = MAXSIZE - 1;
if (i<1 || i>ListLength(L) + 1)
return ERROR;
j = Malloc_SSL(L);
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;
}
/*静态链表删除操作*/
void Free_SSL(StaticLinkList space, int k)
{
space[k].cur = space[0].cur; //删除的位置指向原来第一个为空的位置
space[0].cur = k; //更新头元素的指向第一个的空位置
}
Status InitInsert(StaticLinkList L, int i)
{
int j, k;
if (i<1 || i>ListLength(L) + 1)
return ERROR;
k = MAXSIZE - 1; //k最后一个元素的下标 99
for (j = 1; j <= i - 1; j++) //循环后得到第i-1个元素的下标
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SLL(L, j);
}
return OK;
}
返回L中数据元素个数
//初始条件:静态链表L已存在。操作结果:返回L中数据元素个数
int ListLength(StaticLinkList L)
{
int j = 0;
int i = L[MAXSIZE - 1].cur;
while (i)
{
i = L[i].cur;
j++;
}
return j;
}
优点:
在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插人和删除操作需要移动大量元素的缺点
缺点:
没有解决连续存储分配带来的表长难以确定的问题
失去了顺序存储结构随机存取的特性
循环链表
将单链表中终端结点的指针端自空指针改为指向头结点,就使整个单链表形成一个环,这种头尾:相接的单链表称为单循环链表,简称循环链表( circular linked list) 。
其实循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断p->next是否为空,现在则是p -> next 不等于头结点,则循环未结束。
为了使终端结点也容易找到,改造的循环链表:
可以看到,终端结点用尾指针rear 指示,则查找终端结点是0(1) ,而开始结点,其实就是rear->next->next ,其时间复杂也为0(1) 。
合并两个循环链表:
p=rearA->next; //保存A表的头结点①
rearA->next=rearB->next->next;
rearB->next=p;
free(p);
双向链表double linked list
- 是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。
typedef struct DulNode
{
ElemType data;
struct DuLNode *prior;
struct DuLNode *next;
}DulNode,*DuLinkList;
- 双向链表同理可以是循环表
双向链表的循环带头结点的空链表如图
非空的循环的带头结点的双向链表:
- 双向链表的求长度ListLength、查找元素GetElem,获得元素LocateElem等与单链表相同,因为只涉及一个方向的指针。但是插入删除的操作需要更改两个指针的变量。
插入:
s->proir=p;
s->next=p->next;
p->next->prior=s;
p->next=s;
删除:
p->prior->next=p->next;
p->next->prior=p->prior;
free(p);