C语言单链表的实现
认识单链表(为何要学习链表)
链表的功能的介绍
单链表的定义
由于顺序表的插入删除操作需要移动大量的元素,影响了运行效率,因此引入了线性表的链式存储——单链表。单链表通过一组任意的存储单元来存储线性表中的数据元素,不需要使用地址连续的存储单元,因此它不要求在逻辑上相邻的两个元素在物理位置上也相邻。
单链表的相应功能:
在单链表中我们可以实现头部插入和删除操作、尾部部插入和删除操作、
指定位置插入和删除操作、单链表的查找等,通过这些操作我们可以更好的了解单链表并且加以掌握使用。
单链表各个功能的实现
单链表的头部插入和删除操作
注:在插入时,直接申请一个新的节点并将其赋值,在插入时会频繁使用到申请新空间的函数,我们可以先将其写好以便后面使用。
//申请空间
SL* addspace(ty x)
{
SL* newnode = (SL*)malloc(sizeof(SL));
if (newnode == NULL)
{
printf("申请空间失败!");
}
else
{
newnode->data = x;
newnode->next = NULL;
return newnode;
}
}
头部插入法中,直接申请空间在将新申请的指针域(单链表中一个节点包含了数据域和指针域,数据域用来储存数据,而指针域用来储存下一个节点的地址,即指向下一节点)指向原来的头节点,在将其更新为头节点,这样头插法就完成了。
//头插法
void pushhead(SL** pphead, ty x)
{
SL* node = addspace(x);
node->next = *pphead;
*pphead = node;
}
在头删法中,先判断链表是否为空,为空直接返回,不为空则进行下一步,先定义一个中间量将其储存起来,再使头节点的下一个成为头节点,再将其释放掉,这样我们就实现了,头节点的删除。
//头删法
void pophead(SL** pphead)
{
if (*pphead == NULL)
{
return;
}
else
{
SL* tmp = *pphead;
*pphead = (*pphead)->next;
free(tmp);
}
}
单链表的尾部部插入和删除操作
单链表的尾插法,插入法构思第一步基本一致,就是要先建立一个新的节点。
要实现尾插法就要先判断头节点是不是空的,如果是空的,直接设置为头节点。如果不是空的,那就要从头节点开始遍历,先找到尾节点,再让尾部节点的指针域指向要插入的节点,再让其指针域指向空,即可实现单链表的尾部插入操作。
//尾插法
void pushback(SL** pphead, ty x)
{
SL* node = addspace(x);
if (*pphead == NULL)//没有节点时
{
*pphead = node;//直接设置为头节点
return;
}
else
{
SL* pcur = *pphead;
while (pcur->next)//先找到尾节点
{
pcur = pcur->next;
}
pcur->next = node;
node->next = NULL;
}
}
在实现单链表的尾部删除操作时,要先判断链表是否是空链表,若是空的则不能删除,直接返回即可。
如不是空链表,要先找到尾部节点的前一个,因为如果直接删除最后一个节点,前一个的指针域就会变成野指针。所以要先找到尾部指针的前一个。再将尾部指针释放时,将倒数第二个的指针域指向NULL避免变成野指针。
//尾删法
void popback(SL** pphead)
{
if (*pphead == NULL)//没有节点时
{
return;
}
else
{
SL* pcur = *pphead;
SL* prev = *pphead;
while (pcur->next)
{
prev = pcur;
pcur = pcur->next;
}
free(pcur);
prev->next = NULL;
}
}
单链表删除指定位置的节点
删除指定节点时要分多种情况考虑
1.当链表为空时,没有可以删除的节点,直接返回。
2.若要删除的节点是头节点时,直接调用之前写好的头删函数
3.若链表存在并且不是头节点时,先遍历找到pos节点前一个位置用prev储存,并且将pos节点储存起来,若找到了,就使prev其指针域指向pos节点的下一个节点,再将pos节点释放掉,即可实现指定位置的删除。
//删除pos位置的节点
void Deletpos(SL** head, ty pos)
{
if (*head == NULL)
{
return;
}
if ((*head)->data == pos)//头删
{
pophead(head);
}
SL* tmp = *head;
SL* prev = *head;
while (tmp != NULL && tmp->data != pos)//找到pos前一个位置
{
prev = tmp;
tmp = tmp->next;
}
if (tmp)//找到了
{
prev->next = tmp->next;
free(tmp);
}
else
{
return;
}
}
单链表的指定位置插入和删除操作
和上面一样,在指定位置进行操作时就要考虑位置在的位置是否有特殊情况要考虑。
在指定位置之前插入时也要考虑多种情况。
1.当链表为空时,将要插入的新节点直接设置为头节点,然后再将其指针域指向NULL
2.当要插入的节点位置是头节点时,直接调用函数头插法即可。
3.若不是以上两种情况,就先遍历找到pos节点前一个位置用cur储存,若找到了,就使要插入的节点的指针域指向cur节点的下一个节点,也就是pos节点,再将cur节点的指针域指向新节点,这样就实现了新节点在pos节点之前插入。
//在指定位置之前插入
void InsertFront(SL** head, ty pos, ty x)
{
SL* newnode = addspace(x);
if (*head == NULL)//若链表为空
{
*head = newnode;
newnode->next = NULL;
return;
}
//链表不为空
if ((*head)->data == pos)//要在第一个位置头插
{
pushhead(head, x);
}
else
{
SL* cur = *head;
while (cur->next != NULL && cur->next->data != pos)//找到pos前一个位置的节点
{
cur = cur->next;
}
newnode->next = cur->next;
cur->next = newnode;
}
}
在指定位置之后插入其实也大同小异,也是先要看位置是否有效若位置无效,直接返回即可,若位置有效,先为新节点申请空间,然后将其指针域指向pos节点的下一个节点即node->next = cur->next;然后再将pos节点指向新节点即可。
//在指定位置后插入数据
void Insertafter(SL* head, ty pos, ty x)
{
SL* cur = head;
int count = 0;
while (cur != NULL && count < pos)
{
cur = cur->next;
count++;
}
if (cur == NULL)//如果位置无效
{
return;
}
SL* node = addspace(x);
node->next = cur->next;
cur->next = node;
}
单链表的查找
通过上面代码的书写及讲解,查找的代码就简单多了。首先遍历链表若链表中有的数据域等于要查的节点的值,则提示找到了,若是遍历完链表还没有找到,就表示在链表中找不到了。
//查找结点
SL* Serchfor(SL* pphead, ty x)
{
SL* tmp = pphead;
while (tmp)
{
if (tmp->data == x)
{
printf("找到了 %d\n", tmp->data);
return tmp;
}
tmp = tmp->next;
}
printf("找不到");
}
总代码的实现
#include<stdio.h>
#include<stdlib.h>
typedef int ty;
typedef struct SlistNode {
struct SlistNode* next;
ty data;
}SL;
//打印链表
void Display(SL* pphead)
{
SL* pcur = pphead;
while (pcur != NULL)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
//申请空间
SL* addspace(ty x)
{
SL* newnode = (SL*)malloc(sizeof(SL));
if (newnode == NULL)
{
printf("申请空间失败!");
}
else
{
newnode->data = x;
newnode->next = NULL;
return newnode;
}
}
//头插法
void pushhead(SL** pphead, ty x)
{
SL*node=addspace(x);
node->next = *pphead;
*pphead = node;
}
//头删法
void pophead(SL** pphead)
{
if (*pphead == NULL)
{
return;
}
else
{
SL* tmp = *pphead;
*pphead = (*pphead)->next;
free(tmp);
}
}
//尾插法
void pushback(SL** pphead, ty x)
{
SL* node = addspace(x);
if (*pphead==NULL)//没有节点时
{
*pphead = node;//直接设置为头节点
return;
}
else
{
SL* pcur = *pphead;
while (pcur->next)//先找到尾节点
{
pcur = pcur->next;
}
pcur->next = node;
node->next = NULL;
}
}
//尾删法
void popback(SL** pphead)
{
if (*pphead == NULL)//没有节点时
{
return;
}
else
{
SL* pcur = *pphead;
SL* prev = *pphead;
while (pcur->next)
{
prev = pcur;
pcur = pcur->next;
}
free(pcur);
prev->next = NULL;
}
}
//查找结点
SL* Serchfor(SL* pphead, ty x)
{
SL* tmp = pphead;
while (tmp)
{
if (tmp->data == x)
{
printf("找到了 %d\n", tmp->data);
return tmp;
}
tmp = tmp->next;
}
printf("找不到");
}
//在指定位置后插入数据
void Insertafter(SL* head, ty pos, ty x)
{
SL* cur = head;
int count = 0;
while (cur != NULL && count < pos)
{
cur = cur->next;
count++;
}
if (cur == NULL)//如果位置无效
{
return;
}
SL*node= addspace(x);
node->next = cur->next;
cur->next = node;
}
//在指定位置之前插入
void InsertFront(SL** head, ty pos, ty x)
{
SL*newnode= addspace(x);
if (*head == NULL)//若链表为空
{
*head = newnode;
newnode->next = NULL;
return;
}
//链表不为空
if ((*head)->data == pos)//头插
{
pushhead(head,x);
}
else
{
SL* cur = *head;
while (cur->next != NULL && cur->next->data != pos)//找到pos前一个位置的节点
{
cur = cur->next;
}
newnode->next = cur->next;
cur->next = newnode;
}
}
//删除pos位置的节点
void Deletpos(SL** head, ty pos)
{
if (*head == NULL)
{
return;
}
if ((*head)->data == pos)//头删
{
pophead(head);
}
SL* tmp = *head;
SL* prev = *head;
while (tmp != NULL && tmp->data != pos)//找到pos前一个位置
{
prev = tmp;
tmp = tmp->next;
}
if (tmp)//找到了<
{
prev->next = tmp->next;
free(tmp);
}
else
{
return;
}
}
int main()
{
//测试头插法
SL* plist = NULL;
pushhead(&plist, 1);
//Display(plist);
pushhead(&plist, 2);
//Display(plist);
pushhead(&plist, 3);
//Display(plist);
pushhead(&plist, 4);
Display(plist);
//Serchfor(plist, 3);
//测试在指定位置之后插入
/*Insertafter(plist, 2, 9);
Display(plist);*/
//测试在指定位置之前插入
/*InsertFront(&plist, 3, 10);
Display(plist);*/
//测试删除pos节点的位置
//Deletpos(&plist, 2);
//Display(plist);
//测试头删法
/*pophead(&plist);
Display(plist);
pophead(&plist);
Display(plist);*/
//测试尾插法
/*pushback(&plist, 0);
Display(plist);
pushback(&plist, -1);
Display(plist);*/
//测试尾删法
/*popback(&plist);
Display(plist);
popback(&plist);
Display(plist);*/
//测试查找
//Serchfor(plist, 9);
return 0;
}