双向链表的实现

1.链表的分类

链表分类有八种:

带头     不带头

单向     双向

循环     不循环(8种);

最常见的是两种:

单链表(不带头单向不循环链表)

双向链表(带头双向循环链表)

2.双向链表的结构

注意:这里的”带头“跟我们前面说的“头节点”是两个概念,实际前面在单链表阶段称呼不严谨。带头链表里的头节点,实际为“哨兵位”,哨兵位节点不存储任何有效数据,只是站在这里”放哨的“

哨兵位存在的意义:遍历链表避免死循环

3.双向链表的实现

1.list.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int datatype;

typedef struct ListNode//双向链表的节点
{
	datatype data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;
//链表初始化
void LTInit(ListNode** pphead);
//链表销毁
void LTDestroy(ListNode* phead);
//链表打印
void LTPrint(ListNode* phead);
//头插和尾插
void LTPushback(ListNode* phead, datatype x);
void LTPushfront(ListNode* phead, datatype x);

//头删和尾删
void LTPopback(ListNode* phead);
void LTPopfront(ListNode* phead);

//在指定位置之前插入数据
void LTInsret(ListNode* pos, datatype x);
//删除pos节点
void LTErase(ListNode* pos);
//查找pos
ListNode* LTFind(ListNode* phead, datatype x);

2.list.c

初始化和链表打印

#include"list.h"
//创建新节点
ListNode* LTBuyNode(datatype x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	node->data = x;
	node->next = node->prev = node;
	return node;
}
//初始化
void LTInit(ListNode** pphead)
{
	*pphead = LTBuyNode(-1);
}

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

双向链表为空时,此时链表中只剩一个头节点(哨兵位)。

所以初始化时next指针和prev指针不能指向空,应指向phead形成循环。

尾插

//尾插
void LTPushback(ListNode* phead, datatype x)
{
	assert(phead);
	ListNode* newnode = LTBuyNode(x);

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

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

头插

//头插
void LTPushfront(ListNode* phead, datatype x)
{
	assert(phead);
	ListNode* newnode = LTBuyNode(x);
	newnode->prev = phead;
	newnode->next = phead->next;

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

}

头删与尾删

//尾删
void LTPopback(ListNode* phead)
{
	assert(phead && phead->next != phead);
	ListNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;
}
//头删
void LTPopfront(ListNode* phead)
{
	assert(phead && phead->next != phead);
	ListNode* del = phead->next;

	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

查找pos节点

//查找pos
ListNode* LTFind(ListNode* phead, datatype x)
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

在指定位置之前插入数据

//在指定位置之前插入数据
void LTInsret(ListNode* pos, datatype x)
{
	assert(pos);
	ListNode* newnode = LTBuyNode(x);

	newnode->next = pos;
	newnode->prev = pos->prev;

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

删除pos节点

//删除pos节点
void LTErase(ListNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;

	free(pos);
	pos = NULL;
}

链表销毁

//链表销毁
void LTDestroy(ListNode* phead)
{
	assert(phead);
	ListNode* pcur = phead->next;
	while (pcur != phead)
	{
		ListNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

删除pos节点和销毁链表理论上应该传二级指针,因为我们要让形参的改变影响实参,但为了保持接口一致性才传一级指针。

传一级的问题时,当形参phead置为NULL后,实参plist不会被修改位NULL,因此解决方法是:调用方法后手动将实参置为空。

3.test.c

#include"list.h"

void test01()
{
	ListNode* plist = NULL;
	LTInit(&plist);


	/*LTPushback(plist, 1);
	LTPrint(plist);
	LTPushback(plist, 2);
	LTPrint(plist);
	LTPushback(plist, 3);
	LTPrint(plist);
	LTPushback(plist, 4);
	LTPrint(plist);*/
	LTPushfront(plist, 1);
	LTPrint(plist);
	LTPushfront(plist, 2);
	LTPrint(plist);
	LTPushfront(plist, 3);
	LTPrint(plist);
	LTPushfront(plist, 4);
	LTPrint(plist);

	/*LTPopback(plist);
	LTPrint(plist);
	LTPopback(plist);
	LTPrint(plist);
	LTPopback(plist);
	LTPrint(plist);
	LTPopback(plist);
	LTPrint(plist);
	LTPopback(plist);*/

	/*LTPopfront(plist);
	LTPrint(plist);
	LTPopfront(plist);
	LTPrint(plist);
	LTPopfront(plist);
	LTPrint(plist);
	LTPopfront(plist);
	LTPrint(plist);
	LTPopfront(plist);*/

	ListNode* find = LTFind(plist, 3);
	if (find == NULL)
	{
		printf("没有找到\n");
	}
	else
	{
		printf("找到了\n");
	}

	/*LTErase(find);
	LTPrint(plist);*/
	LTInsret(find, 66);
	LTPrint(plist);
	LTDestroy(plist);
	find = NULL;
	plist = NULL;
}

int main()
{
	test01();

	return 0;
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值