目录
?双向链表介绍
双向链表的结点:指针域1+数据域+指针域2
?指针域1:指向该结点前面一个结点
?指针域2:指向该结点后面一个结点

结构体表示结点:
typedef struct DulNode { struct DulNode *prev; struct DulNode *next; ElemType data; }DulNode;
带头循环双向链表的头结点:当链表为空时,头结点的指针域1指向自身,头结点的指针域2指向自身。
注意?:头结点的数据域不储存数据,在初始化时可以随机给一个数,但是这个数不具有任何作用。
➖一条简单的带头双向循环链表
⚙物理结构:
⚙逻辑结构:
?双向链表的基本操作及图解分析
?1.双向链表创建新的结点
DulNode* BuyNewNode(ElemType data) { DulNode* newNode = (DulNode*)malloc(sizeof(DulNode)); //结点初始化 newNode->data = data; newNode->prev = NULL; newNode->next = NULL; return newNode; }
?2.双向链表初始化
初始化:创建一个头结点,使得头结点的指针域指向自己,并返回头结点的地址。
DulNode* InitList() { DulNode* newNode = BuyNewNode(0); //让该结点的指针域指向自己,作为头结点 newNode->next = newNode; newNode->prev = newNode; return newNode; }
?3.双向链表的尾插

?Step1:创建新结点,找到尾结点并保存
?Step2:将尾结点与新结点连接

?Step3:将头结点与新结点连接

void ListPushBack(DulNode* phead, ElemType data) { assert(phead); DulNode* newNode = BuyNewNode(data);//创建新结点 DulNode* tail = phead->prev;//找到尾结点 //尾结点与新结点连接 tail->next = newNode; newNode->prev = tail; //头结点与新结点连接 newNode->next = phead; phead->prev = newNode; }
?4.双向链表的头插

?Step1:找到第一个结点。
?Step2:将第一个结点与新结点连接。

?Step3:将新结点与头结点连接

void ListPushFront(DulNode* phead, ElemType data) { assert(phead); DulNode* newNode = BuyNewNode(data); DulNode* first = phead->next;//找到第一个结点 //新结点与第一个结点连接 first->prev = newNode; newNode->next = first; //新结点与头结点连接 newNode->prev = phead; phead->next = newNode; }
?5.双向链表的头删
?Step1:找到第一个结点和第二个结点

?Step2:将第二个结点与头结点连接

?Step3:释放第一个结点
注意?:当链表为空时,使用该操作会将链表销毁,所以当链表为空时使用assert()打印错误信息。
void ListPopFront(DulNode* phead) { assert(phead); assert(phead->next != phead);//当链表为空时 DulNode* first = phead->next;//记录第一个结点 DulNode* second = first->next;//记录第二个结点 //将头结点与第二个结点连接 second->prev=phead; phead->next=second; //释放第一个结点 free(first); first = NULL; }
?6.双向链表的尾删
?Step1:找到最后一个结点和倒数第二个结点

?Step2:将倒数第二个结点与头结点连接

?Step2:将最后一个结点释放
注意?:当链表为空时,使用该操作会将链表销毁,所以当链表为空时使用assert()打印错误信息。
void ListPopBack(DulNode* phead) { assert(phead); assert(phead->next != phead); DulNode* end = phead->prev;//记录尾结点 DulNode* prev = end->prev;//记录倒数第二个结点 //将头结点与倒数第二个结点 prev->next = phead;//尾结点的前一个结点后继尾头结点 phead->prev = prev;//头结点的前驱为尾结点的前一个结点 free(end); end = NULL; }
?7.查找某个结点并返回该节点地址
DulNode* ListFind(DulNode* phead, ElemType data) { DulNode* cur = phead->next;//定义一个遍历链表的指针 while (cur !=phead) { if (cur->data == data) { return cur; } cur = cur->next; } return NULL; }
?8.在某结点前插入一个新的结点
?Step1:找到某结点的前一个结点

?Step2:将新结点与某结点连接

?Step3:将新结点与某结点的前一个结点连接

void ListInsert(DulNode* pos, ElemType data) { assert(pos); DulNode* newNode = BuyNewNode(data); DulNode* prev = pos->prev;//记录插入位置的前一个结点 //新结点和pos结点连接 newNode->next = pos; pos->prev = newNode; //pos前一个结点和新结点连接 newNode->prev = prev; prev->next = newNode; }
?9.删除某个结点
?Step1:找到某结点的前一个结点和后一个结点

?Step2:将前一个结点和后一个结点连接

?Step3:释放删除结点
void ListEarst(DulNode* pos) { assert(pos); DulNode* prev = pos->prev; DulNode* next = pos->next; //将删除结点的前后结点进行连接 prev->next = next; next->prev = prev; free(pos); pos = NULL; }
?10.销毁双向链表
void ListErase(DulNode *phead) { DulNode *cur=phead->next; while(cur != phead) { DulNode *next=cur->next;//先找到下一个结点 free(cur);//释放该结点 cur=next; } }
总结
链表包括八种
?:单向带头循环链表
?:单向带头非循环链表
?:单向不带头循环链表
?:单向不带头非循环链表
?:双向带头循环链表
?:双向带头非循环链表
?:双向不带头循环链表
?:双向不带头非循环链表
?单向不带头非循环链表 一般不会单独用来存数据。实际中更多是作为其他数据结 构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多
?双向带头循环链表:
结构最复杂
,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,
能力有限,有错误之处还请能够指出。谢谢大家的观看。
最后祝大家:岁岁平安,虎虎生威。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/222658.html原文链接:https://javaforall.net
