双向链表(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)
双向循环链表提供了灵活的正向和反向遍历功能,同时保持循环特性,适合需要频繁插入、删除并支持循环操作的场景。代码实现中考虑了各种边界条件(如链表为空、只有一个节点等),确保程序安全性。
综上。希望该内容能对你有帮助,感谢!
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!