链表的优点:进行插入删除的读写数据操作速度快,而且内存空间利用率充足,不需要指定特定的空间大小,相对灵活。
链表的缺点:进行查找数的操作,速度要慢于随机存储。删除节点需要释放内存,开辟节点需要开辟空间,多了很多操作,容易造成内存泄漏的风险。
必要的头文件:stdlib.h 建议头文件: stddef.h , string.h
单链表的组成:头指针,头结点(可以没有),普通结点。普通结点又分为数据域和指针域,指针域存放的是下一个结点的地址。头指针存放在栈空间中,但是指向的是堆中的节点地址。其他结点均使用malloc函数存放于堆空间中。
带头结点和不带头结点的区别:
-
头结点可以存储一些链表的基础信息,使用头指针来访问头结点,头结点的指针域存放的才是真正的链表的第一个指针。
-
使用头结点可以大大减少二级指针的使用,只有修改头指针的指向时才需要二级指针,其他情况全是结构体指针即可完成链表的增删查改,因为头指针始终是指向头结点的,一般不需要移动头结点。
-
不带头结点,因为第一个就是真正的数据,所以增删查改都需要使用二级指针来访问,修改头指针的指向。
链表的结构体与自定义类型
typedef int dataType;
typedef int status;
//链表
typedef struct linkList {
dataType data;
struct linkList* next;
}linkList;
//头结点
typedef struct head {
size_t len;
linkList* phead;
}head;
单链表的创建
head* createLinkList(void)
{
head* h = malloc(sizeof(head));
h->len = 0; //长度为0
h->phead = NULL; //头结点指针为NULL
return h;
}
单链表头插与尾插数据
头插操作很简单, 速度也很快, 但是插入的数据会颠倒过来。尾插首先需要查找一下尾结点在哪里(指针域为NULL),然后在进行插入。
/**
* 头插单链表
* 参数: 头指针
*/
status headInsertList(head* hp, dataType element)
{
//开辟新节点空间
linkList* ptemp = (linkList*)malloc(sizeof(linkList));
if (ptemp == NULL) {
return ERROR;
}
//写入数据
ptemp->next = hp->phead;
ptemp->data = element;
//更新头节点
hp->phead = ptemp;
hp->len++;
return OK;
}
/**
* 尾插
* 参数: 头指针,插入的元素
*/
status tailInsert(head* hp, dataType element)
{
linkList** ptemp = NULL;//二级指针修改尾节点指针域的地址
//找到尾节点,特点是下一个指针指向NULL
if (hp->phead == NULL) {
ptemp = &(hp->phead);
}
else{
ptemp = &(hp->phead->next);
}
while (*ptemp != NULL) {
ptemp = &((*ptemp)->next);
}
//插入数据,开辟一个空间,让尾节点指向它
linkList* ptemp2 = (linkList*)malloc(sizeof(linkList));
if (ptemp2 == NULL) {
return ERROR;
}
ptemp2->data = element;
ptemp2->next = NULL;
*ptemp = ptemp2;
//更新头结点的信息
++hp->len;
return OK;
}
单链表的删除节点
删除节点一般是不需要删除头节点的,因此使用一级指针即可。首先需要找到待删除结点的地址,和删除结点指向的下一个地址,然后将这两个连接起来,free掉删除结点。
/**
* 删除链表一个结点
* 参数: 头指针, 删除节点的位置, 从1开始
* 注意: 头结点不能删除
*/
void delList(head* hp, size_t num)
{
if (num > hp->len || num < 1) {
return;
}
linkList* ptemp1 = hp->phead;//删除节点的前一个节点
linkList* ptemp2 = hp->phead;//待删除节点
//找到该结点
for (int i = 0; i < num-2; i++) {
ptemp1 = ptemp1->next;
}
ptemp2 = ptemp1->next;
ptemp1->next = ptemp2->next;
free(ptemp2);
hp->len--;
}
整表的删除
一个一个节点删除,还需要删除释放头节点的内存空间,所以需要使用二级指针,不能够忘记修改头指针的地址为NULL,否则可能出现非法访问的缺陷。
/**
* 销毁链表
* 参数: 头指针
*/
void clearList(head** hp)
{
linkList* ptemp1 = (*hp)->phead; //下一个节点的位置
linkList* ptemp2 = (*hp)->phead; //待销毁的节点
//销毁后面的节点
while (ptemp1 != NULL) {
ptemp1 = ptemp1->next;
free(ptemp2);
ptemp2 = ptemp1;
}
//销毁头指针
free(*hp);
*hp = NULL;
}