个人学习所记录笔记,便于复习查看,如有错误,请见谅。
一、单链表的初始化(以下默认均为带头结点的)
//在这个示例中,我们使用 struct 关键字定义了一个结构体 LNode,并添加了成员变量 data 和 next。
//然后,使用 using 关键字将 struct LNode* 定义为类型别名 LinkList。
//在 C++ 中不需要在结构体声明之前使用 typedef 关键字,而是可以直接使用 using 关键字来定义类型别名。
强调这是一个单链表一使用 LinkList
强调这是一个结点 -使用 LNode *
struct LNode {
int data;
struct LNode* next;
};
using LinkList = struct LNode*;
//带头节点的单链表
bool InitList(LinkList& L) {
L = new LNode; // 分配一个头结点
if (L == nullptr) {
// 内存不足,分配失败
return false;
}
L->next = nullptr; // 头结点之后暂时还没有节点
return true;
}
//判断是否为空
bool IsEmpty(LinkList L) {
return L->next == nullptr;
}
二、各种操作
1.按位查找
//按位查找
LNode* GetElem(LinkList L, int i) {
if (i < 0) {
// 位序小于1或链表为空,返回nullptr
return nullptr;
}
LNode* p = L; // p指向第一个节点
int j = 0; // 计数器,用于记录当前节点的位序,即当前p指向第几个节点
while (p != nullptr && j < i) {
p = p->next;
j++;
}
if (p == nullptr || j > i) {
// 未找到指定节点或位序超出链表长度,返回nullptr
return nullptr;
}
return p; // 返回找到的节点指针
}
2.后插与前插操作
//指定结点的后插操作
bool InsertNextNode(LNode* p, int e) {
if (p == nullptr) {
// 插入位置无效
return false;
}
// 创建新节点
LNode* newNode = new LNode;
newNode->data = e;
// 插入新节点
newNode->next = p->next;
p->next = newNode;
return true;
}
//给定结点的前插操作
bool InsertPriorNode(LNode* p, int e) {
if (p == nullptr) {
// 插入位置无效
return false;
}
// 创建新节点
LNode* newNode = new LNode;
newNode->data = e;
// 插入新节点
newNode->next = p->next;
p->next = newNode;
// 交换数据
int tmp = p->data;
p->data = newNode->data;
newNode->data = tmp;
return true;
}
3.按位序插入操作
有两种写法,一种是基于按位查找与后插结合的方法,既下面展示的
另一种为展开写,为相应位置注释掉的,两种方法均可实现
//按位序插入
bool ListInsert(LinkList& L, int i, int e) {
if (i < 1) {
// 位序小于1无效
return false;
}
//使用了按位查找的方法
LNode* p = GetElem(L, i-1);
//int j = 0; // 计数器,用于记录当前节点的位序,既当前p指向第几个结点
//LNode* p = L; // p指向头节点
寻找要插入位置的前一个节点i-1
//while (p != nullptr && j < i - 1) {
// p = p->next;
// j++;
//}
//调用了后插操作
return InsertNextNode(p, e);
//if (p == nullptr) {
// // 位序超过链表长度,或者链表为空
// return false;
//}
创建新节点
//LNode* s = new LNode;
//s->data = e;
插入新节点
//s->next = p->next;
//p->next = s;
这两句不能颠倒
//return true;
}
4.按位序删除
部分代码也可以替换为引用按位查找方法
//按位序删除(带头结点的)
bool ListDelete(LinkList& L, int i, int& e) {
if (i < 1) {
// 位序小于1无效
return false;
}
LNode* p = GetElem(L, i - 1);
if (p == nullptr || p->next == nullptr) {
// 未找到待删除节点或待删除节点是最后一个节点,删除失败
return false;
}
LNode* q = p->next; // 待删除节点
e = q->data; // 将待删除节点的值赋给e
p->next = q->next; // 将待删除节点的下一个节点链接到前一个节点上
delete q; // 删除节点
return true; // 删除成功
}
5.删除指定结点
//删除指定结点
void Delete(LNode* p) {
if (p == nullptr || p->next == nullptr) {
// 要删除的节点为空或为尾节点,无法删除
return ;
}
LNode* q = p->next; // q指向要删除的节点
p->data = q->data; // 将后继节点的值赋给当前节点
p->next = q->next; // 修改当前节点的指针指向后继节点的后继节点
delete q; // 删除要删除的节点
}
6.打印链表和输出链表的长度
// 输出链表数据
void PrintList(LinkList L) {
LNode* p = L->next; // p指向第一个节点
while (p != nullptr) {
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
//求表的长度
int GetLength(LinkList L) {
int count = 0;
LNode* p = L->next; // p指向首节点
while (p != nullptr) {
count++;
p = p->next;
}
return count;
}
7.按位查找
//按值查找,返回节点
LNode* LocateElem(LinkList L, int e) {
LNode* p = L->next; // p指向首节点
while (p != nullptr && p->data != e) {
p = p->next;
}
return p;
}
8.按值查找
第一种返回的是指针地址,第二种可以返回数据在第几位,既i。
//按值查找,返回第几位
int LocateElem1(LinkList L, int e) {
LNode* p = L->next; // p指向首节点
int i = 1; // 初始化计数器
while (p != nullptr && p->data != e) {
p = p->next;
i++;
}
if (p == nullptr) {
return 0; // 未找到元素,返回0
}
else {
return i; // 返回元素在链表中的位置
}
}
//求表的长度
int GetLength(LinkList L) {
int count = 0;
LNode* p = L->next; // p指向首节点
while (p != nullptr) {
count++;
p = p->next;
}
return count;
}
9.头插法与尾插法,在表头或者表尾插入数据
两种方法类似,核心为:初始化操作,后插操作
//尾插法
LinkList List_TailInsert(LinkList& L) {
int x;
L = new LNode; // 创建头节点
LNode* s = L; // 初始化尾指针
while (cin >> x) { // 循环输入元素值
if (x == -1) { // 输入-1结束输入
break;
}
LNode* p = new LNode; // 创建新节点
p->data = x; // 赋值新节点数据
s->next = p; // 将新节点插入到尾部
s = p; // 更新尾指针
}
s->next = nullptr; // 尾节点的next指向nullptr
return L;
}
//头插法
LinkList List_HeadInsert(LinkList& L) {
int x;
L = new LNode; // 创建头节点
L->next = nullptr; // 初始化头节点的next指针为nullptr
while (cin >> x) { // 循环输入元素值
if (x == -1) { // 输入-1结束输入
break;
}
LNode* p = new LNode; // 创建新节点
p->data = x; // 赋值新节点数据
p->next = L->next; // 将新节点插入到头部
L->next = p; // 更新头节点的next指针
}
return L;
}
10.函数调用
int main() {
LinkList L; //声明一个指向单链表的指针
//初始化一个空表
if (InitList(L)) {
cout << "初始化成功" << endl;
}
else {
cout << "初始化失败" << endl;
}
//插入数据
ListInsert(L, 1, 10);
ListInsert(L, 2, 20);
ListInsert(L, 3, 30);
// 判断链表是否为空
if (IsEmpty(L)) {
cout << "链表为空" << endl;
}
else {
cout << "链表非空" << endl;
}
// 输出链表中的数据
PrintList(L);
int e;
// 调用ListDelete函数删除指定位置的节点
if (ListDelete(L, 3, e)) {
cout << "成功删除节点,被删除节点的值为:" << e << endl;
}
else {
cout << "删除节点失败" << endl;
}
PrintList(L);
//按位置查找
LNode* result = GetElem(L, 1);
if (result != nullptr) {
cout << "找到了位序为3的节点,该节点的值为:" << result->data << endl;
}
else {
cout << "未找到位序为3的节点" << endl;
}
//按值查找
// 按值查找元素,找到的是节点指针
int target = 20;
LNode* result1 = LocateElem(L, target);
if (result1 != nullptr) {
cout << "找到元素 " << target << ",节点的地址为:" << result1 << endl;
}
else {
cout << "未找到元素 " << target << endl;
}
// 按值查找元素,返回i
int target1 = 30;
int result2 = LocateElem1(L, target1);
if (result2 != 0) {
cout << "找到元素 " << target1 << ",节点的位置为:" << result2 << endl;
}
else {
cout << "未找到元素 " << target1 << endl;
}
// 获取链表的长度
int length = GetLength(L);
cout << "链表的长度为:" << length << endl;
//尾插法代码实现
//L = List_TailInsert(L);
//PrintList(L); // 输出链表
//头插法实现与尾插类似
return 0;
}
11.完整代码实现
#include<iostream>
#include<new>
using namespace std;
struct LNode {
int data;
struct LNode* next;
};
using LinkList = struct LNode*;
//带头节点的单链表
bool InitList(LinkList& L) {
L = new LNode; // 分配一个头结点
if (L == nullptr) {
// 内存不足,分配失败
return false;
}
L->next = nullptr; // 头结点之后暂时还没有节点
return true;
}
//判断是否为空
bool IsEmpty(LinkList L) {
return L->next == nullptr;
}
//按位查找
LNode* GetElem(LinkList L, int i) {
if (i < 0) {
// 位序小于1或链表为空,返回nullptr
return nullptr;
}
LNode* p = L; // p指向第一个节点
int j = 0; // 计数器,用于记录当前节点的位序,即当前p指向第几个节点
while (p != nullptr && j < i) {
p = p->next;
j++;
}
if (p == nullptr || j > i) {
// 未找到指定节点或位序超出链表长度,返回nullptr
return nullptr;
}
return p; // 返回找到的节点指针
}
//指定结点的后插操作
bool InsertNextNode(LNode* p, int e) {
if (p == nullptr) {
// 插入位置无效
return false;
}
// 创建新节点
LNode* newNode = new LNode;
newNode->data = e;
// 插入新节点
newNode->next = p->next;
p->next = newNode;
return true;
}
//按位序插入
bool ListInsert(LinkList& L, int i, int e) {
if (i < 1) {
// 位序小于1无效
return false;
}
//使用了按位查找的方法
LNode* p = GetElem(L, i-1);
//int j = 0; // 计数器,用于记录当前节点的位序,既当前p指向第几个结点
//LNode* p = L; // p指向头节点
寻找要插入位置的前一个节点i-1
//while (p != nullptr && j < i - 1) {
// p = p->next;
// j++;
//}
//调用了后插操作
return InsertNextNode(p, e);
//if (p == nullptr) {
// // 位序超过链表长度,或者链表为空
// return false;
//}
创建新节点
//LNode* s = new LNode;
//s->data = e;
插入新节点
//s->next = p->next;
//p->next = s;
这两句不能颠倒
//return true;
}
//给定结点的前插操作
bool InsertPriorNode(LNode* p, int e) {
if (p == nullptr) {
// 插入位置无效
return false;
}
// 创建新节点
LNode* newNode = new LNode;
newNode->data = e;
// 插入新节点
newNode->next = p->next;
p->next = newNode;
// 交换数据
int tmp = p->data;
p->data = newNode->data;
newNode->data = tmp;
return true;
}
//按位序删除(带头结点的)
bool ListDelete(LinkList& L, int i, int& e) {
if (i < 1) {
// 位序小于1无效
return false;
}
LNode* p = GetElem(L, i - 1);
if (p == nullptr || p->next == nullptr) {
// 未找到待删除节点或待删除节点是最后一个节点,删除失败
return false;
}
LNode* q = p->next; // 待删除节点
e = q->data; // 将待删除节点的值赋给e
p->next = q->next; // 将待删除节点的下一个节点链接到前一个节点上
delete q; // 删除节点
return true; // 删除成功
}
//删除指定结点
void Delete(LNode* p) {
if (p == nullptr || p->next == nullptr) {
// 要删除的节点为空或为尾节点,无法删除
return ;
}
LNode* q = p->next; // q指向要删除的节点
p->data = q->data; // 将后继节点的值赋给当前节点
p->next = q->next; // 修改当前节点的指针指向后继节点的后继节点
delete q; // 删除要删除的节点
}
// 输出链表数据
void PrintList(LinkList L) {
LNode* p = L->next; // p指向第一个节点
while (p != nullptr) {
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
//按值查找,返回节点
LNode* LocateElem(LinkList L, int e) {
LNode* p = L->next; // p指向首节点
while (p != nullptr && p->data != e) {
p = p->next;
}
return p;
}
//按值查找,返回第几位
int LocateElem1(LinkList L, int e) {
LNode* p = L->next; // p指向首节点
int i = 1; // 初始化计数器
while (p != nullptr && p->data != e) {
p = p->next;
i++;
}
if (p == nullptr) {
return 0; // 未找到元素,返回0
}
else {
return i; // 返回元素在链表中的位置
}
}
//求表的长度
int GetLength(LinkList L) {
int count = 0;
LNode* p = L->next; // p指向首节点
while (p != nullptr) {
count++;
p = p->next;
}
return count;
}
//尾插法
LinkList List_TailInsert(LinkList& L) {
int x;
L = new LNode; // 创建头节点
LNode* s = L; // 初始化尾指针
while (cin >> x) { // 循环输入元素值
if (x == -1) { // 输入-1结束输入
break;
}
LNode* p = new LNode; // 创建新节点
p->data = x; // 赋值新节点数据
s->next = p; // 将新节点插入到尾部
s = p; // 更新尾指针
}
s->next = nullptr; // 尾节点的next指向nullptr
return L;
}
//头插法
LinkList List_HeadInsert(LinkList& L) {
int x;
L = new LNode; // 创建头节点
L->next = nullptr; // 初始化头节点的next指针为nullptr
while (cin >> x) { // 循环输入元素值
if (x == -1) { // 输入-1结束输入
break;
}
LNode* p = new LNode; // 创建新节点
p->data = x; // 赋值新节点数据
p->next = L->next; // 将新节点插入到头部
L->next = p; // 更新头节点的next指针
}
return L;
}
int main() {
LinkList L; //声明一个指向单链表的指针
//初始化一个空表
if (InitList(L)) {
cout << "初始化成功" << endl;
}
else {
cout << "初始化失败" << endl;
}
//插入数据
ListInsert(L, 1, 10);
ListInsert(L, 2, 20);
ListInsert(L, 3, 30);
// 判断链表是否为空
if (IsEmpty(L)) {
cout << "链表为空" << endl;
}
else {
cout << "链表非空" << endl;
}
// 输出链表中的数据
PrintList(L);
int e;
// 调用ListDelete函数删除指定位置的节点
if (ListDelete(L, 3, e)) {
cout << "成功删除节点,被删除节点的值为:" << e << endl;
}
else {
cout << "删除节点失败" << endl;
}
PrintList(L);
//按位置查找
LNode* result = GetElem(L, 1);
if (result != nullptr) {
cout << "找到了位序为3的节点,该节点的值为:" << result->data << endl;
}
else {
cout << "未找到位序为3的节点" << endl;
}
//按值查找
// 按值查找元素,找到的是节点指针
int target = 20;
LNode* result1 = LocateElem(L, target);
if (result1 != nullptr) {
cout << "找到元素 " << target << ",节点的地址为:" << result1 << endl;
}
else {
cout << "未找到元素 " << target << endl;
}
// 按值查找元素,返回i
int target1 = 30;
int result2 = LocateElem1(L, target1);
if (result2 != 0) {
cout << "找到元素 " << target1 << ",节点的位置为:" << result2 << endl;
}
else {
cout << "未找到元素 " << target1 << endl;
}
// 获取链表的长度
int length = GetLength(L);
cout << "链表的长度为:" << length << endl;
//尾插法代码实现
//L = List_TailInsert(L);
//PrintList(L); // 输出链表
//头插法实现与尾插类似
return 0;
}
三、特点
优点:不要求大片连续空间,改变容量方便
缺点:不可随机存放,要耗费一定空间存放指针
四、时间复杂度(简写版)
按位插入:
最好:1
最坏:n
平均:n
后插:1
前插:1
按位序删除
最坏:n
最好:1
平均:n
删除指定结点:1
按位查找:n
按值查找:n
尾插法:n
头插法:n