一、背景
作为机械狗转行,数据结构当然是不可缺少的,疫情假期里闲来在家无事,刚好接下给小孩教数据结构的活,所以自学了简单的数据结构用法,算是数据结构的入门吧。做个笔记记下来,其实平常喜欢用思维导图进行记录,这算复习一遍,所以再用MarkDown进行记录,顺便发个博。
话不多说,今天先来记录一下链表简单的概念、实现等。
二、链表概念及特点
概念
链表是物理存储单元上非连续的、非顺序的存储结构,它是由一个个结点,通过指针来联系起来的,其中每个结点包括数据和指针。
适用场合
-
大内存空间分配
-
元素频繁删除和插入
-
若数据以查找为主,很少涉及增删,则选择数组
基本操作
书上主要都是利用抽象数据类型进行定义的,我这里直接用大白话,最终目的还是要理解每种数据结构的特点及实现。
- 初始化
- 插入
- 删除
- 查询
- 取值
分类
-
单链表
单链表不用多做介绍,看一眼图都懂。
-
循环链表
循环链表(Circular Linked List),表中最后一个结点的指针域指向头结点,整个链表形成一个环。
-
双向链表
双向链表(Double Linked List),为了克服单链表查找某结点的直接前驱结点,必须要从表头指针出发的缺点。故在双向链表的结点中有两个指针域,一个指向后继,一个指向前驱,如下图。
三、单链表的实现
本文只先实现单链表,利用C++编码。链表这里的代码加了一些自己的方法和书本的不完全一样。直接上码。注意主函数的调用在最后哦。
-
单链表的存储结构及初始化链表
首先是定义单链表的存储结构及初始化链表:
#include<iostream>
using namespace std;
//单链表的存储结构
typedef char ElemType; //存储元素类型
typedef struct LNode
{
ElemType data;//结点数据域
struct LNode *next;//结点指针域,指向下一结点
}LNode,*LinkList;//LinkList为指向结构体LNode的指针类型
//链表初始化
void InitList(LinkList &L)
{
L=new LNode;//生成新结点作为头结点,用头指针L指向头结点
L->next=NULL;//头结点指针域置空
}
注意我们这里的头结点是一直不用的结点,数据域不存数据,第一个数据存到的是首元结点,不要混淆,如下图:
OK,这三个概念不要搞混淆,继续下一步。
-
创建单链表
创建单链表,创建单链表又可以分为前插法和后插法,顾名思义,就是在链表头结点后插入新结点,还是链表尾部插入新结点。
//创建单链表
//前插法
void CreateList_H(LinkList &L,int n)
{//这里输入一个n,限定了链表的长度,可以不这么做,利用其它方式停止链表插入,
//这里为了方便展示头插法的算法逻辑,因此不必在意这点
InitList(L);//先建立一个带头结点的空链表
for(int i;i<n;i++)
{
LNode *p=new LNode;//生成新结点*p
cin>>p->data;//输入数据
p->next=L->next;//令新结点指针域指向原首元结点
L->next=p;//令头结点指向新结点
}
}
//尾插法
void CreateList_R(LinkList &L,int n)
{
InitList(L);//先建立一个带头结点的空链表
LNode *r=L;//临时指针r指向头结点
for(int i=0;i<n;i++)
{
LNode *p=new LNode;//生成新结点
cin>>p->data;//输入数据,填充数据域
p->next=NULL;//新结点指针域初始化为NULL
r->next=p;//将新结点*p插入尾结点 r->next后
r=p; //令r指向新的尾结点p
}
}
-
插入
下面我们来实现链表在任意位置的插入,先拿一个图来表示该插入过程,以便理解:
①查找第ai-1个结点,并将指针p指向该结点;
②生成新结点*s;
③将新结点*s的数据域置为e;
④将新结点*s的指针域指向ai;
⑤将结点p的指针域指向新结点s。
//单链表插入
//在带头结点的单链表L中第i个位置插入值为e的新结点
void ListInsert(LinkList &L,int i,ElemType e)
{
LNode *p=L;
int j=0;
while(p!=NULL&&(j<i-1))
{//查找第i-1个结点,令p指向该结点
p=p->next;
j++;
}
if(p==NULL||j>i-1)
{//这里判断i值是否存在或合法,当i>n+1或者i<1时,i值非法
cout<<"i值非法!"<<endl;
return;
}
LNode *s=new LNode;//生成新结点*s
s->data=e;//新结点数据域填充
s->next=p->next;//将新结点的指针域指向p->next
p->next=s;//p->next指向s
}
-
删除
删除元素,老样子,先来个图。
代码如下:
//删除
//在单链表L中,删除第i个元素
void ListDelete(LinkList &L,int i)
{
LNode *p=L;
int j=0;
while((p->next!=NULL)&&(j<i-1))
{//查找第i-1个结点,p指向该结点
p=p->next;
j++;
}
if(p->next==NULL||(j>i-1))
{//判断i值是否合理,当i>n或i<1时,删除位置不合理
cout<<"i值非法,删除失败!"<<endl;
return;
}
LNode *q=p->next;//临时保存被删结点,以备释放
p->next=q->next;//改变删除结点前驱结点的指针域
delete q; //释放删除结点的空间
}
-
取值和查找
取值和查找的就简单了,来一起整。
//取值//在单链表L中根据序号i获取元素的值
ElemType GetElem(LinkList &L,int i)
{
int j=1;
LNode *p=L->next;//初始化,p指向链表L的头结点,计数器j初值赋为1
while((p!=NULL)&&j<i)
{//顺着链表向后扫描,直到p为空或p指向第i个元素为止
p=p->next;//p指向下一个结点
j++;//计数器j++
}
if(p==NULL||j>i)
{//判断i值是否合法,i>n或i<=0时,不合法
cout<<"i值不合法!"<<endl;
return NULL;
}
return p->data;//返回元素
}
//查找
//在单链表L中查找值为e的结点
LNode* LocateElem(LinkList &L,ElemType e)
{
LNode *p=L->next;//初始化,令p指向头结点
while(p!=NULL&&p->data!=e)
{//向后扫描,直到p为空或p指向结点的数据域为e
p=p->next;
}
return p;//查找成功返回值为e的结点地址p,查找失败则p为NULL
}
-
调用及链表打印
这里我们进行调用以上方法,进行测试,同时打印穿件的链表。
//打印链表
void PrintList(LinkList &L)
{
LNode *p=L->next;
while(p!=NULL)
{
cout<<p->data<<" ";
p=p->next;
}
cout<<"
";
}
int main()
{
LinkList L1;
int n;
cout<<"请输入链表元素数量"<<endl;
cin>>n;
CreateList_R(L1,n);//后插法创建链表
PrintList(L1);//打印链表
ListDelete(L1,2);//链表删除第2个元素
PrintList(L1);//打印链表
ListInsert(L1,3,'z');
PrintList(L1);
return 0;
}
以上代码测试结果如下:
OK,以上查找和查询功能大家可以自己去测试,应该都是没问题的。
欢迎大家留言讨论,转载请注明原文地址就好哟yo~
参考引用:
《数据结构(C语言版 第2版)》,好书啊,推荐入门小白去看~例如我,哈哈哈。
写文不易~因此做以下申明:
1.博客中标注原创的文章,版权归原作者 煦阳(本博博主) 所有;
2.未经原作者允许不得转载本文内容,否则将视为侵权;
3.转载或者引用本文内容请注明来源及原作者;
4.对于不遵守此声明或者其他违法使用本文内容者,本人依法保留追究权等。