定义一个带头节点的双向链表需要一个数据域两个指针域,其中一个next指针是指向当前节点的下一个节点,另外一个prev指针是指向当前节点的前一个节点。如下图所示:
该头结点本质上只是一个傀儡节点并无实际用处,带头节点的好处就是我们在实际操作链表时不用考虑头指针的指向。而双向链表的前后指针域也大大方便了我们对于链表的实际操作。
代码示例:
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,"尝试对空链表删除");
}
//头插函数测试
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,"尝试对空链表删除");
}
//查找指定元素的地址函数测试
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时删除之后的链表元素");
}
//在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之后的链表元素");
}
//删除指定元素,若存在多个该元素则只删除第一个的函数测试
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");
}
//求链表元素个数的函数测试
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,"尝试对一个已经被销毁的链表进行打印");
}