喜欢的话可以扫码关注我们的公众号哦,更多精彩尽在微信公众号【程序猿声】
01 单链表(Singly Linked List )
1.1 什么是单链表?
单链表是一种链式存储的结构。它动态的为节点分配存储单元。当有节点插入时,系统动态的为结点分配空间。在结点删除时,应该及时释放相应的存储单元,以防止内存泄露。由于是链式存储,所以操作单链表时,必须知道头结点或者头指针的位置。并且,在查找第i个节点时,必须找到第i-1个节点。
1.2 单链表的存储结构代码描述
对于链式存储,通过上一节的讲解相信大家已经了解得够清楚了。如下图所示:
下面我们来看看单链表存储是如何用代码来实现的。
//单链表的存储结构C语言代码
typedef struct SListNode
//数据域
struct SListNode * pnext;//指针域
由上面的结构我们可以看出,一个节点由存放数据的数据域和存放地址的指针域组成。假如p指向了第i个节点,那么p->data就是该节点存放的数据,而p->pnext自然就是指向下一个节点的指针。如下图所示:
那么接下来我们看看单链表的各个操作具体实现吧。(只讲几个关键步骤)
备注:下面的代码基于这样的一个单链表:
有一个头指针phead
有一个头结点node
头指针指向头结点,头结点位置记为0
1.3 单链表的读取
在拿到头指针以后,单链表的读取也并非一件难事。一开始设置一个计数变量,不断遍历链表,让计数器自增。找到合适的位置将数据读取出来。具体代码实现如下:
#define status bool
#define ERROR false
#define OK true
/*
status GetSListIndexNode(Node * phead,DType *e, int index){
int icount = 0; //计数器
//注:0号位为头结点,头结点不存放任何数据
if (phead->pnext == nullptr || index GetSListLength()/*此处为链表长度*/)
return ERROR; //异常 处理
while (phead->pnext != nullptr)
if (icount == index)
return OK;
return ERROR;
1.4 单链表的插入
1.4.1 指定位置后插
其实链表的插入和删除都是很简单的操作,初学者只要抓住指针指向的节点,并加以区分开来,就很easy了。如下图:
图中,假如此时p指向了我们要插入的节点的位置。那么,怎样把我们的S节点给插入到p指向的节点之后?在这里我们先不要惊动p以及p后面的节点:
我们先让S节点指向p之后的节点(步骤①)
之后我们切断p和p后面那个节点的关系(步骤②)
最后让p节点的指针域指向s节点(步骤③),搞定
算法描述:
声明一个指针p指向链表头结点,向后遍历p=p->next,找到正确的位置。
新建一个结点s。
s->next = p->next ①
p->next = s ②③
具体代码如下:
#define status bool
#define ERROR false
#define OK true
/*
status InsertSListNodeFront(Node * phead, DType IData, int index){
if (phead->pnext == nullptr || index GetSListLength())
return ERROR; //异常 处理
int iCount = 0; //计数器
nullptr; //备用
while (phead->pnext != nullptr)
if ( iCount == index )
new Node;
//前插
return OK;
return ERROR;
1.4.2 指定位置前插
咳咳,聪明的小伙伴,用脑子想想。指定位置前插 == 指定位置的前一个位置进行后插。懂了吧?直接看具体代码:
/*
status InsertSListNodeBack(Node * phead, DType IData, int index){
if (phead->pnext == nullptr || index GetSListLength())
return ERROR; //异常 处理
int iCount = 0; //计数器
nullptr; //备用
while (phead->pnext != nullptr)
if (iCount == index)
new Node;
//后插就是后一个节点的前插咯
return OK;
return ERROR;
1.5 单链表的删除
单链表的删除其实也是很简单。只要比如要删除p指向的节点,只需要让p之前的节点的指针域直接指向p之后的节点,再把p给free就OK了。如下图:
算法描述:
声明一个指针p指向链表头结点,向后遍历p=p->next,找到要删除的节点位置。
q = p->next
p->next = q->next ①②
free(q) ③④
具体代码如下:
/*
//删除指定位置节点(e获取删除元素)
template
status DeleteSListIndexNode(Node * phead, DType *e, int index){
int i = 0; //计数器
nullptr;
if (phead->pnext == nullptr || index GetSListLength())
return ERROR; //异常 处理
while (phead->pnext != nullptr)
//保存备用
if (i == index)
//删除出局
return OK;
return ERROR;
代码应该不难,相信大家都能很容易看懂。
1.6.1 单链表的完整代码
好了,前面介绍了几个重要的操作,接下来请大家看看完整的代码吧。小编为了使用方便,就用C++的class和template将整个链表封装到了一个类里面,通过模板实现泛型编程。
/*
#pragma once //VC编译器防止头文件被重复包含的一条预编译指令
#define status bool
#define OK true
#define ERROR false
#define YES true
#define NO false
template
class Node
public:
template
class CSingleLinkList
private:
//链表头指针
public:
//构造,类被创建时调用
//析构,类被销毁时调用
public:
//初始化链表
status InitSList();
//获取链表长度
int GetSListLength();
//增加一个节点 前插法
status AddSListNodeFront(DType idata);
//增加一个节点 后插法
status AddSListNodeBack( DType idata);
//判断链表是否为空
status IsSListEmpty();
//获取指定位置节点值(注意,本程序规定0号为头节点,e获取删除元素)
status GetSListIndexNode(DType *e, int index);
//删除指定位置节点(e获取删除元素)
status DeleteSListIndexNode(DType *e, int index);
//查找链表中指定值(pindex获取位置0==>not found)
status SearchSListNode(DType SData, int *pindex);
//指定位置前插
status InsertSListNodeFront(DType IData, int index);
//指定位置后插
status InsertSListNodeBack(DType IData, int index);
//清空链表(保留根节点)
status ClearSList();
//销毁链表(all delete)
status DestorySList();
//尾部删除一个元素
status DeleteSListNodeBack();
//打印链表 此函数根据实际情况而定
void PrintSList();
/*
#include "SingleLinkList.h"
#include
template
cout <
template
cout <
//初始化链表
template
new Node;
if (ph != NULL)
nullptr;
this->phead = ph; //头结点
return OK;
return ERROR;
//获取链表长度(head_node is not included)
template
int CSingleLinkList::GetSListLength()
int length = 0;
this->phead;
while (phead->pnext != nullptr)
return length;
//增加一个节点 前插法
template
new Node;
if (pnode != NULL)
this->phead->pnext;
this->phead->pnext = pnode; //挂载
//printf("pnode = %p pnode->pnext = %p this->phead->pnext = %p this->phead = %p\n", pnode, pnode->pnext, this->phead->pnext, this->phead);
return OK;
return ERROR;
//增加一个节点 尾插法
template
new Node;
this->phead;
if (pnode != NULL)
while (phead->pnext != nullptr)
nullptr;
//挂载
return OK;
return ERROR;
//判断链表是否为空
template
if (this->phead->pnext == nullptr)
return YES;
return NO;
//获取指定位置节点值(注意,本程序规定0号为头节点,e获取节点的值)
template
int index)
this->phead;
int i = 0; //计数器
if (phead->pnext == nullptr || index GetSListLength())
return ERROR; //异常 处理
while (phead->pnext != nullptr)
if (i == index)
return OK;
return ERROR;
//删除指定位置节点(e获取删除元素)
template
int index)
this->phead;
int i = 0; //计数器
nullptr;
if (phead->pnext == nullptr || index GetSListLength())
return ERROR; //异常 处理
while (phead->pnext != nullptr)
//保存备用
if (i == index)
//删除出局
return OK;
return ERROR;
//查找链表中指定值(pindex获取位置 0==>not found)
template
int *pindex)
this->phead;
int iCount = 0;//计数器
while (phead->pnext != nullptr)
if (phead->data == SData)
return YES;
0;
return NO;
//指定位置前插
template
int index)
this->phead;
if (phead->pnext == nullptr || index GetSListLength())
return ERROR; //异常 处理
int iCount = 0; //计数器
nullptr; //备用
while (phead->pnext != nullptr)
if ( iCount == index )
new Node;
//前插
return OK;
return ERROR;
//指定位置后插
template
int index)
this->phead;
if (phead->pnext == nullptr || index GetSListLength())
return ERROR; //异常 处理
int iCount = 0; //计数器
nullptr; //备用
while (phead->pnext != nullptr)
if (iCount == index)
new Node;
//后插就是后一个节点的前插咯
return OK;
return ERROR;
//清空链表(保留根节点)
template
this->phead;
nullptr; //防止那啥,野指针
//保留头节点
while (phead != nullptr)
delete q; //释放
this->phead->pnext = nullptr;
return OK;
//销毁链表(all delete)
template
delete this->phead;//释放头结点
return OK;
template
this->phead;
nullptr; //备用
if (phead->pnext == nullptr)
return ERROR; //链表都空了还删鸡毛
while (phead->pnext != nullptr)
nullptr;
delete phead;
return OK;
template
void CSingleLinkList::PrintSList()
this->phead;
if (phead->pnext == nullptr || phead == nullptr)
cout <
return;
int i = 1;
cout <
while (phead->pnext != nullptr)
cout
/* #include #include "SingleLinkList.h" #include "SingleLinkList.cpp" using namespace std; int main(){ int> * pMySList = new CSingleLinkList; cout 111); 222); 333); cout 444); 555); 666); cout cout int GetElem, GetIndex; 2); cout < 6); cout < 4); cout < 1); cout < 3); cout < 555, &GetIndex); cout < 111, &GetIndex); cout < 666, &GetIndex); cout < 333, 1); 444, 4); 777, 3); 888, 5); return 0; 代码如果有不正确的地方,欢迎大家来指正哈。 02 静态链表(circular linked list) 2.1 什么是静态链表? 我们把线性表的元素存放在数组中,这些元素由两个域组成: 数据域data 指针域cur 数据域是存放数据的,而指针域,这里和链表不同是,它存的不再是指向下一个节点的内存地址。而是下一个节点在数组中的下标。我们就把这种用数组描述的链表称为静态表,该方法也称之为游标实现法。如下图所示: 由上图我们需要注意以下几点: 我们对数组的第一个元素和最后一个元素做特殊处理,不存放数据。 把未使用的数组元素称为备用链表。 数组的第一个元素(下标为0)的cur域存放备用链表第一个节点的下标。 数组的最后一个元素的cur域存放第一个有数据的节点的下标,相当于链表中头结点的存在。链表为空时,其值为0。 如下图: 引出的问题:数组的长度定义的问题,无法预支。所以,为了防止溢出,我们一般将静态表开得大一点。 2.2 静态链表存储的代码描述 基于上面的讲解,我们来看看代码是怎么描述这种存储结构的。 //---------线性表的静态单链表存储结构-------- #define MAXSIZE 1000 /*假设链表最大长度为1000*/ typedef struct int cur; //为0时表示无指向 接下来我们讲解几个重要的操作实现。 2.3 静态链表的插入操作 前面我们讲动态链表的时候说过,增加和删除一个节点我们可以用malloc()和free()函数(C++可用new和delete)来实现。但是现在由于我们操作的是静态表,它可是用数组存的,可没有这种操作了。因此我们首先来自己实现一个静态表的malloc和free。 那么怎么辨别数组中哪些空间没有被使用呢?一个好的解决办法是,将所有未使用或者被删除的空间串成一个备用链表。插入节点时便可以从备用链表获取第一个未使用的空间的下标。因此我们在初始化的时候会做这样的工作: void SListInit(SLinkList space){ int i; for(i = 0; i 1; //将所有结点链入备用链表 //备用链表头指针链像第二个结点 0].cur = space[1].cur; //第一个结点作为链表的头结点 1].cur = 0; 分配内存 //若为0,则说明备用链表用完 int Malloc_SL(SLinkList space){ int i = space[0].cur; //判断备用链表是否非空 if(space[0].cur) //备用链表头指针指向第二个空结点 0].cur = space[i].cur; return i; //返回第一个空结点 上面的代码应该是没有难度的。写完了这个函数,我们来看看静态表中具体如何插入: //在链表的第i个位置插入元素e void SlistInsert(SLinkList space, int i, ElemType e){ //超出范围 if(i SListLength(space)+1) return; int k = 1, j; //使k指示要插入的结点的前一个结点 for(j = 0; j int v = Malloc_SL(space); if(v) //如果有空间 //链入链表 注意几点: 首先我们让k指向了要插入节点(记为X)的前一个位置(记为Y节点),前插法。 然后我们在静态表内申请空间,存放新的节点(记为N)。 把数据放进新申请的节点里面。 新节点N的cur域指向X节点,然后让Y节点指向N节点。 该过程不难理解。就不上图了…… 2.4 静态链表的删除操作 删除同样需要自己实现free函数,我们来看看代码: 回收内存 //将下标为k的空闲结点回收到备用链表 void Free_SL(SLinkList space, int k){ //将备用链表链到k之后 0].cur; //将k链到备用链表头之后 0].cur = k; 删除以后记得把空间重新挂载到备用链表上以便下一次使用。下面我们实现删除的代码: //删除第i个位置的元素 void SListDelete(SLinkList space, int i){ //超出范围退出 if(i SListLength(space)) return ; int k = 1, j; //使k指示要删除的结点的前一个结点 for(j = 0; j int temp = space[k].cur; 其实代码也很好理解了。假如X、Y、Z……等等排列,我们要删除Y。无非就是告诉X,你不要指向Y了,你直接指向Z,然后我们再把Y给free了。就直接变成了X、Z……简单吧。 2.5 静态链表的完整代码 熬了一个下午,终于写完了.哈哈,用了C++的类模板封装了一个静态链表,简单的增删查改功能都有了.具体可以看代码: StaticLinkList.h #pragma once #include #define MAXSIZE 100 #define status bool #define YES true #define NO false #define OK true #define ERROR false template class Component public: //数据域 int cur; //cur域,指向下一个元素的下标 template class CStaticLinkList public: //静态表 //自定义malloc和free public: int MallocNodeSSL(); status FreeNodeSSL(int index); public: status InitList(); //初始化静态表 status BackAddList( DATATYPE AddData); //尾增加 status InsertNodeList(DATATYPE InsertData, int index);//指定位置插入 status DeleteNodeList(DATATYPE *DelData, int index); //指定位置删除 int SearchList(DATATYPE sData); //搜索数据为sData的节点,返回其在数组中的下标,0表示失败 status GetIndexList(DATATYPE *gData, int index);//获取指定索引的节点数据 int GetLengthList(); //获取静态表的长度 status ClearList(); //清空静态表 status IsEmptyList(); //判断静态表是否为空 status IsFullList(); //判断静态表是否满了 void PrintList(); //打印静态表,此函数根据实际情况编写 public: StaticLinkList.cpp #include "StaticLinkList.h" template cout < template cout < template int CStaticLinkList::MallocNodeSSL() int index = StaticLinkList[0].cur; //把备用链表第一个节点拿出来用 if (StaticLinkList[0].cur) //判断是否还有位置 0].cur = StaticLinkList[index].cur; //让备用链表第二个节点上来顶替第一个的位置 return index; //返回0表示分配失败 template int index) //将删除节点挂接到备用链表上 this->StaticLinkList[index].cur = this->StaticLinkList[0].cur; this->StaticLinkList[0].cur = index; return OK; template int i; for (i = 0; i 1;//全部塞入备用链表 1].cur = 0;/*因为目前静态表为空,最后一个节点的cur域为0*/ return OK; template //尾增加 if (IsFullList()) return ERROR; int index = MAXSIZE - 1; int last = index; while (index != 0) int k = MallocNodeSSL(); //获取空闲位置下标 if (k) //存入数据 0; //末尾指向0 return OK; return ERROR; //在List中第i个节点之前插入新的节点 template int index)//指定位置插入 int i, GetFree, pos; 1;//最后节点下标 if (index GetLengthList() || IsFullList()) return ERROR; //位置异常处理 if (GetFree) for (i = 0; i //定位 //插入 int q = StaticLinkList[MAXSIZE - 1].cur; if (q == 0) //静态表为空 1].cur = 1; return OK; return ERROR; //判断是否为空 template if (StaticLinkList[MAXSIZE-1].cur == 0) return YES; return NO; //判断静态表是否满了 template if (GetLengthList() == MAXSIZE - 2) //因为首位不存数据,因此pass掉 return YES; return NO; template int CStaticLinkList::GetLengthList() //获取静态表的长度 int iCount = 0; int k = MAXSIZE - 1; while (StaticLinkList[k].cur != 0) return iCount; template int index)//指定位置删除 int i, pos, k; 1;//最后节点下标 if (index GetLengthList() || IsEmptyList()) return ERROR; //位置异常处理 for (i = 0; i //定位到被删除节点的前一个节点 //获取数据 //让前一个节点直接指向后一个节点.把夹在中间的踢掉 //释放空间 return OK; //搜索数据为sData的节点,返回其在静态表中的第i个位置,0表示没找到 template int CStaticLinkList::SearchList(DATATYPE sData) int pos = StaticLinkList[MAXSIZE-1].cur; int iCount = 1; while (pos != 0) if (StaticLinkList[pos].data == sData) //找到数据 return iCount; //循环遍历 return 0; template int index)//获取第index个节点数据 int i, pos; 1;//最后节点下标 if (index GetLengthList() || IsEmptyList()) return ERROR; //位置异常处理 for (i = 0; i //定位到第index个节点 return OK; template //清空静态表 //再初始化一次就是清空了 return OK; template void CStaticLinkList::PrintList() int pos = StaticLinkList[MAXSIZE - 1].cur; if (pos == 0) cout < return; cout < cout < int iCount = 1; while (pos != 0) cout < //循环遍历 StaticLinkListTest.cpp测试代码 #include #include #include "StaticLinkList.h" #include "StaticLinkList.cpp" using namespace std; int main(){ char get; char> *p = new CStaticLinkList; 'a'); //pass 'b'); 'c'); 'd'); 'e'); //p->ClearList(); //pass 2); //pass cout < 2); cout < 'f'); cout 3); //pass cout < 'h', 2); 'i', 3); cout cout cout cout delete p; return 0; 运行结果 运行结果 代码未经过严谨测试,如果有错,欢迎大家指正和交流啊. 这次就先到这里吧。更多精彩内容,请期待下回分解。