不带头节点且不依赖结构体的链表学习,需要从 ** 节点逻辑、核心操作(增删查)** 等方面入手,以下是通俗的讲解:

一、节点的逻辑理解

不带头节点且无结构体时,我们可以把 “节点” 想象成两个部分的组合

  • 数据部分:存储具体的值(如整数、字符串)。
  • 指针部分:存储下一个节点的 “位置”(在 C/C++ 中用指针表示,其他语言用引用)。

由于没有结构体封装,我们需要手动管理数据和指针的关联(这在实际中几乎不会这么用,仅用于理解原理)。

二、核心操作实现(以 C 语言为例,模拟无结构体的逻辑)

1.结构体设计
typedef int ELEM_TYPE;
typedef struct ListNode
{
	ELEM_TYPE data;//数据域
	struct ListNode* next;//指针域
}ListNode;
2.初始化
void InitList(ListNode** plist)//1初始化
{
	assert(plist != NULL);
	*(plist) = NULL;
}
3.获取元素个数
int GetSize( ListNode** plist)//2获取元素个数
{
	assert(plist != NULL);
	int i = 0;
	ListNode* p = (*plist);
	while (p != NULL) {
		i++;
		p= p->next;
	}
	return i;
}
4.判断链表是否为空
bool Is_Empty(ListNode** plist)//3判空
{
	assert(plist != NULL);
	if ((*plist) == NULL)return true;
	else { return false; }
}
5.找到pos位置的节点与前驱节点
ListNode* FindPos(ListNode** plist, int pos)//3找到pos节点
{
	assert(plist != NULL);
	if (pos < 0 || pos>GetSize(plist)) { printf("下标超出范围或者为空"); return NULL; }
	ListNode* p = (*plist);
	while ( pos>1) {
		pos--;
		p = p->next;
	}
	return p;
}
ListNode* PrevFindPos(ListNode** plist, int pos)//4找到pos节点的前驱
{
	assert(plist != NULL);
	return FindPos(plist, pos - 1);
}
6.在prt指针后插入
bool insertNext(ListNode** plist, ListNode* prt, ELEM_TYPE val)//7在ptr->后插入
{
	assert(plist != NULL&&prt!=NULL);
	if (*plist == NULL) { Push_Front(plist, val); return true; }
	if (prt == NULL) { printf("prt无效"); return false; }
	ListNode* p = (ListNode*)malloc(sizeof(ListNode));
	if (p == NULL) { printf("分配内存失败"); return false; }
	p->data = val;
	p->next = prt->next;
	prt->next = p;
	return true;
}
7.在pos位置插入
bool insertElem(ListNode** plist, int pos, ELEM_TYPE val)//插入元素6
{
	assert(plist != NULL);
	return insertNext(plist, PrevFindPos(plist, pos), val);

}
8.尾插
bool Push_Back(ListNode** plist, ELEM_TYPE val)//尾插8
{
	assert(plist != NULL);
	if (FindPos(plist, GetSize(plist)) == NULL)return Push_Front(plist, val);
	return insertNext(plist, FindPos(plist,GetSize(plist)), val);
}
9.头插
bool Push_Front(ListNode** plist, ELEM_TYPE val) {
	assert(plist != NULL);
	ListNode* p = (ListNode*)malloc(sizeof(ListNode));
	if (p == NULL) { printf("分配内存失败"); return false; }
		p->data = val;
		p->next=(*plist);
		(*plist)= p;
	return true;
}
10.prt指针后面删除
bool Pop_Next(ListNode** plist, ListNode* prt)//11在prt后删除
{
	assert(plist != NULL);
	if (Is_Empty(plist)) { printf("没有元素"); return false; }
	if (prt == NULL || prt->next == NULL) { printf("删除位置不合理"); return false; }
	if (prt == (*plist)) { Pop_Front(plist); return true; }
	ListNode* n = prt->next;
	prt->next = prt->next->next;
	free(n);
	return true;
}
11.按位置删除
bool Pop_Pos(ListNode** plist, int pos)//按位置删除10
{
	assert(plist != NULL);
	return Pop_Next(plist, PrevFindPos(plist, pos));
}
12.尾删
bool Pop_Back(ListNode** plist)//12删除尾
{
	assert(plist != NULL);
	return Pop_Pos(plist, GetSize(plist));
}
13.头删
bool Pop_Front(ListNode** plist)//13删除头
{
	assert(plist != NULL);
	ListNode* p = *plist;
	*plist = (*plist)->next;
	free(p);
	return true;
}
14.按值查找
ListNode* FindValue(ListNode** plist, ELEM_TYPE val) {
	assert(plist != NULL);
	if (Is_Empty(plist)) { printf("没有元素"); return NULL; }
	ListNode *n= *plist;
	while (n != NULL) {
		if (n->data == val)return n;
		n = n->next;
	}
	return NULL;
}
15.打印函数
void printinto(ListNode** plist) {
	assert(plist != NULL);
	ListNode* p = (*plist);
	while (p != NULL) {
		printf("%d ", p->data);
		p = p->next;
	}
}

三、总结(实际开发中的建议)

在真实开发中,不带头节点且不用结构体的链表几乎不会存在,因为结构体是封装节点(数据 + 指针)的最自然方式。正确的学习路径是:

  1. 先掌握带结构体、带头节点的链表(更规范、易维护)。
  2. 再理解不带头节点的链表(需注意空链表、只有一个节点等边界情况)。

如果你是初学者,建议从带结构体的带头节点链表入手,先掌握标准的实现方式,再深入理解各种变体的差异~

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