前言
1.无头单向非循环链表: 结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构如哈希桶、图的邻接表等等。
2.带头双向循环链表: 结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势。这也是我们今天要了解的链表。
顺序表和链表的区别:
不同点 | 顺序表 | 链表 |
---|---|---|
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持O(1) | 不支持:O(N) |
任意位置插入或者删除元素 | 可能需要搬移元素,效率低O(N) | 只需修改指针指向 |
插入 | 动态顺序表,空间不够时需要扩容 | 没有容量概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
缓存利用率 | 高 | 低 |
头结点head
1.记录下一个结点的地址
2.记录上一个结点的地址
3.不作为数据的存储
双向链表
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;//下一个结点
struct ListNode* prev;//上一个结点
LTDataType data;//数据
}LTNode;
函数
初始化
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
return NULL;
}
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
LTNode* LTInit()
{
LTNode* phead = BuyListNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
带头双向循环链表的初始状态应该为:
循环的体现:
1、尾next指向哨兵位的头
2、哨兵位的头的prev指向尾
尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
LTNode* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
尾删
尾删之前我们需要判断链表是否为空:
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;//如果相等则返回ture(链表为空)
}
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
tailPrev->next = phead;
phead->prev = tailPrev;
free(tail);
tail = NULL;
}
打印
void LTPrint(LTNode* phead)
{
assert(phead);
printf("<=head=>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
int main()
{
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPushBack(plist, 5);
LTPrint(plist);
LTPopBack(plist);
LTPopBack(plist);
LTPopBack(plist);
LTPrint(plist);
return 0;
}
运行效果:
头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
newnode->next = phead->next;//1
phead->next->prev = newnode;//2
phead->next = newnode;//3
newnode->prev = phead;//4
}
查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
//注:要从哨兵位的后一个开始走,当结点循环到头指针时,结束循环。
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
//如果找到返回结点地址
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
//找不到则返回空
return NULL;
}
在指定位置前插入
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
//与尾插思路类似
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
尾插和头插可以复用以上函数:
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead, x);
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead->next, x);
}
对指定位置删除
删除需配合查找函数使用
//删除
void LTErase(LTNode* pos)
{
assert(pos);
//找到前一个
LTNode* p = pos->prev;
//找到后一个
LTNode* n = pos->next;
//前一个的next指向后一个
p->next = n;
//后一个的prev指向前一个
n->prev = p;
free(pos);
}
int main()
{
LTNode* plist = LTInit();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPushBack(plist, 5);
LTPrint(plist);
LTNode* pos = LTFind(plist, 3);
if (pos != NULL)
{
LTErase(pos);
pos = NULL;
}
LTPrint(plist);
return 0;
}
运行效果:
头删和尾删也可以复用LTErase函数:
//头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTErase(phead->next);
}
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTErase(phead->prev);
}
链表的释放
void LTDestroy(LTNode* phead)
{
assert(phead);
//先从哨兵位的后一个开始释放,最后再释放哨兵位
LTNode* cur = phead->next;
while (cur != phead)
{
//保存下一个
LTNode* next = cur->next;
//释放
free(cur);
cur = next;
}
//最后释放哨兵位
free(phead);
}
以上就是本篇文章的全部内容了,希望大家看完能有所收获。