C语言实现双向链表的增删查改

定义一个带头节点的双向链表需要一个数据域两个指针域,其中一个next指针是指向当前节点的下一个节点,另外一个prev指针是指向当前节点的前一个节点。如下图所示:
C语言实现双向链表的增删查改_第1张图片
该头结点本质上只是一个傀儡节点并无实际用处,带头节点的好处就是我们在实际操作链表时不用考虑头指针的指向。而双向链表的前后指针域也大大方便了我们对于链表的实际操作。
代码示例:
DBlinklist.h文件

#pragma once

typedef char DLinkType;

typedef struct DLinkNode
{
    DLinkType data;//数据域
    struct DLinkNode *prev;//前指针域
    struct DLinkNode *next;//后指针域
}DLinkNode;

DLinkNode *head;
//初始化函数
void DLinkListInit(DLinkNode **head);
//尾插函数
DLinkNode *DLinkListPushBack(DLinkNode *head,DLinkType value);
//尾删函数
void DLinkListPopBack(DLinkNode *head);
//头插函数
void DLinkListPushFront(DLinkNode *head,DLinkType value);
//头删函数
void DLinkListPopFront(DLinkNode *head);
//给定一个值,查找地址,返回地址
DLinkNode* DLinkListFind(DLinkNode *head,DLinkType to_find);
//在pos位置之前插入指定元素
void DLinkListInsert(DLinkNode *head,DLinkNode *pos,DLinkType value);
//在pos位置之后插入指定元素
void DLinkListInsertAfter(DLinkNode *head,DLinkNode *pos,DLinkType value);
//删除指定位置的元素
void DLinkListErase(DLinkNode *head,DLinkNode *pos);
//删除指定值,若存在多个该元素则只删除第一个
void DLinkListRemove(DLinkNode *head,DLinkType to_remove);
//删除指定值,若存在多个该元素则删除全部
void DLinkListRemoveAll(DLinkNode *head,DLinkType to_remove);
//求链表元素个数,返回元素个数
size_t DLinkListSize(DLinkNode *head);
//判断链表是否为空,非空则返回1否则返回0
int DLinkListEmpty(DLinkNode *head);

DBlinklist.c文件

#include<stdlib.h>
#include<stdio.h>
#include<stddef.h>
#include"DBlinklist.h"

