双向链表完整实现(C语言版)

双向链表完整实现(C语言版)

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

/*
 * 双向链表完整实现
 * 
 * 特点:
 * 1. 支持头插法、尾插法、指定位置插入
 * 2. 支持头节点删除、尾节点删除、指定节点删除
 * 3. 完善的错误处理和内存管理
 * 4. 包含详细的注释说明
 */

// 定义数据类型
typedef int TypData;

// 链表节点结构体
typedef struct DoubleListNode {
    TypData data;                // 数据域
    struct DoubleListNode *prev; // 指向前一个节点的指针
    struct DoubleListNode *next; // 指向下一个节点的指针
} DListNode;

/*
 * 创建头结点
 * 
 * 参数:无
 * 返回值:成功返回头节点指针,失败退出程序
 */
DListNode * CreHeadNode() {
    DListNode *Head = (DListNode *)calloc(1, sizeof(DListNode));
    if (NULL == Head) {
        perror("创建头节点失败");
        exit(EXIT_FAILURE);
    }
    Head->prev = NULL;  // 初始化为空链表
    Head->next = NULL;  // 初始化为空链表
    return Head;
}

/*
 * 创建新节点
 * 
 * 参数:data - 节点数据
 * 返回值:成功返回新节点指针,失败退出程序
 */
DListNode * CreaOneNode(TypData data) {
    DListNode *Node = (DListNode *)calloc(1, sizeof(DListNode));
    if (NULL == Node) {
        perror("创建节点失败");
        exit(EXIT_FAILURE);
    }
    Node->data = data;
    Node->prev = NULL;
    Node->next = NULL;
    return Node;
}

/*
 * 判断链表是否为空
 * 
 * 参数:List - 链表头节点
 * 返回值:true表示空链表,false表示非空
 */
bool IsEmpty(DListNode *List) {
    return (List->next == NULL);
}

/*
 * 头插法插入节点
 * 
 * 参数:
 *   Head - 链表头节点
 *   data - 要插入的数据
 * 返回值:成功返回true,失败返回false
 */
bool InstHeadNode(DListNode *Head, TypData data) {
    DListNode *New = CreaOneNode(data);
    if (NULL == New) return false;
    
    // 空链表情况
    if (IsEmpty(Head)) {
        Head->next = New;
        New->prev = Head;      // 设置新节点的prev指向头节点
        return true;
    }
    
    // 非空链表情况
    New->next = Head->next;
    New->prev = Head;          // 新节点prev指向头节点
    Head->next->prev = New;    // 原首节点的prev指向新节点
    Head->next = New;          // 头节点next指向新节点
    return true;
}

/*
 * 尾插法插入节点
 * 
 * 参数:
 *   Head - 链表头节点
 *   data - 要插入的数据
 * 返回值:成功返回true,失败返回false
 */
bool InstTailNode(DListNode *Head, TypData data) {
    DListNode *p = Head;
    
    // 定位到最后一个节点
    while (p->next != NULL) {
        p = p->next;
    }
    
    DListNode *New = CreaOneNode(data);
    if (NULL == New) return false;
    
    p->next = New;  // 最后一个节点指向新节点
    New->prev = p;  // 新节点的prev指向其前驱
    return true;
}

/*
 * 在指定数据后插入节点
 * 
 * 参数:
 *   Head - 链表头节点
 *   NewData - 要插入的新数据
 *   targetData - 目标数据(新数据插入在此节点之后)
 * 返回值:成功返回true,失败返回false
 */
bool InstDataNode(DListNode *Head, TypData NewData, TypData targetData) {
    DListNode *p = Head->next;  // 从第一个实际节点开始查找
    
    while (p != NULL) {
        if (p->data == targetData) {
            DListNode *New = CreaOneNode(NewData);
            if (NULL == New) return false;
            
            New->next = p->next;
            New->prev = p;  // 新节点的prev指向当前节点
            
            // 如果当前节点不是尾节点
            if (p->next != NULL) {
                p->next->prev = New;  // 原后继节点的prev指向新节点
            }
            
            p->next = New;  // 当前节点next指向新节点
            return true;
        }
        p = p->next;
    }
    
    printf("未找到数据 %d,插入失败\n", targetData);
    return false;
}

/*
 * 遍历并打印链表
 * 
 * 参数:Head - 链表头节点
 */
