list 链式表基本功能模拟实现(双向有头指针循环链表)

目录

链式表的分类方式

按节点连接方式分类

按存储结构分类

按功能扩展分类

带头双向循环动态链表模拟实现


链式表的分类方式

链式表(链表)根据不同的结构和特性,可以分为以下几类:

按节点连接方式分类
  1. 单向链表

    每个节点包含数据和指向下一个节点的指针,只能单向遍历。

    • 优点:结构简单,内存占用较小。
    • 缺点:无法反向遍历,删除节点需从头查找前驱节点。
  2. 双向链表

    每个节点包含数据、指向下一个节点的指针和指向前一个节点的指针。

    • 优点:支持双向遍历,删除和插入操作更高效。
    • 缺点:每个节点多占用一个指针空间,内存开销较大。
  3. 循环链表

    尾节点指向头节点,形成闭环。可分为单向循环链表和双向循环链表。

    • 优点:适合环形数据处理(如轮询调度)。
    • 缺点:需注意循环终止条件,避免无限循环。
按存储结构分类
  1. 静态链表

    使用数组模拟链表,通过数组下标代替指针。

    • 优点:无需动态内存分配,适合嵌入式系统等受限环境。
    • 缺点:容量固定,灵活性差。
  2. 动态链表

    节点通过动态内存分配(如malloc)创建,内存可动态扩展。

    • 优点:灵活性强,内存利用率高。
    • 缺点:需手动管理内存,易产生内存泄漏。
按功能扩展分类
  1. 带头节点的链表

    在链表头部添加一个不存储数据的节点(头节点),简化插入/删除操作。

    • 优点:统一操作逻辑,避免对头节点的特殊处理。
  2. 不带头节点的链表

    直接以第一个数据节点作为头节点。

    • 优点:节省一个节点的空间。
    • 缺点:需单独处理头节点操作。

而今天我要模拟实现的是带头双向循环动态链表。

带头双向循环动态链表模拟实现

链表的结构结构体:

cpp 复制代码
typedef int DCLDataType;
typedef struct DCListNode {
    DCLDataType data;               // 存储数据元素的值
    struct DCListNode* prev;        // 存放前驱结点的指针
    struct DCListNode* next;        // 存放后继结点的指针
}DCListNode;

链表的初始化:

cpp 复制代码
// 链表初始化
DCListNode* DCListInit()
{
	DCListNode* head = (DCListNode*)malloc(sizeof(DCListNode));
	if (head == NULL)
	{
		perror("malloc");
		exit(1);
	}
	head->next = head->prev = head;

	return head;
} 

注意点:在该链式表内,因为要实现的循环链表,所以需要在初始化的时候需要进行一个头尾相连向头指针。

链式表的销毁:

cpp 复制代码
// 销毁链表
void DCListDestroy(DCListNode* L)
{
	if (L == NULL)
	{
		return;
	}
	DCListNode* dest ,*head = L;
	L = L->next;
	while (L != head)
	{
		dest = L;
		L = L->next;
		free(dest);
		dest = NULL;
	}
	free(head);
	head = NULL;
	return;
}

因为是一个循环链表需要进行特殊的处理,先从链式表头指针往后的第一个节点进行销毁,遇到头指针停止循环销毁,对头指针单独销毁。

获取链式表的为序i节点:

cpp 复制代码
// 获取链表的位序i的结点
DCListNode* DCListGetElem(DCListNode* L, int i)
{
	if (i <= 0 || L == NULL)//错误操作,指针为空
	{
		return NULL;
	}
	DCListNode* node = L->next;
	int I = 1;
	for (I = 1; I < i && L != node; I++)
	{
		node = node->next;
	}

	if (L == node)//超出链式表的最大访问,遇到
		return NULL;
	return node;
}

1.判断头指针是否为空或者i小于等于0

2.声明节点跳过头指针,开始查找。

3.超出最大链式的极限返回NULL ,没有就返回node 节点

在pos位置后插入值为x的结点 和 头插 尾插

cpp 复制代码
void DCListInsert(DCListNode* pos, DCLDataType x)
{
	DCListNode* news = (DCListNode*)malloc(sizeof(DCListNode));
	if (news == NULL)
	{
		perror("malloc");
		exit(1);
	}
	news->next = pos->next;
	news->prev = pos;
	pos->next->prev = news;
	pos->next = news;
	news->data = x;

	return;
}
// 头插
void DCListPushFront(DCListNode* L, DCLDataType x)
{
	assert(L != NULL);
	DCListInsert(L, x);
	return;
}