#define Test_Header  printf("\n========%s========\n",__FUNCTION__);
//以下是链表打印函数
//因为是双向链表,所以从两个方向遍历打印出来
//结果符合逻辑才说明链表指针指向正确
void DLinkListPrint(DLinkNode *head,const char *msg)
{
    printf("[%s]\n",msg);
    if(head == NULL)
    {
        //非法输入
        printf("非法输入\n");
        return;
    }
    if(head->prev == head)
    {
        //空链表
        printf("空链表\n");
        return;
    }
    DLinkNode *cur = head->next;
    for(;cur != head;cur = cur->next)
    {
        //正向循环遍历打印
        printf("[%c|%p] ",cur->data,cur);
    }
    printf("\n");
    DLinkNode *pre = head->prev;
    for(;pre != head;pre = pre->prev)
    {
        //反向循环遍历打印
        printf("[%c|%p] ",pre->data,pre);
    }
    printf("\n");
}
//以下是创建节点函数
DLinkNode* CreatNode(DLinkType value)
{
    DLinkNode *new_node = (DLinkNode*)malloc(sizeof(DLinkNode));
    new_node->data = value;
    new_node->prev = new_node;
    new_node->next = new_node;
    return new_node;
}
//以下为链表初始化函数
//头结点并无实质意义,因此节点中存放的值可随意设置
void DLinkListInit(DLinkNode **head)
{
    if(head == NULL)
    {
        return;
    }
    *head = CreatNode(0);
}
//以下是尾插函数
DLinkNode* DLinkListPushBack(DLinkNode* head, DLinkType value)
{
    if(head == NULL)
    {
        //非法输入
        return NULL;
    }
    //last_node为链表中最后一个节点
    DLinkNode *last_node = head->prev;
    DLinkNode *new_node = CreatNode(value);
    //last_node 与 new_node两个节点的指针指向的修改
    last_node->next = new_node;
    new_node->prev = last_node;
    //head 与 new_node两个节点的指针的指向的修改
    head->prev = new_node;
    new_node->next = head;

    return new_node;
}
//以下为尾删函数
void DLinkListPopBack(DLinkNode* head)
{
    if(head == NULL)
    {
        //非法输入
        return;
    }
    if(head->prev == head)
    {
        //空链表没有元素可删
        return;
    }
    //最后一个元素为我们需要删除的元素
    DLinkNode *to_delete = head->prev;
    //last_node为倒数第二个元素
    DLinkNode *last_node = to_delete->prev;
    //修改指针指向
    last_node->next = head;
    head->prev = last_node;
    //释放需要删除的节点
    free(to_delete);
    return;
}
//以下为头删函数
void DLinkListPushFront(DLinkNode* head, DLinkType value)
{
    if(head == NULL)
    {
        //非法输入
        return;
    }
    DLinkNode *new_node = CreatNode(value);
    //first_node为第一个元素
    DLinkNode *first_node = head->next;
    //head 与 new_node两个节点的指针的指向的修改
    head->next = new_node;
    new_node->prev = head;
    //new_node 与 first_node两个节点的指针的指向的修改
    new_node->next = first_node;
    first_node->prev = new_node;

    return;
}
//以下为头删函数
void DLinkListPopFront(DLinkNode* head)
{
    if(head == NULL)
    {
        //非法输入
        return;
    }
    if(head->prev == head)
    {
        //空链表没有元素可删
        return;
    }
    //头结点的下一个元素就是我们要删除的元素
    DLinkNode *to_delete = head->next;
    //删除第一个节点以,删除元素的下一个元素就是新的第一个元素
    DLinkNode *next_first_node = to_delete->next;
    //更改指针的指向
    head->next = next_first_node;
    next_first_node->prev = head;
    //释放待删除的元素
    free(to_delete);

    return;
}
//以下为查找元素地址的函数
DLinkNode* DLinkListFind(DLinkNode* head, DLinkType to_find)
{
    if(head == NULL)
    {
        //非法输入
        return NULL;
    }
    //定义一个指针指向第一个元素
    DLinkNode *cur = head->next;
    for(;cur->next != head;cur = cur->next)
    {
        //遍历,并且判断当前节点的值是否与待查找元素的值相等
        if(cur->data == to_find)
        {
            //找到带查找的值则返回该节点
            return cur;
        }
    }
    //遍历完以后没有找到待查找的值
    return NULL;
}
//以下为往指定位置之前插入一个元素的函数 
void DLinkListInsert(DLinkNode *head,DLinkNode* pos, DLinkType value)
{
    if(head == NULL || pos == NULL)
    {
        //非法输入
        return;
    }
    //找到pos位置之前的节点
    DLinkNode *before_pos_node = pos->prev;
    //以value值创建一个新的节点
    DLinkNode *new_node = CreatNode(value);
    //new_node 与 before_pos_node两个节点的指针的指向的修改
    new_node->prev = before_pos_node;
    before_pos_node->next = new_node;
    //new_node 与 pos两个节点的指针的指向的修改
    new_node->next = pos;
    pos->prev = new_node;

    return;
}
//以下为往指定位置之后插入一个元素的函数 
void DLinkListInsertAfter(DLinkNode *head,DLinkNode* pos, DLinkType value)
{
    if(head == NULL || pos == NULL)
    {
        //非法输入
        return;
    }
    //找到pos位置之后的节点
    DLinkNode *next_pos_node = pos->next;
    //以value值创建一个新的节点
    DLinkNode *new_node = CreatNode(value);

    //new_node 与 next_pos_node两个节点的指针的指向的修改
    new_node->next = next_pos_node;
    next_pos_node->prev = new_node;
    //new_node 与 pos两个节点的指针的指向的修改
    pos->next = new_node;
    new_node->prev = pos;

    return;
}
//以下为删除任意位置节点的函数
void DLinkListErase(DLinkNode *head,DLinkNode *pos)
{
    if(head == NULL || pos == NULL || pos == head)
    {
        //非法输入
        //若pos==head则无意义,因为头结点没有实际意义
        return;
    }
    if(head->prev == head)
    {
        //空链表没有可删除的位置
        return;
    }
    //找到pos位置的前一个和后一个节点
    DLinkNode *before_pos_node = pos->prev;
    DLinkNode *next_pos_node = pos->next;
    //before_pos_node 与 next_pos_node两个节点的指针的指向的修改
    before_pos_node->next = next_pos_node;
    next_pos_node->prev = before_pos_node;

    return;
}
//以下为删除指定元素,若存在多个元素则只删除第一个该元素的函数
void DLinkListRemove(DLinkNode *head,DLinkType to_remove)
{
    if(head == NULL)
    {
        //非法输入
        return;
    }
    DLinkNode *cur = head->next;
    //遍历链表,找到待删除元素的位置
    for(;cur->next != head && cur->data != to_remove;cur = cur->next)
        ;
    if(cur->next == head && cur->data != to_remove)
        //该元素不存在
        return;
    //调用之前的删除指定位置的元素的函数
    DLinkListErase(head,cur);
}
//以下为删除指定元素,若存在多个元素则全部删除的函数
void DLinkListRemoveAll(DLinkNode *head,DLinkType to_remove)
{
    if(head == NULL)
    {
        //非法输入
        return;
    }
    DLinkNode *cur = head->next;
    while(1)
    {
        for(cur = head->next;cur->next != head && cur->data != to_remove;cur = cur->next)
            ;
        if(cur->next == head && cur->data != to_remove)
            //该元素不存在
            return;
        DLinkListErase(head,cur);
    }
}
//以下为求链表元素个数的函数,返回元素个数
size_t DLinkListSize(DLinkNode *head)
{
    if(head == NULL)
    {
        //非法输入
        return 0;
    }
    if(head->prev == head)
    {
        //空链表返回0
        return 0;
    }
    //定义一个变量用于计数
    size_t count = 1;
    DLinkNode *cur = head->next;
    //遍历
    for(;cur != NULL && cur->next != head;cur = cur->next)
    {
        count++;
    }
    return count;
}
//以下为判断链表是否为空的函数
//空返回0非空返回1
int DLinkListEmpty(DLinkNode *head)
{
    if(head == NULL)
    {
        return 0;
    }
    if(head->prev != head)
        return 1;
    else
        return 0;
}
//以下为销毁链表函数
void DLinkListDestroy(DLinkNode **head)
{
    if(head == NULL)
    {
        //非法输入
        return;
    }
    DLinkNode *cur = (*head)->next;
    //遍历链表,先释放链表中已经存在的每一个元素
    while(cur != *head)
    {
        DLinkNode *next = cur->next;
        free(cur);
        cur = next;
    }
    //释放头结点
    free(*head);
    //将头结点指向空,避免野指针问题
    *head = NULL;
}
///以下为测试函数//

