单链表的定义
线性表的链式存储又称为单链表,单链表的结点结构如下所示:
data是数据域,用于存放数据,next为指针域,用于存放后继节点,因为有了next这个指针域,链表中的每一个结点才可以连接起来成为链表,但是同时因为next指针域只能指向后继节点,所以如果在单链表中寻找某个特定值,不可以直接进行寻找,而是需要从头结点开始依次遍历查找。
上图是带头结点的单链表,从上图可以发现,头结点是单链表的第一个元素结点,但是头结点的数据域可以不存放任何链表元素。这里注意需要对头结点和头指针进行区分:
不管链表是否带有头结点, 头指针始终指向链表的第一个结点,头结点是带头结点链表的第一个结点,用上图举例,上图就是带头结点单链表,第一个箭头就是头指针,它指向头结点,如果没有头结点,那么头指针就指向a1那个数据域。
头插法
头插法是指从一个空表开始,生成新节点,并将读取到的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后。如下图所示是用头插法构建单链表的方式。每次将s所指的结点插在最前端。第一步让s->next=L->next,将原来头结点的后继节点变成需要插入的结点的后继节点,第二步,让L->next=s,将s变成头结点的后继节点。
从图示可以发现,采用头插法建立单链表,读入数据的顺序和生成的链表顺序是相反的,最先读入的数据是链表的最后一个元素,头插法建立单链表的算法如下:
LinkList CreatList(LinkList &L){
//从表尾到表头逆向建立单链表L,每次均在头结点之后插入元素
LNode *s;
int x;
L=(LinkList)malloc(sizeof(LNode));//创立头结点
L->next=NULL;//初始为空链表
scanf("%d",&x);//输入结点的值
while(x!=9999){//输入9999表示结束
s=(LNode*)nalloc(sizeof(LNode));//创建新的结点
s->data=x;
s->next=L->next;
L->next=s;//将新的结点插入表中,L为指针
scanf("%d",&x);
}
return i;
}
尾插法
尾插法如下图绿色所示,其中an是尾结点,am是需要插入的结点,从图中可以发现,尾插法每次插入结点都是将结点插入链表尾部,因此需要加入一个新的指针作为尾指针,始终指向尾结点。对比头插法和尾插法可以发现,尾插法因为每次读取的数据都放在最后,所以生成的链表顺序和读入元素的顺序一致。
尾插法建立单链表的算法如下:
LinkList CreatList(LinkList &L){
//从表头到表尾正向建立单链表L,每次均在表尾插入元素
int x;//设元素类型为整型
L=(LinkList)malloc(sizeof(LNode));
LNode *s,*r=L;//r为表尾指针
scanf("%d",&x);//输入结点的值
while(x!=9999){
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;//r指向新的表尾结点
scanf("%d",&x);
}
r->next=NULL;
return r;
}
按序号查找结点值
在单链表中从第一个结点出发,顺指针next域逐个往下搜索,直到找到第i个结点为止,否则返回最后一个结点指针域NULL。
之前说了,因为链表当中每个结点只能指向他的后继节点,因此寻找链表中的某个值或者某个元素的位置,需要从表头依次遍历查找,这个算法的时间复杂度是O(n)
LNode *GetElem(LinkList L,int i){
//本算法取出单链表L(带头结点)中第i个位置的结点指针
int j=1;//计数,初始为1
LNode *p=L->next;//头结点指针赋给P
if(i==0)
return L;//若i=0,则返回头结点
if(i<1)
return NULL;//若i无效,则返回NULL
while(p&&j<i){//从第1个结点开始找,查找第i个结点
p=p->next;
j++;
}
return p;//返回第i个结点的指针,如果i大于表长,p=NULL,直接返回p即可
}
按值查找表结点
给定一个值,从单链表第一个结点开始不断往后,依次比较各节点元素的值和给定值,如果相等则返回该结点的指针,如果一直遍历到表尾还没有找到,则表示整个链表都没有这个结点,因此返回NULL,整个算法的时间复杂度同样为O(n)
LNode *LocateElem(LinkList L,ElemType e){
//本算法查找单链表L(带头结点)中数据域值等于e的结点指针,否则返回NULL
LNode *p=L->next;
while(p!=NULL&&p->data!=e)//从第1个结点开始查找data域为e的结点
p=p->next;
return p;//找到后返回该结点的指针,否则返回NULL
}
插入结点
插入是将值为x的新结点插入到单链表的第i个位置上,先检查插入位置的合法性,然后找到待插入位置的前驱结点,即第i-1个结点,再在其后新结点。
算法首先调用按序号查找结点中的GetElem(L,i-1),查找第i-1个结点。假设返回的第i-1个结点为*p,然后令新结点*s的指针域指向*p的后继节点,再令结点*p的指针域指向新插入的结点*s,操作过程如下:
删除结点操作
删除操作时将单链表的第i个结点删除。先检查删除位置的合法性,然后查找表中i-1个结点,即被删结点的前驱结点,再将其删除。假设结点*p为找到的被删节点的前驱结点,这时只需将p->next=q->next
实现删除结点的代码片段如下:
p=GetElem(L,i-1);//查找删除位置的前驱结点
q=p->next;//令q指向被删除结点
p->next=q->next;//将*q结点从链中“断开”
free(q);//释放结点的存储空间
求链表长度
表长是不包括头结点的,因此带头结点和不带头结点的单链表求表长的操作会有些不同,同时对于不带头结点的单链表,当链表为空的时候需要单独处理。