// 尾插
void DCListPushBack(DCListNode* L, DCLDataType x)
{
	assert(L);
	DCListInsert(L->prev, x);
	return;
}

节点插入都要先申请一个节点,这个节点为了方便解释设为C点,要插入某个节点前面我们设A点下一个节点为B,节点的各个节点一定要遵循,A->B的节点最后改不然会导致找不到B点,或者可以用其他方式来实现,总之要保证B点有能够存取的地址的方式。

我的步骤就是先把申请来的新地址,先把新节点C的前后指针存入B和A,把B的后指针存入C,再把A的前指针存入C,再把C的data存入x。

头插和尾插都只需要把对应的地址传参即可,如果要实现对应位置插入就用刚才的DCListGetElem函数利用起来,就可以实现对应位置插入。

cpp 复制代码
//对应位置插入
void DCListPushNode(DCListNode* L, DCLDataType x,int i)
{
	DCListNode* node = DCListGetElem(L, i);
	assert(node);
	DCListInsert(node, x);
	return;
}

删除pos位置的结点头删和尾删

cpp 复制代码
// 删除pos位置的结点
void DCListDelete(DCListNode* pos)
{
	assert(pos != NULL);
	if (pos == pos->next)
	{
		return;
	}
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
	return;
}

// 头删
void DCListPopFront(DCListNode* L)
{
	assert(L != NULL);
	DCListDelete(L->next);
	return;
}

// 尾删
void DCListPopBack(DCListNode* L)
{
	assert(L != NULL);
	DCListDelete(&L->prev);
	return;
}

对应位置删除只关注前后指针前后指针从新链接就可以。当前位置的前指针节点的后指针 改成 当前位置的指针的后一个节点;当前位置的后指针节点的前指针 改成 当前位置的指针的前一个节点;这样及保证前后指针完美链接,也可以不用声明多的变量,只用pos 进行空间释放。

注意点:

头删时把头指针的往后一个节点进行释放,而不是头指针释放,所以传参传L->next;尾删就是把头指针的L->prev 进行释放。原则就是保证删除的不是头指针,只有销毁才需要把头指针进行销毁。

打印输出:

cpp 复制代码
// 打印链表中的元素
void DCListPrint(DCListNode* L)
{
	assert(L!=NULL);
	DCListNode* node = L->next;
	while (L!=node)
	{
		printf("%d -> ",node->data);
		node = node->next;
	}
	printf("\n");
	return;
}

模拟实现

cpp 复制代码
void List_test()
{
	DCListNode* head = DCListInit();
	DCListPushBack(head, 1);
	DCListPushBack(head, 2);
	DCListPushBack(head, 3);
	DCListPushBack(head, 4);
	DCListPushBack(head, 5);
	DCListPushBack(head, 6);
	DCListPrint(head);
	DCListPushFront(head, 2);
	DCListPushFront(head, 3);
	DCListPushFront(head, 4);
	DCListPushFront(head, 5);
	DCListPushFront(head, 6);
	DCListPrint(head);

	DCListPopFront(head);
	DCListPrint(head);	
	DCListPopFront(head);
	DCListPrint(head);
	DCListPopFront(head);
	DCListPrint(head);


	DCListDestroy(head);

	
	return;
}

int main()
{
	List_test();
	return 0;
}

感谢观看!

悠仁さん

相关推荐
三品吉他手会点灯1 小时前
C语言学习笔记 - 42.数据类型 - scanf函数深度解析
c语言·开发语言·笔记·学习
xxwxx__2 小时前
栈(Stack)详解:概念、实现与避坑指南
c语言·数据结构
草莓熊Lotso2 小时前
【Linux网络】深入理解 HTTP 协议(四):完善 C++ HTTP 服务器:从协议原理到生产级实现
linux·运维·服务器·c语言·网络·c++·http
少司府2 小时前
C++进阶:map和set的使用
开发语言·数据结构·c++·容器·stl·set·map
cpp_25012 小时前
P11375 [GESP202412 六级] 树上游走
数据结构·c++·算法·题解·洛谷·树形结构·gesp六级
天才程序YUAN2 小时前
Windows 11 C 盘扩容完整教程:恢复分区拦路、页面文件锁盘、WinRE 重建全记录
c语言·开发语言·windows
川冰ICE2 小时前
JavaScript进阶③|Map_Set_WeakMap_WeakSet,新型数据结构
开发语言·javascript·数据结构
0x3F(小茶)2 小时前
STM32 Bootloader与OTA升级
c语言·stm32·单片机·嵌入式硬件·物联网
我是一颗柠檬2 小时前
C语言最全面复习:从入门到精通(2026年)
c语言·开发语言