void ListPrint(DListNode *Head) {
    if (IsEmpty(Head)) {
        printf("链表为空\n");
        return;
    }
    
    DListNode *p = Head->next;
    printf("链表内容: ");
    while (p != NULL) {
        printf("%d -> ", p->data);
        p = p->next;
    }
    printf("NULL\n");
}

/*
 * 删除头节点(首元节点)
 * 
 * 参数:Head - 链表头节点
 * 返回值:成功返回true,失败返回false
 */
bool DelHeadNode(DListNode *Head) {
    if (IsEmpty(Head)) {
        printf("链表为空,删除失败\n");
        return false;
    }
    
    DListNode *delNode = Head->next;  // 要删除的节点
    
    Head->next = delNode->next;  // 头节点指向新的第一个节点
    
    // 如果链表有多个节点
    if (delNode->next != NULL) {
        delNode->next->prev = Head;  // 新首节点的prev指向头节点
    }
    
    free(delNode);  // 释放内存
    return true;
}

/*
 * 删除尾节点
 * 
 * 参数:Head - 链表头节点
 * 返回值:成功返回true,失败返回false
 */
bool DelTailNode(DListNode *Head) {
    if (IsEmpty(Head)) {
        printf("链表为空,删除失败\n");
        return false;
    }
    
    DListNode *curr = Head->next;  // 当前节点
    
    // 定位到最后一个节点
    while (curr->next != NULL) {
        curr = curr->next;
    }
    
    curr->prev->next = NULL;  // 前驱节点设为新的尾节点
    free(curr);  // 释放原尾节点
    return true;
}

/*
 * 删除指定数据的节点
 * 
 * 参数:
 *   Head - 链表头节点
 *   data - 要删除的数据
 * 返回值:成功返回true,失败返回false
 */
bool DelSpecNode(DListNode *Head, TypData data) {
    if (IsEmpty(Head)) {
        printf("链表为空,删除失败\n");
        return false;
    }
    
    DListNode *curr = Head->next;  // 当前节点
    
    while (curr != NULL) {
        if (curr->data == data) {
            // 前驱节点的next指向当前节点的next
            curr->prev->next = curr->next;
            
            // 如果当前节点不是尾节点
            if (curr->next != NULL) {
                curr->next->prev = curr->prev;  // 后继节点的prev指向前驱节点
            }
            
            free(curr);  // 释放当前节点
            return true;
        }
        curr = curr->next;  // 移动到下一个节点
    }
    
    printf("未找到数据 %d,删除失败\n", data);
    return false;
}

/*
 * 释放整个链表
 * 
 * 参数:Head - 链表头节点
 */
void FreeList(DListNode *Head) {
    DListNode *current = Head;
    while (current != NULL) {
        DListNode *temp = current;
        current = current->next;
        free(temp);
    }
}

/*
 * 测试函数
 */
void TestLinkedList() {
    printf("===== 双向链表测试 =====\n");
    
    // 创建链表
    DListNode *list = CreHeadNode();
    printf("创建空链表\n");
    ListPrint(list);
    
    // 测试尾插法
    printf("\n尾插法插入10, 20, 30:\n");
    InstTailNode(list, 10);
    InstTailNode(list, 20);
    InstTailNode(list, 30);
    ListPrint(list);
    
    // 测试头插法
    printf("\n头插法插入5:\n");
    InstHeadNode(list, 5);
    ListPrint(list);
    
    // 测试指定位置插入
    printf("\n在20后插入25:\n");
    InstDataNode(list, 25, 20);
    ListPrint(list);
    
    // 测试删除头节点
    printf("\n删除头节点(5):\n");
    DelHeadNode(list);
    ListPrint(list);
    
    // 测试删除尾节点
    printf("\n删除尾节点(30):\n");
    DelTailNode(list);
    ListPrint(list);
    
    // 测试删除指定节点
    printf("\n删除节点(20):\n");
    DelSpecNode(list, 20);
    ListPrint(list);
    
    // 测试边界情况
    printf("\n测试边界情况:\n");
    printf("删除唯一节点(10):\n");
    DelHeadNode(list);
    ListPrint(list);
    
    printf("尝试删除空链表节点:\n");
    DelHeadNode(list);
    DelTailNode(list);
    DelSpecNode(list, 99);
    
    // 释放链表
    FreeList(list);
    printf("\n链表已释放\n");
}

