目录

一、双向链表的实现

二、顺序表和带头双向循环链表的区别


愿你熬过万丈孤苦,藏下星辰大海。


一、双向链表的实现

带头、双向、循环

头部的prev指向尾部,

List.h

#pragma once#include #include #include typedef int LTDataType;typedef struct ListNode{LTDataType data;struct ListNode* next;struct ListNode* prev;}LTNode;void ListPrint(LTNode* phead);//void ListInit(LTNode** pplist);//初始化,二级指针LTNode* ListInit(LTNode* phead);void ListPushBack(LTNode* phead, LTDataType x);//尾插void ListPopBack(LTNode* phead);//尾删void ListPushFront(LTNode* phead, LTDataType x);void ListPopFront(LTNode* phead);LTNode* ListFind(LTNode* phead, LTDataType x);//pos之前插入void ListInsert(LTNode* pos, LTDataType x);//删除pos位置的节点void ListErase(LTNode* pos);void ListDestory(LTNode* phead);

List.c

#define _CRT_SECURE_NO_WARNINGS 1#include "List.h"LTNode* BuyLTNode(LTDataType x){LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){printf("malloc fail\n");exit(-1);}newnode->prev = NULL;newnode->data = x;newnode->next = NULL;}初始化,二级指针的思路;//void ListInit(LTNode** pphead)//初始化,next指向自己,prev指向自己,才算是循环,头的地址还不清楚//初始化个哨兵位//{//assert(pphead);//*pphead = BuyLTNode(0);//(*pphead)->prev = *pphead;//(*pphead)->next = *pphead;//}//初始化,一级指针的思路,返回头即可,既可以改变头的地址,又可以传一级指针LTNode* ListInit(LTNode* phead){phead = BuyLTNode(0);phead->prev = phead;phead->next = phead;return phead;}//打印void ListPrint(LTNode* phead){//从head的next = cur->data开始打印,当cur->next = head结束assert(phead);LTNode* cur = phead->next;while (cur != phead){printf("%d ", cur->data);cur = cur->next;}printf("\n");}void ListPushBack(LTNode* phead, LTDataType x){assert(phead);//带头ListInsert(phead, x);/*LTNode* tail = phead->prev;LTNode* newnode = BuyLTNode(x);tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;*/}//尾删,尾删到没有节点,也不用处理void ListPopBack(LTNode* phead){assert(phead);//链表为空assert(phead->next != phead);/*LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;phead->prev = tailPrev;tailPrev->next = phead;free(tail);tail = NULL;*/ListErase(phead->prev);}//头插void ListPushFront(LTNode* phead, LTDataType x){assert(phead);ListInsert(phead->next, x);/*LTNode* newnode = BuyLTNode(x);LTNode* next = phead->next;next->prev = newnode;newnode->next = next;phead->next = newnode;newnode->prev = phead;*/}//头删void ListPopFront(LTNode* phead){assert(phead);ListErase(phead->next);/*assert(phead->next != phead);LTNode* head = phead->next;LTNode* next = head->next;phead->next = next;next->prev = phead;free(head);head = NULL;*/}//查找LTNode* ListFind(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 ListInsert(LTNode* pos, LTDataType x){assert(pos);LTNode* newnode = BuyLTNode(x);LTNode* prev = pos->prev;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;}//删除void ListErase(LTNode* pos)//在主函数中,pos的实参要置空,否则实参就会成为野指针{assert(pos);LTNode* next = pos->next;LTNode* prev = pos->prev;prev->next = next;next->prev = prev;free(pos);pos = NULL;}//传一级指针是为了保持接口一致性void ListDestory(LTNode* phead)//注意,phead的实参要进行free,否则会导致野指针{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = phead->next;free(cur);cur = next;}free(phead);phead = NULL;}

(1)首先进行哨兵位的初始化,哨兵位进行初始化之后,地址就不会再发生变化,所以在进行头插、尾插时,不需要传二级指针,仅仅需要哨兵位即可【尾插、头插也是在哨兵位之后】

(2)改变的是结构体的地址,改变的是指针的内容,改变指针的内容,需要传递指针的地址。

二、顺序表和带头双向循环链表的区别

这两个结构是相辅相成的结构,

顺序表优点:(1)物理空间是连续的,方便用下标随机访问。【二分查找、排序】

(2)CPU高速缓存命中率会更高。

缺点:(1)由于需要物理空间连续,空间不够需要扩容。扩容本身有一定的消耗,其次扩容机制还存在一定的空间消耗。(2)头部或者中间的插入删除,需要挪动数据,效率低。O(N)

链表优点(1)按需申请释放空间(2)任意位置可以O(1)的插入删除数据

缺点:不支持下标的随机访问,有些算法不适合在他上面进行。如:二分查找、排序等

编译连接之后,生成可执行程序,CPU执行这个程序,CPU要去访问内存,CPU不会直接访问内存,CPU嫌弃内存速度太慢,会把数据加载到三级缓存或者寄存器。4或者8byte小数据放到寄存器,大数据放到缓存。CPU会看数据是否在缓存,在的话就命中,直接访问,不在的话,先把数据从内存加载到缓存,再访问。会缓存一段连续的空间,所以顺序表命中更高。