心有半亩花田
藏于世俗人间
烟雨长虹,孤鹜齐飞的个人主页
个人专栏
前期回顾-单链表
期待小伙伴们的支持与关注!!!
目录
双向链表的介绍
双向链表的结构
双向链表的功能
为节点分配动态内存空间
创建双向链表的哨兵位
打印双向链表
双向链表的头插
双向链表的尾插
双向链表的头删
双向链表的尾删
双向链表的查找
双向链表的指定位置之后插入节点
双向链表的删除指定节点
双向链表的销毁
代码的整体实现
总结#
双向链表的介绍
双向链表:它的每个数据结点中都有两个指针,分别指向前驱和后驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后驱结点
双向链表的结构
双向链表的全称:带头双向循环链表
注意:这里的 带头 跟前⾯我们说的 头节点 是两个概念,实际前面的在单链表阶段称呼不严谨,但是为了大家更好的理解就直接称为单链表的头节点
带头链表里的头节点,实际为 哨兵位 ,哨兵位节点不存储任何有效元素,只是起个带头作用
哨兵位存在的意义: 遍历循环链表避免死循环
双向链表的结构定义:
typedef int ListType; //将数据类型重命名,防止以后换类型所带来的麻烦 typedef struct ListNode { ListType data; struct ListNode* next; //指向该节点的后驱指针 struct ListNode* prev; //指向该节点的前驱指针 }ListNode; //结构体类型的重命名,为了以后书写方便
跟我们讲到过的火车模拟差不多,现在是一个车厢存放了两把钥匙,即可以访问该车厢的前车厢也可以访问该车厢的后车厢
今天我将带大家去实现双向链表的 增删查改
为节点分配动态内存空间
后面我们要在单链表中进行头插和尾插,为了方便和减少代码的重复度,我们分装一个函数用来专门创建新结点
ListNode* ListMalloc(ListType x) { ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));//利用malloc开辟动态空间 assert(newNode); //判断空间是否开辟成功 newNode->data = x; //读入数据 newNode->next = newNode->prev = newNode; //该空间的后驱以及前驱指针都指向该节点形成一个环 return newNode; //这个节点空间已经创建好了返回出去 }
创建双向链表的哨兵位
我们设置了 -1 节点就是我们的带头节点也就是 哨兵位
ListNode* LTInit() { ListNode* head = ListMalloc(-1); return head; }
打印双向链表
我们写代码最好是写一点测一点,不要最后才调试,所以我们先把打印接口先规划好
void ListPrint(ListNode* head) { assert(head); //判断链表为不为空 ListNode* pcur = head->next; //定义一个指针指向哨兵位的下一个节点,也就是第一个节点 while (pcur != head) //因为双向链表是一个循环链表,尾节点的下一个就是哨兵位 { printf("%d->", pcur->data);//打印数据 pcur = pcur->next; //访问下一个节点数据 } printf("\n"); }
双向链表的头插
void ListPushFront(ListNode* head, ListType x) { assert(head); //判断链表为不为空 ListNode* newNode = ListMalloc(x);//开辟动态空间 newNode->next = head->next; //新节点的后驱指针指向第一个节点 newNode->prev = head; //新节点的前驱指针指向哨兵位 head->next->prev = newNode; //第一个节点的前驱指针指向新节点 head->next = newNode; //哨兵位的后驱指针指向新节点 }
双向链表的尾插
void ListPushBack(ListNode* head, ListType x) { assert(head); //判断链表是否为空 ListNode* newNode = ListMalloc(x);//开辟动态空间 newNode->next = head; //新节点的后驱指针指向哨兵位 newNode->prev = head->prev; //新节点的前驱指针指向尾节点 head->prev->next = newNode; //尾节点的后驱指针指向新节点 head->prev = newNode; //哨兵位的前驱节点指向新节点 }
双向链表的头删
void ListPopFront(ListNode* head) { assert(head); //判断链表是否为空 assert(head->next != head); //判断哨兵位的下一个节点为不为空 ListNode* pulic = head->next;//定义一个指针记录要删除首节点的位置 ListNode* next = pulic->next;//定义一个指针指向首节点的后驱节点 next->prev = head; //首节点的后驱节点也就是第二节点的前驱指针指向哨兵位 head->next = next; //哨兵位的后驱指针指向第二节点 free(pulic); //释放首节点 pulic = NULL; }
双向链表的尾删
void ListPopBack(ListNode* head) { assert(head); //判断链表是否为空 assert(head->next != head); //判断哨兵位的下一个节点是否为空 ListNode* pulic = head->prev; //定义一个指针指向尾节点 ListNode* prev = pulic->prev; //定义一个指针指向尾节点的前驱节点 prev->next = head; //尾节点的前驱节点的后驱指针指向哨兵位 head->prev = prev; //哨兵位的前驱指针指向尾节点的前驱节点 free(pulic); //释放尾节点 pulic = NULL; }
双向链表的查找
ListNode* ListFind(ListNode* head, ListType x) { assert(head); //判断链表是否为空 ListNode* pcur = head->next; //定义一个指针指向哨兵位 while (pcur != head) //遍历链表寻找指定数据 { if (pcur->data == x) { return pcur; //找到了返回当前指针 } pcur = pcur->next; } return NULL; //找不到返回NULL }
双向链表的指定位置之后插入节点
void ListInsert(ListNode* pos,ListType x) { assert(pos); //判断pod合不合法 ListNode* newnode = ListMalloc(x); newnode->next = pos->next; newnode->prev = pos; pos->next->prev = newnode; pos->next = newnode; }
双向链表的删除指定节点
void ListErase(ListNode* pos) { pos->next->prev = pos->prev;//指定元素的后驱节点的前驱指针指向指定节点的前驱节点 pos->prev->next = pos->next;//指定元素的前驱节点的后驱指针指向指定节点的后驱节点 free(pos); //释放指定元素的空间 pos = NULL; }
双向链表的销毁
void ListDesTroy(ListNode* head) { //哨兵位不能为空 assert(head); ListNode* pcur = head->next; while (pcur != head) { ListNode* next = pcur->next; free(pcur); pcur = next; } //链表中只有一个哨兵位 free(head); head = NULL; }
代码的整体实现
#include
#include #include typedef int ListType; typedef struct ListNode { ListType data; struct ListNode* next; struct ListNode* prev; }ListNode; ListNode* ListMalloc(ListType x) { ListNode* newNode = (ListNode*)malloc(sizeof(ListNode)); assert(newNode); newNode->data = x; newNode->next = newNode->prev = newNode; return newNode; } ListNode* LTInit() { ListNode* head = ListMalloc(-1); return head; } void ListPrint(ListNode* head) { assert(head); ListNode* pcur = head->next; while (pcur != head) { printf("%d->", pcur->data); pcur = pcur->next; } printf("\n"); } void ListPushBack(ListNode* head, ListType x) { assert(head); ListNode* newNode = ListMalloc(x); newNode->next = head; newNode->prev = head->prev; head->prev->next = newNode; head->prev = newNode; } void ListPushFront(ListNode* head, ListType x) { assert(head); ListNode* newNode = ListMalloc(x); newNode->next = head->next; newNode->prev = head; head->next->prev = newNode; head->next = newNode; } void ListPopBack(ListNode* head) { assert(head); assert(head->next != head); ListNode* pulic = head->prev; ListNode* prev = pulic->prev; prev->next = head; head->prev = prev; free(pulic); pulic = NULL; } void ListPopFront(ListNode* head) { assert(head); assert(head->next != head); ListNode* pulic = head->next; ListNode* next = pulic->next; next->prev = head; head->next = next; free(pulic); pulic = NULL; } ListNode* ListFind(ListNode* head, ListType x) { assert(head); ListNode* pcur = head->next; while (pcur != head) { if (pcur->data == x) { return pcur; } pcur = pcur->next; } return NULL; } void ListInsert(ListNode* pos,ListType x) { assert(pos); ListNode* newnode = ListMalloc(x); newnode->next = pos->next; newnode->prev = pos; pos->next->prev = newnode; pos->next = newnode; } void ListErase(ListNode* pos) { pos->next->prev = pos->prev; pos->prev->next = pos->next; free(pos); pos = NULL; } void ListDesTroy(ListNode* head) { assert(head); ListNode* pcur = head->next; while (pcur != head) { ListNode* next = pcur->next; free(pcur); pcur = next; } free(head); head = NULL; } void CeShi() { ListNode* plist = LTInit(); ListPushBack(plist, 1); ListPushBack(plist, 2); ListPushBack(plist, 3); ListPushBack(plist, 4); ListPushBack(plist, 5); ListPrint(plist); } int main() { CeShi(); system("pause"); return 0; }
总结#
其实双向链表和单向链表相比改变并不是很大,也只是多了一个指向前驱的指针而已,变化最大的也就是添加节点和删除节点,需要将前面的和后面的节点都进行连接