《数据结构与算法》 青岛大学-王卓 线性表(链式存储)C++
B站链接:[https://www.bilibili.com/video/BV1nJ411V7bd?p=20]
本人能力有限,本想利用类和模板去实现一个链表,但有点问题,最终没能实现,最后跟随视频利用结构体实现的链表。
没能实现的原因,我觉得模板不能够这么用,包括上一章线性表的顺序存储内容,线性表就是一种数据结构,如果想存放什么类型,只要将此类型重定义typedef就可以了。没必要利用模板进行,另外模板和重定义不能重新出现,因为重定义必须是对已知类型的定义。而模板是未知类型,只有程序运行到那才知道,所以这一想法以失败告终。
本人理解:数据结构没必要利用模板,等我们需要此种链表形式的数据类型的时候,需要的数据data是何种数据类型,那么直接typedef就可以用了。
链表只是一种数据结构的表现形式,完全可以将此种形式应用到各个示例,比如图书管理系统的数据结构可以采用链表形式,也可以采用顺序表形式,完全可以根据应用环境选择数据结构形式。
单纯用类实现的话,应该还是可以实现的,后续有空的话就补充上。
(注:图片截取自《数据结构与算法》-青岛大学王卓bilibili视频)
- 数据类型:线性表
- 数据存储方式:链式存储
/*
利用结构体去实现一个单链表
数据类型:线性表
储存类型:链式存储
结构上 实现一个单链表
功能上 初始化、是否为空、表长、销毁;
重点:取值、查找、插入、删除、头插法与尾插法
*/
#include<iostream>
using namespace std;
#define MAX_SIZE 100
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status; // Status是函数的类型,其值是函数结果状态代码
typedef int TypeData;
typedef struct Lnode
{
TypeData data; // 数据域
struct Lnode* next; // 指针域
}LNode,*LinkList;
// 初始化链表
Status InitList(LinkList& L) // L为头指针
{
L = new LNode; // 生成新结点作为头结点,用头指针L指向头结点
L->next = NULL;
return OK;
}
// 判断是否为空
int ListEmpty(LinkList& L)
{
if (L->next)return 0; // 非空
return 1;
}
// 销毁单链表 链表的头指针、头结点不存在
Status DestoryList_L(LinkList& L)
{
LinkList p; // 指针,或LNode* p
while (L!=NULL) // 直到L为空值
{
p = L; // 将此时L指针,即地址赋值给临时变量p,以便于对L进行操作!!
L = L->next; // 重点:将L的下一个结点的地址赋值给L,相当于L指向了下一个结点,即移动到后面的结点处!!!
delete p; // 释放内存
}
return OK;
}
// 清空链表
Status ClearList(LinkList& L)
{
LinkList p; // 指针
p = L->next; // 将p指针指向 头指针L的下一个地址,即头结点的地址
LinkList q; // 不能够直接释放p,不然就不知道头结点的下一个地址了,所以还需要临时变量q
while (p!=NULL) // 结束条件
{
q = p->next; // 将p的下一个地址,即头结点的下一个地址,即第二个结点地址赋值给q
delete p; // 此时可以释放p的内存
p = q; // 释放掉之后将q的地址赋值给p,再让p去释放
}
L->next = NULL; // 全部释放完毕,将头结点指针域的置空
return OK;
}
// 求链表长度
int LengthList(LinkList& L)
{
int i = 0;
LinkList p;
p= L->next; // 不能将指向首结点L,改为指向第一个节点L->next;即L = L->next,因为这样后续L的指向将失效!!!
//我们只是记录长度,不能改变原本的数据,所以必须创建临时变量
while (p!=NULL)
{
i++;
p = p->next;
}
return i;
}
// 取值——取链表中第i个元素的内容
Status GetValueList(LinkList& L, int i,TypeData &e)
{
if (i <= 0)return ERROR;
LinkList p; // 创建临时变量p
p = L->next; // 此时p指向第一个结点的地址,此时j=1
int j = 1; // 创建计数器j,当j=i时,说明找到
while (p!=NULL && j<i)
{
p = p->next;
++j;
}
if (p==NULL || j > i)return ERROR;
e = p->data;
return OK;
}
// 查找——按值查找,返回地址
Lnode* LocateElem_L(LinkList& L, TypeData e)
{
LinkList p;
p = L->next;
// 只有不为空或不等于目标值时才进行遍历,如果没有目标值,此时的p指向也是NULL空值,所以直接返回p
while (p!=NULL && p->data!=e)
{
p = p->next;
}
return p;
}
// 查找——按值查找,返回位置
int PostionElem_L(LinkList& L, TypeData e)
{
LinkList p;
p = L->next;
int j = 1;
while (p != NULL && p->data != e)
{
p = p->next;
j++;
}
if (p)return j; // p!=NULL 等价于 p
else 0;
}
// 插入——在第i个结点前插入值为e的新结点
/*
步骤:
1、首先找到 a(i-1) 的存储位置p
2、生成一个数据域为e的新结点s
3、插入新结点: 1)新结点的指针域指向结点ai s->next = p->next 2)结点a(i-1)的指针域指向新结点 p->next = s
注意:顺序不能颠倒,除非找临时变量,保存p->next的地址
*/
Status ListInsert_L(LinkList& L, int i, TypeData e)
{
LinkList p = L;
int j = 0;
while (p && j<i-1) // 要插入第i个结点,则要找到第i-1个结点
{
p = p->next;
++j;
}
if (!p || j > i - 1)return ERROR; // 查看位置是否非法
LNode* s = new LNode; // 创建新结点s
s->data = e; // 将数据e写入
s->next = p->next; // 将原本p下一个结点的地址,赋值给s的下一个地址,从而让s与链表建立联系
p->next = s; // 将新结点s的地址,赋值给p的下一个地址,从而完成插入
return OK;
}
// 删除——删除第i个结点
Status ListDelete_L(LinkList& L, int i, TypeData &e)
{
LinkList p = L;
int j = 0;
while (p && j < i - 1) // 要删除第i个结点,则要找到第i-1个结点
{
p = p->next;
++j;
}
if (!p || j > i - 1)return ERROR; // 查看位置是否非法
LinkList q;
q = p->next; // 第i个结点
e = q->data; // 保存第i个结点的值
p->next = q->next; // 将p点处的结点下一个指向,赋值为指向为p->next的->next
delete q;
return OK;
}
// 显示,打印单链表
void ListPrint_L(LinkList& L)
{
LinkList p;
p = L->next;
while (p)
{
cout << p->data << "\t";
p = p->next;
}
cout << endl;
}
// 头插法——创建单链表
void CreatList_L(LinkList& L, int n)
{
L = new LNode;
L->next = NULL; // 先创建一个带头结点的单链表
cout << " 请依次输入数据 :" << endl;
for (size_t i = n; i >0; --i)
{
LinkList p = new LNode; // 创建一个新结点
cin >> p->data; // 输入数据
p->next = L->next; // 插入到表头,将原本表头指向的地址,赋值给新结点的下一个地址 =>换尾巴
L->next = p; // 将表头指向的地址 更新为 新结点的地址 =>换头
}
}
// 尾插法——创建单链表
void BackCreatList_L(LinkList& L, int n)
{
L = new LNode;
L->next = NULL; // 先创建一个带头结点的单链表
LNode* r = new LNode; // 创建尾指针
r= L; // 尾指针和L的指向相同
cout << " 请依次输入数据 :" << endl;
for (size_t i = 0; i < n; ++i)
{
LinkList p = new LNode; // 创建一个新结点
cin >> p->data; // 输入数据
p->next = NULL;
r->next = p; // 尾指针插入表尾
r = p; // 移动尾指针
}
}
// 链表的合并,按照数据大小排序
void MergeList_L(LinkList& La, LinkList& Lb, LinkList& Lc)
{
LNode* pa = La->next; // pa的头结点的地址
LNode* pb = Lb->next; // pb的头结点的地址
Lc = La; // 将La的头指针 赋值为 Lc
LNode* pc = La; // 将头指针赋值给Pc的头指针
while (pa && pb)
{
if (pa->data <= pb->data)
{
pc->next = pa; // 将pa头结点,赋值给pc头指针
pc = pa; // 将pa的指针,赋值给pc,以便于pc的下一步操作
pa = pa->next; // 将pa移动到下一个结点
}
else
{
pc->next = pb;
pc = pb;
pb = pb->next;
}
}
pc->next = pa ? pa : pb; // pa是否为空,不为空则pa的剩余段赋值给pc的next
delete Lb; // 释放Lb的头结点
}
int main()
{
LinkList L;
InitList(L);
cout << ListEmpty(L) << endl;
BackCreatList_L(L, 3);
ListInsert_L(L, 4, 10);
ListInsert_L(L, 5, 30);
ListInsert_L(L, 6, 40);
ListInsert_L(L, 7, 60);
ListInsert_L(L,8, 90);
cout << LengthList(L) << endl;
ListPrint_L(L);
DestoryList_L(L);
return 0;
}