线性表_双向链表
在之前我们已经学习过单链表,循环链表,这次我们来看一下双向链表。为什么要有循环链表呢?之前的单链表功能虽然已经很完备,但是要是想要倒着遍历该怎么办?这会是一个很棘手的问题,但是如果我们给每个节点加一个pre域,让它指向它的前驱节点,这样不就可以倒着遍历了吗?所以双向链表应运而生。
它的基本api与循环链表很类似,无非插入,删除等基本操作外加一些关于游标的重置,后移等操作,下面还是着重分析插入与删除操作与之前的异同。
int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos)
这里我们假设在1号位置插入元素,两个辅助指针变量,pCur和pNext,一个指向待插入位置前,一个指向待插入位置。
区别之处在于:
1.是否是第一次插入节点,如果是,则需要让游标指向0号位置。
2.插入时的三四部操作有些不一样,对于3:如果是尾插法,不需要指回来,只要指向NULL就可以了,而一般位置插入则需要指回来。对于4:如果是头插法,那么需要指回到NULL,因为它不存在前驱节点。
DLinkListNode* DLinkList_Delete(DLinkList* list, int pos)
删除操作则需要三个辅助指针变量:pCur(指向删除节点的前一个节点),Deletenode(指向要删除的节点),pAfter(指向要删除节点之后的节点)。
删除分三种情况:
//5 开启删除操作 分三种情况
pCur->next = pAfter;//普通情况1
if (pAfter !=NULL)
{
pAfter->pre = pCur;//普通情况2
}
DeletNode->pre = NULL;//第三种情况需要置空 第二种已经自己置空
下面是源码:(源码来自 双向链表的存储设计与实现(C语言版本) 侵删)
//DLinkList.h
#ifndef _DLINKLIST_H_
#define _DLINKLIST_H_
//双向链表的存储结构
//结点中包含后继结点的地址的指针域可以理解为指向下一个结构体(结点)
//(这里不包含数据域,是实现了 链表的api(链表的算法) 和 具体的数据分离)
typedef struct _tag_DLinkListNode
{
struct _tag_DLinkListNode *next;//指向后继的next指针
struct _tag_DLinkListNode *pre;//指向前驱的pre指针
}DLinkListNode;
//为void 再重新多取一个名字,DLinkList等价于void
//typedef + 已有的数据类型+新的数据类型(自己取的新名字)
typedef void DLinkList;
//创建并且返回一个空的双向链表
DLinkList* DLinkList_Create();
//销毁一个双向链表list
void DLinkList_Destroy(DLinkList* list);
//将一个双向链表list中的所有元素清空, 循环链表回到创建时的初始状态
void DLinkList_Clear(DLinkList* list);
//返回一个双向链表list中的元素个数
int DLinkList_Length(DLinkList* list);
//向一个双向链表list的pos位置处插入元素
int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos);
//获取一个双向链表list中pos位置处的元素
DLinkListNode* DLinkList_Get(DLinkList* list, int pos);
//删除一个双向链表list中pos位置处的元素,返回值为被删除的元素,NULL表示删除失败
DLinkListNode* DLinkList_Delete(DLinkList* list, int pos);
//add
//直接指定删除双向链表中的某个数据元素
DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node);
//将游标重置指向双向链表中的第一个数据元素
DLinkListNode* DLinkList_SliderReset(DLinkList* list);
//双向链表 获取当前游标指向的数据元素
DLinkListNode* DLinkList_SliderCurrent(DLinkList* list);
//将游标移动指向到双向链表中的下一个数据元素
DLinkListNode* DLinkList_SliderNext(DLinkList* list);
//将游标移动指向双向链表中的前一个数据元素
DLinkListNode* DLinkList_SliderPre(DLinkList *list);
#endif
//DLinkList.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "DLinkList.h"
//定义双向链表的头结点 双向链表头结点:表示链表中第一个节点,包含指向第一个数据元素的指针以及链表自身的一些信息
//这样能把所有结点串起来
typedef struct _tag_DLinkList
{
DLinkListNode header; //要有个头指针---指向头结点的指针
DLinkListNode *slider;//在双向链表中可以定义一个“当前指针”,这个指针通常称为游标,可以通过游标来遍历链表中所有元素
int length;//链表的长度
}TDLinkList;
//创建并且返回一个空的双向链表
DLinkList* DLinkList_Create()
{
int ret = 0;
//1 申请动态内存空间
TDLinkList *tmp = (TDLinkList *)malloc(sizeof(TDLinkList));
if (NULL == tmp)
{
ret = -1;
printf("func err malloc:%d\n",ret);
return NULL;
}
//2 让开辟的内存 完成链式线性表初始化
memset(tmp, 0, sizeof(TDLinkList));
//3 链表的初始化
tmp->header.next = NULL;
tmp->header.pre = NULL;
tmp->slider = NULL;
tmp->length = 0;
return tmp;
}
//销毁一个双向链表list
//由于数据元素节点的生命周期不是由链表算法管理的,所以只需要销毁申请的头节点元素即可
void DLinkList_Destroy(DLinkList* list)
{
//1 缓存下来 进行操作
TDLinkList *tmp = NULL;
tmp = (TDLinkList *)list;
if (NULL == list)
{
printf("func err DLinkList_Destroy()\n");
}
//2 释放头结点空间
if (tmp != NULL)
{
free(tmp);
}
}
//将一个双向链表list中的所有元素清空, 双向链表回到创建时的初始状态
void DLinkList_Clear(DLinkList* list)
{
//1 缓存下来 进行操作
TDLinkList *tmp = NULL;
tmp = (TDLinkList *)list;
if (NULL == list)
{
printf("func err DLinkList_Clear()\n");
}
//2 清空链表
tmp->header.next = NULL;
tmp->header.pre = NULL;
tmp->slider = NULL;
tmp->length = 0;
}
//返回一个双向链表list中的元素个数
int DLinkList_Length(DLinkList* list)
{
int ret = 0;
//1 缓存下来 进行操作
TDLinkList *tmp = NULL;
tmp = (TDLinkList *)list;
if (NULL == list)
{
ret = -1;
printf("func err DLinkList_Length():%d\n", ret);
return ret;
}
ret = tmp->length;
return ret;
}
//向一个双向链表list的pos位置处插入元素
int DLinkList_Insert(DLinkList* list, DLinkListNode* node, int pos)
{
int ret = 0;
//1 缓存下来 进行操作
TDLinkList *tmp = NULL;
tmp = (TDLinkList *)list;
//辅助指针 用来遍历当前指针位置
DLinkListNode *pCur = NULL;
//辅助指针 用来缓存当前指针下移位置
DLinkListNode *pNext = NULL;
if (NULL == list || NULL == node || pos < 0)
{
ret = -1;
printf("func err (NULL == list || NULL == node || pos < 0):%d\n", ret);
return ret;
}
//注意:容错修正,假如链表当前长度为5,你插入pos位置是10,这个时候可以做容错修正,直接修正为尾插法
if (pos > tmp->length)
{
pos = tmp->length;
}
//1 当前指针 初始化 指向 头结点
pCur = &(tmp->header);
//2 进行遍历 找到插入位置
for (int i = 0; i < pos; i++)
{
pCur = pCur->next;
}
//3 进行插入操作
//进行缓存pCur next域
pNext = pCur->next;
//插入操作
pCur->next = node;//1
node->next = pNext;//2
if (pNext != NULL)
{
pNext->pre = node;//3 若是尾插法,就不需要这部操作
}
node->pre = NULL;//若是头插法,需要这部操作,如图1 4
if (pCur !=(DLinkListNode *)tmp)//若不是头插法,普通插入如图2 3情况
{
node->pre = pCur;//4
}
//若第一次插入结点 让游标指向0号结点
if (tmp->length == 0)
{
tmp->slider = node;
}
//4 链表长度++
tmp->length++;
return ret;
}
//获取一个双向链表list中pos位置处的元素
DLinkListNode* DLinkList_Get(DLinkList* list, int pos)
{
//1 缓存下来 进行操作
TDLinkList *tmp = (TDLinkList *)list;
//辅助指针 用来遍历当前指针位置
DLinkListNode *pCur = NULL;
if (NULL == list || pos < 0)
{
printf("func err DLinkList_Get\n");
return NULL;
}
//2 当前指针 初始化 指向 头结点
pCur = &(tmp->header);
//3 搜索要获得的结点
for (int i = 0; i < pos; i++)
{
pCur = pCur->next;
}
return pCur->next;
}
//删除一个循环链表list中pos位置处的元素,返回值为被删除的元素,NULL表示删除失败
DLinkListNode* DLinkList_Delete(DLinkList* list, int pos)
{
//1 缓存下来 进行操作
TDLinkList *tmp = (TDLinkList *)list;
//辅助指针 用来遍历当前指针位置
DLinkListNode *pCur = NULL;
//辅助指针 用来缓存删除结点下一元素
DLinkListNode *pAfter = NULL;
//辅助指针 用来缓存删除元素
DLinkListNode *DeletNode = NULL;
if (NULL == list || pos < 0)
{
printf("func err DLinkList_Delete\n");
return NULL;
}
//删除位置的pos点不能大于链表长度 做一个容错修正
//当删除位置pos大于双向链表长度,就尾部删除
if (pos > DLinkList_Length(tmp))
{
pos = tmp->length;
}
//2 当前指针 初始化 指向 头结点
pCur = &(tmp->header);
//3 搜索要获得的结点
for (int i = 0; i < pos; i++)
{
pCur = pCur->next;
}
//4 缓存结点位置
DeletNode = pCur->next;
pAfter = DeletNode->next;
//5 开启删除操作 分三种情况
pCur->next = pAfter;//普通情况1
if (pAfter !=NULL)
{
pAfter->pre = pCur;//普通情况2
}
DeletNode->pre = NULL;//第三种情况需要置空 第二种已经自己置空
//6 链表长度--
tmp->length--;
//7 若删除元素为游标所指元素 需要后移
if (tmp->slider == DeletNode)
{
tmp->slider = pAfter;
}
//8 若删除元素后链表长度为0
if (tmp->length == 0)
{
tmp->header.next = NULL;
tmp->header.pre = NULL;
tmp->slider = NULL;
}
return DeletNode;
}
//add
//根据结点 直接指定删除链表中的某个数据元素
DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node)
{
//1 缓存下来 进行操作
TDLinkList *tmp = (TDLinkList *)list;
//辅助指针 用来遍历当前指针位置
DLinkListNode *pCur = NULL;
//辅助指针 用来缓存删除元素
DLinkListNode *DeletNode = NULL;
if (NULL == list || 0 == node)
{
printf("func err DLinkList_DeleteNode\n");
return NULL;
}
//2 当前指针 初始化 指向 头结点
pCur = &(tmp->header);
//3 搜索要获得的结点
int i;
for ( i = 0; i < tmp->length; i++)
{
if (pCur->next == node)//根据删除结点搜索到要删除位置
{
DeletNode = pCur->next;//缓存删除元素
break;//这里搜索到要删除的结点,结束循环,否则会报错
}
pCur = pCur->next;
}
//4 根据pos位置删除元素
if (DeletNode != NULL)
{
DLinkList_Delete(tmp,i);
}
return DeletNode;
}
//将游标重置指向链表中的第一个数据元素
DLinkListNode* DLinkList_SliderReset(DLinkList* list)
{
//1 缓存下来 进行操作
TDLinkList *tmp = (TDLinkList *)list;
//辅助指针 用来缓存重置游标位置
DLinkListNode *tmpReset = NULL;
if (NULL == list)
{
printf("func err DLinkList_SliderReset\n");
return NULL;
}
if (tmp!=NULL)
{
tmp->slider = tmp->header.next;//重置指向第一个数据元素
tmpReset = tmp->slider;//缓存游标位置
}
return tmpReset;
}
//获取当前游标指向的数据元素
DLinkListNode* DLinkList_SliderCurrent(DLinkList* list)
{
//1 缓存下来 进行操作
TDLinkList *tmp = (TDLinkList *)list;
//辅助指针 用来缓存当前游标位置
DLinkListNode *tmpCur = NULL;
if (NULL == list)
{
printf("func err DLinkList_SliderCurrent\n");
return NULL;
}
if (tmp!=NULL)
{
tmpCur = tmp->slider;//缓存当前游标位置
}
return tmpCur;
}
//将游标移动指向到链表中的下一个数据元素
DLinkListNode* DLinkList_SliderNext(DLinkList* list)
{
//1 缓存下来 进行操作
TDLinkList *tmp = (TDLinkList *)list;
//辅助指针 用来缓存游标位置
DLinkListNode *tmpNext = NULL;
if (NULL == list)
{
printf("func err DLinkList_SliderNext\n");
return NULL;
}
if (tmp != NULL)
{
tmpNext = tmp->slider;//缓存当前游标位置
tmp->slider = tmpNext->next;//将游标下移
}
return tmpNext;
}
//将游标移动指向双向链表中的前一个数据元素
DLinkListNode* DLinkList_SliderPre(DLinkList *list)
{
//1 缓存下来 进行操作
TDLinkList *tmp = (TDLinkList *)list;
//辅助指针 用来缓存游标位置
DLinkListNode *tmpPre = NULL;
if (NULL == list)
{
printf("func err DLinkList_SliderPre\n");
return NULL;
}
if (tmp != NULL)
{
tmpPre = tmp->slider;//缓存当前游标位置
tmp->slider = tmpPre->pre;//将游标上移
}
return tmpPre;
}
//双向链表的设计与实现测试框架
//test.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "DLinkList.h"//c里面.h和.cpp没有差别 但是c++里如果模块化编程,两个头文件都必须包含进来
//业务结点
typedef struct _tag_Teacher
{
DLinkListNode node;//包含底层结点
//业务域
int age;
}Teacher;
int main()
{
int ret = 0;
DLinkList *dlist = NULL;
Teacher t1, t2, t3, t4;
t1.age = 1;
t2.age = 2;
t3.age = 3;
t4.age = 4;
//1 创建并且返回一个空的双向链表
dlist = DLinkList_Create();
if (NULL == dlist)
{
ret = -1;
printf("func err DLinkList_Create:%d\n", ret);
return ret;
}
//2 向一个双向链表list的pos位置处插入元素
ret = DLinkList_Insert(dlist, (DLinkListNode *)&t1, DLinkList_Length(dlist));
if (ret != 0)
{
ret = -2;
printf("func err DLinkList_Insert:%d\n", ret);
return ret;
}
ret = DLinkList_Insert(dlist, (DLinkListNode *)&t2, DLinkList_Length(dlist));
ret = DLinkList_Insert(dlist, (DLinkListNode *)&t3, DLinkList_Length(dlist));
ret = DLinkList_Insert(dlist, (DLinkListNode *)&t4, DLinkList_Length(dlist));
//返回一个双向链表list中的元素个数
ret = DLinkList_Length(dlist);
printf("%d ",ret);
printf("\n========================我是分界线====================\n");
//遍历双向链表
for (int i = 0; i < DLinkList_Length(dlist); i++)
{
//获取游标所指元素,然后游标下移
//双向链表 获取当前游标指向的数据元素
Teacher *tmp = (Teacher *)DLinkList_SliderNext(dlist);
if (NULL == tmp)
{
ret = -3;
printf("func err DLinkList_SliderNext:%d\n", ret);
return ret;
}
printf("%d ",tmp->age);
}
printf("\n========================我是分界线====================\n");
////将一个双向链表list中的所有元素清空, 循环链表回到创建时的初始状态
//DLinkList_Clear(dlist);
////返回一个双向链表list中的元素个数
//ret = DLinkList_Length(dlist);
//printf("%d ", ret);
//再一次 遍历双向链表
for (int i = 0; i < DLinkList_Length(dlist); i++)
{
//获取一个双向链表list中pos位置处的元素
//DLinkListNode* DLinkList_Get(DLinkList* list, int pos);
Teacher *tmp = (Teacher *)DLinkList_Get(dlist,i);
if (NULL == tmp)
{
ret = -4;
printf("func err DLinkList_SliderCurrent:%d\n", ret);
return ret;
}
printf("%d ", tmp->age);
}
printf("\n========================我是分界线====================\n");
//将游标重置指向双向链表中的第一个数据元素
//DLinkListNode* DLinkList_SliderReset(DLinkList* list);
Teacher *tmp1 = (Teacher *)DLinkList_SliderReset(dlist);
printf("%d ", tmp1->age);
Teacher *tmp2 = (Teacher *)DLinkList_SliderNext(dlist);
printf("%d ", tmp2->age);
//直接指定删除双向链表中的某个数据元素
//DLinkListNode* DLinkList_DeleteNode(DLinkList* list, DLinkListNode* node);
tmp2 = (Teacher *)DLinkList_DeleteNode(dlist, (DLinkListNode*)tmp2);
printf("%d ", tmp2->age);
//删除一个双向链表list中pos位置处的元素,返回值为被删除的元素,NULL表示删除失败
//DLinkListNode* DLinkList_Delete(DLinkList* list, int pos);
tmp2 = (Teacher *)DLinkList_Delete(dlist, 1);
printf("%d ", tmp2->age);
//将游标移动指向双向链表中的前一个数据元素
//DLinkListNode* DLinkList_SliderPre(DLinkList *list);
tmp2 = (Teacher *)DLinkList_SliderPre(dlist);
printf("%d ", tmp2->age);
DLinkList_Destroy(dlist);
return 0;
}