数据结构--双向链表

        链表的结构⾮常多样,主要分为带头与不带头、单向与双向、循环与不循环。三个种类可以任意搭配,所以总共可以形成八种链表,但是最常用的是单向不带头不循环链表和双向带头循环链表。

        1. ⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。另外这种结构在笔试⾯试中出现很多。

        2. 带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使⽤代码实现以后会发现结构会带
来很多优势,实现反⽽简单了,后⾯我们代码实现了就知道了。

        前者即单链表,在前一篇文章中已经讲过了,所以我们再来看看双向链表。

         注意:这⾥的“带头”跟前⾯我们说的“头节点”是两个概念,实际前⾯的在单链表阶段称呼不严
谨,但是为了同学们更好的理解就直接称为单链表的头节点。带头链表⾥的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效元素,只是站在这⾥“放哨的”。
        “哨兵位”存在的意义:遍历循环链表避免死循环。

双向链表的实现

文件:DoubleListNode.h

//双链表结构体创建
typedef int slDataType;
typedef struct DoubleList
{
	slDataType data;
	struct DoubleList* next;
	struct DoubleList* prev;
}DLN;

//双向链表的初始化
void DbleInit(DLN** pphead);

//双向链表打印
void DLNPrint(DLN* phead);

//双向链表尾插函数
void DbleTailAdd(DLN* phead,int data);

//双向链表头插函数
void DbleHeadAdd(DLN* phead, slDataType data);

//双向链表头删函数
void DbleHeadDel(DLN* phead);

//双向链表尾删函数
void DbleTailDel(DLN* phead);

//双向链表查找
DLN* DbleSearch(DLN* phead);

//双向链表修改函数
void DbleModify(DLN* phead);

//双向链表销毁函数
void DbleStroy(DLN* phead);

文件DoubleListNode.c

//申请新节点
DLN* ListByNode(slDataType data)
{
	DLN* newnode = (DLN*)malloc(sizeof(DLN));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->data = data;
	return newnode;
}

//双向链表的初始化
void DbleInit(DLN** pphead)
{
	*pphead = (DLN*)malloc(sizeof(DLN));
	if (*pphead == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	(*pphead)->data = -1;
	(*pphead)->next = (*pphead);
	(*pphead)->prev = (*pphead);
}

//双向链表打印
void DLNPrint(DLN* phead)
{
	assert(phead);
	DLN* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->",pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

//双向链表尾插函数
void DbleTailAdd(DLN* phead, slDataType data)
{
	assert(phead);
	DLN* newnode = ListByNode(data);
	newnode->data = data;
	//尾插新节点
	newnode->next = phead;
	newnode->prev = phead->prev;

	phead->prev->next = newnode;
	phead->prev = newnode;
}

//双向链表头插函数
void DbleHeadAdd(DLN* phead, slDataType data)
{
	assert(phead);
	DLN* newnode = ListByNode(data);

	newnode->next = phead->next;
	newnode->prev = phead;
	phead->next->prev = newnode;
	phead->next = newnode;
}

//双向链表头删函数
void DbleHeadDel(DLN* phead)
{
	assert(phead);
	DLN* pcur = phead->next;
	if (pcur == phead)
	{
		printf("删除失败,不存在有效节点\n");
	}
	else
	{
		phead->next = pcur->next;
		pcur->next->prev = phead;
		free(pcur);
		pcur = NULL;
	}
}

//双向链表尾删函数
void DbleTailDel(DLN* phead)
{
	assert(phead);
	DLN* pcur = phead->next;
	while (pcur->next != phead)
	{
		pcur = pcur->next;
	}
	phead->prev = pcur->prev;
	pcur->prev->next = phead;
	free(pcur);
	pcur = NULL;
}

//双向链表查找
DLN* DbleSearch(DLN* phead,slDataType pos)
{
	assert(phead);
	DLN* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == pos)
		{
			printf("找到了\n");
			return pcur;
		}
		pcur = pcur->next;
	}
	printf("没找到\n");
	return NULL;
}

//双向链表修改函数
void DbleModify(DLN* phead,slDataType pos,slDataType data)
{
	DLN* find = DbleSearch(phead,pos);
	if (find != NULL)
	{
		find->data = data;
		printf("修改成功\n");
		return;
	}
	else
	{
		printf("修改失败,未找到该节点\n");
	}
}

//双向链表销毁函数
void DbleStroy(DLN* phead)
{
	assert(phead);
	DLN* pcur = phead->next;
	DLN* next = pcur->next;
	while (pcur != phead)
	{
		free(pcur);
		pcur = next;
		next = next->next;
	}
	printf("销毁成功\n");
}

        这里单独讲解一个点,我们在创建“哨兵位”的时候使用的参数是一级指针的地址,并用二级指针接收,目的是创建一个节点的地址用作节点的链表头部,之后的其他函数均使用一级指针是为了防止头节点被修改。

        测试代码由各位自行书写,我们下期间。

上一篇:【数据结构】排序-归并排序


下一篇:Linux逻辑方式合并物理磁盘