C -- 双向链表

本文介绍了如何在C语言中创建双向链表,包括建立抽象类型、定义接口和实现接口的过程。作者分享了自己对双向链表优势的理解,指出双向链表在查找时提供了逆序查找的可能性,方便增删改等操作,但也可能在实现插入操作时增加复杂性。最后,作者强调选择单向或双向链表应根据实际需求来定。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C双向链表

第一步:建立抽象类型,定义接口

//实现双向链表
#ifndef _LIST_H
#define _LIST_H

#include <stdbool.h>
#define SIZE 20

//抽象一个数据类型
typedef struct _item
{
	char title[20];
	double value;
}Item;
//定义节点,每个结点包括指向上一个和指向下一个结点的指针
typedef struct _node
{
	struct _node * last;
	Item item;
	struct _node * next;
}Node;
//定义双向链表类型,成员包括头指针、尾部指针
typedef struct _list
{
	Node * head;
	Node * tail;
}List;

//已定义一个指向链表的指针,将链表初始化
void InitializeList(List * ls);

//判断链表是否已满,(内存是否用尽)
bool ListIsFull(const List * ls);

//判断链表是否为空
bool ListIsEmpty(const List * ls);

//返回链表中项数
unsigned int CountSize(const List * ls);

//已初始化的链表中添加结点
void AddToList(List * ls, Item item);

//非空的链表中,任意位置插入一个结点
void InsertToList(List * ls, int index, Item item);

//已初始化的链表中删除一个结点
void DelInList(List * ls, Item item);

//打印展示非空链表中的数据
void ShowInList(const List * ls);

#endif

第二步:实现接口

自己的一些想法,实现或许不是最优的,还有很多需要改进的地方。以后回过头再来看看。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"

static void CopyToNode(Node * p, Item item)
{
	/*	以具体Item类型变化		*/
	p->item = item;
}

void InitializeList(List * ls)
{
	ls->head = ls->tail = NULL;
	printf("Initialize successfully!\n");
}

bool ListIsEmpty(const List * ls)
{
	bool ret = false;

	if(ls->head == NULL && ls->tail == NULL)
		ret = true;

	printf("The list is %s\n",ret? "empty":"not empty");

	return ret;
}

bool ListIsFull(const List * ls)
{
	bool ret = false;

	Node * p = (Node *) malloc (sizeof(Node));
	if(p == NULL)
		ret = true;
	else
	{
		ret = false;
		free(p);
	}

	printf("The list is %s\n",ret? "full":"not full");
	return ret;
}

unsigned int CountSize(const List * ls)
{
	unsigned int count = 0;

	Node * p;
	for(p = ls->head;p;p = p->next)
		count++;
	printf("Totally %d node(s)!\n",count);

	return count;
}

void AddToList(List * ls, Item item)
{
	Node * p = (Node *) malloc (sizeof(Node));
	if(p == NULL)
	{
		fprintf(stderr, "Can't get a new node!\n");
		exit(1);
	}
	CopyToNode(p,item);
	p->last = p->next = NULL;

	if(ls->head == NULL)		//添加第一个双向结点
		ls->head = ls->tail = p;
	else						//添加后续
	{
		ls->tail->next = p;
		p->last = ls->tail;
		ls->tail = p;
	}

	printf("Add to list successfully!\n");
}

void InsertToList(List * ls, int index, Item item)
{
	Node * p = (Node *) malloc (sizeof(Node));
	if(p == NULL)
	{
		fprintf(stderr, "Can't get a new node!\n");
		exit(2);
	}
	CopyToNode(p, item);
	p->last = p->next = NULL;

	if(index < 1 || index > (CountSize(ls) + 1))
	{
		fprintf(stderr, "Can't insert an item to %d,\
please enter an index from (1 - %d)\n",index, CountSize(ls)+1);
		exit(3);
	}

	Node * q;
	int i;
	for(i = 1, q = ls->head;q;q = q->next, i++)
	{
		if(ListIsEmpty(ls))
		{
			ls->head = ls->tail = p;
			break;
		}else{
			if(index == 1)		//当插头部
			{
				p->next = ls->head;
				ls->head->last = p;
				ls->head = p;
				break;
			}else if(index == CountSize(ls) + 1)	//当插尾部
			{
				p->last = ls->tail;
				ls->tail->next = p;
				ls->tail = p;
				break;
			}else if(i == index)		//当插中间
			{
				q->last->next = p;
				p->last = q->last;
				p->next = q;
				q->last = p;
				break;
			}
		}
	}
	printf("Insert successfully!\n");
}

void DelInList(List * ls, Item item)
{
	Node * p;
	for(p = ls->head;p;p = p->next)
		/* 可依据检索条件变化比较方式	*/
		if(strcmp(p->item.title,item.title) == 0)
		{
			//删除头结点
			if(p == ls->head)
			{
				ls->head = p->next;
				ls->head->last = NULL;
				free(p);
				break;
			//删除尾结点
			}else if(p == ls->tail)
			{
				p->last->next = NULL;
				free(p);
				break;
			//删除中间的结点
			}else
			{
				p->last->next = p->next;
				p->next->last = p->last;
				free(p);
				break;
			}
		}
	printf("Delete successfully!\n");
}

void ShowInList(const List * ls)
{
	Node * p;
	if(ListIsEmpty(ls))
		printf("No data in list!\n");
	else{
		//正序输出
		for(p = ls->head;p;p = p->next)
			/*	以具体需要变化	*/
			printf("title: %s, value: %.1f\n",p->item.title,p->item.value);
		printf("\n");
		//逆序输出
		for(p = ls->tail;p;p = p->last)
			/*	以具体需要变化	*/
			printf("title: %s, value: %.1f\n",p->item.title,p->item.value);
		printf("\n");
	}
}

至于双向链表有何优势?

  • 双向与单向链表唯一的区别就是节点中多一个指向上一个的指针,单向节点中仅有指向下一个,而双向既有指向上一个也有指向下一个的指针。
  • 也就意味着我们在查找时双向提供了逆序查找的可能,这种查找方便给我们在编写其他接口如增删改等操作时提供了方便,也许会更方便。
  • 但也不一定方便,在实现任意插入的时候可能就会相对麻烦一些,因为插入时得考虑头插、尾插、中间插的区别,头插只需要将新结点的next指向原有的头指针处,last为NULL,将链表头移至新结点即可;尾插,需将新结点链接到链表原尾指针的next,并让新指针last指回上一个,新指针的next为NULL即可,并移动链表尾到新结点;而插在中间的情况,链表的头指针尾指针不需要移动,新指针的last要与上个位置指针双向指,next要与下个位置双向指;
  • 总的来说,单向双向各有优势,双向查找起来自然是很方便,比如可以顺序查找或者逆序查找,但实现其他接口时可能就需要多考虑一些,而单向你想逆序查找的话就比较麻烦了;主要还是依据需要选择ADT形式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值