int main() {
    TestLinkedList();
    return 0;
}

双向链表实现详解

数据结构设计

// 双向链表节点结构
typedef struct DoubleListNode {
    TypData data;                // 数据域
    struct DoubleListNode *prev; // 指向前一个节点的指针
    struct DoubleListNode *next; // 指向下一个节点的指针
} DListNode;

双向链表节点包含三个部分:

  1. 数据域:存储节点数据
  2. prev指针:指向前一个节点
  3. next指针:指向下一个节点

核心功能实现

1. 创建链表头节点

DListNode * CreHeadNode() {
    DListNode *Head = (DListNode *)calloc(1, sizeof(DListNode));
    // 错误处理...
    Head->prev = NULL;
    Head->next = NULL;
    return Head;
}

2. 插入操作

头插法:在链表头部插入新节点

bool InstHeadNode(DListNode *Head, TypData data) {
    // 处理空链表情况
    if (IsEmpty(Head)) {
        Head->next = New;
        New->prev = Head;
        return true;
    }
    
    // 处理非空链表
    New->next = Head->next;
    New->prev = Head;
    Head->next->prev = New;
    Head->next = New;
}

尾插法:在链表尾部插入新节点

bool InstTailNode(DListNode *Head, TypData data) {
    // 定位到尾节点
    while (p->next != NULL) {
        p = p->next;
    }
    
    p->next = New;
    New->prev = p;
}

指定位置插入:在特定节点后插入新节点

bool InstDataNode(DListNode *Head, TypData NewData, TypData targetData) {
    // 查找目标节点
    while (p != NULL) {
        if (p->data == targetData) {
            // 插入操作
            New->next = p->next;
            New->prev = p;
            
            if (p->next != NULL) {
                p->next->prev = New;
            }
            
            p->next = New;
            return true;
        }
        p = p->next;
    }
}

3. 删除操作

删除头节点

bool DelHeadNode(DListNode *Head) {
    DListNode *delNode = Head->next;
    Head->next = delNode->next;
    
    if (delNode->next != NULL) {
        delNode->next->prev = Head;
    }
    
    free(delNode);
}

删除尾节点

bool DelTailNode(DListNode *Head) {
    // 定位到尾节点
    while (curr->next != NULL) {
        curr = curr->next;
    }
    
    curr->prev->next = NULL;
    free(curr);
}

删除指定节点

bool DelSpecNode(DListNode *Head, TypData data) {
    // 查找目标节点
    while (curr != NULL) {
        if (curr->data == data) {
            curr->prev->next = curr->next;
            
            if (curr->next != NULL) {
                curr->next->prev = curr->prev;
            }
            
            free(curr);
            return true;
        }
        curr = curr->next;
    }
}

测试结果

运行测试函数后,输出如下:

===== 双向链表测试 =====
创建空链表
链表为空

尾插法插入10, 20, 30:
链表内容: 10 -> 20 -> 30 -> NULL

头插法插入5:
链表内容: 5 -> 10 -> 20 -> 30 -> NULL

在20后插入25:
链表内容: 5 -> 10 -> 20 -> 25 -> 30 -> NULL

删除头节点(5):
链表内容: 10 -> 20 -> 25 -> 30 -> NULL

删除尾节点(30):
链表内容: 10 -> 20 -> 25 -> NULL

删除节点(20):
链表内容: 10 -> 25 -> NULL

测试边界情况:
删除唯一节点(10):
链表内容: 25 -> NULL
删除唯一节点(25):
链表为空
尝试删除空链表节点:
链表为空,删除失败
链表为空,删除失败
未找到数据 99,删除失败

链表已释放

双向链表特点

  1. 双向遍历:可以从头到尾或从尾到头遍历链表
  2. 高效删除:删除任意节点时间复杂度为O(1)
  3. 灵活插入:可在任意位置高效插入新节点
  4. 空间换时间:相比单链表需要额外存储prev指针

适用场景

  1. 需要频繁在链表中间插入/删除元素的场景
  2. 需要双向遍历的场景
  3. 实现LRU缓存淘汰算法
  4. 实现浏览器的前进/后退功能
posted @ 2025-07-18 19:45  Rare_30  阅读(25)  评论(0)    收藏  举报