1. 链表的概念及结构
概念:链表是一种
物理存储结构上非连续
、非顺序的存储结构,数据元素的
逻辑顺序是通过链表
中的
指针链接
次序实现的 。
单向链表结构图
注意:
1.从上可以发现,链式结构在逻辑上是连续的,但是在物理上不一定连续。
2.申请的节点空间都是从堆上开辟的。
3.从堆上申请的空间是按照一定策略分配的,两次申请的空间可能连续也可能不连续。
1.1链表的设计
注:
1.将int 重命名为为STLDataType 是为了修改储存类型时更为轻松,增加了兼容性。
2.next为链表下一个节点的指针,方便寻找到下一个节点。
2. 链表的分类
实际中链表的结构非常多样,以下情况组合起来就有
8
种链表结构:
1. 单向或者双向
2. 带头或者不带头
3. 循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1.
无头单向非循环链表:
结构简单
,一般不会单独用来存数据。实际中更多是作为
其他数据结
构的子结构,如哈希桶、图的邻接表等等。
2.
带头双向循环链表:
结构最复杂
,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带
来很多优势,另一篇文章介绍。
3.3
链表的实现
// 动态申请一个节点并初始化节点
SListNode* BuySListNode(SLTDateType x)
{
//和顺序对比则更加节省空间
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)
{
perror("BuySListNode Error");
assert(0);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
// 单链表打印
void SListPrint(SListNode* plist)
{
SListNode* pf = plist;
while (pf)
{
printf("%d-> ", pf->data);
pf = pf->next;
}
printf("NULL");
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
//注意设计时传过来的是二级指针,当链表为空时修改函数外的指针时需要用二级指针。
//修改结构体内部指针时只需一级指针。
{
SListNode *newnode = BuySListNode(x);
SListNode* end = *pplist;
if (end == NULL)
{
*pplist = newnode;
}
else
{
while (end->next != NULL) //判断条件为当前节点的下一个节点不为空。
{
end = end->next;
}
end->next = newnode; //如果循环结束则在末尾的节点连接新的节点。
}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
SListNode* newnode = BuySListNode(x);
SListNode* onenode = *pplist;
*pplist = newnode;
newnode->next = onenode;
}
// 单链表的尾删
void SListPopBack(SListNode** pphead)
{
assert(*pphead != NULL); //防止传来空链表。
SListNode* upend = NULL;
SListNode* end = *pphead;
while (end->next != NULL) //寻找到尾节点和尾节点上一个。
{
upend = end;
end = end->next;
}
if (upend == NULL) //如果只有一个节点时需要用到设计时传过来的头指针的二级指针。
{
*pphead = NULL;
free(end);
return;
}
upend->next = NULL;
free(end);
}
// 单链表头删
void SListPopFront(SListNode** pphead)
{
assert(*pphead != NULL); //注意修改时要使用二级指针修改头指针
SListNode* head = *pphead;
SListNode* headnextnode = head->next;
free(head);
*pphead = headnextnode;
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
SListNode* pfind = plist;
while (pfind)
{
if (pfind->data == x)
{
return pfind;
}
pfind = pfind->next;
}
return NULL;
}
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos );
SListNode* newnode = BuySListNode(x);
if (pos->next == NULL)
{
pos->next = newnode;
return;
}
newnode->next = pos->next;
pos->next = newnode;
}
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
// 无法找到pos前一位置的节点连接pos后一节点
void SListEraseAfter(SListNode* pos)
{
assert(pos != NULL );
//分为一个节点,删除最后一个节点,和在这之间这三种情况。
if(pos->next == NULL)
{
return;
}
if ((pos->next)->next != NULL)
{
//如何删除头节点?
SListNode* nextnode = (pos->next)->next;
free(pos->next);
pos->next = nextnode;
}
else if (pos->next->next == NULL)
{
free(pos->next);
pos->next = NULL;
}
}
// 在pos的前面插入
SListNode* SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x)
{
assert(pphead);
assert(pos);//如何处理pos为空且*pphead为空?
assert(*pphead);
SListNode* pfind = *pphead;
SListNode* paerv = NULL;
SListNode* newnode = BuySListNode(x);
while (pfind)
{
if (pfind == pos)
{
newnode->next = pos;
if (paerv != NULL) //如果第一个节点就是pos
{
paerv->next = newnode;
}
else
{
*pphead = newnode;
}
return *pphead;
}
paerv = pfind;
pfind = pfind->next;
}
return NULL;
}
//void SLTInsert(SLNode** pphead, SLNode* pos, STLDataType x)
//{
// assert(pphead);
// assert(pos);
// assert(*pphead);
// SLNode* prev = *pphead;
// if (pos == prev)
// {
// STLpushFront(pphead,x);
// }
// else
// {
// while (prev->next != pos)
// {
// prev = prev->next;
// }
// SLNode* newnode = Createcode(x);
// newnode->next = pos;
// prev->next = newnode;
// }
//}
//pos位置删除前面节点
void SLTErase(SListNode** pphead, SListNode* pos)
{
assert(pphead);
assert(pos);
assert(*pphead);
SListNode* prev = *pphead;
if (pos == prev) //如果第一个节点为删除对象。
{
SListPopFront(pphead);
}
else
{
while ((prev->next)->next != pos) //prev后面第二个节点为pos
{
prev = prev->next;
}
free(prev->next);
prev->next = pos;
}
}
void SLTDestroy(SListNode** pphead)
{
SListNode* slow = *pphead;
SListNode* fast = *pphead;
while (fast != NULL)
{
fast = fast->next;
free(slow);
slow = fast;
}
*pphead = NULL;
}
3.顺序表和链表的区别
不同点
|
顺序表
|
链表
|
存储空间上
|
物理上一定连续
|
逻辑上连续,但物理上不一定
连续
|
随机访问
|
支持
O(1)
|
不支持:
O(N)
|
任意位置插入或者删除
元素
|
可能需要搬移元素,效率低O(N)
|
只需修改指针指向
|
插入
|
动态顺序表,空间不够时需要
扩容
|
没有容量的概念
|
应用场景
|
元素高效存储
+
频繁访问
|
任意位置插入和删除频繁
|
缓存利用率
|
高
|
低
|
与程序员相关的CPU缓存知识 | 酷 壳 - CoolShellhttps://coolshell.cn/articles/20793.html