1.删除一个无头单链表的非尾节点 2.从尾到头打印单链表

本文详细介绍了单链表的基本操作,包括如何删除非尾节点及从尾到头打印链表的两种方法:非递归和递归实现。通过具体代码示例展示了数据结构与算法的应用。

1.删除一个无头单链表的非尾节点


这里主要是采用数据替换,将需要删除的节点的下一个结点放在需要删除结点的位置,依次将需要删除结点的后续结点前移,即可完成删除。

void DeleteNotTail(pNode pos)
{
	pNode cur = pos->_next;
	assert(pos->_next);          //数据替换
	pos->_data = pos->_next->_data;
	pos->_next = pos->_next->_next;
}

 2.从尾到头打印单链表 

主要有两种方法:

(1)非递归实现:

若只有一个结点或没有结点直接返回,有多个结点时需要逆置。

void PrintfReverseNon(pList* pplist)     //非递归实现
{
	pList cur = *pplist;
	pList newList = NULL;
	assert(pplist);
	if ((*pplist == NULL) || ((*pplist)->_next == NULL))
	{
		return;
	}

	while (cur)
	{
		pNode tmp = cur;
		cur = cur->_next;
		tmp->_next = newList;
		newList = tmp;
	}
	*pplist = newList;
}

(2)递归实现

同样,没有结点直接返回,有多个结点时,找第一个节点的下一个结点,直到找到该链表的最后一个结点,打印出该节点的数据,相当于栈,先将数据一次放入栈中,直到所有的数据完全放入,再一次弹出栈,即可完成逆置。

void PrintfReverse(pList plist)        //递归实现
{
	pNode cur = plist;
	if (cur==NULL)
	{
		return;
	}
	if (cur->_next)
	{
		PrintfReverse(cur->_next);
	}
	printf(" %d\n",cur->_data);
	
}

完整代码:

#include 
#include 
#include 
typedef int DataType;

typedef struct ListNode
{
	DataType _data;
	struct ListNode* _next;
}Node,*pNode,*pList;


void Init(pList* pplist)
{
	assert(pplist);
	*pplist = NULL;
}
pNode BuyNode(DataType x)
{
	pNode pnode = (pNode)malloc(sizeof(Node));
	if (pnode == NULL)
	{
		perror("malloc");
		return NULL;
	}
	pnode->_data = x;
	pnode->_next = NULL;
	return pnode;
}
void Push(pList* pplist,DataType x)
{
	pNode NewNode = BuyNode(x);
	if (*pplist == NULL)
	{
		*pplist = NewNode;
	}
	else
	{
		pNode cur = *pplist;
		while (cur->_next)
		{
			cur = cur->_next;
		}
		cur->_next = NewNode;
	}
}
pNode Find(pList plist,DataType x)
{
	if (plist == NULL)
	{
		return NULL;
	}

	else
	{
		pNode cur = plist;
		while (cur)
		{
			if (cur->_data == x)
			{
				return cur;
			}
			cur = cur->_next;
		}
		return NULL;
	}
}
void DeleteNotTail(pNode pos)
{
	pNode cur = pos->_next;
	assert(pos->_next);          //数据替换
	pos->_data = pos->_next->_data;
	pos->_next = pos->_next->_next;
}

void PrintfReverseNon(pList* pplist)     //非递归实现
{
	pList cur = *pplist;
	pList newList = NULL;
	assert(pplist);
	if ((*pplist == NULL) || ((*pplist)->_next == NULL))
	{
		return;
	}

	while (cur)
	{
		pNode tmp = cur;
		cur = cur->_next;
		tmp->_next = newList;
		newList = tmp;
	}

	*pplist = newList;

}
void PrintfReverse(pList plist)        //递归实现
{
	pNode cur = plist;
	if (cur==NULL)
	{
		return;
	}
	if (cur->_next)
	{
		PrintfReverse(cur->_next);
	}
	printf(" %d\n",cur->_data);
	
}
void Printf(pList plist)
{
	pNode cur = plist;
	while (cur)
	{
		printf(" %d", cur->_data);
		cur = cur->_next;
	}
	printf(" NULL\n");
}

void Test()
{
	pList plist;
	Init(&plist);
	Push(&plist, 1);
	Push(&plist, 2);
	Push(&plist, 3);
	Push(&plist, 4);
	Push(&plist, 5);

	Printf(plist);

	pNode ret = NULL;
	ret = Find(plist, 4);
	/*if (ret != NULL)
	{
		printf("找到了\n");
	}
	else
	{
		printf("没找到\n");
	}*/
	DeleteNotTail(ret);
	Printf(plist);
	PrintfReverse(plist);
	PrintfReverseNon(&plist);
	Printf(plist);
}

