深入解析:【数据结构】不带头节点单链表的基本操作
2025-10-01 09:21 tlnshuju 阅读(23) 评论(0) 收藏 举报1. 初始化算法(InitList_NoHead)
// 1. 初始化(不带头节点):空表时L=NULL
Status InitList_NoHead(LinkList &L) {
L = NULL; // 不带头节点的空表,链表指针直接为NULL(无首元节点)
return OK;
}
功能
创建一个不带头节点的空单链表(空表时无任何节点,链表指针L直接为NULL)。
核心原理
不带头节点的单链表无 “占位头节点”,空表的唯一标识是链表指针L指向NULL(区别于带头节点的空表:头节点存在,head->next=NULL)。
详细步骤
- 直接将链表指针
L赋值为NULL:L = NULL(表示无首元节点,链表为空)。 - 返回成功状态
OK。
时间复杂度
O(1)(仅执行固定次数的赋值操作,无循环)。
关键说明
- 无需为 “头节点” 分配内存,节省一个节点的存储空间;
- 后续所有操作需先判断
L是否为NULL(避免空表非法访问)。
2. 取值算法(GetElem_NoHead)
// 2. 取值(不带头节点):首元节点为L,位置i从1开始
Status GetElem_NoHead(LinkList L, int i, ElemType &e) {
LinkList p = L; // 从首元节点开始遍历(不带头节点,无“头结点”可跳)
int j = 1; // 计数器:当前指向第j个节点
// 遍历到第i个节点,或p为NULL(位置超出表长)
while (p != NULL && j < i) {
p = p->next;
j++;
}
// 位置不合法:i<1 或 j>i(p为NULL,未找到第i个节点)
if (p == NULL || j > i) {
return ERROR;
}
e = p->data; // 取出第i个节点的数据
return OK;
}
功能
根据位置i(1-based,即首元节点为第 1 个位置) ,从链表中获取对应元素的值,存入e。
核心原理
从首元节点(L指向的节点)开始遍历,用计数器j记录当前遍历到的 “位置”,直到j == i(找到目标节点)或p == NULL(位置i超出表长)。
详细步骤
- 初始化指针
p = L(指向首元节点),计数器j = 1(首元节点为第 1 个位置)。 - 循环遍历:若
p != NULL且j < i,则p = p->next(指针后移)、j++(计数器加 1)。 - 合法性判断:
- 若
p == NULL(未找到第i个节点,i超出表长)或j > i(i < 1,位置非法),返回ERROR; - 否则,将目标节点的值存入
e:e = p->data,返回OK。
- 若
时间复杂度
O(n)(最坏情况需遍历到表尾,如i为表长)。
关键说明
- 遍历起点为
L(首元节点),区别于带头节点的取值(从head->next开始); - 位置
i必须满足1 ≤ i ≤ 表长,否则取值失败。
3. 查找算法 1(LocateElem_NoHead)
// 3. 查找(不带头节点):返回元素e所在节点的地址,未找到返回NULL
LinkList LocateElem_NoHead(LinkList L, ElemType e) {
LinkList p = L; // 从首元节点开始遍历
// 遍历所有节点,对比数据域
while (p != NULL && p->data != e) {
p = p->next;
}
return p; // 找到则返回节点地址,未找到返回NULL
}
功能
根据元素值e ,查找链表中第一个值等于e的节点,返回该节点的地址(指针);未找到则返回NULL。
核心原理
从首元节点开始遍历,逐个对比节点的data域与e,找到匹配节点则返回其指针,遍历结束仍无匹配则返回NULL。
详细步骤
- 初始化指针
p = L(指向首元节点)。 - 循环遍历:若
p != NULL且p->data != e,则p = p->next(指针后移)。 - 返回
p:若找到匹配节点,p指向该节点;否则p为NULL。
时间复杂度
O(n)(最坏情况需遍历所有节点)。
关键说明
- 适用于需要修改找到节点数据的场景(通过返回的指针直接操作节点);
- 若链表中有多个值等于
e的节点,仅返回第一个匹配节点的地址。
4. 查找算法 2(LocateELem_L_NoHead)
// 4. 查找(不带头节点):返回元素e的位序(1开始),未找到返回0(实现与声明一致)
int LocateELem_L_NoHead(LinkList L, ElemType e) {
LinkList p = L; // 从首元节点开始遍历
int j = 1; // 记录当前节点的位序
// 遍历所有节点,对比数据域
while (p != NULL && p->data != e) {
p = p->next;
j++;
}
return (p != NULL) ? j : 0; // 找到返回位序,未找到返回0
}
功能
根据元素值e ,查找链表中第一个值等于e的节点,返回其位置(1-based) ;未找到则返回0。
核心原理
遍历过程中增加 “位置计数器”j,找到匹配节点时返回j,未找到则返回0。
详细步骤
- 初始化指针
p = L(指向首元节点),计数器j = 1。 - 循环遍历:若
p != NULL且p->data != e,则p = p->next、j++。 - 返回结果:若
p != NULL(找到),返回j;否则返回0。
时间复杂度
O(n)(与查找算法 1 一致)。
关键说明
- 适用于仅需获取元素位置的场景(如后续插入 / 删除操作需位置参数);
- 返回
0是明确的 “未找到” 标识,避免与合法位置(≥1)混淆。
5. 插入算法(ListInsert1)
// 5. 插入(不带头节点):在第i个位置前插入元素e,i=1时插入首元
Status ListInsert1(LinkList &L, int i, ElemType e) {
// 位置合法性初步判断:i<1 或 空表时i>1(空表只能插入第1个位置)
if (i < 1 || (L == NULL && i > 1)) {
return ERROR;
}
LinkList s = new LNode; // 生成新节点
s->data = e; // 赋值新节点数据域
// 情况1:插入首元节点(i=1,此时L为NULL或指向原首元)
if (i == 1) {
s->next = L; // 新节点指向原首元(空表时L=NULL,新节点next=NULL)
L = s; // 链表指针指向新节点(新首元)
return OK;
}
// 情况2:插入非首元节点(i>1):先找第i-1个节点
LinkList p = L;
int j = 1;
while (p != NULL && j < i - 1) {
p = p->next;
j++;
}
// 位置不合法:未找到第i-1个节点(i超出表长+1)
if (p == NULL || j > i - 1) {
delete s; // 释放未插入的新节点,避免内存泄漏
return ERROR;
}
// 插入新节点:第i-1个节点后接新节点,新节点接原第i个节点
s->next = p->next;
p->next = s;
return OK;
}
功能
在不带头节点的单链表中,第i个位置之前插入元素e(插入后原第i个及之后的节点后移)。
核心原理
不带头节点的插入需分两种场景:
- 插入首元节点(
i=1):需修改链表指针L,让L指向新节点; - 插入非首元节点(
i>1):需找到第i-1个节点,建立 “i-1节点→新节点→原i节点” 的链接。
详细步骤
- 合法性初步判断:若
i < 1,或空表(L=NULL)时i > 1(空表只能插入第 1 个位置),返回ERROR。 - 为新节点分配内存:
LinkList s = new LNode;,赋值s->data = e。 - 分场景插入:
- 场景 1:插入首元(
i=1) :- 新节点指向原首元:
s->next = L(空表时L=NULL,新节点next也为NULL); - 链表指针指向新节点:
L = s(新节点成为首元);
- 新节点指向原首元:
- 场景 2:插入非首元(
i>1) :- 找第
i-1个节点:p = L,j=1,循环p != NULL && j < i-1,p=p->next,j++; - 合法性二次判断:若
p == NULL(未找到i-1节点,i超出表长 + 1)或j > i-1,释放新节点(delete s),返回ERROR; - 插入新节点:
s->next = p->next; p->next = s;。
- 找第
- 场景 1:插入首元(
- 返回
OK。
时间复杂度
O(n)(最坏情况需遍历到表尾,如插入到最后一个位置)。
关键说明
- 未插入成功时需释放新节点(
delete s),避免内存泄漏; - 插入后表长增加 1(无需显式记录表长,通过遍历可计算)。
6. 删除算法(ListDelete1)
// 6. 删除(不带头节点):删除第i个位置的元素,i=1时删除首元
Status ListDelete1(LinkList &L, int i) {
// 位置合法性初步判断:i<1 或 空表(L=NULL)
if (i < 1 || L == NULL) {
return ERROR;
}
LinkList q; // 暂存待删除节点
// 情况1:删除首元节点(i=1)
if (i == 1) {
q = L; // 待删除节点为当前首元
L = L->next; // 链表指针指向原首元的下一个节点(新首元,空表时为NULL)
delete q; // 释放原首元节点内存
return OK;
}
// 情况2:删除非首元节点(i>1):先找第i-1个节点
LinkList p = L;
int j = 1;
while (p != NULL && j < i - 1) {
p = p->next;
j++;
}
// 位置不合法:未找到第i-1个节点,或第i个节点不存在(p->next=NULL)
if (p == NULL || p->next == NULL || j > i - 1) {
return ERROR;
}
// 删除第i个节点:第i-1个节点跳过待删除节点,释放待删除节点内存
q = p->next;
p->next = q->next;
delete q;
return OK;
}
功能
删除不带头节点的单链表中第i个位置的元素(删除后原第i+1个及之后的节点前移)。
核心原理
与插入类似,需分 “删除首元” 和 “删除非首元” 场景,核心是 “断链 + 释放内存”:
- 删除首元:修改
L指向原首元的下一个节点,释放原首元; - 删除非首元:找到第
i-1个节点,跳过第i个节点,释放其内存。
详细步骤
- 合法性初步判断:若
i < 1或空表(L=NULL),返回ERROR。 - 分场景删除:
- 场景 1:删除首元(
i=1) :- 暂存原首元节点:
LinkList q = L; - 链表指针指向新首元:
L = L->next(空表时L变为NULL); - 释放原首元:
delete q;
- 暂存原首元节点:
- 场景 2:删除非首元(
i>1) :- 找第
i-1个节点:p = L,j=1,循环p != NULL && j < i-1,p=p->next,j++; - 合法性二次判断:若
p == NULL(未找到i-1节点)或p->next == NULL(无第i个节点),返回ERROR; - 暂存待删除节点:
LinkList q = p->next; - 断链:
p->next = q->next(跳过待删除节点); - 释放内存:
delete q。
- 找第
- 场景 1:删除首元(
- 返回
OK。
时间复杂度
O(n)(最坏情况需遍历到表尾,如删除最后一个节点)。
关键说明
- 必须释放待删除节点的内存(
delete q),避免内存泄漏; - 删除后表长减少 1,空表时
L会变为NULL。
7. 判空算法(ListEmpty_NoHead)
// 7. 判空(不带头节点):L==NULL即为空表
int ListEmpty_NoHead(LinkList L) {
return (L == NULL) ? 1 : 0; // 1-空表,0-非空表
}
功能
判断不带头节点的单链表是否为空。
核心原理
不带头节点的空表唯一标识是 “链表指针L == NULL”(无任何节点),区别于带头节点的空表(头节点存在,head->next == NULL)。
详细步骤
- 若
L == NULL,返回1(表示空表); - 否则返回
0(表示非空表)。
时间复杂度
O(1)(仅需一次指针比较)。
关键说明
- 所有涉及 “修改链表” 的操作(插入、删除)前,需先调用此算法判断是否为空表,避免非法访问;
- 是后续操作(如取值、查找)的前置合法性判断条件。
8. 求长算法(ListLength_L_NoHead)
// 8. 求长(不带头节点):从首元节点L开始遍历,统计节点数
int ListLength_L_NoHead(LinkList L) {
int len = 0;
LinkList p = L;
while (p != NULL) {
len++;
p = p->next;
}
return len;
}
功能
计算不带头节点的单链表的长度(即实际存储的节点个数)。
核心原理
从首元节点(L指向的节点)开始遍历,用计数器记录节点数量,直到指针指向NULL(遍历结束)。
详细步骤
- 初始化计数器
len = 0,指针p = L。 - 循环遍历:若
p != NULL,则len++、p = p->next。 - 返回
len。
时间复杂度
O(n)(需遍历所有节点,n为表长)。
关键说明
- 不带头节点的链表无 “表长变量”,需通过遍历计算(若频繁求长,可优化为在链表结构中增加
length成员); - 遍历起点为
L,区别于带头节点的求长(从head->next开始)。
9. 销毁算法(DestroyList_NoHead)
// 9. 销毁(不带头节点):遍历删除所有节点,最后L=NULL
Status DestroyList_NoHead(LinkList &L) {
LinkList p;
while (L != NULL) {
p = L; // 暂存当前节点
L = L->next; // 链表指针后移
delete p; // 释放当前节点内存
}
L = NULL; // 确保销毁后链表指针为NULL,避免野指针
return OK;
}
功能
释放不带头节点单链表中所有节点的动态内存,避免内存泄漏,最终将L置为NULL。
核心原理
链表节点通过new动态分配,必须手动遍历释放每个节点;若直接L = NULL,会导致所有节点内存无法回收(内存泄漏)。
详细步骤
- 初始化指针
p(暂存当前节点)。 - 循环遍历释放:
- 暂存当前节点:
p = L; - 链表指针后移:
L = L->next; - 释放当前节点:
delete p;
- 暂存当前节点:
- 遍历结束后,
L已为NULL,无需额外赋值(若遍历前L=NULL,循环不执行)。
时间复杂度
O(n)(需遍历并释放所有节点)。
关键说明
- 程序退出前必须调用此算法,尤其是多次创建链表的场景;
- 释放后
L为NULL,再次操作前需重新初始化。
10. 打印算法(PrintList_NoHead)
// 10. 打印(不带头节点):适配空表(L=NULL)、非空表(从L开始遍历)
void PrintList_NoHead(LinkList L) {
if (L == NULL) {
cout << "空表" << endl;
return;
}
// 非空表:从首元节点L开始遍历打印
cout << "[";
LinkList p = L;
while (p != NULL) {
cout << p->data;
if (p->next != NULL) {
cout << ", ";
}
p = p->next;
}
cout << "]" << endl;
}
功能
格式化打印不带头节点的单链表,空表输出 “空表”,非空表输出[元素1, 元素2, ..., 元素n]。
核心原理
适配空表和非空表的不同场景,遍历非空表时按格式拼接元素。
详细步骤
- 若
L == NULL(空表),输出 “空表” 并换行,返回; - 非空表处理:
- 输出左括号:
cout << "["; - 初始化指针
p = L,循环遍历:- 输出当前节点数据:
cout << p->data; - 若不是最后一个节点(
p->next != NULL),输出 “,”; - 指针后移:
p = p->next;
- 输出当前节点数据:
- 输出右括号并换行:
cout << "]" << endl。
- 输出左括号:
时间复杂度
O(n)(需遍历所有节点)。
关键说明
- 格式化输出便于直观查看链表内容,是调试和验证其他算法(如插入、删除)的重要工具;
- 处理了 “最后一个节点无后缀逗号” 的细节,保证输出美观。
11. 前插法建表(CreateList_H_NoHead)
// 11. 前插法建表(不带头节点):每次插入到表头,最终元素逆序
void CreateList_H_NoHead(LinkList &L, int n) {
L = NULL; // 先初始化空表
for (int i = 0; i < n; i++) {
LinkList p = new LNode; // 生成新节点
cin >> p->data; // 输入节点数据
p->next = L; // 新节点指向当前表头(空表时L=NULL)
L = p; // 表头更新为新节点
}
}
功能
通过 “前插法”(每次新节点插入到表头)创建不带头节点的单链表,最终链表元素顺序与输入顺序相反。
核心原理
前插法的核心是 “新节点始终成为新表头”:链表指针L每次指向新节点,新节点的next指向原表头,导致先输入的元素被后输入的元素 “挤到后面”。
详细步骤
- 初始化空表:
L = NULL。 - 循环
n次(n为元素个数):- 为新节点分配内存:
LinkList p = new LNode; - 输入节点数据:
cin >> p->data; - 新节点指向原表头:
p->next = L; - 链表指针指向新表头:
L = p。
- 为新节点分配内存:
- 建表完成,
L指向最终的首元节点。
时间复杂度
O(n)(循环n次,每次操作均为O(1))。
关键说明
- 元素逆序示例:输入
1.5 2.8 3.2,链表顺序为3.2 → 2.8 → 1.5; - 优点是建表效率高(无需找表尾),缺点是元素顺序与输入相反,适合无需保持输入顺序的场景。
12. 后插法建表(CreateList_R_NoHead)
// 12. 后插法建表(不带头节点):每次插入到表尾,最终元素正序
void CreateList_R_NoHead(LinkList &L, int n) {
L = NULL; // 先初始化空表
LinkList r = NULL; // 尾指针:始终指向当前表尾
for (int i = 0; i < n; i++) {
LinkList p = new LNode; // 生成新节点
cin >> p->data; // 输入节点数据
p->next = NULL; // 新节点为表尾,next=NULL
if (L == NULL) {
L = p; // 空表时,新节点即为首元,L指向首元
} else {
r->next = p; // 非空表时,尾指针后接新节点
}
r = p; // 尾指针更新为新节点(保持指向表尾)
}
}
功能
通过 “后插法”(每次新节点插入到表尾)创建不带头节点的单链表,最终链表元素顺序与输入顺序一致。
核心原理
后插法需额外维护 “尾指针r”(始终指向当前表尾),避免每次插入都遍历找表尾;新节点直接插入到r之后,r更新为新节点,保证元素顺序与输入一致。
详细步骤
- 初始化空表:
L = NULL,尾指针r = NULL。 - 循环
n次:- 为新节点分配内存:
LinkList p = new LNode; - 输入节点数据:
cin >> p->data,设置p->next = NULL(新节点为表尾,无后续节点); - 分场景插入:
- 空表时(
L == NULL):L = p(L指向首元),r = p(r指向表尾); - 非空表时(
L != NULL):r->next = p(表尾后接新节点),r = p(r更新为新表尾)。
- 空表时(
- 为新节点分配内存:
- 建表完成,
L指向首元,r指向表尾。
时间复杂度
O(n)(循环n次,每次操作均为O(1),尾指针避免了遍历找表尾)。
关键说明
- 元素正序示例:输入
1.5 2.8 3.2,链表顺序为1.5 → 2.8 → 3.2; - 优点是元素顺序与输入一致,适合需要保持输入顺序的场景;尾指针是后插法高效的关键(若无尾指针,时间复杂度会退化为
O(n²))。
完整C++代码如下:
#include
using namespace std;
// 不带头节点单链表的节点结构
typedef double ElemType; // 支持小数输入
typedef int Status;
#define OK 1
#define ERROR 0
#define OVERFLOW -2
// 不带头节点单链表的节点结构
typedef struct LNode *LinkList;
struct LNode {
ElemType data; // 数据域
LinkList next; // 指针域:指向下一个节点
};
// -------------------------- 不带头节点单链表核心函数声明 --------------------------
Status InitList_NoHead(LinkList &L); // 初始化(不带头节点,空表时L=NULL)
Status GetElem_NoHead(LinkList L, int i, ElemType &e); // 取值(不带头节点)
LinkList LocateElem_NoHead(LinkList L, ElemType e); // 查找:返回节点地址(不带头节点)
int LocateELem_L_NoHead(LinkList L, ElemType e); // 修复:去掉多余的int i参数
Status ListInsert1(LinkList &L, int i, ElemType e); // 插入(不带头节点,i=1时插入首元)
Status ListDelete1(LinkList &L, int i); // 删除(不带头节点,i=1时删除首元)
int ListEmpty_NoHead(LinkList L); // 判空(不带头节点:L==NULL为空)
int ListLength_L_NoHead(LinkList L); // 求长(不带头节点:从L开始遍历)
Status DestroyList_NoHead(LinkList &L); // 销毁(不带头节点:遍历删除所有节点)
void PrintList_NoHead(LinkList L); // 打印(适配不带头节点:空表显示“空表”)
void CreateList_H_NoHead(LinkList &L, int n); // 前插法建表(不带头节点:每次插表头)
void CreateList_R_NoHead(LinkList &L, int n); // 后插法建表(不带头节点:需记录尾指针)
// -------------------------- 主函数 --------------------------
int main() {
LinkList L_NoHead = NULL; // 不带头节点的链表指针(空表时为NULL)
int choice, n, i, pos;
ElemType e;
bool isDestroyed_NoHead = false; // 标记链表是否已销毁(避免野指针操作)
while (true) {
// 不带头节点单链表专属测试菜单
cout << "\n======= 不带头节点单链表测试菜单 =======" << endl;
cout << "1. 初始化不带头节点链表" << endl;
cout << "2. 建表测试(前插法/后插法)" << endl;
cout << "3. 取值测试(根据位置获取元素)" << endl;
cout << "4. 查找测试(根据元素找位置/地址)" << endl;
cout << "5. 插入测试(指定位置插入元素)" << endl;
cout << "6. 删除测试(指定位置删除元素)" << endl;
cout << "7. 表属性测试(判空/求表长)" << endl;
cout << "8. 销毁不带头节点链表" << endl;
cout << "0. 退出程序" << endl;
cout << "======================================" << endl;
cout << "请输入选择(0-8):";
cin >> choice;
// 退出逻辑:销毁链表并释放内存
if (choice == 0) {
cout << "程序退出,释放所有内存..." << endl;
if (!isDestroyed_NoHead) DestroyList_NoHead(L_NoHead);
break;
}
// 1. 初始化不带头节点链表(空表时L_NoHead=NULL)
if (choice == 1) {
if (InitList_NoHead(L_NoHead) == OK) {
cout << "不带头节点链表初始化成功!(空表时指针为NULL)" << endl;
PrintList_NoHead(L_NoHead);
isDestroyed_NoHead = false;
} else {
cout << "不带头节点链表初始化失败!" << endl;
}
continue;
}
// 2. 建表测试(前插法/后插法,不带头节点)
if (choice == 2) {
if (isDestroyed_NoHead) {
cout << "链表已销毁,请先初始化!" << endl;
continue;
}
int buildType;
cout << "请选择建表方式:1-前插法(逆序) 2-后插法(正序):";
cin >> buildType;
cout << "请输入要插入的元素个数:";
cin >> n;
if (n <= 0) {
cout << "元素个数必须为正整数!" << endl;
continue;
}
cout << "请依次输入" << n << "个元素(支持小数,用空格分隔):";
if (buildType == 1) {
CreateList_H_NoHead(L_NoHead, n);
cout << "前插法建表完成!";
} else if (buildType == 2) {
CreateList_R_NoHead(L_NoHead, n);
cout << "后插法建表完成!";
} else {
cout << "无效建表方式!";
continue;
}
PrintList_NoHead(L_NoHead);
continue;
}
// 3. 取值测试(不带头节点:首元节点为L_NoHead)
if (choice == 3) {
if (isDestroyed_NoHead || ListEmpty_NoHead(L_NoHead)) {
cout << "链表已销毁或为空,无法取值!" << endl;
continue;
}
int len = ListLength_L_NoHead(L_NoHead);
cout << "当前链表长度:" << len << endl;
cout << "请输入要取值的位置i(1-" << len << "):";
cin >> i;
if (GetElem_NoHead(L_NoHead, i, e) == OK) {
cout << "位置" << i << "的元素值为:" << e << endl;
} else {
cout << "取值失败!位置" << i << "不合法(超出表长或小于1)" << endl;
}
continue;
}
// 4. 查找测试(不带头节点:从L_NoHead开始遍历)
if (choice == 4) {
if (isDestroyed_NoHead || ListEmpty_NoHead(L_NoHead)) {
cout << "链表已销毁或为空,无法查找!" << endl;
continue;
}
cout << "请输入要查找的元素值(支持小数):";
cin >> e;
// 测试1:返回元素位序(调用修正后的函数,仅传2个参数)
pos = LocateELem_L_NoHead(L_NoHead, e);
if (pos != 0) {
cout << "查找成功(位置序号):元素" << e << "在链表中的位置为" << pos << endl;
} else {
cout << "查找失败(位置序号):链表中无元素" << e << endl;
}
// 测试2:返回元素节点地址(简化为“是否找到”)
LinkList addr = LocateElem_NoHead(L_NoHead, e);
if (addr != NULL) {
cout << "查找成功(地址验证):元素" << e << "的地址有效(存在该元素)" << endl;
} else {
cout << "查找失败(地址验证):元素" << e << "的地址为NULL(不存在)" << endl;
}
continue;
}
// 5. 插入测试(不带头节点:i=1时插入首元,需改链表指针)
if (choice == 5) {
if (isDestroyed_NoHead) {
cout << "链表已销毁,请先初始化!" << endl;
continue;
}
int len = ListLength_L_NoHead(L_NoHead);
int maxPos = len + 1; // 空表时maxPos=1(插入首元)
cout << "当前链表长度:" << len << endl;
cout << "请输入插入位置i(1-" << maxPos << "):";
cin >> pos;
cout << "请输入插入的元素值(支持小数):";
cin >> e;
if (ListInsert1(L_NoHead, pos, e) == OK) {
cout << "插入成功!插入后链表:";
PrintList_NoHead(L_NoHead);
} else {
cout << "插入失败!位置" << pos << "不合法(需在1-" << maxPos << "之间)" << endl;
}
continue;
}
// 6. 删除测试(不带头节点:i=1时删除首元,需改链表指针)
if (choice == 6) {
if (isDestroyed_NoHead || ListEmpty_NoHead(L_NoHead)) {
cout << "链表已销毁或为空,无法删除!" << endl;
continue;
}
int len = ListLength_L_NoHead(L_NoHead);
cout << "当前链表长度:" << len << endl;
cout << "请输入删除位置i(1-" << len << "):";
cin >> pos;
if (ListDelete1(L_NoHead, pos) == OK) {
cout << "删除成功!删除后链表:";
PrintList_NoHead(L_NoHead);
} else {
cout << "删除失败!位置" << pos << "不合法(需在1-" << len << "之间)" << endl;
}
continue;
}
// 7. 表属性测试(判空+求长,适配不带头节点)
if (choice == 7) {
if (isDestroyed_NoHead) {
cout << "链表已销毁,请先初始化!" << endl;
continue;
}
cout << "不带头节点链表属性测试:" << endl;
cout << "是否为空表:" << (ListEmpty_NoHead(L_NoHead) ? "是" : "否") << endl;
cout << "当前表长:" << ListLength_L_NoHead(L_NoHead) << endl;
cout << "当前链表元素:";
PrintList_NoHead(L_NoHead);
continue;
}
// 8. 销毁不带头节点链表(遍历删除所有节点,最后L_NoHead=NULL)
if (choice == 8) {
if (isDestroyed_NoHead) {
cout << "不带头节点链表已销毁,无需重复操作!" << endl;
continue;
}
if (DestroyList_NoHead(L_NoHead) == OK) {
cout << "不带头节点链表销毁成功!" << endl;
isDestroyed_NoHead = true;
L_NoHead = NULL; // 确保野指针置空
} else {
cout << "不带头节点链表销毁失败!" << endl;
}
continue;
}
// 无效选择提示
cout << "无效选择,请重新输入(0-8)!" << endl;
}
return 0;
}
// -------------------------- 不带头节点单链表核心函数实现 --------------------------
// 1. 初始化(不带头节点):空表时L=NULL
Status InitList_NoHead(LinkList &L) {
L = NULL; // 不带头节点的空表,链表指针直接为NULL(无首元节点)
return OK;
}
// 2. 取值(不带头节点):首元节点为L,位置i从1开始
Status GetElem_NoHead(LinkList L, int i, ElemType &e) {
LinkList p = L; // 从首元节点开始遍历(不带头节点,无“头结点”可跳)
int j = 1; // 计数器:当前指向第j个节点
// 遍历到第i个节点,或p为NULL(位置超出表长)
while (p != NULL && j < i) {
p = p->next;
j++;
}
// 位置不合法:i<1 或 j>i(p为NULL,未找到第i个节点)
if (p == NULL || j > i) {
return ERROR;
}
e = p->data; // 取出第i个节点的数据
return OK;
}
// 3. 查找(不带头节点):返回元素e所在节点的地址,未找到返回NULL
LinkList LocateElem_NoHead(LinkList L, ElemType e) {
LinkList p = L; // 从首元节点开始遍历
// 遍历所有节点,对比数据域
while (p != NULL && p->data != e) {
p = p->next;
}
return p; // 找到则返回节点地址,未找到返回NULL
}
// 4. 查找(不带头节点):返回元素e的位序(1开始),未找到返回0(实现与声明一致)
int LocateELem_L_NoHead(LinkList L, ElemType e) {
LinkList p = L; // 从首元节点开始遍历
int j = 1; // 记录当前节点的位序
// 遍历所有节点,对比数据域
while (p != NULL && p->data != e) {
p = p->next;
j++;
}
return (p != NULL) ? j : 0; // 找到返回位序,未找到返回0
}
// 5. 插入(不带头节点):在第i个位置前插入元素e,i=1时插入首元
Status ListInsert1(LinkList &L, int i, ElemType e) {
// 位置合法性初步判断:i<1 或 空表时i>1(空表只能插入第1个位置)
if (i < 1 || (L == NULL && i > 1)) {
return ERROR;
}
LinkList s = new LNode; // 生成新节点
s->data = e; // 赋值新节点数据域
// 情况1:插入首元节点(i=1,此时L为NULL或指向原首元)
if (i == 1) {
s->next = L; // 新节点指向原首元(空表时L=NULL,新节点next=NULL)
L = s; // 链表指针指向新节点(新首元)
return OK;
}
// 情况2:插入非首元节点(i>1):先找第i-1个节点
LinkList p = L;
int j = 1;
while (p != NULL && j < i - 1) {
p = p->next;
j++;
}
// 位置不合法:未找到第i-1个节点(i超出表长+1)
if (p == NULL || j > i - 1) {
delete s; // 释放未插入的新节点,避免内存泄漏
return ERROR;
}
// 插入新节点:第i-1个节点后接新节点,新节点接原第i个节点
s->next = p->next;
p->next = s;
return OK;
}
// 6. 删除(不带头节点):删除第i个位置的元素,i=1时删除首元
Status ListDelete1(LinkList &L, int i) {
// 位置合法性初步判断:i<1 或 空表(L=NULL)
if (i < 1 || L == NULL) {
return ERROR;
}
LinkList q; // 暂存待删除节点
// 情况1:删除首元节点(i=1)
if (i == 1) {
q = L; // 待删除节点为当前首元
L = L->next; // 链表指针指向原首元的下一个节点(新首元,空表时为NULL)
delete q; // 释放原首元节点内存
return OK;
}
// 情况2:删除非首元节点(i>1):先找第i-1个节点
LinkList p = L;
int j = 1;
while (p != NULL && j < i - 1) {
p = p->next;
j++;
}
// 位置不合法:未找到第i-1个节点,或第i个节点不存在(p->next=NULL)
if (p == NULL || p->next == NULL || j > i - 1) {
return ERROR;
}
// 删除第i个节点:第i-1个节点跳过待删除节点,释放待删除节点内存
q = p->next;
p->next = q->next;
delete q;
return OK;
}
// 7. 判空(不带头节点):L==NULL即为空表
int ListEmpty_NoHead(LinkList L) {
return (L == NULL) ? 1 : 0; // 1-空表,0-非空表
}
// 8. 求长(不带头节点):从首元节点L开始遍历,统计节点数
int ListLength_L_NoHead(LinkList L) {
int len = 0;
LinkList p = L;
while (p != NULL) {
len++;
p = p->next;
}
return len;
}
// 9. 销毁(不带头节点):遍历删除所有节点,最后L=NULL
Status DestroyList_NoHead(LinkList &L) {
LinkList p;
while (L != NULL) {
p = L; // 暂存当前节点
L = L->next; // 链表指针后移
delete p; // 释放当前节点内存
}
L = NULL; // 确保销毁后链表指针为NULL,避免野指针
return OK;
}
// 10. 打印(不带头节点):适配空表(L=NULL)、非空表(从L开始遍历)
void PrintList_NoHead(LinkList L) {
if (L == NULL) {
cout << "空表" << endl;
return;
}
// 非空表:从首元节点L开始遍历打印
cout << "[";
LinkList p = L;
while (p != NULL) {
cout << p->data;
if (p->next != NULL) {
cout << ", ";
}
p = p->next;
}
cout << "]" << endl;
}
// 11. 前插法建表(不带头节点):每次插入到表头,最终元素逆序
void CreateList_H_NoHead(LinkList &L, int n) {
L = NULL; // 先初始化空表
for (int i = 0; i < n; i++) {
LinkList p = new LNode; // 生成新节点
cin >> p->data; // 输入节点数据
p->next = L; // 新节点指向当前表头(空表时L=NULL)
L = p; // 表头更新为新节点
}
}
// 12. 后插法建表(不带头节点):每次插入到表尾,最终元素正序
void CreateList_R_NoHead(LinkList &L, int n) {
L = NULL; // 先初始化空表
LinkList r = NULL; // 尾指针:始终指向当前表尾
for (int i = 0; i < n; i++) {
LinkList p = new LNode; // 生成新节点
cin >> p->data; // 输入节点数据
p->next = NULL; // 新节点为表尾,next=NULL
if (L == NULL) {
L = p; // 空表时,新节点即为首元,L指向首元
} else {
r->next = p; // 非空表时,尾指针后接新节点
}
r = p; // 尾指针更新为新节点(保持指向表尾)
}
}
完整Python代码如下:
# 定义状态常量(对应原C++的Status)
OK = 1
ERROR = 0
OVERFLOW = -2
# 1. 节点类(对应原C++的LNode结构体)
class Node:
def __init__(self, data=None):
self.data = data # 数据域(支持float,对应原C++的double)
self.next = None # 引用域(替代指针,指向下一个节点)
# 2. 不带头节点单链表类(封装所有核心操作)
class LinkedListNoHead:
def __init__(self):
"""初始化:不带头节点的空链表(head=None表示空表)"""
self.head = None # 链表头引用(直接指向首元节点,空表时为None)
self.is_destroyed = False # 标记链表是否已销毁
def InitList_NoHead(self):
"""重新初始化(对应原InitList_NoHead,空表时head=None)"""
if self.is_destroyed:
self.head = None
self.is_destroyed = False
return OK
def GetElem_NoHead(self, i):
"""取值:获取第i个位置(1-based)的元素,返回(状态, 数据)"""
if self.is_destroyed or self.head is None:
return (ERROR, None) # 链表已销毁或空表
p = self.head # 从首元节点开始遍历
j = 1 # 计数器:当前指向第j个节点
# 遍历到第i个节点或p为None(位置超出表长)
while p is not None and j < i:
p = p.next
j += 1
# 位置不合法:i<1或未找到第i个节点
if p is None or j > i:
return (ERROR, None)
return (OK, p.data)
def LocateElem_NoHead(self, e):
"""查找:返回第一个值为e的节点对象(对应原指针),未找到返回None"""
if self.is_destroyed or self.head is None:
return None
p = self.head
while p is not None and p.data != e:
p = p.next
return p # 找到返回节点,未找到返回None
def LocateELem_L_NoHead(self, e):
"""查找:返回第一个值为e的节点位置(1-based),未找到返回0"""
if self.is_destroyed or self.head is None:
return 0
p = self.head
j = 1 # 位置计数器
while p is not None and p.data != e:
p = p.next
j += 1
return j if p is not None else 0
def ListInsert1(self, i, e):
"""插入:在第i个位置前插入元素e,返回状态(OK/ERROR)"""
# 合法性初步判断:i<1 或 空表时i>1(空表只能插第1个位置)
if i < 1 or (self.head is None and i > 1):
return ERROR
# 创建新节点(Python自动管理内存,无需手动分配)
new_node = Node(e)
# 场景1:插入首元节点(i=1)
if i == 1:
new_node.next = self.head # 新节点指向原首元(空表时为None)
self.head = new_node # 头引用指向新首元
return OK
# 场景2:插入非首元节点(i>1):找第i-1个节点
p = self.head
j = 1
while p is not None and j < i - 1:
p = p.next
j += 1
# 位置不合法:未找到第i-1个节点(i超出表长+1)
if p is None or j > i - 1:
return ERROR
# 插入新节点:建立链接
new_node.next = p.next
p.next = new_node
return OK
def ListDelete1(self, i):
"""删除:删除第i个位置的元素,返回状态(OK/ERROR)"""
# 合法性初步判断:i<1 或 空表
if i < 1 or self.head is None or self.is_destroyed:
return ERROR
# 场景1:删除首元节点(i=1)
if i == 1:
self.head = self.head.next # 头引用指向原首元的下一个节点
# Python垃圾回收自动释放原首元节点内存
return OK
# 场景2:删除非首元节点(i>1):找第i-1个节点
p = self.head
j = 1
while p is not None and j < i - 1:
p = p.next
j += 1
# 位置不合法:未找到第i-1个节点 或 第i个节点不存在
if p is None or p.next is None or j > i - 1:
return ERROR
# 跳过待删除节点(自动回收内存)
p.next = p.next.next
return OK
def ListEmpty_NoHead(self):
"""判空:返回1(空表)或0(非空表),对应原C++逻辑"""
return 1 if (self.head is None and not self.is_destroyed) else 0
def ListLength_L_NoHead(self):
"""求长:返回链表节点个数(表长)"""
if self.is_destroyed or self.head is None:
return 0
count = 0
p = self.head
while p is not None:
count += 1
p = p.next
return count
def DestroyList_NoHead(self):
"""销毁:释放链表所有节点(Python自动回收,只需置空head)"""
self.head = None
self.is_destroyed = True
return OK
def PrintList_NoHead(self):
"""打印:适配空表/非空表,格式为[元素1, 元素2, ...]"""
if self.is_destroyed:
print("已销毁", end="")
return
if self.head is None:
print("空表", end="")
return
# 遍历打印非空表
print("[", end="")
p = self.head
while p is not None:
print(p.data, end="")
if p.next is not None:
print(", ", end="")
p = p.next
print("]", end="")
def CreateList_H_NoHead(self, n):
"""前插法建表:每次插表头,元素顺序与输入相反"""
if self.is_destroyed:
return
self.head = None # 先初始化空表
print(f"请依次输入{n}个元素(支持小数,用空格分隔):", end="")
elements = list(map(float, input().split()))
# 检查输入元素个数是否匹配n
if len(elements) != n:
print("输入元素个数不匹配!建表失败。")
self.head = None
return
# 前插:新节点始终成为首元
for e in elements:
new_node = Node(e)
new_node.next = self.head
self.head = new_node
def CreateList_R_NoHead(self, n):
"""后插法建表:每次插表尾,元素顺序与输入一致"""
if self.is_destroyed:
return
self.head = None
tail = None # 尾指针:始终指向当前表尾
print(f"请依次输入{n}个元素(支持小数,用空格分隔):", end="")
elements = list(map(float, input().split()))
if len(elements) != n:
print("输入元素个数不匹配!建表失败。")
self.head = None
return
# 后插:新节点追加到表尾
for e in elements:
new_node = Node(e)
if self.head is None:
self.head = new_node # 空表时,新节点即为首元
tail = new_node
else:
tail.next = new_node # 非空表时,尾指针后接新节点
tail = new_node # 更新尾指针
# 3. 主函数:菜单驱动测试(与原C++ main逻辑完全一致)
def main():
ll = LinkedListNoHead() # 实例化不带头节点链表
while True:
# 打印菜单
print("\n======= 不带头节点单链表测试菜单 =======")
print("1. 初始化不带头节点链表")
print("2. 建表测试(前插法/后插法)")
print("3. 取值测试(根据位置获取元素)")
print("4. 查找测试(根据元素找位置/地址)")
print("5. 插入测试(指定位置插入元素)")
print("6. 删除测试(指定位置删除元素)")
print("7. 表属性测试(判空/求表长)")
print("8. 销毁不带头节点链表")
print("0. 退出程序")
print("======================================")
# 获取用户选择
try:
choice = int(input("请输入选择(0-8):"))
except ValueError:
print("无效输入!请输入整数(0-8)。")
continue
# 退出逻辑
if choice == 0:
print("程序退出,释放所有内存...")
if not ll.is_destroyed:
ll.DestroyList_NoHead()
break
# 1. 初始化
elif choice == 1:
if ll.InitList_NoHead() == OK:
print("不带头节点链表初始化成功!(空表时指针为NULL)")
ll.PrintList_NoHead()
print() # 换行
else:
print("不带头节点链表初始化失败!")
# 2. 建表测试
elif choice == 2:
if ll.is_destroyed:
print("链表已销毁,请先初始化!")
continue
try:
build_type = int(input("请选择建表方式:1-前插法(逆序) 2-后插法(正序):"))
n = int(input("请输入要插入的元素个数:"))
except ValueError:
print("无效输入!个数和方式需为整数。")
continue
if n <= 0:
print("元素个数必须为正整数!")
continue
if build_type == 1:
ll.CreateList_H_NoHead(n)
print("前插法建表完成!", end="")
elif build_type == 2:
ll.CreateList_R_NoHead(n)
print("后插法建表完成!", end="")
else:
print("无效建表方式!", end="")
ll.PrintList_NoHead()
print() # 换行
# 3. 取值测试
elif choice == 3:
if ll.is_destroyed or ll.ListEmpty_NoHead() == 1:
print("链表已销毁或为空,无法取值!")
continue
len_ll = ll.ListLength_L_NoHead()
print(f"当前链表长度:{len_ll}")
try:
i = int(input(f"请输入要取值的位置i(1-{len_ll}):"))
except ValueError:
print("无效输入!位置需为整数。")
continue
status, e = ll.GetElem_NoHead(i)
if status == OK:
print(f"位置{i}的元素值为:{e}")
else:
print(f"取值失败!位置{i}不合法(超出表长或小于1)")
# 4. 查找测试
elif choice == 4:
if ll.is_destroyed or ll.ListEmpty_NoHead() == 1:
print("链表已销毁或为空,无法查找!")
continue
try:
e = float(input("请输入要查找的元素值(支持小数):"))
except ValueError:
print("无效输入!元素需为数字。")
continue
# 测试1:返回位置
pos = ll.LocateELem_L_NoHead(e)
if pos != 0:
print(f"查找成功(位置序号):元素{e}在链表中的位置为{pos}")
else:
print(f"查找失败(位置序号):链表中无元素{e}")
# 测试2:返回节点地址(简化为是否找到)
node = ll.LocateElem_NoHead(e)
if node is not None:
print(f"查找成功(地址验证):元素{e}的地址有效(存在该元素)")
else:
print(f"查找失败(地址验证):元素{e}的地址为NULL(不存在)")
# 5. 插入测试
elif choice == 5:
if ll.is_destroyed:
print("链表已销毁,请先初始化!")
continue
len_ll = ll.ListLength_L_NoHead()
max_pos = len_ll + 1 # 空表时max_pos=1
print(f"当前链表长度:{len_ll}")
try:
pos = int(input(f"请输入插入位置i(1-{max_pos}):"))
e = float(input("请输入插入的元素值(支持小数):"))
except ValueError:
print("无效输入!位置需为整数,元素需为数字。")
continue
status = ll.ListInsert1(pos, e)
if status == OK:
print("插入成功!插入后链表:", end="")
ll.PrintList_NoHead()
print()
else:
print(f"插入失败!位置{pos}不合法(需在1-{max_pos}之间)")
# 6. 删除测试
elif choice == 6:
if ll.is_destroyed or ll.ListEmpty_NoHead() == 1:
print("链表已销毁或为空,无法删除!")
continue
len_ll = ll.ListLength_L_NoHead()
print(f"当前链表长度:{len_ll}")
try:
pos = int(input(f"请输入删除位置i(1-{len_ll}):"))
except ValueError:
print("无效输入!位置需为整数。")
continue
status = ll.ListDelete1(pos)
if status == OK:
print("删除成功!删除后链表:", end="")
ll.PrintList_NoHead()
print()
else:
print(f"删除失败!位置{pos}不合法(需在1-{len_ll}之间)")
# 7. 表属性测试
elif choice == 7:
if ll.is_destroyed:
print("链表已销毁,请先初始化!")
continue
is_empty = ll.ListEmpty_NoHead()
len_ll = ll.ListLength_L_NoHead()
print("不带头节点链表属性测试:")
print(f"是否为空表:{'是' if is_empty == 1 else '否'}")
print(f"当前表长:{len_ll}")
print("当前链表元素:", end="")
ll.PrintList_NoHead()
print()
# 8. 销毁测试
elif choice == 8:
if ll.is_destroyed:
print("不带头节点链表已销毁,无需重复操作!")
continue
if ll.DestroyList_NoHead() == OK:
print("不带头节点链表销毁成功!")
ll.is_destroyed = True
else:
print("不带头节点链表销毁失败!")
# 无效选择
else:
print("无效选择,请重新输入(0-8)!")
if __name__ == "__main__":
main()
完整Java代码如下:
import java.util.Scanner;
// 1. 结果封装类:解决Java无引用传参问题(封装状态码+数据)
class Result {
private int status; // 状态码:OK=1,ERROR=0
private double data; // 取值结果(仅status=OK时有效)
// 构造器:无数据(如插入、删除操作)
public Result(int status) {
this.status = status;
}
// 构造器:带数据(如取值操作)
public Result(int status, double data) {
this.status = status;
this.data = data;
}
// Getter方法
public int getStatus() {
return status;
}
public double getData() {
return data;
}
}
// 2. 节点类:不带头节点单链表的节点结构(对应原C++的LNode)
class Node {
double data; // 数据域(支持小数,对应原C++的ElemType)
Node next; // 引用域(替代C++指针,指向下一个节点)
// 构造器:初始化数据
public Node(double data) {
this.data = data;
this.next = null;
}
// 构造器:空节点(仅用于临时占位)
public Node() {
this.data = 0.0;
this.next = null;
}
}
// 3. 核心类:不带头节点单链表的所有操作+菜单测试
public class LinkedListNoHead {
// 状态常量(对应原C++的宏定义)
public static final int OK = 1;
public static final int ERROR = 0;
public static final int OVERFLOW = -2;
private Node head; // 链表头引用(指向首元节点,空表时为null)
private boolean isDestroyed; // 标记链表是否已销毁
private Scanner scanner; // 输入工具(替代C++的cin)
// 构造器:初始化空链表
public LinkedListNoHead() {
this.head = null; // 不带头节点,空表标识为head=null
this.isDestroyed = false;
this.scanner = new Scanner(System.in);
}
// -------------------------- 核心操作方法(与原C++一一对应) --------------------------
/**
* 1. 初始化:空表时head=null(对应原C++ InitList_NoHead)
*/
public int InitList_NoHead() {
if (isDestroyed) {
this.head = null;
this.isDestroyed = false;
}
return OK;
}
/**
* 2. 取值:获取第i个位置(1-based)的元素(对应原C++ GetElem_NoHead)
*/
public Result GetElem_NoHead(int i) {
if (isDestroyed || head == null) {
return new Result(ERROR);
}
Node p = head; // 从首元节点开始遍历
int j = 1; // 位置计数器
// 遍历到第i个节点或超出表长
while (p != null && j < i) {
p = p.next;
j++;
}
// 位置不合法(i<1或无第i个节点)
if (p == null || j > i) {
return new Result(ERROR);
}
return new Result(OK, p.data);
}
/**
* 3. 查找:返回第一个值为e的节点引用(对应原C++ LocateElem_NoHead)
*/
public Node LocateElem_NoHead(double e) {
if (isDestroyed || head == null) {
return null;
}
Node p = head;
while (p != null && p.data != e) {
p = p.next;
}
return p; // 找到返回节点,未找到返回null
}
/**
* 4. 查找:返回第一个值为e的节点位置(1-based,对应原C++ LocateELem_L_NoHead)
*/
public int LocateELem_L_NoHead(double e) {
if (isDestroyed || head == null) {
return 0;
}
Node p = head;
int j = 1;
while (p != null && p.data != e) {
p = p.next;
j++;
}
return (p != null) ? j : 0;
}
/**
* 5. 插入:在第i个位置前插入元素e(对应原C++ ListInsert1)
*/
public int ListInsert1(int i, double e) {
// 空表只能插入第1个位置,i<1非法
if (i < 1 || (head == null && i > 1)) {
return ERROR;
}
Node newNode = new Node(e);
// 场景1:插入首元节点(i=1)
if (i == 1) {
newNode.next = head;
head = newNode;
return OK;
}
// 场景2:插入非首元节点(找第i-1个节点)
Node p = head;
int j = 1;
while (p != null && j < i - 1) {
p = p.next;
j++;
}
// 未找到第i-1个节点(i超出表长+1)
if (p == null || j > i - 1) {
return ERROR;
}
// 建立新节点链接
newNode.next = p.next;
p.next = newNode;
return OK;
}
/**
* 6. 删除:删除第i个位置的元素(对应原C++ ListDelete1)
*/
public int ListDelete1(int i) {
if (i < 1 || head == null || isDestroyed) {
return ERROR;
}
// 场景1:删除首元节点(i=1)
if (i == 1) {
head = head.next; // 头引用指向新首元(空表时为null)
return OK;
}
// 场景2:删除非首元节点(找第i-1个节点)
Node p = head;
int j = 1;
while (p != null && j < i - 1) {
p = p.next;
j++;
}
// 未找到第i-1个节点或无第i个节点
if (p == null || p.next == null || j > i - 1) {
return ERROR;
}
// 跳过待删除节点(GC自动回收)
p.next = p.next.next;
return OK;
}
/**
* 7. 判空:空表返回1,非空返回0(对应原C++ ListEmpty_NoHead)
*/
public int ListEmpty_NoHead() {
return (head == null && !isDestroyed) ? 1 : 0;
}
/**
* 8. 求长:返回链表节点个数(对应原C++ ListLength_L_NoHead)
*/
public int ListLength_L_NoHead() {
if (isDestroyed || head == null) {
return 0;
}
int count = 0;
Node p = head;
while (p != null) {
count++;
p = p.next;
}
return count;
}
/**
* 9. 销毁:释放所有节点(GC自动处理,对应原C++ DestroyList_NoHead)
*/
public int DestroyList_NoHead() {
head = null; // 断开头引用,节点无引用后被回收
isDestroyed = true;
return OK;
}
/**
* 10. 打印:空表输出“空表”,非空表输出[元素1, 元素2, ...](对应原C++ PrintList_NoHead)
*/
public void PrintList_NoHead() {
if (isDestroyed) {
System.out.print("已销毁");
return;
}
if (head == null) {
System.out.print("空表");
return;
}
System.out.print("[");
Node p = head;
while (p != null) {
System.out.print(p.data);
if (p.next != null) {
System.out.print(", ");
}
p = p.next;
}
System.out.print("]");
}
/**
* 11. 前插法建表:元素顺序与输入相反(对应原C++ CreateList_H_NoHead)
*/
public void CreateList_H_NoHead(int n) {
if (isDestroyed) {
System.out.println("链表已销毁,请先初始化!");
return;
}
head = null;
System.out.printf("请依次输入%d个元素(支持小数,用空格分隔):", n);
double[] elements = new double[n];
for (int i = 0; i < n; i++) {
elements[i] = scanner.nextDouble();
}
// 新节点始终插入表头
for (double e : elements) {
Node newNode = new Node(e);
newNode.next = head;
head = newNode;
}
}
/**
* 12. 后插法建表:元素顺序与输入一致(对应原C++ CreateList_R_NoHead)
*/
public void CreateList_R_NoHead(int n) {
if (isDestroyed) {
System.out.println("链表已销毁,请先初始化!");
return;
}
head = null;
Node tail = null; // 尾指针:始终指向表尾
System.out.printf("请依次输入%d个元素(支持小数,用空格分隔):", n);
double[] elements = new double[n];
for (int i = 0; i < n; i++) {
elements[i] = scanner.nextDouble();
}
// 新节点追加到表尾
for (double e : elements) {
Node newNode = new Node(e);
if (head == null) {
head = newNode; // 空表时新节点为首选
tail = newNode;
} else {
tail.next = newNode;
tail = newNode;
}
}
}
// -------------------------- 菜单驱动测试(对应原C++ main函数) --------------------------
public static void main(String[] args) {
LinkedListNoHead ll = new LinkedListNoHead();
int choice, n, i, pos;
double e;
while (true) {
// 打印测试菜单
System.out.println("\n======= 不带头节点单链表测试菜单 =======");
System.out.println("1. 初始化不带头节点链表");
System.out.println("2. 建表测试(前插法/后插法)");
System.out.println("3. 取值测试(根据位置获取元素)");
System.out.println("4. 查找测试(根据元素找位置/地址)");
System.out.println("5. 插入测试(指定位置插入元素)");
System.out.println("6. 删除测试(指定位置删除元素)");
System.out.println("7. 表属性测试(判空/求表长)");
System.out.println("8. 销毁不带头节点链表");
System.out.println("0. 退出程序");
System.out.println("======================================");
System.out.print("请输入选择(0-8):");
// 读取用户选择(处理非整数输入)
try {
choice = ll.scanner.nextInt();
} catch (Exception ex) {
System.out.println("无效输入!请输入整数(0-8)。");
ll.scanner.next(); // 清除无效输入缓存
continue;
}
// 退出逻辑:销毁链表+关闭输入流
if (choice == 0) {
System.out.println("程序退出,释放所有内存...");
if (!ll.isDestroyed) {
ll.DestroyList_NoHead();
}
ll.scanner.close();
break;
}
// 1. 初始化操作
if (choice == 1) {
if (ll.InitList_NoHead() == OK) {
System.out.println("不带头节点链表初始化成功!(空表时指针为NULL)");
ll.PrintList_NoHead();
System.out.println();
} else {
System.out.println("不带头节点链表初始化失败!");
}
continue;
}
// 2. 建表操作
if (choice == 2) {
if (ll.isDestroyed) {
System.out.println("链表已销毁,请先初始化!");
continue;
}
// 选择建表方式
System.out.print("请选择建表方式:1-前插法(逆序) 2-后插法(正序):");
int buildType;
try {
buildType = ll.scanner.nextInt();
} catch (Exception ex) {
System.out.println("无效输入!方式需为整数(1或2)。");
ll.scanner.next();
continue;
}
// 输入元素个数
System.out.print("请输入要插入的元素个数:");
try {
n = ll.scanner.nextInt();
} catch (Exception ex) {
System.out.println("无效输入!个数需为整数。");
ll.scanner.next();
continue;
}
if (n <= 0) {
System.out.println("元素个数必须为正整数!");
continue;
}
// 执行建表并打印
if (buildType == 1) {
ll.CreateList_H_NoHead(n);
System.out.print("前插法建表完成!");
} else if (buildType == 2) {
ll.CreateList_R_NoHead(n);
System.out.print("后插法建表完成!");
} else {
System.out.print("无效建表方式!");
continue;
}
ll.PrintList_NoHead();
System.out.println();
continue;
}
// 3. 取值操作
if (choice == 3) {
if (ll.isDestroyed || ll.ListEmpty_NoHead() == 1) {
System.out.println("链表已销毁或为空,无法取值!");
continue;
}
int len = ll.ListLength_L_NoHead();
System.out.printf("当前链表长度:%d\n", len);
System.out.printf("请输入要取值的位置i(1-%d):", len);
try {
i = ll.scanner.nextInt();
} catch (Exception ex) {
System.out.println("无效输入!位置需为整数。");
ll.scanner.next();
continue;
}
Result result = ll.GetElem_NoHead(i);
if (result.getStatus() == OK) {
System.out.printf("位置%d的元素值为:%f\n", i, result.getData());
} else {
System.out.printf("取值失败!位置%d不合法(超出表长或小于1)\n", i);
}
continue;
}
// 4. 查找操作
if (choice == 4) {
if (ll.isDestroyed || ll.ListEmpty_NoHead() == 1) {
System.out.println("链表已销毁或为空,无法查找!");
continue;
}
System.out.print("请输入要查找的元素值(支持小数):");
try {
e = ll.scanner.nextDouble();
} catch (Exception ex) {
System.out.println("无效输入!元素需为数字。");
ll.scanner.next();
continue;
}
// 测试1:返回位置
pos = ll.LocateELem_L_NoHead(e);
if (pos != 0) {
System.out.printf("查找成功(位置序号):元素%f在链表中的位置为%d\n", e, pos);
} else {
System.out.printf("查找失败(位置序号):链表中无元素%f\n", e);
}
// 测试2:返回节点引用(简化为是否找到)
Node node = ll.LocateElem_NoHead(e);
if (node != null) {
System.out.printf("查找成功(地址验证):元素%f的地址有效(存在该元素)\n", e);
} else {
System.out.printf("查找失败(地址验证):元素%f的地址为NULL(不存在)\n", e);
}
continue;
}
// 5. 插入操作
if (choice == 5) {
if (ll.isDestroyed) {
System.out.println("链表已销毁,请先初始化!");
continue;
}
int len = ll.ListLength_L_NoHead();
int maxPos = len + 1; // 空表时maxPos=1
System.out.printf("当前链表长度:%d\n", len);
System.out.printf("请输入插入位置i(1-%d):", maxPos);
// 读取插入位置
try {
pos = ll.scanner.nextInt();
} catch (Exception ex) {
System.out.println("无效输入!位置需为整数。");
ll.scanner.next();
continue;
}
// 读取插入元素
System.out.print("请输入插入的元素值(支持小数):");
try {
e = ll.scanner.nextDouble();
} catch (Exception ex) {
System.out.println("无效输入!元素需为数字。");
ll.scanner.next();
continue;
}
// 执行插入并打印
int insertStatus = ll.ListInsert1(pos, e);
if (insertStatus == OK) {
System.out.print("插入成功!插入后链表:");
ll.PrintList_NoHead();
System.out.println();
} else {
System.out.printf("插入失败!位置%d不合法(需在1-%d之间)\n", pos, maxPos);
}
continue;
}
// 6. 删除操作
if (choice == 6) {
if (ll.isDestroyed || ll.ListEmpty_NoHead() == 1) {
System.out.println("链表已销毁或为空,无法删除!");
continue;
}
int len = ll.ListLength_L_NoHead();
System.out.printf("当前链表长度:%d\n", len);
System.out.printf("请输入删除位置i(1-%d):", len);
try {
pos = ll.scanner.nextInt();
} catch (Exception ex) {
System.out.println("无效输入!位置需为整数。");
ll.scanner.next();
continue;
}
// 执行删除并打印
int deleteStatus = ll.ListDelete1(pos);
if (deleteStatus == OK) {
System.out.print("删除成功!删除后链表:");
ll.PrintList_NoHead();
System.out.println();
} else {
System.out.printf("删除失败!位置%d不合法(需在1-%d之间)\n", pos, len);
}
continue;
}
// 7. 表属性测试
if (choice == 7) {
if (ll.isDestroyed) {
System.out.println("链表已销毁,请先初始化!");
continue;
}
int isEmpty = ll.ListEmpty_NoHead();
int len = ll.ListLength_L_NoHead();
System.out.println("不带头节点链表属性测试:");
System.out.printf("是否为空表:%s\n", isEmpty == 1 ? "是" : "否");
System.out.printf("当前表长:%d\n", len);
System.out.print("当前链表元素:");
ll.PrintList_NoHead();
System.out.println();
continue;
}
// 8. 销毁操作
if (choice == 8) {
if (ll.isDestroyed) {
System.out.println("不带头节点链表已销毁,无需重复操作!");
continue;
}
if (ll.DestroyList_NoHead() == OK) {
System.out.println("不带头节点链表销毁成功!");
ll.isDestroyed = true;
} else {
System.out.println("不带头节点链表销毁失败!");
}
continue;
}
// 无效选择
System.out.println("无效选择,请重新输入(0-8)!");
}
}
}
程序运行结果如下:
======= 不带头节点单链表测试菜单 =======
1. 初始化不带头节点链表
2. 建表测试(前插法/后插法)
3. 取值测试(根据位置获取元素)
4. 查找测试(根据元素找位置/地址)
5. 插入测试(指定位置插入元素)
6. 删除测试(指定位置删除元素)
7. 表属性测试(判空/求表长)
8. 销毁不带头节点链表
0. 退出程序
======================================
请输入选择(0-8):1
不带头节点链表初始化成功!(空表时指针为NULL)
空表
======= 不带头节点单链表测试菜单 =======
1. 初始化不带头节点链表
2. 建表测试(前插法/后插法)
3. 取值测试(根据位置获取元素)
4. 查找测试(根据元素找位置/地址)
5. 插入测试(指定位置插入元素)
6. 删除测试(指定位置删除元素)
7. 表属性测试(判空/求表长)
8. 销毁不带头节点链表
0. 退出程序
======================================
请输入选择(0-8):2
请选择建表方式:1-前插法(逆序) 2-后插法(正序):2
请输入要插入的元素个数:6
请依次输入6个元素(支持小数,用空格分隔):34 12 98 67 43 71
后插法建表完成![34, 12, 98, 67, 43, 71]
======= 不带头节点单链表测试菜单 =======
1. 初始化不带头节点链表
2. 建表测试(前插法/后插法)
3. 取值测试(根据位置获取元素)
4. 查找测试(根据元素找位置/地址)
5. 插入测试(指定位置插入元素)
6. 删除测试(指定位置删除元素)
7. 表属性测试(判空/求表长)
8. 销毁不带头节点链表
0. 退出程序
======================================
请输入选择(0-8):3
当前链表长度:6
请输入要取值的位置i(1-6):2
位置2的元素值为:12
======= 不带头节点单链表测试菜单 =======
1. 初始化不带头节点链表
2. 建表测试(前插法/后插法)
3. 取值测试(根据位置获取元素)
4. 查找测试(根据元素找位置/地址)
5. 插入测试(指定位置插入元素)
6. 删除测试(指定位置删除元素)
7. 表属性测试(判空/求表长)
8. 销毁不带头节点链表
0. 退出程序
======================================
请输入选择(0-8):4
请输入要查找的元素值(支持小数):67
查找成功(位置序号):元素67在链表中的位置为4
查找成功(地址验证):元素67的地址有效(存在该元素)
======= 不带头节点单链表测试菜单 =======
1. 初始化不带头节点链表
2. 建表测试(前插法/后插法)
3. 取值测试(根据位置获取元素)
4. 查找测试(根据元素找位置/地址)
5. 插入测试(指定位置插入元素)
6. 删除测试(指定位置删除元素)
7. 表属性测试(判空/求表长)
8. 销毁不带头节点链表
0. 退出程序
======================================
请输入选择(0-8):5
当前链表长度:6
请输入插入位置i(1-7):4
请输入插入的元素值(支持小数):87
插入成功!插入后链表:[34, 12, 98, 87, 67, 43, 71]
======= 不带头节点单链表测试菜单 =======
1. 初始化不带头节点链表
2. 建表测试(前插法/后插法)
3. 取值测试(根据位置获取元素)
4. 查找测试(根据元素找位置/地址)
5. 插入测试(指定位置插入元素)
6. 删除测试(指定位置删除元素)
7. 表属性测试(判空/求表长)
8. 销毁不带头节点链表
0. 退出程序
======================================
请输入选择(0-8):6
当前链表长度:7
请输入删除位置i(1-7):3
删除成功!删除后链表:[34, 12, 87, 67, 43, 71]
======= 不带头节点单链表测试菜单 =======
1. 初始化不带头节点链表
2. 建表测试(前插法/后插法)
3. 取值测试(根据位置获取元素)
4. 查找测试(根据元素找位置/地址)
5. 插入测试(指定位置插入元素)
6. 删除测试(指定位置删除元素)
7. 表属性测试(判空/求表长)
8. 销毁不带头节点链表
0. 退出程序
======================================
请输入选择(0-8):7
不带头节点链表属性测试:
是否为空表:否
当前表长:6
当前链表元素:[34, 12, 87, 67, 43, 71]
======= 不带头节点单链表测试菜单 =======
1. 初始化不带头节点链表
2. 建表测试(前插法/后插法)
3. 取值测试(根据位置获取元素)
4. 查找测试(根据元素找位置/地址)
5. 插入测试(指定位置插入元素)
6. 删除测试(指定位置删除元素)
7. 表属性测试(判空/求表长)
8. 销毁不带头节点链表
0. 退出程序
======================================
请输入选择(0-8):8
不带头节点链表销毁成功!
======= 不带头节点单链表测试菜单 =======
1. 初始化不带头节点链表
2. 建表测试(前插法/后插法)
3. 取值测试(根据位置获取元素)
4. 查找测试(根据元素找位置/地址)
5. 插入测试(指定位置插入元素)
6. 删除测试(指定位置删除元素)
7. 表属性测试(判空/求表长)
8. 销毁不带头节点链表
0. 退出程序
======================================
请输入选择(0-8):0
程序退出,释放所有内存...
总结:不带头节点单链表算法的核心特点
| 特点 | 说明 |
|---|---|
| 空表标识 | L == NULL(无任何节点),区别于带头节点的head->next == NULL |
| 操作首元需特殊处理 | 插入 / 删除首元时需修改L指针,非首元操作需找i-1节点 |
| 内存效率 | 节省一个头节点的内存,适合内存受限场景 |
| 边界条件更复杂 | 空表插入只能i=1,删除首元后需确保L置为NULL(避免野指针) |
| 遍历起点 | 所有遍历操作(取值、查找、求长)均从L开始,无需跳过头节点 |
浙公网安备 33010602011771号