目录
双链表(线性表的链式存储)—C语言版
单链表结点中只有一个指向其后继结点的指针,使得单链表只能从头结点依次顺序地向后遍历。
要访问某个结点的前驱结点,只能从头开始遍历。
也就是说:访问后继结点的时间复杂度为O(1),访问前驱结点的时间复杂度为O(n)
为了克服单链表的上述缺点,引入了双链表。
双链表中有两个指针prior和next,分别指向其前驱结点和后继结点
一、双链表的定义
定义
仍旧使用该例子,我们要如何存储该表呢?
id | name | description |
---|---|---|
1 | 史强 | 最强辅助 |
2 | 章北海 | 我不需要钢印,我是自己思想的主人 |
3 | 罗辑 | 人类不感谢罗辑 |
4 | 维德 | 失去人性,失去很多。失去兽性,失去一切 |
步骤一:声明数据元素类型
首先我们使用结构体声明数据元素(对应表中的一行数据)的类型
typedef struct{
int id;//对应表中的id
char name[100];//对应表中的姓名
char description[200];//对应表中的描述
}ElemType;//此处的ElemType是个类型名
步骤二:声明结点
双链表中结点定义如下:
typedef struct DNode{
ElemType data;//数据域
struct DNode *prior;//前驱指针
struct DNode *next;//后继指针
}DNode,*DLinkList;
双链表逻辑示意图:
二、双链表上具体操作的实现和时间复杂度
使用含头结点的双链表
1、初始化表。构造一个空表。
/*初始化*/
int InitList_DLink(DLinkList *L){
*L=(DLinkList)malloc(sizeof(DNode));//创建头结点,并让头指针指向头结点
if (!(*L)) return FALSE;//分配失败
(*L)->prior=NULL;//头结点的prior永远指向NULL
(*L)->next=NULL;//初始化为空
return TRUE;//初始化成功
}
空表:
时间复杂度:
时间复杂度为O(1)
2、根据数组创建双链表(头插法和尾插法)
1.头插法
/*头插法创建双链表*/
int create_HeadInsert(DLinkList *L,ElemType a[],int n){
// InitList_DLink(L);
DNode *s;//用来指示新创建的结点
int i;
for(i=n-1;i>=0;i--){//由于D头插法是倒序插入,所以这里我们从数组最后开始遍历
s=(DLinkList)malloc(sizeof(DNode));//创建一个新结点
s->data=a[i];//为结点赋值
s->next=(*L)->next;//让该结点的后继指向第一个结点
if((*L)->next!=NULL) (*L)->next->prior=s;//第一个结点的前驱指向该结点
s->prior=(*L);//让该结点的前驱指向头结点
(*L)->next=s;//让头结点指向该D结点(该结点成为第一个结点)
}
}
如图所示:
2.尾插法
/*尾插法创建双链表*/
int create_TailInsert(DLinkList *L,ElemType a[],int n){
// InitList_DLink(L);
DNode *s,*r=(*L); //s用来指示新创建的结点,r用来移动以连接链表
int i;
for(i=0;i<n;i++){//遍历数组
s=(DLinkList)malloc(sizeof(DNode));//创建一个新结点
s->data=a[i];//为结点赋值
r->next=s;//r始终指向链表的尾端,这里将新结点连接到r的后面
s->prior=r;//该结点的前驱指向r指向的结点
r=s;//r移动到链表尾端o
}
r->next=NULL;//结束时,最后一个结点的指针域赋值为空
}
如图所示:
时间复杂度:
每个结点插入的时间为O(1),设表长为n
所有方式的时间复杂度均为O(n)
3、插入操作。在表L中的第i个位置上插入指定元素e。
/*插入*/
int ListInsert_DLink(DLinkList *L,int i,ElemType e){
int j=0;//空表序号为0
DNode *p=(*L);//p指向头结点
DNode *s;//s用来指示新创建的结点
while (p&&j<i-1){//找到被插入序号的前一个位置
p=p->next;//向后移动一个
j++;
}
if(!p||i<1) return FALSE;//序号在无效范围内
s=(DLinkList)malloc(sizeof(DNode));//创建一个新结点
s->data=e;//赋值
s->next=p->next;//让该结点指向p(插入位置的前一个结点)的下一个结点
if (p->next!=NULL) p->next->prior=s;//让p的下一个结点的前驱指向该结点
s->prior=p;//让该结点的前D驱指向p
p->next=s; //让p指向该结点
}
如图所示:
时间复杂度:
在给定的结点后插入新结点的时间复杂度为O(1)
查找第i-1个节点的时间复杂度为O(n)
综上:时间复杂度为O(n)
4、删除操作。删除表中L中第i个位置的元素,并用e返回删除的元素。
/*删除*/
int ListDelete_DLink(DLinkList *L,int i,ElemType *e){
int j=0;//空表序号为0
DNode *p=(*L);//p指向头结点
DNode *q;//q用来指向被删除的结点
while (p->next&&j<i-1){//找到要被删除结点的前一个结点
p=p->next;//向后移动一个
j++;
}
if (!(p->next)||i<1) return FALSE;//序号在无效范围内
q=p->next;//让q指向被删除的结点
p->next=q->next;//让p(被删除结点的前一个结点)指向被删除结点的下一个结点,q从链表中断开
if(q->next!=NULL)q->next->prior=p;
*e=q->data;//把被删除的结点的值返回
free(q);//释放该结点
return TRUE; //删除成功
}
如图所示:
时间复杂度:
在给定的结点后删除结点的时间复杂度为O(1)
查找第i-1个节点的时间复杂度为O(n)
综上:时间复杂度为O(n)
其他操作与单链表一样,不展示
三、完整代码实现
#include<stdio.h>
#include<stdlib.h>
#define TRUE 1
#define FALSE 0
typedef struct{
int id;//对应表中的id
char name[100];//对应表中的姓名
char description[200];//对应表中的描述
}ElemType;//此处的ElemType是个类型名
typedef struct DNode{
ElemType data;//数据域
struct DNode *prior;//前驱指针
struct DNode *next;//后继指针
}DNode,*DLinkList;
/*初始化*/
int InitList_DLink(DLinkList *L){
*L=(DLinkList)malloc(sizeof(DNode));//创建头结点,并让头指针指向头结点
if (!(*L)) return FALSE;//分配失败
(*L)->prior=NULL;//头结点的prior永远指向NULL
(*L)->next=NULL;//初始化为空
return TRUE;//初始化成功
}
/*头插法创建双链表*/
int create_HeadInsert(DLinkList *L,ElemType a[],int n){
// InitList_DLink(L);
DNode *s;//用来指示新创建的结点
int i;
for(i=n-1;i>=0;i--){//由于D头插法是倒序插入,所以这里我们从数组最后开始遍历
s=(DLinkList)malloc(sizeof(DNode));//创建一个新结点
s->data=a[i];//为结点赋值
s->next=(*L)->next;//让该结点的后继指向第一个结点
if((*L)->next!=NULL) (*L)->next->prior=s;//第一个结点的前驱指向该结点
s->prior=(*L);//让该结点的前驱指向头结点
(*L)->next=s;//让头结点指向该D结点(该结点成为第一个结点)
}
}
/*尾插法创建双链表*/
int create_TailInsert(DLinkList *L,ElemType a[],int n){
// InitList_DLink(L);
DNode *s,*r=(*L); //s用来指示新创建的结点,r用来移动以连接链表
int i;
for(i=0;i<n;i++){//遍历数组
s=(DLinkList)malloc(sizeof(DNode));//创建一个新结点
s->data=a[i];//为结点赋值
r->next=s;//r始终指向链表的尾端,这里将新结点连接到r的后面
s->prior=r;//该结点的前驱指向r指向的结点
r=s;//r移动到链表尾端o
}
r->next=NULL;//结束时,最后一个结点的指针域赋值为空
}
/*求表长*/
int Length_DLink(DLinkList L){
int i=0;//空表返回0
while (L->next!=NULL){//遍历,直到尾结点,空表不进入循环
L=L->next;//向后移动一个
i++;//表长加一
}
return i;//返回表长
}
/*插入*/
int ListInsert_DLink(DLinkList *L,int i,ElemType e){
int j=0;//空表序号为0
DNode *p=(*L);//p指向头结点
DNode *s;//s用来指示新创建的结点
while (p&&j<i-1){//找到被插入序号的前一个位置
p=p->next;//向后移动一个
j++;
}
if(!p||i<1) return FALSE;//序号在无效范围内
s=(DLinkList)malloc(sizeof(DNode));//创建一个新结点
s->data=e;//赋值
s->next=p->next;//让该结点指向p(插入位置的前一个结点)的下一个结点
if (p->next!=NULL) p->next->prior=s;//让p的下一个结点的前驱指向该结点
s->prior=p;//让该结点的前D驱指向p
p->next=s; //让p指向该结点
}
/*删除*/
int ListDelete_DLink(DLinkList *L,int i,ElemType *e){
int j=0;//空表序号为0
DNode *p=(*L);//p指向头结点
DNode *q;//q用来指向被删除的结点
while (p->next&&j<i-1){//找到要被删除结点的前一个结点
p=p->next;//向后移动一个
j++;
}
if (!(p->next)||i<1) return FALSE;//序号在无效范围内
q=p->next;//让q指向被删除的结点
p->next=q->next;//让p(被删除结点的前一个结点)指向被删除结点的下一个结点,q从链表中断开
if(q->next!=NULL)q->next->prior=p;
*e=q->data;//把被删除的结点的值返回
free(q);//释放该结点
return TRUE; //删除成功
}
/*按位查找*/
int GetElem_DList(DLinkList L,int i,ElemType *e){
DNode *p=L; //p指向头结点
int j=0;//空表序号为0
while (p&&j<i){//找到第i个位置
p=p->next;//向后移动一个
++j;
}
if(!(p)||i<1) return FALSE; //i不在有效范围内
*e=p->data; //把第i个结点的值用e返回
return TRUE;
}
/*按值查找*/
int LocateElem_DList(DLinkList L,ElemType e,int *i){
int j=0;//空表序号为0
while (L->next!=NULL){//遍历链表
L=L->next;//向后移动
j++;//序号加一
if (L->data.id==e.id){//这里我们只用id来判断元素的值是否相等,假设id唯一
*i=j;//如果相等,用i返回序号
return TRUE;//查找成功
}
}
return FALSE;
}
/*打印*/
void PrintList_DLink(DLinkList L){
int i=0;//空表序号为0
while (L->next!=NULL)//遍历表
{
L=L->next;
i++;
printf("第%d行:id=%d,name=%s,description=%s\n",i,L->data.id,L->data.name,L->data.description);
}
}
/*销毁双链表*/
void DestroyDList(DLinkList *L){
while((*L)->next!=NULL) {//遍历
DNode *p=(*L)->next;
(*L)->next=p->next;
if(p->next!=NULL) p->next->prior=(*L);
free(p);
}
}
int main(){
DLinkList L;
int i=InitList_DLink(&L);
if(i==1){
printf("初始化成功\n");
}
printf("\n");
///
ElemType a[4]={
{1,"史强","最强辅助"},
{2,"章北海","我不需要钢印,我是自己思想的主人"},
{3,"罗辑","人类不感谢罗辑"},
{4,"维德","失去人性,失去很多。失去兽性,失去一切"}
};
//create_HeadInsert(&L,a,4);
create_TailInsert(&L,a,4);
PrintList_DLink(L);
printf("\n");
///
ElemType e={5,"xxx","xxxxxxxxxxxxxxxxxxxx"};
ListInsert_DLink(&L,5,e);
PrintList_DLink(L);
printf("\n");
///
ListDelete_DLink(&L,5,&e);
PrintList_DLink(L);
printf("被删除的元素:id=%d,name=%s,description=%s\n",e.id,e.name,e.description);
printf("\n");
///
GetElem_DList(L,4,&e);
printf("查询到的元素:id=%d,name=%s,description=%s\n",e.id,e.name,e.description);
printf("\n");
//
LocateElem_DList(L,a[2],&i);
printf("查询到的位序为:%d\n",i);
printf("\n");
i=Length_DLink(L);
printf("表长为:%d\n",i);
printf("\n");
//
DestroyDList(&L);
PrintList_DLink(L);
}
运行结果:
初始化成功
第1行:id=1,name=史强,description=最强辅助
第2行:id=2,name=章北海,description=我不需要钢印,我是自己思想的主人
第3行:id=3,name=罗辑,description=人类不感谢罗辑
第4行:id=4,name=维德,description=失去人性,失去很多。失去兽性,失去一切
第1行:id=1,name=史强,description=最强辅助
第2行:id=2,name=章北海,description=我不需要钢印,我是自己思想的主人
第3行:id=3,name=罗辑,description=人类不感谢罗辑
第4行:id=4,name=维德,description=失去人性,失去很多。失去兽性,失去一切
第5行:id=5,name=xxx,description=xxxxxxxxxxxxxxxxxxxx
第1行:id=1,name=史强,description=最强辅助
第2行:id=2,name=章北海,description=我不需要钢印,我是自己思想的主人
第3行:id=3,name=罗辑,description=人类不感谢罗辑
第4行:id=4,name=维德,description=失去人性,失去很多。失去兽性,失去一切
被删除的元素:id=5,name=xxx,description=xxxxxxxxxxxxxxxxxxxx
查询到的元素:id=4,name=维德,description=失去人性,失去很多。失去兽性,失去一切
查询到的位序为:3
表长为:4