一 链表的特点
1 按需申请空间
由于每一块物理空间都是单独申请的,因此需要考虑如何关联每一个内存块
-》用指针关联(每个节点存放两个东西:内容和指向下一个内存块的指针)
相比于顺序表空间浪费较小:一次开辟两倍,过多浪费;频繁开辟那么频繁访问内存,效率低下。
2 效率更高
头部或者中间插入删除,不需要挪动数据(顺序表需要),相对于顺序表效率更高,因为顺序表再头部或者中间插入的话,需要挪动数据。
总结:链表与顺序表优势互补,各有特点。
二 链表的结构
对于单向不循环链表
每个节点包括所包含的内容和下一个结点的位置。最后一个结点存放所包含的内容和NULL
三基本操作
1 如何创建链表?
每个结点所包含的内容应该是所包含的数据和下一个结点的位置。定义了这样一个结构体之后,通过创建结构体类型的指针对结构体中内容进行访问。注意:对类型进行重定义可以后期存放其他类型的数据。
代码如下:
typedef int slistdatatype;
typedef struct slistnode
{
slistdatatype data;
struct slistnode* next;
}slistnode;
slistnode* n1 = (slistnode*)malloc(sizeof(slistnode));
assert(n1);
slistnode* n2 = (slistnode*)malloc(sizeof(slistnode));
assert(n2);
slistnode* n3 = (slistnode*)malloc(sizeof(slistnode));
assert(n3);
slistnode* n4 = (slistnode*)malloc(sizeof(slistnode));
assert(n4);
n1->data = 1;
n2->data = 2;
n3->data = 3;
n4->data = 4;
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = NULL;
2 如何打印链表?
创建一个指向第一个结构体的结构体指针,如果这个结构体不为NULL,那么就打印其中的数据。同时这个指针指向下一个结点。
void slist_print(slistnode* phead)
{
assert(phead);
while (phead != NULL)//如果是phead->next!=NULL作为判断条件的话,最后一个结点无法打印
{
printf("%d->", phead->data);
phead = phead->next;
}
printf("NULL");
}//打印
3 如何在链表的头部插入一个数据
首先我们需要创建一个结点。这个节点的指针需要指向原先的第一个结构体指针。之后需要修改phead的值。因此需要传入一个二级指针pphead。
//头插 头插的话需要创建一个新的结点 并且在这个结点中存储下一个结点的位置(原先头结点的位置),最后plist需要重新指向最开始创建出来的结点 那么pphead也会随之修改
void slist_frontpush(slistnode** pphead, slistdatatype x)
{
slistnode* newnode = slist_buy(x);
newnode->next = *pphead;
*pphead = newnode;
}//可以同时解决空链表的问题,不需要单独处理
4 如何在链表的尾部插入一个数据
首先需要创建一个新节点,作为插入的新节点。原先链表的最后一个结点的next需要指向这个结点。如何找链表的最后一个结点?需要用tail进行循环遍历查找尾巴。需要注意的是:如果是空链表的话,因此需要单独进行处理,直接将创建的新结点给*phead。
void slist_pushback(slistnode** pphead, slistdatatype x)
{
slistnode* newnode= slist_buy(x);//需要一个变量接受返回的参数
if (*pphead == NULL)
{
*pphead = newnode;
}//如何解决空结点的问题?
else {
slistnode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}//找尾巴
tail->next = newnode;//找到尾结点之后把新创建结点的位置给给它
//需要注意的是,如果第一个结点是个空结点 的话,那么无法尾插数据
}
}//尾插
5 如何在链表的头部删除一个数据
①如果是空链表不能删除
②如果只有一个结点的话,也可以正常处理。
③如果是多个结点的话进行常规的操作。phead指向下一个结点的位置,之后将原先的结点释放掉 。
void slist_popfront(slistnode** pphead)//头删
{assert(*pphead);
slistnode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
6 如何在链表的尾部删除一个数据
①如果是空链表无法删除
②如果只有一个结点的话,会造成空指针的问题,单独考虑
③如果多个结点正常操作
需要先找到尾巴和尾巴前面的一个结点。把尾巴释放掉的时候,把前一个结点的指针设置成NULL。
void slist_popback(slistnode** pphead)
{
assert(*pphead);//不能为空链表
if ((*pphead)->next == NULL)//如果只有一个结点,cur会成为空指针
{
free(*pphead);
*pphead = NULL;
}//为什么不能删除最后一个结点??
//else
//{
// slistnode* cur = NULL;
// slistnode* tail = *pphead;
// while (tail->next != NULL)
// {
// cur = tail;//找到尾删结点的上一个结点指针,并且把它设置成null
// tail = tail->next;
// }//找尾巴
// free(tail);
// cur->next = NULL;
/*}*/
else
{
slistnode* tail = *pphead;
while(tail->next->next!=NULL)
{
tail = tail->next;//找倒数第二个
}
free(tail->next);
tail->next = NULL;
}
}
需要注意的是,由于插入的时候必定要创造新的结点,因此可以将他们封装成函数,进行复用
slistnode* slist_buy(slistdatatype x)//增加一个结点 返回slistnode*类型的参数,要制定增加的内容
{
slistnode* node = (slistnode*)malloc(sizeof(slistnode));
if (node == NULL)
{
printf("fail to malloc");
}
node->data = x;
node->next = NULL;
return node;
}//创建结点