<think>嗯,用户的问题是关于单链表中头节点的next指针指向的含义。他们想知道头节点l的next是指向下一个实际数据节点还是指向自身的指针域。我需要结合之前提供的引用和之前的回答来详细解释这个问题。 首先,回顾之前关于指针p和*p的讨论,用户已经对单链表的指针操作有了一定的理解。现在他们关注的是头节点的next指针的作用。根据引用[2],头指针通常指向头节点,而头节点的指针域初始化为NULL。当链为空时,头节点的next就是NULL。当插入第一个节点时,头节点的next指向该节点,而该节点的next又指向NULL,成为新的节点。这说明头节点的next始终指向链的第一个实际数据节点,而不是头节点自身。 然后,引用[4]中的代码示例显示,在单链表逆置时,头节点的next被用来重新链接节点。例如,L->next = NULL; 然后通过循环将原链节点插入到头节点之后。这进一步证明头节点的next指向第一个数据节点,操作头节点的next会改变整个链的连接结构。 另外,用户可能混淆了头节点和首元节点的概念。头节点是附加的节点,不存储数据,而首元节点才是第一个存储数据的节点。头节点的next指向首元节点,如果链为空,则指向NULL。需要明确区分头节点和实际数据节点,避免误解next指针的指向。 用户还可能对指针域的具体内容有疑问。比如,每个节点的next指针存储的是下一个节点的地址,头节点作为链的入口,其next自然指向第一个数据节点。如果头节点的next指向自身,那会导致循环链的情况,但普通单链表并不如此设计。 需要确保用户理解头节点的作用:简化操作,如插入和删除一个节点时无需特殊处理。通过头节点,所有节点都有前驱,使得操作统一。例如,在引用[3]中删除中间节点的方法,如果存在头节点,处理方式会更一致。 总结起来,头节点的next指向第一个实际数据节点,即首元节点。当链为空时,next为NULL。头节点自身的指针域存储的是首元节点的地址,而不是自身的地址。需要通过示例和对比来澄清这一点,避免用户产生混淆。</think>### 单链表节点`L->next`的指向解析 在单链表中,头节点(Header Node)是一个**附加的辅助节点**,其`next`指针的指向具有明确的语义。以下分层次说明: --- #### 一、头节点的核心作用与内存结构 1. **头节点的定义** 头节点是链的**逻辑起点**,不存储实际数据(或存储元信息)。其结构与其他节点相同: ```c struct Node { int val; // 头节点的val字段通常无意义(或存储链长度等) struct Node* next; // 关键指针域 }; ``` 头节点的存在目的是**简化链操作**,例如插入、删除首元节点时无需特殊处理[^2]。 2. **内存布局示例** 假设链包含两个实际数据节点: $$ \begin{array}{ccc} \text{头节点L} & \rightarrow & \text{首元节点A} & \rightarrow & \text{节点B} \\ \text{(val: 0)} & \text{(next: 0x2000)} & \text{(val: 10)} & \text{(next: 0x3000)} & \text{(val: 20, next: NULL)} \end{array} $$ - **`L->next`的值**:`0x2000`(首元节点A的地址)。 - **若链为空**:`L->next = NULL`。 --- #### 二、`L->next`的具体指向 | **场景** | `L->next`的指向 | 操作语义 | |------------------|---------------------------------|----------------------------------| | **空链** | `NULL` | 示无实际数据节点 | | **空链** | 第一个实际数据节点(首元节点) | 通过`L->next`访问首元节点 | | **插入新节点** | 动态更新指向新首元节点 | 例如头插法修改`L->next`指向新节点 | ##### 关键验证代码(引用[4]) ```c // 链逆置时操作头节点的next域 LNode* Inverse(LNode* L) { LNode *p = L->next; // p指向首元节点 L->next = NULL; // 清空头节点的next(链暂时为空) while (p != NULL) { LNode* q = p; p = p->next; q->next = L->next; // 将原链节点插入到头节点后 L->next = q; // 更新头节点的next指向新首元节点 } return L; } ``` - **操作逻辑**:头节点的`next`始终指向当前链的首元节点。 --- #### 三、常见误解澄清 1. **头节点的`next`指向自身?** **错误**。头节点的`next`指向首元节点(或`NULL`),若指向自身则形成**循环链**,与普通单链表设计矛盾。 2. **`L->next`是头节点的指针域?** **部分正确**。严格来说: - `L`本身是头节点的地址。 - `L->next`是头节点的指针域成员,但该成员的值是首元节点的地址(或`NULL`)。 3. **对比节点的`next`** 节点的`next`固定为`NULL`(引用[1]),而头节点的`next`动态指向首元节点。 --- #### 四、操作示例 1. **遍历链** ```c struct Node* current = L->next; // 从首元节点开始 while (current != NULL) { printf("%d ", current->val); current = current->next; } ``` 2. **头插法插入节点** ```c struct Node* newNode = createNode(5); newNode->next = L->next; // 新节点指向原首元节点 L->next = newNode; // 头节点指向新节点 ``` --- ### 总结 - **头节点`L->next`的指向**: - **空链**:`NULL` - **空链**:**首元节点**(第一个实际存储数据的节点) - **本质作用**:通过头节点的`next`域控制链入口,统一插入/删除操作[^2][^4]。 --- **相关问题** 1. 如何判断单链表是否为空? 2.节点与首元节点的区别是什么? 3. 无头节点单链表操作有何不同? [^1]: 节点通过`NULL`标记链结束。 [^2]: 头节点简化链操作,避免特殊处理首元节点。 [^4]: 链逆置操作依赖头节点的`next`域更新。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值