1、单链表的整表创建:
顺序存储结构的创建就是一个数组的初始化,即声明一个类型和大小的数组并赋值的过程。但是链表的所占用的空间的大小和位置都是不需要预先分配的,根据系统的情况和实际的需求即时生成。
思路:从“空表”的初始状态起,依旧建立各元素结点,并逐个插入链表。
1、声明一结点p和计数器变量i;
2、初始化一空链表L;
3、让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
4、循坏:生成一新结点赋值给p;随机生成一数字赋值给p的数据域p->data;将p插入到头结点与前一新结点之间。
代码:
/*随机产生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)); /*生成新结点*/
p->data = rand() % 100 + 1; /*随机生成100以内的数字*/
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为指向尾部的结点*/
for(i = 0;i < n;i++)
{
p = (Node *)malloc(sizeof(Node)); /*生成新的结点*/
p->data = rand() % 100 + 1; /*随机生成100以内的数字*/
r->next = p; /*将表尾终端结点的指针指向新结点*/
r = p; /*将当前的新结点定义为表尾终端结点*/
}
r->next = NULL; /*表示当前链表结束*/
}
2、单链表的整表删除
思路:
1、声明一结点p和q;
2、将第一个结点赋值给q;
3、循环:将下一个结点赋值给q;释放p;将q赋值给p。
代码:
/*初始条件:顺序线性表L已存在,操作结果:将L重置为空表*/
Status ClearList(LinkList *L)
{
LinkList p,q;
p = (*L)->next; /*p指向第一个结点*/
while(p) /*没到表尾*/
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL; /*头结点指针域为空*/
return OK;
}
3、单链表结构与顺序存储结构优缺点:
1、存储分配方式:顺序存储结构用一段连续的存储单元依次存储线性表的数据元素;单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素;
2、时间性能:查找(顺序存储结构O(1)和单链表O(n));插入和删除(顺序存储结构需要平均移动表长一半的元素时间为O(n),单链表在找出某位置的指针后,插入和删除的时间仅为O(1));
3、空间性能:顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生上溢;单链表不需要分配存储空间。
4、静态链表:利用数组描述的链表,数组的元素都有两个数据域组成,数据域data(存放数据元素)和游标cur(相当于单链表中的next指针,存放该元素的后继在数组中的下标)
/*线性表的静态链表存储结构*/
#define MZXSIZE 1000 /*假设链表的最大长度是1000*/
typedef struct
{
ElemType data;
int cur; /*游标(Currsor),为0时表示无指向*/
}Component,StaticLinkList[MZXSIZE];
静态链表的插入操作:为了辨明数组中哪些分量未被使用,将所有未被使用过的及已被删除的分量用游标链成一个备用的链表,每当进行插入时,便可以从备用链表上取得第一个结点作为待插入的新结点。
/*若备用空间链表非空,则返回分配的结点下标,否则返回0*/
int Malloc_SLL(StaticLinkList space)
{
int i = space[0].cur; /*当前数组第一个元素的cur存的值*/
/*就是要返回的第一备用空间的下标*/
if(space[0].cur)
space[0].cur = space[i].cur; /*由于要拿出一个分量来使用*/
/*我们就得把它的下一个分量用来做备用*/
return i;
}
/*在L中第i个元素之前插入新的数据元素e*/
Status ListInsert(StaticLinkList L,int i,ElemType e)
{
int j,k,l;
k = MAX_SIZE - 1; /*注意k首先是最后一个元素的下标*/
if(i < 1 || i > ListLength(L) + 1)
{
return ERROR;
}
j = Malloc_SSL(L); /*获得空间分量的下标*/
if(j)
{
L[j].data = e; /*将数据赋值给次分量的data*/
for(i = 1;1 <= i - 1;i++) /*找到第i个元素之前的位置*/
{
k = L[k].cur;
}
L[j].cur = L[k].cur; /*把第i个元素之前的cur赋值给新的元素的cur*/
L[k],cur = j; /*把新元素的下标赋值给第i个元素之前元素的cur*/
return OK;
}
return ERROR;
}
静态链表的删除操作:
/*删除在L中第i个数据元素e*/
Status ListDelete(StaticLinkList L,int i)
{
int j,k;
if(i < 1 || i > ListLength(L))
return ERROR;
k = MAX_SIZE - 1;
for(j = 1;j <= i - 1;j++)
{
k = L[k].cur;
}
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SSL(L,j);
return OK;
}
Free_SLL(L,j)的代码:
void Free_SSL(StaticLinkList space,int k)
{
space[k].cur = space[0].cur; /*把第一个元素cur值赋给要删除的分量cur*/
space[0].cur = k; /*把要删除的分量下标赋值给第一个元素的cur*/
}
求链表长度
int ListLength(StaticLinkList L)
{
int j = 0;
int i = L[MAXSIZE - 1].cur;
while(i)
{
i = L[i].cur;
j++;
}
return j;
}
静态链表优缺点:
优点:在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点
缺点:没有解决连续存储分配带来的表长难以确定问题;失去了顺序存储结构随机存取的特性
4、循环链表
定义:将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表现成一个环,头尾相接的单链表。
循环链表可以解决如何从当中一个结点出发,访问到链表的全部结点
循环链表和单链表的主要差异在于循环的判断条件上,原来是判断p->next是否为空,现在是p->next不等于头结点,则循环未结束。
将两个循环链表合并成一个表
p->rearA-next;
rearA->next = rearB->next->next;
rearB-next = p;
free(p);
5、双向链表
定义:在单链表的每一个结点中,再设置一个指向其前驱结点的指针域
/*线性表的双向链表存储结构*/
typedef struct DulNode
{
ElemType data;
struct DulNode *prior; /*指向前驱指针*/
struct DulNode *next; /*直接后驱指针*/
}DulNode,*DuLinkList;
双向链表前驱的后继也是它自己
p = p->next-prior = p->prior->next
双向链表的插入操作:
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;
双向链表的删除操作:;
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);