//初始化函数测试
void TestInit()
{
    Test_Header;
    DLinkListInit(&head);
    printf("expected:0 actual:%d\n",(int)head->data);
}
//尾插函数测试
void TestPushBack()
{
    Test_Header;
    DLinkListInit(&head);
    DLinkListPushBack(head,'a');
    DLinkListPushBack(head,'b');
    DLinkListPushBack(head,'c');
    DLinkListPushBack(head,'d');
    DLinkListPrint(head,"尾插四个元素");
}
//尾删函数测试
void TestPopBack()
{
    Test_Header;
    DLinkListPopBack(head);
    DLinkListPopBack(head);
    DLinkListPrint(head,"尾删两个元素");
    DLinkListPopBack(head);
    DLinkListPopBack(head);
    DLinkListPrint(head,"再尾删两个元素");
    DLinkListPopBack(head);
    DLinkListPrint(head,"尝试对空链表删除");
}

测试结果如下图所示:
C语言实现双向链表的增删查改_第2张图片

//头插函数测试
void TestPushFront()
{
    Test_Header;
    DLinkListPushFront(head,'a');
    DLinkListPushFront(head,'b');
    DLinkListPushFront(head,'c');
    DLinkListPushFront(head,'d');
    DLinkListPrint(head,"头插四个元素");
}
//头删函数测试
void TestPopFront()
{
    Test_Header;
    DLinkListPopFront(head);
    DLinkListPopFront(head);
    DLinkListPrint(head,"头删两个元素");
    DLinkListPopFront(head);
    DLinkListPopFront(head);
    DLinkListPrint(head,"再头删两个元素");
    DLinkListPopFront(head);
    DLinkListPrint(head,"尝试对空链表删除");
}

测试结果如下图所示:
C语言实现双向链表的增删查改_第3张图片

