1.单链表的定义
线性表的链式存储结构是指用一组任意的存储单元(可以连续,也可以不连续)存储线性表中的数据元素。为了反映数据元素之间的逻辑关系,对于每个数据元素不仅要表示它的具体内容,还要附加一个表示它的直接后继元素存储位置的信息,这样构成的链表称为线性单向链接表,简称单链表,其中data部分为数据域,用于存储一个数据元素(结点)的信息。next部分称为指针域,用于存储其直接后继的存储地址的信息。
单链表分为带头结点和不带头结点两种类型。
带头结点的单链表,头结点的数据域可以不存储任何信息,也可以存放特殊的信息;头结点的指针域存储链表中第一个结点的地址。当头结点的指针域为空,则此表为空表。在非空表中,当某个结点的指针域为空,表示它为链表的最后一个结点。
由于单向链表不能随机存取存储的数据,在单向链表中存取第i个元素,必须从头指针出发寻找,其寻找的时间复杂度为O(n)。
2.单链表的类型定义
单链表由一个个结点构成,我们用C语言的结构体指针来描述
typedef int DataType; /* 定义DataType为int类型*/
typedef struct linknode /* 单链表存储类型 */
{
DataType data; /* 定义结点的数据域 */
struct linknode *next; /* 定义结点的指针域 */
}LinkList;
上面定义的LinkList是结点类型,如果想定义一个指向该结点类型的指针head,C语言中的语句如下:
LinkList *head;
如果想动态申请一个结点空间,并让指针head指向该结点空间,语句如下:
head=(LinkList *)malloc(sizeof(LinkList));
这样,head指针就该指向该结点了,则这个结点的数据域为head->data或是(*head).data,而指针域为head->next或(*head).next;
C语言中释放指针head所指结点空间的语句为:
free(head);
3.单链表的基本操作实现
1.单链表的初始化
单链表的初始化即构造一个仅包含头结点的空单链表。其过程是首先申请一个结点并让指针head指向该结点,然后将它的指针域赋为空(NULL),最后返回头指针head。
/* 初始化链表函数 */
LinkList *InitList()
{
LinkList *head;
head = (LinkList *)malloc(sizeof(LinkList));//动态分配一个结点空间
head->next=NULL;
return head; //头结点指针域为空,表示空链表
}
2.单链表的建立
(1)头插法建表
链表是一种动态管理的存储结构,链表中的每个结点占用的存储空间不是预先分配,而是运行时系统根据需求生成的。因此,建立在初始化链表后,建立线性链表从空表开始,每读入有效的数据则申请一个结点s,并将读取的数据存放到新节点s的数据域中,然后将新结点插入到当前链表head的表头上,直到循环结束为止。
/* 头插法建立链表函数 */
void CreatListH(LinkList *head,int n)
{
LinkList *s;
int i;
printf("请输入%d个整数: ",n);
for(i=0;i<n;i++)
{
s=(LinkList *)malloc(sizeof(LinkList)); //生成新节点
scanf("%d",&s->data); //读入新节点的数据域
s->next=head->next; //将新节点的指针域存放头结点的指针域
head->next=s; //将新节点插入头结点之后
}
printf("建立链表操作成功!");
}
(2)尾插法建表
头插法建立链表虽然算法简单易理解,但生成的链表中结点的次序和输入的次序相反。而尾插法建立链表可实现次序的一致,该算法依旧从空表开始,但需增加一个尾指针last,使其指向当前链表的尾结点。其过程是:每读入有效的数据则申请一个结点s,并将读取的数据存放到新结点s的数据域中,将s的尾指针设为空指针(NULL),然后将新结点插入到当前链表尾部(last指针所指的结点后面),直到循环结束为止。
/* 尾插法建表 */
void CreateListL(LinkList *head,int n)
{
LinkList *s,*last;
int i;
last=head; //last始终指向尾结点,开始时指向头结点
printf("请输入%d个整数:",n)
for(i=0;i<n;i++)
{
s=(LinkList *)malloc(sizeof(LinkList)); //生成新节点
scanf("%d",&s->data); //读入新节点的数据域
s->next=NULL; //将新节点的指针域为空
last->next=s; //将新节点插入表尾
last=s; //将last指针指向表尾结点
}
printf("建立链表操作成功!");
}
3.求表长操作
因为链表是链式结构,所以链表中元素个数不是已知的。想求表中元素个数还要设一个计数变量j(初值为0),将一个指针p先指向链表中的第一个元素,当p不为空时,循环将p指针后移,j加1,循环结束后j值即为链表长度。
/* 求链表长度函数 */
int LengthList(LinkList *head)
{
LinkList *p = head->next;
int j=0;
while(p != NULL) //当p不指向链表尾时
{
p = p->next;
j++;
}
return j;
}
4.查找操作
(1)按值查找
从链表的第一个元素结点开始,由前向后依次比较单链表中各结点数据域中的值,若某结点数据域中的值与给定的值x相等,则循环结束;否则继续向后比较直到表结束,然后判断指针p,若p不为空表示单链表中有x结点,输出查找成功的信息并输出x所在表中的位置;否则输出查找失败的信息。
/* 在链表中查找值为x的元素位置 */
void Locate(LengthList *head,DataType x)
{
int j=1; //计数器
LinkList *p;
p = head->next;
while(p! = NULL && p->data != x)
{
p=p->next;
j++;
}
if(p!=NULL)
{
printf("在表的第%d位找到值为%d的结点!",j,x);
}
else
{
printf("未找到值为%d的结点!",x);
}
}
(2)按序号查找
首先判断i值是否大于表长,如果大于表长则输出位置错误信息。否则从链表的头结点开始,判断当前结点序号是否是i,成立则提前结束循环。最后输出第i位上的结点的数据域值和位置i。
/* 在链表中按位置查找元素 */
void SearchList(LinkList *head,int i)
{
LinkList *p;
int j=0;
p=head; //p指向链表的头结点
if(i>LengthList(head))
printf("位置错误,链表中没有该位置! ");
while(p->next!=NULL && j<i)
{
p=p->next;
j++;
}
if(j==i) //判断与给定的序号是否相等
printf("在第%d位上的元素值为%d!",i,p->data);
}
5.插入操作
在指针所指的结点后插入新结点。若要在链表中指针p所指位置后面插入一个结点,则插入操作步骤如下:
(1)先将结点s的指针域指向结点p的下一个结点(执行语句s->next=p->next);
(2)再将结点p的指针域改为指向新结点s(执行语句p->next=s);
由于单链表的结点结构是单向后指的,因此要完成此操作需要找到第i结点的前驱结点即第 i-1 结点的指针p,然后在已知结点p后方插入新结点即可。
/* 按位置插入元素函数 */
void InsList(LinkList *head,int i,DataType x)
{
int j=0;
LengthList *p,*s;
p=head;
while(p->next!=NULL && j<i-1) //定位插入点
{
p=p->next;
j++;
}
if(p!=NULL) //p不为空则将新节点插到p后
{
s=(LengthList *)malloc(sizeof(LengthList)); //生成新节点s
s->data=x; //将数据x放入新节点的数据域
s->next=p->next; //将新节点s的指针域与p结点后面元素相连
p->next=s; //将p与新节点s连接
printf("插入元素成功! ");
}
else{
printf("插入元素失败!");
}
}
链表插入操作主要时间耗费在查找操作上,其时间复杂度为O(n)。
6.删除操作
首先通过循环定位求出第i结点的前驱节点(第i-1结点)p的地址,然后将指针s指向被删除的结点,修改p->next指针,使其指向s后的结点,最后释放指针s所指结点。
/* 按位置删除链表中元素函数 */
void DelList(LengthList *head,int i)
{
int j=0;
DataType x;
LengthList *p=head,*s;
while(p->next!=NULL && j<i-1) //定位插入点
{
p=p->next;
j++;
}
if(p->next!=NULL && j==i-1)
{
s=p->next; //s为要删除结点
x=s.data; //将要删除的数据放入变量x中
p->next=s->next; //将p结点的指针域与q结点后面元素相连
free(s);
printf("删除第%d位上的元素%d成功!",i,x);
}
else
printf("删除结点位置错误,删除失败!");
}
链表的删除操作也主要时间耗费在查找操作上,其时间复杂度为O(n)。
7.单链表的输出操作
扫描单链表,输出各元素的值
/* 显示输出链表函数 */
void DispList(LengthList *head)
{
LengthList *p;
p=head->next; //p指针指向链表第一个结点
if(p!=NULL) //当p指针不为空时输出链表每个数据域值
{
printf("%5d",p->data);
p=p->next;
}
}