一、线性表的链式存储结构
线性表顺序存储结构缺点是插入和删除元素,比较麻烦,需要移动元素,并且会造成空间的浪费。而链式存储结构,插入删除元素,而不用移动其他元素。用一组任意的存储单元存储线性表的数据元素,可以是连续的,也可以是不连续的,这些数据元素可以存在内存未被占用的任意位置。
每一个数据元素,除了存储数据信息外,还会存储下一个元素的内存地址,用一个指针指向这个地址,每个元素与所存的指针构成了线性链表的一个结点。
存储数据元素的叫做数据域;
存储指针信息的叫做指针域。
每一个结点只有一个指针域,各个结点靠指针域链结在一起,这个链表有个开头,存储的第一个数据元素的结点称为头结点(一般我们在第一个结点前还会放置一个结点,这个结点只存放指针不放数据信息);最后一个元素指针域没有存放指针设位NULL,只存有数据元素,表示到这结束了。
头指针:如果链表没有头结点,则是指向链表第一个结点的指针;若这个链表有头结点,则该指针指向头结点。
头结点:存放指向第一个结点的指针,一般数据域不存放信息。若线性表位空,则头结点的指针域为NULL。
单链表存储的结构体
typedef struct node
{
DataType data;
struct Node *next;
}LinkNode; *LinkList;
1.单链表的初始化
void InitList(LinkList *head)
{
*head=(LinkList)malloc(sizeof(LinkNode)); //为头结点申请分配内存空间
(*head)->next=NULL; //让头结点的指针指向空
}
2.判断单链表是否为空
int ListEmpty(LinkList head)
{
if(head->next==NULL)
return 1;
else
return 0;
}
3.根据序号读取单链表的元素内容
(1)声明一个指针p指向链表第一个结点,让计数器j从1开始
(2)当j<i时,不断的循环,并每一次让p指向下一个节点
(3)如果p最后为空,且j>i,说明i结点不存在,否则就是查找成功,返回结点p的数据
Status GetElemType(LinkList head,int i,int *e)
{
int j=1; //定义一个计数器
LinkList p; //定义一个LinkList结构体类型的指针p
p=head->next; //让p指向链表的第一个结点
while(p&&j<i) //让j从第一个结点开始循环,直到读取的位置i,且满足p最后指向的指针域不为空
{
p=p->next; //让p指向下一个结点
j++; //计数器加1
}
if(!p || j>i) //要取的数据下标不在表的范围内
return error;
*e=p->data; //把第i个位置的数据传递给e
return error;
}
单链表没有定义表长,无法使用for循环,当要查询的结点是第一个结点时,则时间复杂度为O(1),当i=n时遍历n-1次,所以最坏情况的时间复杂度为O(n)。这种方法称为指针后移。
4.按内容查找链表中与给定内容是否相等,是则返回指针,否则返回NULL
LinkList* LocateElem(LinkList head,DataType e)
{
LinkList *p; //声明一个LinkList结构体类型的指针p
p=head->next; //让p指向第一个结点
while(p) //p为NULL则跳出循环
{
if(p->data==e) //如果当前节点的数据等于要查找的内容,则跳出循环
break;
else //如果不等于则指针下移一位
p=p->next;
}
return p; //返回指针p
}
5.插入元素
(1)先申明两个指针,p作为可移动的指针用于循环指向i前面一个结点,s作为新插入结点指针。
(2)循环找到i前面的结点
(3)把数据赋值给新结点s
(4)把p的后继赋值给s的后继,把s赋值给p的后继
(5)插入成功返回1,失败返回0
Status LinkInsert(LinkList *head,int i,int e)
{
LinkList p,s; //声明两个LinkList结构体类型的指针
int j=1; //定义一个计数器
p=*head; //让p指向头结点L
while(p&& j<i)
{
p=p->next; //让p指向下一个结点
j++;
}//当跳出循环后,p指向i前面的一个结点,i=p->next
if(!p || j>i) //判断结点i是否在链表的范围内
return 0;
s=(LinkList)malloc(sizeof(Node));//申请分配内存
s->data=e; //把数据传递给新结点的数据域
s->next=p->next; //把p的后继结点(结点i)赋值给s的后继
p->next=s; //把s赋值给p的后继
return 1;
}
6.删除元素
(1)先申明两个指针,p作为可移动的指针用于循环指向i前面一个结点,q作为要删除的结点指针。
(2)循环找到i前面的结点
(3)把p的后继赋值给q(即此时的q就是要删除的结点i)
(4)把q的后继赋值给p的后继
(5)释放结点q
Status LinkDelete(LinkList head,int i,int *e)
{
LinkList p,q; //声明两个LinkList结构体类型的指针
int j=1; //定义一个计数器
p=*head; //让p指向头结点L
while(p&& j<i)
{
p=p->next; //让p指向下一个结点
j++;
}//当跳出循环后,p指向i前面的一个结点,i=p->next
if(!p || j>i) //判断结点i是否在链表的范围内
return 0;
q=p->next; //把p的后继赋值给q(即此时的q就是要删除的结点i)
*e=q->data; //把要删除的结点的数据传递给e
p->next=q->next; //把要删除结点q的后继赋值给p的后继
free(q); //回收q
return 1;
(7)整张链表的删除
void DestryList(LinkList head)
{
LinkNode p,q;
p=head;
while(p!=NULL)
{
q=p;
p=p->next;
free(q);
}
}
二、创建链表的两种方法
1.头插法
void CreateListHead(LinkList *head,int n)
{ //传入的参数,第一个为链表的头结点,第二个参数为要生产链表的元素的个数
LinkList p; //声明一个LinkList类型的指针
int i,e; //定义一个计数器,和要传入的数据类型
*head=(LinkList)malloc(sizeof(Node));//为头结点申请内存空间
head->next=NULL; //让头结点的指针先指向空
for(i=0;i<n;i++) //开始循环,直到给定的链表结点个数为止
{
p=(LinkList)malloc(sizeof(Node)); //为新结点申请空间
printf("请新输入数据:\n");
scanf("%d",&e); //获取结点数据
p->data=e; //把数据传递给新结点的数据域
p->next=*head->next; //让新结点的指针指向NULL
*head->next=p; //让头结点的指针指向新结点
}
}
2.尾插法
void CreateListTail(LinkList *head,int n)
{ //传入的参数,第一个为链表的头结点,第二个参数为要生产链表的元素的个数
LinkList p,r; //声明两个LinkList类型的指针
int i,e; //定义一个计数器,和要传入的数据类型
*head=(LinkList)malloc(sizeof(Node));//为头结点申请内存空间
r=*head; //把头结点赋给指针r
for(i=0;i<n;i++)
{
p=(LinkList)malloc(sizeof(Node)); //为新结点申请空间
printf("请新输入数据:\n");
scanf("%d",&e); //获取结点数据
p->data=e; //把数据传递给新结点的数据域
r->next=p; //先把新结点加入到r(此时r为头结点)结点的后面
r=p; //再把新结点赋值给r,下一次循环时,r就作为这个链表的结尾,然后在进行加新结点的操作
}
r-next=NULL;
}
注:尾插法中,r永远为指向最后一个结点的指针。