双向链表(Doubly Linked List)
定义:
每个节点包含三个部分:
- 数据域。
- 前驱指针域(指向前一个节点)。
- 后继指针域(指向下一个节点)。
支持从任意节点向前或向后遍历。
#define datatype int
typedef struct link{datatype data;struct link* next; //该指针保存的后继结点的地址struct link* prev; //该指针保存的前驱结点的地址
}link_t;
双向链表的特点:
- 双向遍历更灵活,插入和删除操作更高效。
- 占用更多内存(每个节点需存储两个指针)。
1> 初始化link_init
void link_init(link_t **p)
{//申请堆 *p = (link_t *)malloc(sizeof(link_t));if(NULL == p){perror("malloc");return;}(*p)->prev = NULL;(*p)->next = NULL;
}
2> 创建结点create_node
static link_t *create_node(datatype d)
{//1>向堆空间申请link_t *p = (link_t *)malloc(sizeof(link_t));if(NULL == p){perror("malloc");return NULL;}//2>赋值 p->data = d;p->next = NULL; p->prev = NULL;return p;
}
3> 插入函数insert_behind
//将一个结点(a)插到另一个结点(b)的后面
static void insert_behind(link_t *a,link_t *b)
{//遵循先连后断 a->next = b->next; a->prev = b;if(b->next != NULL)b->next->prev = a;b->next = a;
}
4> 头插函数link_insert_head
void link_insert_head(link_t *p,datatype data)
{//创建结点link_t * node = create_node(data);if(NULL == node){perror("create_node");return;}//将node插到p后面insert_behind(node,p);
}
5> 尾插函数link_insert_tail
void link_insert_tail(link_t *p,datatype data)
{//创建结点link_t * node = create_node(data);if(NULL == node){perror("create_node");return;}//遍历找到尾结点while(p->next != NULL)p = p->next;//将node插到尾结点p后面insert_behind(node,p);
}
6> 正序遍历display
void display(link_t *p)
{printf("正序遍历结果为:\n");while(p->next != NULL){p = p->next; printf("%d ",p->data);}printf("\n");
}
7> 逆序遍历dispaly_reverse
void dispaly_reverse(link_t *p)
{printf("逆序遍历结果为:\n");while(p->next != NULL){p = p->next; }//往前遍历while(p->prev != NULL){printf("%d ",p->data);p = p->prev;}printf("\n");
}
8> 删除函数link_del
void link_del(link_t *p,datatype data)
{link_t *node =NULL;//遍历 while(p->next != NULL){//数据对比if(p->next->data == data){//使要删除的结点的前后结点建立联系node = p->next; p->next = node->next;if(p->next != NULL)p->next->prev = p;//释放结点node->next = NULL;node->prev = NULL;free(node);continue;} p = p->next; }
}
9> 替换函数link_replace
void link_replace(link_t *p,datatype old,datatype new)
{link_t *new_node = NULL;link_t *old_node = NULL;//遍历 while(p->next != NULL) {if(p->next->data == old){new_node = create_node(new);if(NULL == new_node){perror("create_node");return;}//替换 old_node = p->next;new_node->prev = p;new_node->next = old_node->next;if(old_node->next != NULL)old_node->next->prev = new_node;p->next = new_node;//释放old_node->next = NULL;old_node->prev = NULL;free(old_node);continue;}p = p->next; //没找到对于数据才会移动}
}
双向循环链表(Doubly Circular Linked List)
定义:
- 双向链表的变体,首尾相连,形成循环。
- 每个节点的前驱指针指向前一个节点,后继指针指向下一个节点。
双向循环链表的特点:
- 支持双向循环遍历。
- 节点链接更紧密,占用更多内存。
代码包含链表的创建、节点插入、节点删除、以及正向和反向遍历、打印等功能:
1> 初始化link_init
void link_init(link_t **p)
{//申请堆 *p = (link_t *)malloc(sizeof(link_t));if(NULL == p){perror("malloc");return;}/*修改处*/ //自己指向自己(*p)->prev = (*p);(*p)->next = (*p);
}
2> 创建结点create_node
static link_t *create_node(datatype d)
{//1>向堆空间申请link_t *p = (link_t *)malloc(sizeof(link_t));if(NULL == p){perror("malloc");return NULL;}//2>赋值 /*修改处*/ p->data = d;p->next = p; p->prev = p;return p;
}
3> 插入函数insert_behind
//将一个结点(a)插到另一个结点(b)的后面
static void insert_behind(link_t *a,link_t *b)
{//遵循先连后断 /*修改处*/a->next = b->next; a->prev = b;b->next->prev = a;b->next = a;
}
4> 头插函数link_insert_head
void link_insert_head(link_t *p,datatype data)
{//创建结点link_t * node = create_node(data);if(NULL == node){perror("create_node");return;}//将node插到p后面insert_behind(node,p);
}
5> 尾插函数link_insert_tail
void link_insert_tail(link_t *p,datatype data)
{//创建结点link_t * node = create_node(data);if(NULL == node){perror("create_node");return;}//将node插到头结点前面 /*修改处*/insert_forward(node,p);
}
5.5> 插入到头前函数insert_forward
static void insert_forward(link_t *node,link_t *p)
{//先连后断 //尾结点地址用头节点去表示node->next = p;node->prev = p->prev;p->prev->next = node;p->prev = node;
}
6> 正序遍历display
void display(link_t *p)
{/*修改处*/link_t *head = p;printf("正序遍历结果为:\n");while(p->next != head) /*修改处*/{p = p->next; printf("%d ",p->data);}printf("\n");
}
7> 逆序遍历dispaly_reverse
void dispaly_reverse(link_t *p)
{/*修改处*/link_t *head = p;printf("逆序遍历结果为:\n");//往前遍历 /*修改处*/while(p->prev != head){p = p->prev;printf("%d ",p->data);}printf("\n");
}
8> 删除函数link_del
void link_del(link_t *p,datatype data)
{/*修改处*/link_t *head =p;link_t *node =NULL;//遍历 while(p->next != head){//数据对比if(p->next->data == data){//使要删除的结点的前后结点建立联系 /*修改处*/node = p->next; p->next = node->next;p->next->prev = p;//释放结点 /*修改处*/node->next = node;node->prev = node;free(node);continue;} p = p->next; }
}
9> 替换函数link_replace
void link_replace(link_t *p,datatype old,datatype new)
{/*修改处*/link_t *head = p;link_t *new_node = NULL;link_t *old_node = NULL;//遍历 while(p->next != head) /*修改处*/{if(p->next->data == old){new_node = create_node(new);if(NULL == new_node){perror("create_node");return;}//替换 /*修改处*/old_node = p->next;new_node->prev = p;new_node->next = old_node->next;old_node->next->prev = new_node;p->next = new_node;//释放 /*修改处*/old_node->next = old_node;old_node->prev = old_node;free(old_node);continue;}p = p->next; //没找到对于数据才会移动}
}
一段完整的代码片
#include <stdio.h>
#include <stdlib.h>// 定义节点结构
typedef struct Node {int data; // 数据域struct Node* next; // 指向下一个节点struct Node* prev; // 指向前一个节点
} Node;// 创建一个新节点
Node* createNode(int data) {Node* newNode = (Node*)malloc(sizeof(Node));newNode->data = data;newNode->next = NULL;newNode->prev = NULL;return newNode;
}// 插入节点到链表末尾
void append(Node** head, int data) {Node* newNode = createNode(data);// 如果链表为空if (*head == NULL) {*head = newNode;newNode->next = newNode; // 自己指向自己(形成循环)newNode->prev = newNode;return;}// 查找到链表的最后一个节点Node* tail = (*head)->prev;// 更新新节点的指针newNode->next = *head; // 新节点的 next 指向头节点newNode->prev = tail; // 新节点的 prev 指向尾节点// 更新头节点和尾节点的指针tail->next = newNode; // 尾节点的 next 指向新节点(*head)->prev = newNode; // 头节点的 prev 指向新节点
}// 删除链表中的指定节点
void deleteNode(Node** head, int key) {if (*head == NULL) return; // 链表为空,直接返回Node* current = *head; // 从头节点开始查找Node* temp = NULL;// 遍历链表找到值为 key 的节点do {if (current->data == key) {temp = current;break;}current = current->next;} while (current != *head); // 回到头节点时终止循环if (temp == NULL) {printf("节点 %d 未找到。\n", key);return;}// 如果链表中只有一个节点if (temp->next == temp && temp->prev == temp) {*head = NULL; // 删除唯一节点后链表为空free(temp);return;}// 更新前驱和后继节点的指针temp->prev->next = temp->next;temp->next->prev = temp->prev;// 如果删除的是头节点,更新头指针if (temp == *head) {*head = temp->next;}free(temp); // 释放删除的节点
}// 正向遍历链表
void printListForward(Node* head) {if (head == NULL) {printf("链表为空。\n");return;}Node* temp = head;printf("正向遍历链表:");do {printf("%d -> ", temp->data);temp = temp->next;} while (temp != head); // 回到头节点时终止printf("(head)\n");
}// 反向遍历链表
void printListBackward(Node* head) {if (head == NULL) {printf("链表为空。\n");return;}Node* tail = head->prev; // 从尾节点开始Node* temp = tail;printf("反向遍历链表:");do {printf("%d -> ", temp->data);temp = temp->prev;} while (temp != tail); // 回到尾节点时终止printf("(tail)\n");
}// 主函数
int main() {Node* head = NULL; // 初始化空链表// 添加节点append(&head, 10);append(&head, 20);append(&head, 30);append(&head, 40);// 打印链表printListForward(head);printListBackward(head);// 删除节点printf("删除节点 20:\n");deleteNode(&head, 20);printListForward(head);printListBackward(head);// 删除节点 10(头节点)printf("删除节点 10:\n");deleteNode(&head, 10);printListForward(head);printListBackward(head);return 0;
}
完整代码片 分析:
- 结构定义:
- 每个节点结构包含:
- data:存储节点数据。
- next:指向下一个节点。
- prev:指向前一个节点。
- 每个节点结构包含:
- append 函数:
- 用于在链表末尾插入新节点。
- 维护双向循环链表的特性:更新新节点、头节点和尾节点的指针。
- deleteNode 函数:
- 在链表中查找值等于key的节点并删除。
- 特别处理了链表为空、只有一个节点、删除头节点等特殊情况。
- 遍历函数:
- printListForward:正向遍历,从头节点出发,依次打印每个节点。
- printListBackward:反向遍历,从尾节点出发,依次打印每个节点。
- 主函数测试:
- 插入多个节点。
- 正向和反向打印链表。
- 删除指定节点,并再次验证。
双向循环链表运行结果示例:
正向遍历链表:10 -> 20 -> 30 -> 40 -> (head)
反向遍历链表:40 -> 30 -> 20 -> 10 -> (tail)删除节点 20:
正向遍历链表:10 -> 30 -> 40 -> (head)
反向遍历链表:40 -> 30 -> 10 -> (tail)删除节点 10:
正向遍历链表:30 -> 40 -> (head)
反向遍历链表:40 -> 30 -> (tail)
双向循环链表提供了灵活的正向和反向遍历功能,同时保持循环特性,适合需要频繁插入、删除并支持循环操作的场景。代码实现中考虑了各种边界条件(如链表为空、只有一个节点等),确保程序安全性。
综上。希望该内容能对你有帮助,感谢!
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!