数据结构之双链表

为什么需要双链表

单链表相对于顺序表在空间复杂度上有了很大的提升,但是它在时间复杂度上还有很大缺陷

  • 尾插,尾删时需要遍历整个链表找到尾结点或其前一个结点,这样就造成了时间上的浪费
  • 在指定位置插入数据时需要找到前一个结点,有种多此一举的感觉

为了解决上述问题,我们引入了双链表

双链表的结构

在这里插入图片描述

项目的创建

创建三个文件SList.h , SList.c , test.c

  • SList.h:头文件的包含,结构体定义,函数的声明
  • SList.c:函数的实现
  • test.c:测试程序

在这里插入图片描述

双链表的功能实现

结点的定义

从上面的结构图我们可以知道结构体内容

  • 指向前一结点的指针
  • 存放数据的空间
  • 指向后一结点的指针
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

双链表初始化

创建一个头结点,并使其头指针和尾指针都指向其自己

ListNode* ListCreate(LTDataType x)
{
	//创建
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	node->data = x;
	node->next = NULL;
	node->prev = NULL;

	return node;
}

ListNode* ListInit()
{
	ListNode* phead = ListCreate(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

双链表打印

遍历打印头结点之后的数据

void ListPrint(ListNode* pHead)
{
	assert(pHead);

	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

双链表判空

判断双链表是否不存储数据

bool ListEmpty(ListNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

双链表查找

遍历链表找到指定数值,将其结点地址返回

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	ListNode* cur = pHead->next;
	//遍历
	while (cur != pHead)
	{
		//找到返回地址
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}
	//找不到返回空指针
	return NULL;
}

指定位置插入

思路

  1. 记录该结点前一个结点
  2. 创建一个新节点
  3. 将新结点连入双链表

在这里插入图片描述

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* newnode = ListCreate(x);
	ListNode* prev = pos->prev;

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

指定位置删除

思路

  1. 记录指定结点的前后结点
  2. 删除指定结点
  3. 将前后结点相连

在这里插入图片描述

void ListErase(ListNode* pos)
{
	assert(pos);
	//记录指定结点前后结点
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	free(pos);
	//连接前后结点
	prev->next = next;
	next->prev = prev;
}

双链表尾插,尾删

上文在指定结点插入删除,尾插,尾删可利用该函数

尾插

void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead, x);
}

检测
在这里插入图片描述

尾删

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	ListErase(pHead->prev);
}

检测
在这里插入图片描述

双链表头插,头删

和尾插,尾删类似

头插

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead->next, x);
}

检测
在这里插入图片描述

头删

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	ListErase(pHead->next);
}

检测
在这里插入图片描述

计算结点个数

遍历计数

size_t ListSize(ListNode* phead)
{
	assert(phead);
	size_t n = 0;
	ListNode* cur = phead->next;
	while (cur!=phead)
	{
		++n;
		cur = cur->next;
	}
	return n;
}

检测
在这里插入图片描述

双链表的销毁

遍历释放即可

void ListDestory(ListNode* pHead)
{
	ListNode* cur = pHead;
	while (cur != pHead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(pHead);
	pHead = NULL;
}

总结

SList.h文件

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

//初始化
//void ListInit(ListNode** pphead);
ListNode* ListInit();
// 创建返回链表的头结点.
ListNode* ListCreate(LTDataType x);
// 双向链表打印
void ListPrint(ListNode* pHead);
//判空
bool ListEmpty(ListNode* phead);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
//计算链表的个数
size_t ListSize(ListNode* phead);
// 双向链表销毁
void ListDestory(ListNode* pHead);

SList.c文件

#include "SList.h"

ListNode* ListCreate(LTDataType x)
{
	//创建
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	node->data = x;
	node->next = NULL;
	node->prev = NULL;

	return node;
}

ListNode* ListInit()
{
	ListNode* phead = ListCreate(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

void ListPrint(ListNode* pHead)
{
	assert(pHead);

	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

bool ListEmpty(ListNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	ListNode* cur = pHead->next;
	//遍历
	while (cur != pHead)
	{
		//找到返回地址
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}
	//找不到返回空指针
	return NULL;
}

void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);

	ListNode* newnode = ListCreate(x);
	ListNode* prev = pos->prev;

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

void ListErase(ListNode* pos)
{
	assert(pos);
	//记录指定结点前后结点
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	free(pos);
	//连接前后结点
	prev->next = next;
	next->prev = prev;
}

void ListPushBack(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead, x);
}

void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));

	//ListNode* tail = pHead->prev;
	//ListNode* tailPrev = tail->next;
	//free(tail);
	//tailPrev->next = pHead;
	//pHead->prev = tailPrev;
	ListErase(pHead->prev);
}

void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListInsert(pHead->next, x);
}

void ListPopFront(ListNode* pHead)
{
	assert(pHead);
	assert(!ListEmpty(pHead));
	ListErase(pHead->next);
}

size_t ListSize(ListNode* phead)
{
	assert(phead);
	size_t n = 0;
	ListNode* cur = phead->next;
	while (cur!=phead)
	{
		++n;
		cur = cur->next;
	}
	return n;
}

void ListDestory(ListNode* pHead)
{
	ListNode* cur = pHead;
	while (cur != pHead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}

	free(pHead);
	pHead = NULL;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值