赞
踩
数据结构世界——单链表(Single Linked List)
让我们回顾一下顺序表:
空间浪费
。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。那么,如何解决以上问题呢?
链表,是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
在链表里,每节“车厢”是怎样的呢?
结点/节点
。图中指针变量 plist保存的是第一个节点的地址,我们称plist此时“指向”第一个节点,如果我们希望plist“指向”第二个节点时,只需要修改plist保存的内容为0x0012FFA0
。
为什么还需要指针变量来保存下一个节点的位置?
链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点。
//单链表
typedef int SLDataType;
typedef struct SListNode
{
SLDataType data;
struct SListNode* next;
}SLNode;
先来看看怎么实现链表的打印,这样更有助于我们理解它的结果。
void SLPrint(SLNode* phead)
{
SLNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
NULL
时,则打印当前节点的数据,并将该节点存储的下一节点的地址赋值给curNULL
)每次插入,要生成新节点,申请空间……一系列操作 ,所以我们可以把它该过程写成一个函数,以增强函数的复用性。
SLNode* BuySLNode(SLDataType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
malloc
动态开辟内存生成一个新节点,再将数据放入新节点中,初始地址为NULL
void SLPushFront(SLNode** pphead, SLDataType x)
{
assert(pphead);
SLNode* newnode = BuySLNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
可能很多人有疑惑,为什么要用二级指针
呢?请看接下来的测试:
与顺序表不同的是,我们不再创建结构体,而是创建结构体指针,初始指向NULL
。那么,我们要在函数内改变外部指针指向的地址,就要使用二级指针。
void SLPushBack(SLNode** pphead, SLDataType x) { assert(pphead); //1.空链表 //2.非空链表 SLNode* newnode = BuySLNode(x); if (*pphead == NULL) { *pphead = newnode; } else { SLNode* tail = *pphead; while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; } }
void SLPopFront(SLNode** pphead)
{
assert(pphead);
assert(*pphead);
SLNode* cur = *pphead;
*pphead = (*pphead)->next;
free(cur);
}
void SLPopBack(SLNode** pphead) { assert(pphead); assert(*pphead); //一个节点 //多个节点 if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } else { SLNode* tail = *pphead; while (tail->next->next) { tail = tail->next; } free(tail->next); tail->next = NULL; }
NULL
NULL
SLNode* SLFind(SLNode* phead, SLDataType x)
{
SLNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
这里说一下关于assert
断言的情况 ,并不是所有函数参数的指针都需要断言,而是要根据实际情况分析而定。
//打印
void SLPrint(SLNode* phead);
//查找
SLNode* SLFind(SLNode* phead, SLDataType x);
打印与查找,则不需要断言,因为空链表也可以打印,也可以查找,就比如你的银行账户没有钱,就不能显示出来看看,不能查询吗?
//头插
void SLPushFront(SLNode** pphead, SLDataType x);
//尾插
void SLPushBack(SLNode** pphead, SLDataType x);
头插与尾插,对于其二级指针需要断言,一级指针不用,因为pphead指向链表指针plist,所以不能为空,而链表指针可为空(即为空链表)。
//头删
void SLPopFront(SLNode** pphead);
//尾删
void SLPopBack(SLNode** pphead);
头删与尾删,其二级指针与一级指针都要断言,除了pphead不能为空,*pphead也不能为空,因为空链表就不能进行删除操作。
这里有两种插入方式,在pos前插入
和在pos后插入
。
在pos前插入
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x) { assert(pphead); assert(pos); if (pos == *pphead) { SLPushFront(pphead, x); } else { SLNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } SLNode* newnode = BuySLNode(x); newnode->next = pos; prev->next = newnode; } }
在pos后插入
void SLInsertAfter(SLNode* pos, SLDataType x)
{
assert(pos);
SLNode* newnode = BuySLNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
ps:链接的过程有顺序问题,不能写反了,要不然会环状链接。
这里也有两种删除方式,在pos删除
和在pos后删除
。
在pos删除
void SLErase(SLNode** pphead, SLNode* pos) { assert(pphead); assert(pos); if (pos == *pphead) { SLPopFront(pphead); } else { SLNode* prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); } }
在pos后删除
void SLEraseAfter(SLNode* pos)
{
assert(pos);
assert(pos->next);
SLNode* next = pos->next;
pos->next = pos->next->next;
free(next);
}
void SLDestroy(SLNode** pphead)
{
assert(pphead);
SLNode* cur = *pphead;
while (cur)
{
SLNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
当然,这里你也可以传一级指针
,不在函数内部把外部的链表指针置为NULL
,而在外部手动置空,跟free
函数的用法一样,实现半自动。
void SLDestroy(SLNode* phead)
{
SLNode* cur = phead;
while (cur)
{
SLNode* next = cur->next;
free(cur);
cur = next;
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。