gitee代码提交–带头双向循环链表
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
因为是双向链表,所以结点应该包含prev指针指向前结点,next指向后结点
LTNode* ListInit()
{
LTNode* guard = malloc(sizeof(LTNode));
if (guard == NULL)
{
perror("malloc fail");
exit(-1);
}
guard->next = guard;
guard->prev = guard;
return guard;
}
将头结点(guard)初始化,使头结点的prev和next指针指向自身
初始化接口也可以设计成 void ListInit(LTNode** pphead);
的形式
(因为ListInit 函数改变了链表指向,要修改链表指向要传二级指针的形式)
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
LTNode* tail = phead->prev;
newnode->prev = tail;
tail->next = newnode;
phead->prev = newnode;
newnode->next = phead;
}
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = BuyListNode(x);
LTNode* first = phead->next;
newnode->next = first;
first->prev = newnode;
newnode->prev = phead;
phead->next = newnode;
}
这样实现接口就不需要考虑先后顺序
// 先链接newnode 和 phead->next节点之间的关系
/*LTNode* newnode = BuyListNode(x);
newnode->next = phead->next;
phead->next->prev = newnode;
phead->next = newnode;
newnode->prev = phead;*/
如果不创建 first 结构体指针的话,需要首先链接newnode和phead->next 结点之间的关系
不然等phead->next 变成了 newnode 就无法找到 phead->next->prev
遍历链表,打印出结点对应的data域数据
但是链表为循环链表,判断条件要控制妥当,不然可能出现死循环的情况
cur 结点从phead->next 开始遍历,若cur == phead 说明遍历结束
void LTPrint(LTNode* phead)
{
assert(phead);
printf("phead<=>");
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d<=>", cur->data);
cur = cur->next;
}
printf("\n");
}
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
若phead->next == phead 则链表为空
在尾删和头删的接口中要加入assert断言,判断当前链表是否为空
assert(!LTEmpty(phead));
若phead->next == phead 说明链表中只有phead(guard) 哨兵位结点,无法进行删除操作 – 报错
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* tail = phead->prev;
LTNode* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
free(tail);
tail = NULL;
}
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* first = phead->next;
LTNode* second = first->next;
second->prev = phead;
phead->next = second;
free(first);
first = NULL;
}
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;
}
根据所要查找的LTDataType的值遍历一遍链表找到结点,返回类型为LTNode*
size_t ListSize(LTNode* phead)
{
assert(phead);
size_t n = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
++n;
cur = cur->next;
}
return n;
}
遍历链表,++n即可
void ListInsert(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 ListErase(LTNode* pos)
{
assert(pos);
LTNode* next = pos->next;
LTNode* prev = pos->prev;
next->prev = prev;
prev->next = next;
free(pos);
pos = NULL;
}
void ListDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
//phead = NULL;
}
cur遍历链表,用next存储cur的next(下一结点),最后再释放掉哨兵位结点phead
在接口中写的phead = NULL不起作用
要改变结构体指针所指向的内容需要传二级指针的方式void ListDestroy(LTNode** pphead)
或者拿返回值接收的方式 (LTNode* LIstDestroy(LTNode* phead))
这里选择不改变结构体指针,让调用ListDestory的人置空NULL (主要是为了保持接口一致性)
而且销毁接口调用完,该项目也就完成了,没必要复杂化
到这里,整个带头双向循环链表的代码就实现完成了!!!✿✿ヽ(°▽°)ノ✿
那代码部分还存在什么问题吗?
既然可以在任意位置进行插入删除,那么尾插尾删头插头删的接口就可以进行优化
void LTPushBack(LTNode* phead, LTDataType x)
{
ListInsert(phead, x);
}
phead->prev 即是尾结点,在phead 前插入就是尾插
void LTPushFront(LTNode* phead, LTDataType x)
{
ListInsert(phead->next, x);
}
phead->next 就是头结点,在phead->next 前插入就是头插
void LTPopBack(LTNode* phead)
{
ListErase(phead->prev);
}
void LTPopFront(LTNode* phead)
{
ListErase(phead->next);
}
其实带头双向循环链表的实现也就是主要实现任意位置的插入删除即可,至于尾插尾删头插头删这类接口只是方便学习理解
要想了解cpu高速缓存命中率,首先就需要了解存储器的相关概念:
再拓展一点:
链表遍历时,将结点附近的数据都加载进高速缓存(而高速缓存空间小,如果空间不够会将原来的数据移出缓存区)
那么大量无效数据进入缓存会造成缓存污染