1 前言
上一篇文章主要描述单链表的创建、插入、删除、查找、清空、销毁操作和C语言的实现。本文描述双向链表的操作和C语言实现,并与单链表的操作进行比较。
2 什么是双向链表
双向链表属于链表的一种,俗称为双链表。双向链表每个数据节点都存在两个指针域,分别指向节点的直接后继和直接前驱;最后一个节点后继指针指向NULL。双向链表与单链表一样,也分为带头节点和不带头节点;如果是存在头节点的双向链表,第一个节点前驱指针指向头节点;如果是不存在头节点的双向链表,第一个节点的前驱指针指向NULL。双向链表中的任意一个节点开始,可以很方便地遍历它的前驱节点和后继节点 。
因此,一个双向链表的节点元素数结构可以这样表示:
struct _doubly_link_list
{
int data; /* 有效数据 */
struct _doubly_link_list *pnext;/* 后继指针域 */
struct _doubly_link_list *pfront;/* 前驱指针域 */
};
2.1 双向链表与单链表异同
相同点:
- 都属于链表一种
- 插入、删除时间复杂度都是O(1)
- 支持链式访问
- 支持带头节点和不带头节点
不同点:
- 组成结构不同,双向链表带前驱和后继指针;单链表只有后继指针
- 访问方向不同,双向链表支持前驱、后继访问;单链表支持后继访问
- 访问效率不同, 单链表节点无法直接访问其前驱节点,逆序访问单链表时效率低
- 空表判断方式不同
/* 带头节点双向链表判断空表 */
if ((head->font==NULL) && (head->next==NULL))
{
/* todo */
}
/* 不带头节点双向链表判断空表,与单链表判断方式相同 */
if (head->font==NULL)
{
/* todo */
}
2.2 双向链表优点
双向链表的最大优点是支持前驱、后继的双向遍历。只要知道任一节点,就能以该节点为基准,往节点前驱或者后继遍历,遍历效率要比只能往前驱遍历的单链表高。
3 双向链表创建
双向链表创建,一般是创建一个空的链表,这里我们创建一个带头节点的空链表。
doubly_link_list_t* create_doubly_link_list(void)
{
doubly_link_list_t* head = NULL;
head = (doubly_link_list_t*)malloc(sizeof(doubly_link_list_t));
if (head != NULL)
{
head->data=0; /* 头结数据域为空 */
head->pnext = NULL;
head->pfont = NULL;
}
return head;
}
注:
带头节点的双向链表空表,头节点的前驱指针域和后继指针域都指向NULL。
4 双向链表清空与销毁
链表清空指的是删除链表有效节点,释放节点内存空间。链表销毁指的是删除所有节点,包括头结点,并释放节点内存空间。双向链表清空与销毁都是遍历整个链表。
双向链表清空伪代码:
int clear_doubly_link_list(doubly_link_list_t *list)
{
doubly_link_list_t *p = NULL;
doubly_link_list_t *q = NULL;
p = list->pnext;
while(p != NULL)
{
q = p->pnext;
free(p);
p = q;
}
list->pnext=NULL;
return 0;
}
双向链表销毁伪代码:
int destory_doubly_link_list(doubly_link_list_t *list)
{
doubly_link_list_t *p = NULL;
while(list != NULL)
{
p = list->pnext;
free(list);
list = p;
}
list = NULL;
return 0;
}
5 双向链表查找
双向链表与单链表的查找方式一样,有两种方式,分别是根据元素索引号和节点数据元素值查找,这两种方式都需要从链表头开始遍历链表,直至查找到指定节点。对于元素值查找,只适用于链表中存储的元素值都是唯一的情况,否则只能使用节点索引号查找。
如下图,查找一个带头节点双向链表的第二个节点,可以通过索节点引号[1]查找或者通过唯一的节点数据元素[a1]查找。
C语言实现伪代码:
/* 通过节点索引号查找 */
doubly_link_list_t *get_doubly_link_list_node_pos(doubly_link_list_t *list, int pos)
{
doubly_link_list_t *p = NULL;
p = list;
while(pos>=0)
{
p = p->pnext;
if (p == NULL)
{
break;
}
pos--;
}
return p;
}
/* 通过节点数据元素查找 */
doubly_link_list_t *get_doubly_link_list_node_elem(doubly_link_list_t *list, int elem)
{
doubly_link_list_t *p = NULL;
p = list->pnext;
while(p!=NULL)
{
if (p->data == elem)
{
return p;
}
p = p->pnext;
}
return NULL;
}
6 双向链表插入
双向链表插入时间复杂度为O(1)。与单链表的插入类型一样,分为三种。
- 表头插入,无需遍历链表
- 表尾插入,无需遍历链表
- 表中间插入,需遍历链表,即是查找操作
双向链表插入步骤与单链表稍有不同,主要区别是需处理前驱指针的指向:
【1】查找到插入位置节点的前一节点,可通过节点索引号或者唯一节点数据元素查找
【2】申请待插入新节点内存并赋值
【3】新节点后继指针域指向插入位置的原节点,如下图第1步
【4】新节点前驱指针域指向插入位置原节点的前一节点,如下图第2步
【5】插入位置原节点的前驱指针域指向新节点,如下图第3步
【6】插入位置原节点的前一节点的后继节指针域指向新节点,如下图第4步
C语言实现伪代码:
int insert_doubly_link_list_node_pos(doubly_link_list_t *list, int value, int pos)
{
doubly_link_list_t *p = NULL;
doubly_link_list_t *node = NULL;
p = get_doubly_link_list_node_pos(list, pos); /* 获取插入位置的前一结点 */
if (p == NULL)
{
return -1;
}
node = (doubly_link_list_t*)malloc(sizeof(doubly_link_list_t));
if (node == NULL)
{
return -1;
}
node->data = value;
node->pnext = p->pnext;
node->pfront = p;
if (p->pnext != NULL)
{
/* 非表尾插入 */
p->pnext->pfront = node;
}
p->pnext = node;
return 0;
}
对于表头或者表尾的插入操作,与上述表间插入操作过程步骤一致。对表尾插入操作,图中的第3步可以省略,因为此时插入的新节点作为尾节点,下一节点为“NULL”。
7 双向链表删除
双向链表删除时间复杂度为O(1)。双向链表删除与插入是一个相反的的过程,删除类型有三种,与插入类型对应。实质上,三种类型可以认为是一种类型,并且实现方式都是一样的。
- 表头删除
- 表尾删除
- 表中间删除
双向链表删除步骤与单链表稍有不同,主要区别是需处理前驱指针的指向:
【1】查找到删除位置的节点,可通过节点索引号或者唯一节点数据元素查找
【2】删除位置节点的前一节点的后继指针域指向其后一节点,如下图第1步
【3】删除位置节点的后一节点的前驱指针域指向其前一节点,如下图第2步
【4】释放删除节点内存
C语言实现伪代码:
int delete_doubly_link_list_node_pos(doubly_link_list_t *list, int pos)
{
doubly_link_list_t *p = NULL;
p = get_doubly_link_list_node_pos(list, pos); /* 获取删除位置的结点 */
if (p!=NULL)
{
p->pfont->pnext = p->pnext;
if (p->pnext != NULL)
{
/* 非表尾删除 */
p->pnext->pfront = p->pfront;
}
free(p);
}
else
{
return -1;
}
}
- 与单链表删除操作比较,单链表删除一个节点需查找到目标节点的前一节点,然后执行删除操作;而双向链表可以直接查找到目标节点进行删除操作。因为,单链表只支持后继访问,所以只能在目标节点的前一节点操作。双向链表支持前驱、后继访问,则不需要。这也是体现双向链表一个便捷性之一。
对于表头或者表尾的删除操作,与上述表间删除操作过程步骤一致。对表尾删除操作,图中的第2步可以省略,因为删除尾节点后,前一节点作为尾节点,下一节点为“NULL”。
8 实例
- 实现一个带头节点的双向链表,头节点不纳入链表长度计算
- 提供双向链表创建、长度查询、空表检查、插入、删除、查找、清空、销毁操作接口
- 提供节点索引号和节点数据元素查找实现方法
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <stdbool.h>
typedef struct _doubly_link_list
{
int data;
struct _doubly_link_list *pnext; /* 后继指针域 */
struct _doubly_link_list *pfront; /* 前驱指针域 */
}doubly_link_list_t;
doubly_link_list_t* create_doubly_link_list(void)
{
doubly_link_list_t* head = NULL;
head = (doubly_link_list_t*)malloc(sizeof(doubly_link_list_t));
if (head != NULL)
{
head->data=0; /* 头结数据域为空 */
head->pnext = NULL;
head->pfront = NULL;
}
return head;
}
bool check_doubly_link_list_empty(doubly_link_list_t *list)
{
if (list == NULL)
{
return true;
}
if ((list->pfront==NULL) && (list->pnext==NULL))
{
return true;
}
else
{
return false;
}
}
int destory_doubly_link_list(doubly_link_list_t *list)
{
doubly_link_list_t *p = NULL;
if (list==NULL)
{
return 0;
}
while(list != NULL)
{
p = list->pnext;
free(list);
list = p;
}
list = NULL;
return 0;
}
int clear_doubly_link_list(doubly_link_list_t *list)
{
doubly_link_list_t *p = NULL;
doubly_link_list_t *q = NULL;
if (list==NULL)
{
return 0;
}
if (check_doubly_link_list_empty(list))
{
return 0;
}
p = list->pnext;
while(p != NULL)
{
q = p->pnext;
free(p);
p = q;
}
list->pnext=NULL;
return 0;
}
int get_doubly_link_list_capacity(doubly_link_list_t *list)
{
doubly_link_list_t *p = NULL;
int size = 0;
if (list == NULL)
{
return 0;
}
p = list->pnext;
while(p != NULL)
{
size++;
p = p->pnext;
}
return size;
}
doubly_link_list_t *get_doubly_link_list_node_pos(doubly_link_list_t *list, int pos)
{
doubly_link_list_t *p = NULL;
if ((list==NULL) || (pos<0))
{
return NULL;
}
p = list;
while(pos>=0)
{
p = p->pnext;
if (p == NULL)
{
break;
}
pos--;
}
return p;
}
doubly_link_list_t *get_doubly_link_list_node_elem(doubly_link_list_t *list, int elem)
{
doubly_link_list_t *p = NULL;
if ((list==NULL) || (list->pnext==NULL))
{
return NULL;
}
p = list->pnext;
while(p!=NULL)
{
if (p->data == elem)
{
return p;
}
p = p->pnext;
}
return NULL;
}
int insert_doubly_link_list_node_pos(doubly_link_list_t *list, int value, int pos)
{
doubly_link_list_t *p = NULL;
doubly_link_list_t *node = NULL;
if ((list==NULL) || (pos<0))
{
return -1;
}
if (pos == 0)
{
p = list; /* 插入第一个节点位置 */
}
else
{
p = get_doubly_link_list_node_pos(list, pos-1); /* 获取插入位置的前一结点 */
}
if (p == NULL)
{
return -1;
}
node = (doubly_link_list_t*)malloc(sizeof(doubly_link_list_t));
if (node == NULL)
{
return -1;
}
node->data = value;
node->pnext = p->pnext;
node->pfront = p;
if (p->pnext != NULL)
{
/* 非表尾插入 */
p->pnext->pfront = node;
}
p->pnext = node;
return 0;
}
int insert_doubly_link_list_node_elem(doubly_link_list_t *list, int value, int elem)
{
doubly_link_list_t *p = NULL;
doubly_link_list_t *node = NULL;
if (list==NULL)
{
return -1;
}
p = get_doubly_link_list_node_elem(list, elem); /* 获取插入位置结点 */
if (p == NULL)
{
/* 空表,从头节点开始插入 */
p = list;
}
node = (doubly_link_list_t*)malloc(sizeof(doubly_link_list_t));
if (node == NULL)
{
return -1;
}
node->data = value;
node->pnext = p->pnext;
node->pfront = p;
if (p->pnext != NULL)
{
/* 非表尾插入 */
p->pnext->pfront = node;
}
p->pnext = node;
return 0;
}
int delete_doubly_link_list_node_pos(doubly_link_list_t *list, int pos)
{
doubly_link_list_t *p = NULL;
if ((list==NULL) || (pos<0))
{
return -1;
}
if (check_doubly_link_list_empty(list))
{
return -1;
}
p = get_doubly_link_list_node_pos(list, pos); /* 获取删除位置的结点 */
if (p!=NULL)
{
p->pfront->pnext = p->pnext;
if (p->pnext != NULL)
{
/* 非表尾删除 */
p->pnext->pfront = p->pfront;
}
free(p);
}
else
{
return -1;
}
}
int delete_link_list_node_elem(doubly_link_list_t *list, int elem)
{
doubly_link_list_t *p = NULL;
if (list==NULL)
{
return -1;
}
if (check_doubly_link_list_empty(list))
{
return -1;
}
p = get_doubly_link_list_node_elem(list, elem); /* 获取删除位置的结点 */
if (p!=NULL)
{
p->pfront->pnext = p->pnext;
if (p->pnext != NULL)
{
/* 非表尾删除 */
p->pnext->pfront = p->pfront;
}
free(p);
}
else
{
return -1;
}
}
int main(int argc, char *argv[])
{
doubly_link_list_t *linklist = NULL;
doubly_link_list_t *ptemp;
int elem = 0;
int i;
/* 创建双向链表 */
linklist = create_doubly_link_list();
/* 插入操作 */
insert_doubly_link_list_node_pos(linklist, 0, 0);
insert_doubly_link_list_node_pos(linklist, 1, 1);
insert_doubly_link_list_node_pos(linklist, 3, 0);
insert_doubly_link_list_node_pos(linklist, 5, 1);
printf("doubly link list capacity:[%d]\n", get_doubly_link_list_capacity(linklist));
for(i=0; i<get_doubly_link_list_capacity(linklist); i++)
{
/* 查找操作 */
ptemp = get_doubly_link_list_node_pos(linklist, i);
printf("doubly link list node[%d]=%d\n", i, ptemp->data);
}
/* 删除操作 */
printf("delete doubly link list node[2]\n");
delete_doubly_link_list_node_pos(linklist, 2);
printf("doubly link list capacity:[%d]\n", get_doubly_link_list_capacity(linklist));
for(i=0; i<get_doubly_link_list_capacity(linklist); i++)
{
/* 查找操作 */
ptemp = get_doubly_link_list_node_pos(linklist, i);
printf("doubly link list node[%d]=%d\n", i, ptemp->data);
}
destory_doubly_link_list(linklist); /* 销毁双向链表 */
}
编译执行
- 在Ubuntu16.04下执行结果
acuity@ubuntu:/mnt/hgfs/LSW/STHB/temp$ gcc doubly_link_list.c -o doubly_link_list
acuity@ubuntu:/mnt/hgfs/LSW/STHB/temp$ ./doubly_link_list
doubly link list capacity:[4]
doubly link list node[0]=3
doubly link list node[1]=5
doubly link list node[2]=0
doubly link list node[3]=1
delete doubly link list node[2]
doubly link list capacity:[3]
doubly link list node[0]=3
doubly link list node[1]=5
doubly link list node[2]=1