//查找指定元素的地址函数测试
void TestFind()
{
    Test_Header;
    DLinkListPushBack(head,'a');
    DLinkListPushBack(head,'b');
    DLinkListPushBack(head,'c');
    DLinkListPushBack(head,'d');
    DLinkListPrint(head,"先尾插四个元素");
    DLinkNode *ret1 = DLinkListFind(head,'b');
    printf("元素b的地址为:%p\n",ret1);
    DLinkNode *ret2 = DLinkListFind(head,'e');
    printf("元素e的地址为:%p\n",ret2);
    DLinkListPopFront(head);
    DLinkListPopFront(head);
    DLinkListPopFront(head);
    DLinkListPopFront(head);
    DLinkListPrint(head,"头删四个元素形成空链表");
    DLinkNode *ret3 = DLinkListFind(head,'b');
    printf("元素b的地址为:%p\n",ret3);
}
//删除指定元素的函数测试
void TestErase()
{
    Test_Header;
    DLinkListPushBack(head,'a');
    DLinkListPushBack(head,'b');
    DLinkListPushBack(head,'c');
    DLinkListPushBack(head,'d');
    DLinkListPrint(head,"先尾插四个元素");
    DLinkNode *pos = DLinkListFind(head,'b');
    DLinkListErase(head,pos);
    DLinkListPrint(head,"尝试删除b位置的元素");
    DLinkListErase(head,NULL);
    DLinkListErase(head,head);
    DLinkListPrint(head,"pos为空或者为head时删除之后的链表元素");
}

测试结果如下图所示:
C语言实现双向链表的增删查改_第4张图片

//在pos位置之前插入指定元素的函数测试
void TestInsert()
{
    Test_Header;
    DLinkNode *pos = DLinkListFind(head,'c');
    DLinkListInsert(head,pos,'b');
    DLinkListPrint(head,"c之前插入b之后的链表元素");
    DLinkListInsert(head,head->next,'A');
    DLinkListPrint(head,"a之前插入A之后的链表元素");
}
//在pos位置之后插入指定元素的函数测试
void TestInsertAfter()
{
    Test_Header;

    DLinkNode *pos = DLinkListFind(head,'a');
    DLinkListInsertAfter(head,pos,'B');
    DLinkListPrint(head,"a后面插入B之后的链表元素");
    DLinkListInsertAfter(head,head->prev,'e');
    DLinkListPrint(head,"d后面插入e之后的链表元素");
}

测试结果如下图所示:
C语言实现双向链表的增删查改_第5张图片

//删除指定元素,若存在多个该元素则只删除第一个的函数测试
void TestRemove()
{
    Test_Header;
    DLinkListPushBack(head,'c');
    DLinkListPushBack(head,'d');
    DLinkListPrint(head,"先尾插两个元素");
    DLinkListRemove(head,'a');
    DLinkListPrint(head,"先删除一个出现一次的元素a");
    DLinkListRemove(head,'d');
    DLinkListPrint(head,"再删除一个出现两次的元素d");
    DLinkListRemove(head,'d');
    DLinkListPrint(head,"再删除一个出现一次的元素d");
}
//删除指定元素,若存在多个该元素则全部删除的函数测试
void TestRemoveAll()
{
    Test_Header;
    DLinkListRemoveAll(head,'e');
    DLinkListPrint(head,"先删除一个出现一次的元素e");
    DLinkListRemoveAll(head,'c');
    DLinkListPrint(head,"再删除一个出现两次的元素c");
}

测试结果如下图所示:
C语言实现双向链表的增删查改_第6张图片

//求链表元素个数的函数测试
void TestSize()
{
    Test_Header;
    size_t ret = DLinkListSize(head);
    DLinkListPrint(head,"链表中的元素为");
    printf("链表中共有:%lu 个元素\n",ret);
}
//判断链表是否为空的函数测试
void TestEmpty()
{
    Test_Header;
    DLinkListPrint(head,"链表中的元素为");
    int ret = DLinkListEmpty(head);
    printf("判空返回值为:%d\n",ret);
    DLinkListPopFront(head);
    DLinkListPopFront(head);
    DLinkListPopFront(head);
    DLinkListPrint(head,"删除三个元素后链表中的元素为");
    ret = DLinkListEmpty(head);
    printf("判空返回值为:%d\n",ret);
}
void TestDestroy()
{
    Test_Header;
    DLinkListDestroy(&head);
    DLinkListPrint(head,"尝试对一个已经被销毁的链表进行打印");
}

测试结果如下图所示:
C语言实现双向链表的增删查改_第7张图片

你可能感兴趣的:(c语言,数据结构,双向链表基本操作,线性表,数据结构)