数据结构回顾——双向链表操作详解及C语言实现


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

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/108066834