文章目录
前言
- 链表是一种最基本的数据结构。
- 全部代码分布在四个文件内。
- 注意事项有对疑难点讲解,请结合代码阅读!
程序实现
存储结构
typedef struct LNode
{
ElemType data;
struct LNode *next;
} * LinkList;
- 链表的数据结构包含两种,一种是数据,另一种是指针。
- 链表的数据可以有很多个、很多种。
- 单链表的指针只包含一个指向下一个结点的指针,而双向链表等则包含多个指针。在某些情景下,会使用双向链表替代单链表,比如要能满足双向遍历的需求就要用到双向链表。
- LinkList为定义的一种指针别名。“LinkList L;”相当于“LNode* L;”。
- **注意:**这里是给“typedef struct LNode{} *”起一个别名叫LinkList,而不是给“typedef struct LNode{}”起一个别名叫“*LinkList”。
操作方法
Status InitList(LinkList &L); //创建空链表
Status CreateList_H(LinkList L, ElemType e); //头插法插入链表
Status CreateList_T(LinkList L, ElemType e); //尾插法插入链表
Status ListEmpty(LinkList L); //检查表是否为空
int ListLength(LinkList L); //返回表长
Status GetElem(LinkList L, int location, ElemType &e); //返回指定位置的结点数据
int LocateElem(LinkList L, ElemType e, Status (*compare)(ElemType, ElemType)); //已知结点数据返回结点位置
Status PriorElem(LinkList L, ElemType cur_e, ElemType &pre_e, Status (*compare)(ElemType, ElemType)); //已知结点数据返回前一个结点,已知数据的结点不为头结点
Status NextElem(LinkList L, ElemType cur_e, ElemType &next_e, Status (*compare)(ElemType, ElemType)); //已知结点数据返回后一个结点,已知数据的结点不为尾结点
Status InsertLNode(LinkList L, int location, ElemType e); //在指定位置插入结点
Status DeleteLNode(LinkList L, int location, ElemType &e); //删除指定位置的结点
Status ReverseList(LinkList L); //倒序链表
Status ClearList(LinkList L); //清空链表
Status DestroyList(LinkList &L); //销毁链表
void PrintList(LinkList L); //遍历打印链表
Status comp(ElemType c1, ElemType c2); //数据元素判定函数(相等为TRUE,否则为FALSE)
代码汇总
main.h
/*
* Copyright (c) 2019 WUST
*
* 文件名 : main.h
*
* Author: Mr.Killer
* Date : 2019-12-28 19:21:36
*/
#include <iostream>
const int TRUE = 1;
const int OK = 1;
const int FALSE = 0;
const int ERROR = 0;
typedef int Status;
typedef int ElemType;
using namespace std;
methods.h
/*
* Copyright (c) 2019 WUST
*
* 文件名 : methods.h
* 单链表的数据结构
*
* Author: Mr.Killer
* Date : 2019-12-28 19:24:18
*/
typedef struct LNode
{
ElemType data;
struct LNode *next;
} * LinkList;
methods.cpp
/*
* Copyright (c) 2019 WUST
*
* 文件名 : methods.cpp
* 单链表的操作方法
*
* Author: Mr.Killer
* Date : 2019-12-28 19:27:44
*/
Status InitList(LinkList &L); //创建空链表
Status CreateList_H(LinkList L, ElemType e); //头插法插入链表
Status CreateList_T(LinkList L, ElemType e); //尾插法插入链表
Status ListEmpty(LinkList L); //检查表是否为空
int ListLength(LinkList L); //返回表长
Status GetElem(LinkList L, int location, ElemType &e); //返回指定位置的结点数据
int LocateElem(LinkList L, ElemType e, Status (*compare)(ElemType, ElemType)); //已知结点数据返回结点位置
Status PriorElem(LinkList L, ElemType cur_e, ElemType &pre_e, Status (*compare)(ElemType, ElemType)); //已知结点数据返回前一个结点,已知数据的结点不为头结点
Status NextElem(LinkList L, ElemType cur_e, ElemType &next_e, Status (*compare)(ElemType, ElemType)); //已知结点数据返回后一个结点,已知数据的结点不为尾结点
Status InsertLNode(LinkList L, int location, ElemType e); //在指定位置插入结点
Status DeleteLNode(LinkList L, int location, ElemType &e); //删除指定位置的结点
Status ReverseList(LinkList L); //倒序链表
Status ClearList(LinkList L); //清空链表
Status DestroyList(LinkList &L); //销毁链表
void PrintList(LinkList L); //遍历打印链表
Status comp(ElemType c1, ElemType c2); //数据元素判定函数(相等为TRUE,否则为FALSE)
Status InitList(LinkList &L)
{
L = (LinkList)malloc(sizeof(LNode));
if (L == NULL)
{
exit(ERROR); //内存分配失败,程序退出
}
L->next = NULL;
return OK;
}
Status CreateList_H(LinkList L, ElemType e)
{
LinkList p, temp;
p = L;
temp = (LinkList)malloc(sizeof(LNode));
temp->data = e;
temp->next = p->next;
p->next = temp;
return OK;
}
Status CreateList_T(LinkList L, ElemType e)
{
LinkList p, temp;
p = L;
while (p->next != NULL)
{
p = p->next;
}
temp = (LinkList)malloc(sizeof(LNode));
temp->data = e;
p->next = temp;
return OK;
}
Status ListEmpty(LinkList L)
{
if (L->next == NULL)
return TRUE;
else
return FALSE;
}
int ListLength(LinkList L)
{
LinkList p;
int count = 0;
p = L->next;
while (p != NULL)
{
count++;
p = p->next;
}
return count;
}
Status GetElem(LinkList L, int location, ElemType &e)
{
LinkList p;
int i = 1;
p = L->next;
while (p && i < location)
{
i++;
p = p->next;
}
if (!p || i > location) //!p是把L遍历完了还未到达location位置,
return ERROR;
e = p->data;
return OK;
}
int LocateElem(LinkList L, ElemType e, Status (*compare)(ElemType, ElemType))
{
LinkList p;
int count = 1;
p = L->next;
while (p != NULL)
{
if (compare(p->data, e))
return count;
count++;
p = p->next;
}
return ERROR;
}
Status PriorElem(LinkList L, ElemType cur_e, ElemType &pre_e, Status (*compare)(ElemType, ElemType))
{
LinkList p, q;
p = L->next;
while (p != NULL)
{
q = p->next;
if (compare(q->data, cur_e))
{
pre_e = p->data;
return OK;
}
p = q;
}
return ERROR;
}
Status NextElem(LinkList L, ElemType cur_e, ElemType &next_e, Status (*compare)(ElemType, ElemType))
{
LinkList p;
p = L->next;
while (p->next != NULL)
{
if (compare(p->data, cur_e))
{
next_e = p->next->data;
return OK;
}
p = p->next;
}
return ERROR;
}
Status InsertLNode(LinkList L, int location, ElemType e)
{
LinkList p, temp;
int i = 1;
/*
起始位置设为1
定位到location时停下
p的next指针指向的结点为第一个结点
所以定位到location时,p指向的是location位置的结点的前一个结点
*/
p = L;
while (p && i < location)
{
p = p->next;
i++;
}
/*
确保p指针不在最后一个结点的下一个空结点
(插入是在最后一个结点之前插入)
*/
if (!p || i > location)
{
return ERROR;
}
temp = (LinkList)malloc(sizeof(LNode));
temp->data = e;
temp->next = p->next;
p->next = temp;
return OK;
}
Status DeleteLNode(LinkList L, int location, ElemType &e)
{
LinkList p, temp;
int i = 1;
p = L;
while (p->next && i < location) // 寻找第i个结点,并令p指向其前趋
{
p = p->next;
i++;
}
if (!p->next || i > location) // 删除位置不合理
return ERROR;
temp = p->next; // 删除并释放结点
p->next = temp->next;
e = temp->data;
free(temp);
return OK;
}
Status ReverseList(LinkList L)
{
LinkList p, q;
p = L->next;
while (p->next)
{
q = p->next;
p->next = q->next;
q->next = L->next;
L->next = q;
}
return OK;
}
Status ClearList(LinkList L)
{
LinkList p, q;
p = L->next;
while (p != NULL)
{
q = p->next;
free(p);
p = q;
}
L->next = NULL;
return OK;
}
Status DestroyList(LinkList &L)
{
LinkList p;
while (L != NULL)
{
p = L->next;
free(L);
L = p;
}
return OK;
}
void PrintList(LinkList L)
{
LinkList p = L->next;
while (p)
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
Status comp(ElemType c1, ElemType c2)
{ // 数据元素判定函数(相等为TRUE,否则为FALSE)
if (c1 == c2)
return TRUE;
else
return FALSE;
}
main.cpp
/*
* Copyright (c) 2019 WUST
*
* 文件名 : main.cpp
*
* Author: Mr.Killer
* Date : 2019-12-28 19:21:08
*/
#include "main.h"
#include "methods.h"
#include "methods.cpp"
int main()
{
LinkList L;
ElemType e;
int location;
int result;
result = InitList(L);
if (result)
cout << "链表初始化!" << endl;
else
exit(ERROR);
for (int i = 0; i < 6; i++)
CreateList_H(L, i);
for (int i = 6; i < 10; i++)
CreateList_T(L, i);
cout << "分别使用头插法和尾插法建立链表:" << endl;
PrintList(L);
result = ListEmpty(L);
if (result)
cout << "链表不为空!" << endl;
result = ListLength(L);
cout << "链表长为: " << result << endl;
location = 6;
GetElem(L, location, e);
cout << "第" << location << "个结点的数据为:" << e << endl;
e = 9;
location = LocateElem(L, e, comp);
cout << "'" << e << "'"
<< "在第" << location << endl;
int i = 7;
PriorElem(L, i, e, comp);
cout << i << "的前一个结点的数据为:" << e << endl;
NextElem(L, i, e, comp);
cout << i << "的后一个结点的数据为:" << e << endl;
location = 4;
e = 66;
result = InsertLNode(L, location, e);
if (result)
cout << "在第" << location << "个位置插入:" << e << endl;
PrintList(L);
location = 2;
result = DeleteLNode(L, location, e);
if (result)
cout << e << "在第" << location << "个位置删除:" << endl;
PrintList(L);
result = ReverseList(L);
if (result)
cout << "链表倒序:" << endl;
PrintList(L);
ClearList(L);
result = ListEmpty(L);
if (result)
cout << "链表清空!" << endl;
PrintList(L);
DestroyList(L);
if (!L)
cout << "链表销毁!" << endl;
return 0;
}
注意事项
Status别名
细心的朋友应该察觉到了所有返回链表操作状态的方法的返回值用的“Status”型,而Status在头文件中定义成int型,那为什么不直接使用int呢?
- 区别返回链表长度、结点位置的方法
那既然返回操作状态为什么不使用bool型呢?
- C++的bool型只有两种返回值,true和false,当然这份代码里面的Status都可以用bool型替代,但是细节上难以使强迫症患者看得很爽。
ElemType别名
- 可以理解为一种通用的类型,但是又达不到泛型的通用型。在链表的每一个结点的数据类型都相同时,可以用ElemType(或者定义其他别名),比如全是int型数据,就定义为“typedef int ElemType;”;全是string型数据,就定义为“typedef string ElemType;”。当链表每个节点的数据类型不确定时,就可以用到泛型来构建链表。
以函数名作为参数传递
int LocateElem(LinkList L, ElemType e, Status (*compare)(ElemType, ElemType)); //已知结点数据返回结点位置
Status PriorElem(LinkList L, ElemType cur_e, ElemType &pre_e, Status (*compare)(ElemType, ElemType)); //已知结点数据返回前一个结点,已知数据的结点不为头结点
Status NextElem(LinkList L, ElemType cur_e, ElemType &next_e, Status (*compare)(ElemType, ElemType)); //已知结点数据返回后一个结点,已知数据的结点不为尾结点
Status comp(ElemType c1, ElemType c2)
{ // 数据元素判定函数(相等为TRUE,否则为FALSE)
if (c1 == c2)
return TRUE;
else
return FALSE;
}
LocateElem(L, e, comp);
PriorElem(L, i, e, comp);
NextElem(L, i, e, comp);
-
在调用这三个函数时,都传递了一个函数名comp。这三个函数接收的形参是comp转化成的comp型的函数指针compare
-
好处:结点数据可能有多种比较方式,本案例中的结点数据为int型,直接比较两个int型数据是否相等或者孰大孰小。
但是当结点数据为其他类型时,就可能要用到其他的比较流程。若比较函数的形参都相同,则在调用链表的操作方法时只用直接传递要用到的比较函数函数名就可以完成比较。
为什么不用重载?
- 参数的数据类型未知。
链表形参传递
不知大家有没有观察到,上面列出的链表的操作方法中,只有两个方法的形参是链表的引用。
为什么呢?
- 这两个方法一个是初始化链表,一个是销毁链表
- 在main函数中,定义的是一个LinkList的对象L,也是LNode的指针型对象。在列出的方法中除了刚提到的两个方法,都没有改变LinkList的对象L的物理地址,只是改变其中的数据,所以在传递时不需要传递引用。但是在这几个方法中及时传递引用也不会影响程序的运行。(出于编码规范)
- 初始化链表:给头结点分配空间
- 销毁链表:把所有结点分配的空间释放
- 而这两个方法一个是给头结点分配存储空间,一个是释放头结点的存储空间,都对这个对象的存储空间有操作,所以要用到引用传递。(可以试试删了‘&’之后程序是否可以重新运行)
重中之重
觉得不错点个赞或者关注或者收藏再走吧<3