线性表---单链表部分
1、知识承接
顺序表可以随时存取表中的任意一个元素,它的存储位置可以用一个简单直观的公式来表示。但效率低插入和删除操作需要移动大量的元素。临时扩大存储空间有困难,存储分配只能预先进行必须占用地址连续的内存空间 .
链式存储线性表时,不需要使用地址连续的存储单元,即不要求逻辑上相邻的元素在物理位置上也相邻,它通过“链”建立起数据元素之间的逻辑关系,因此插入和删除操作不需要移动大量的元素,只需要修改指针,但也会失去顺序表可以随机存取的优点。
单链表
链表:
通过一组任意的存储单元来存储线性表中的数据元素,存储单元可以连续,也可以不连续
1)单链表的定义
线性表的链式存储又称单链表,它是通过一组任意的存储单元来存储线性表中的数据元素,为了建立数据元素之间的线性关系,对每个链表结点,除了存放元素自身的信息外,还需要存放一个指向其后继的指针。图示如下:
其中data为数据域,存放数据元素;next为指针域,存放其后继结点的地址。
代码定义
/******单链表定义*******
为了提高程序可读性,对同一结构体指针类型起了两个名称。LinkList和Lnode本质上是等价的
习惯用LinkList定义单链表,强调定义的是某个单链表的头指针;
用LNode *定义指向单链表中任意结点的指针变量
申请空间:p = new LNode; //C++实现
p = (LNode *)malloc(sizeof(LNode)); //C语言实现
**********************/
#include <iostream>
typedef int ElemType;
using namespace std;
typedef struct LNode //定义结点类型
{
ElemType data; //数据域,每个结点存放一个数据元素
struct LNode *next; //指针域,指针指向下一个结点,其类型为指向结点的指针类型LNode *;
}LNode, *LinkList; //LinkList为指向结构体LNode的指针类型
申请空间:
p = new LNode; //C++实现
p = (LNode *)malloc(sizeof(LNode)); //c语言实现
相关概念说明
首元结点:链表中存储第一个数据元素a1的结点
头结点:一般情况,为了处理方便,在单链表的第一个结点之前附设一个结点,其指针域指向首元结点。
头指针:指向链表中第一个结点的指针
2)单链表的两种形式
带头结点的和不带头结点,如下:
带头结点:
//初始化一个单链表带头结点
bool InitList(LinkList &L)
{
L = new LNode; //分配一个头结点
if (L == NULL) //内存不足,分配失败
return false;
L->next = NULL; //头结点之后暂时没有结点,将结点指针域置空
return true;
}
不带头结点:
//初始化不带头结点
bool InitList(LinkList &L)
{
L = NULL //空表,暂时没有任何结点(防止脏数据)
return true;
}
比较:带头节点的好处,头指针L永远指向头结点不用修改(插入删除代码不带头结点时,由于L指向首元结点,所以插入第一个结点和删除最后一个结点时都需要修改L(代码会多一个条件处理)。
结点类型的别名
单链表的基本操作(没有特别说明,默认都带头结点)
单链表的初始化
算法步骤:
1、生成新结点为头结点,用头指针指向头结点
2、头结点指针域置空
//初始化
bool InitList(LinkList &L)
{ //构造一个空的单链表L
L = new LNode; //生成新结点为头结点,用头指针指向头结点
L->next = NULL; //头结点指针域置空
return true;
}
单链表的判空操作
//判断是否为空表
bool Empty(LinkList L)
{
return (L == NULL);
// 或者
// if (L == NULL)
// return true;
// else
// return false;
}
单链表求表长
//表长
int Length(LinkList L)
{
int len = 0;
LNode *p = L;
while (!p->next)
{
p = p->next;
len++;
}
return len;
}
单链表的取值
算法步骤
1、用指针变量p指向首元结点,用j作为计数器初值赋为1
2、从首元结点开始依次顺着链域next往下访问,只要指向当前结点的指针p不为空, 并且没有到达序号为i的结点,则循环执行以下操作:P指向下一个结点;j相应+1
3、退出循环时,如果指针P为空,或者计数器j>i,说明指定的序号i值不合法(i > 表长 || i <= 0),取值失败返回error;否则取值成功,此时j = i时,p所指的结点就是要找的第i个结点,用参数e保存当前结点的数据域,返回Ok
//单链表的取值
bool GetElem(LinkList L, int i, ElemType &e)
{ //在带头结点的单链表L中根据序号i获取元素的值,用e返回L中的第i个数据元素的值
LNode *p;
p = L->next; //初始化,P指向首元结点,计数器j初始值为1
int j = 1;
while(p && j < i) //顺链域向后扫描,直到P为空或者p指向第i个数据元素
{
p = p->next; //p指向下一个结点
j++; //计数器加1
}
if(!p || j > i) //如果p为空或者i的值不合法,i > n或者 i < 0
return false;
e = p->data; //将第i个结点的数据域赋给e,带回
return true;
}
单链表的按值查找
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。
算法步骤:
1、用指针p指向首元结点;
2、从首元结点开始依此顺着链域next向下查找,只要指向当前结点的指针p不为空,并且p所指向的结点的数据域不等于给定值e,则循环执行以下操作:p指向下一个结点
3、返回p。若查找成功,p此时为结点的地址值,若查找失败,p的值即为NULL。
//按值查找
LNode *LocateElem(LinkList L, ElemType e)
{ //在带头结点的单链表L下查找值为e的元素
LNode *p;
p = L->next; //初始化p指向首元结点
while (p && p->data != e) //如果p不为空且p所指向的数据域不等于e
p = p->next; //p指向下一个结点
return p; //查找成功返回e的结点地址p,查找失败p为NULL
}
单链表按位查找
GetElem(L,i):按位查找操作。获取表L中第i个位置的元素的值。在第i个位置插入元素e
算法步骤:
1、用指针p指向首元结点
2、判断要查找元素的位置,如果要查找的元素在第一个,则直接返回L头结点;如果要查找的元素小于1,则位置不合法,返回空值;如果位置合法且p不为空的话,则p指向下一节点的地址,即p = p->next;查找成功返回p.
//按位查找,返回第i个元素(带头结点)
LNode *GetElem(LinkList L, int i)
{
int j = 1;
LNode *p = L->next; //定义一个指针P,指向首元结点
if (i == 0) //如果i的值为0,说明要查在的元素是头结点
return L;
if (i < 1) //i < 1位置不合法,返回NULL
return NULL;
if (j < i) //j < i,p就顺着链域向下找
{
p = p->next;
j++;
}
return p;
}
//按位查找
Status GetElem(LinkList L, int i)
{
int j = 1;
LNode *p = L->next;
while (p && j < i)
{
p = p->next;
j++;
}
if (!p || j > i)
return error;
return p;
}
单链表的插入
ListInsert(&L,i,e):插入操作。在表L中的第i个位置(找到第 i-1 个结点,将新结点插入其后)上插入指定元素e。
带头结点:头结点可以看作“第0个”结点
算法步骤:
1、首先找到ai-1个结点(也就是要插入结点的前驱结点),p指向该结点;
2、生成一个新结点*s;
3、将新结点的数据域置为e;
4、将新结点*s的指针域指向结点ai;
5、将结点*p的指针域指向新结点s;
//插入算法,将元素e插入到第i个位置上 (带头结点)
Status ListInsert(LinkList &L, int i, ElemType e)
{
LNode *p;
p = L; //p指向头结点
int j = 0; //记录当前p指向第几个结点,L是头结点,第0个,不存放数据
while (p && j < i - 1) //循环到j-1个结点
{
p = p->next;
j++;
}
if (!p || j > i - 1)
return error;
LNode *s;
s = new LNode; //new一个新结点用来存放要插入的元素,其数据域为e
s->data = e; //新结点的数据域置为e
s->next = p->next; //新结点的指针域指向ai
p->next = s; //结点ai-1的指针域指向新结点
return ok;
}
不带头结点
如果不带头结点,则插入、删除第1个元素时,需要更改头指针L
不存在 “第0个”结点,因此 i=1 时需要特殊处理
//插入算法,(不带头结点)
//不存在第0个结点,因此i = 1要做特殊处理
Status ListInsert (LinkList &L, int i, ElemType e)
{
if (i < 1)
return error;
if (i == 1) //插入第一个结点与其他结点的操作不同
{
LNode *s = new LNode;
s->data = e;
s->next = L;
L = s; //头指针指向新结点
return ok;
}
LNode *p; //P指向扫描到当前的结点
int j = 1; //p指向的是第几个结点
p = L; //p指向第一个结点(注意:不是头结点哦)
while (!p && j < i - 1)
{
p = p->next;
j++;
}
if (!p)
return error; //i值不合法,返回error
LNode *s = new LNode;
s->data = e; //新结点数据域置为e
s->next = p->next; //将ai结点的地址放到新结点的next域
p->next = s; //将新结点的地址放到ai-1的next域
return ok;
}
单链表的删除:
ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
算法步骤:
1、查找结点ai-1(要删除结点的前驱结点),并由指针p指向该结点;
2、临时保存待删除结点a1的地址在q中,以备释放;
3、将结点*p的指针域指向ai的直接后继结点;
4、释放结点ai的空间。
//删除
Status ListDelete(LinkList &L, int i, ElemType &e)
{
LNode *p, *q; //p指向当前扫描的结点
int j = 0; //记录当前结点p指向第几个结点
p = L; //L指向头结点,第0个结点(不存数据)
while (p->next && j < i- 1) //循环找到删除结点的前驱结点,即第i-1个结点
{
p = p->next;
j++;
}
if (!p->next || j > i - 1) //位置不合法
return error;
q = p->next; //q指向被删除的结点
p->next = q->next; //将*q结点从链中断开
e = q->data; //用e返回元素的值
delete q; //释放结点空间
return ok;
}
单链表---头插法创建单链表
元素插在链表的头部
链表的结点链接顺序与逻辑次序相反
算法步骤:
1、创建一个只有头结点的空链表;
2、根据待创建链表包括的元素个数n,循环执行以下操作:
- 生成一个新结点*p;
- 输入元素值赋给新结点*p的数据域;
- 将新结点*p插入到头结点之后
//头插法
void CreatList_H(LinkList &L, int n)
{
L = new LNode ;
L->next = NULL; //建立一个带头结点的单链表
for (int i = 0; i < n; i++)
{
LNode *p = new LNode; //生成新结点*p
cin >> p->data; //输入元素值赋给新结点的数据域
//将新结点插入到头结点之后
p->next = L->next;
L->next = p;
}
}
单链表---尾插法创建单链表
元素插在链表的尾部
链表的结点链接顺序与逻辑次序相同

算法步骤:
1、创建一个只有头结点的空链表;
2、尾指针 r 初始化,指向头结点;
3、根据创建链表包括的元素个数n,循环n次执行以下操作:
- 生成一个新结点*p;
- 输入元素值赋给新结点*p的数据域;
- 将新结点*p插入到尾结点*r之后;
- 尾指针 r 指向新的尾结点*p
//尾插法
void CreatList_R(LinkList &L, int n)
{
L = new LNode ;
L->next = NULL; //创建一个到头结点的空链表
LNode *r = L; //尾指针指向头结点
for (int i = 0; i < n; i++)
{
LNode *p = new LNode; //生成新结点
cin >> p->data; //输入元素值赋给新结点*p的数据域
//将新结点*p插入到尾结点*r之后
p->next = NULL;
r->next = p;
r = p; //r指向新的尾结点*p
}
}
单链表的输出
//输出
Status DispLsit(LinkList L)
{
LNode *p;
p = L->next;
while (p)
{
cout << p->data << ' ';
p = p->next;
}
cout << endl;
return ok;
}
循环链表---单循环链表
循环链表
是一种头尾相接的链表,即表中的最后一个结点的指针域指向头结点,整个链表形成一个环
优点:从表中任意一个结点出发均可找到表中其他结点
注意:
由于循环链表中没有空指针,所以涉及到在遍历操作时,其终止条件就不再像非循环链表那样判断 p 或者 p->next是否为空,而是判断它们是否等于头指针。
p! = L
p->next != L
头指针表示循环链表时(不方便)
找a1的时间复杂度是O(1)
找an的时间复杂度是O(n)
尾指针表示循环链表时(相对方便一些)
找a1的时间复杂度是O(1) -----------a1的存储位置也就是 r->next->next
找an的时间复杂度是O(1) -----------an的存储位置是 r
两个仅有尾指针的循环链表的合并
将其中一个链表接在另一个链表的表尾,例如,把下面的链表接在上面链表的表尾
1、首先我们可以把La这个链表的头指针找到,在循环链表中,尾指针的next域所指向的就是头指针的地址,也就是 p = ra->next ,p指向头结点
2、然后将Lb链表接上去,此时就可以直接将Lb的首元结点接在La链表的尾指针后面,Lb的首元结点的地址存放在头结点的next域中,而头结点的地址又存放在尾结点的next域中,所以要找Lb链表中的首元结点,就得一层层的向前推,即 ra->next = rb->next->next
3、将Lb链表中的头结点存放在Lb链表的尾指针的next域中。即 rb->next = p
/*带尾指针的循环链表合并*/
LinkList Connect(LinkList Ta, LinkList Tb)
{
//假设Ta,Tb都非空
p = Ta->next; //p存放头结点地址
Ta->next = Tb->next->next; //Tb表头连Ta的表尾
delete Tb->next; //释放Tb头结点
Tb->next = p; //修改指针
return Tb;
}
双向链表
双向链表的每个结点内有两个指针域,一个指向其直接前驱,一个指向其直接后继
双向链表插入算法
在带头结点的双向链表L中的第i个位置之前插入元素e
插入语句步骤:
- s->prior=p->prior
- p->prior->next=s
- s->next=p
- p->prior=s
//插入,在带头结点的双向链表L中的第i个位置之前插入元素e
Status ListInsert_D(DListLink &L , int i, ElemType &e)
{
if (!(p = GetElem_D(L, i ))) //表为空的话,返回error
return error;
DNode *s = new DNode; //new一个新结点s
s->data = e; //将元素e存放在新结点的数据域
s->prior = p->prior; //将第ai-1个结点的地址存放在新结点s的prior中
p->prior->next = s; //将新结点s的地址存放在第ai-1个结点的next
s->next = p; //将第i个结点的地址存放在新结点s的next域中
p->prior = s; //将新结点s的地址存放在第ai个结点的prior中
return ok;
}
//详细版,其实都差不多,上面只不过是调用了取值算法
//插入,在带头结点的双向链表L中的第i个位置之前插入元素e
Status ListInsert_D(DLinkList &L , int i, ElemType &e)
{
DNode *p = L; //p指向头结点
int j = 0;
while (p && j < i - 1)
{
p = p->next; //找到要删除结点的前驱结点
j++;
}
if (!p|| j > i - 1)
return error;
DNode *s = new DNode; //new一个新结点s
s->data = e; //将元素e存放在新结点的数据域
s->prior = p->prior; //将第ai-1个结点的地址存放在新结点s的prior中
p->prior->next = s; //将新结点s的地址存放在第ai-1个结点的next域中
s->next = p; //将第i个结点的地址存放在新结点s的next域中
p->prior = s; //将新结点s的地址存放在第ai个结点的prior中
return ok;
}
双向链表的删除算法
删除算法语句
p->prior->next = p->next;
p->next->prior = p->prior;
//删除,删除带头结点的双向链表L中的第i个元素,并用e返回
Status LsitDelete_D(DLinkList &L, int i, ElemType &e)
{
if (!(p = GetElem_D(L, i)))
return error;
DNode *p ;
e = p->data; //e保存要删除结点的数据域
p->prior->next = p->next; //将ai+1个结点的地址存放在ai-1结点的next域中
p->next->prior = p->prior; //将ai-1结点的地址存放在ai+1结点的prior域中
free(p); //释放结点
return ok;
}
//上下都差不多
Status LsitDelete_D(DLinkList &L, int i, ElemType &e)
{
DNode *p = L; //p指向头结点
int j = 0;
while (p->next && j < i-1)
{
p = p->next;
j++;
}
if (!(p->next) || j > i - 1)
return error;
e = p->data; //e保存要删除结点的数据域
p->prior->next = p->next; //将ai+1个结点的地址存放在ai-1结点的next域中
p->next->prior = p->prior; //将ai-1结点的地址存放在ai+1结点的prior域中
free(p); //释放结点
return ok;
}
本文是关于数据结构中线性表的单链表部分的学习笔记,介绍了单链表的定义、存储结构,包括带头结点和不带头结点的形态。详细阐述了单链表的插入、删除、查找等基本操作,以及头插法、尾插法创建单链表的算法步骤。还探讨了循环链表和双向链表的概念,以及如何在循环链表中进行合并操作。














学习笔记&spm=1001.2101.3001.5002&articleId=123957105&d=1&t=3&u=9d0e3ba9a3a34329ba55c8bfcf1288a1)
7392

被折叠的 条评论
为什么被折叠?



