线性表 链表
开始更新我的数据结构学习笔记,因为前段时间太忙了,正好最近数据结构也快要考试了,也当复习吧~
文章目录
一.单链表
结构体形式
typedef struct LNode
{
ElemType data;//数据域
struct LNode *next;//指针域
}LNode;
typedef LNode *LinkList;
说明:LinkList 和 LNode*是不同名字的同一个指针类型。LinkList 类型的指针变量表示是单链表的头指针,LNode * 类型的指针变量表示是指向某一结点的指针。
即:LNode *p = LinkList p
初始化操作
//时间复杂度为O(1)
//生成一个新结点作为头结点
//设置头结点的指针域为空
void Init(LinkList &L)
{
//构造一个单链表L
L = new LNode;
L->next = NULL;
}
销毁操作
//时间复杂度为O(n)
void Destroy(LinkList &L)
{
//释放单链表L所占用的存储空间
LinkList p;
while(L)
{
p = L;
L = L->next;
delete p;
}
}
清空操作
清空操作不释放头结点,销毁操作释放头结点
//时间复杂度为O(n)
void clear(LinkList &L)
{
LinkList p,q;
p = L->next;//p指向第一个结点
while(p)
{
q = p->next;
delete p;
p = q;
}
L->next = NULL;//令头指针的节点域为空
}
求表长
//时间复杂度为O(n)
int ListLength(LinkList L)
{
LinkList p;
p = L->next;//设置指针,初始指向L的第一个结点
length = 0;
while(p)
{
// 顺表向后扫描遍历,计算表长
length++;
p = p->next;
}
return length;
}
取值操作
//时间复杂度为O(n),ASL = (n-1)/2
void GetElem_L(LinkList L,int i,ElemType &e)
{
// 用e返回单链表L中第i个结点的数据域值;
//若i值不合理,则给出相应信息并退出运行,i的合理值为1≤i≤表长
LinkList p = L->next; // 设置指针,初始时指向L的第一个结点
int j=1; // 设置计数器,初始时为1
while(p&&(j<i))
{ // 顺链向后扫描
p=p->next; //p指向下一个结点
++j; //计数器j相应加1
}
if(!p||(j>i)) Error(" Position Error!");
else e=p->data;
}
定位操作
(1)返回地址
//时间复杂度为O(n)
LNode* LocateElem(LinkList L, ElemType e)
{
// 查找单链表L中第一个数据域值和e相等的结点
// 若存在则返回其指针;若不存在则返回NULL
LinkList p=L->next; // 设置指针,初始时指向L第一个结点
while(p&&(p->data!=e)) // 顺链向后扫描
p = p->next;
return p;//返回该元素所在位置的指针
}
(2)返回位置序号
//时间复杂度为O(n)
int LocateELem_L (LinkList L,Elemtype e) {
//返回L中值为e数据元素的位置序号,查找失败返回0
LinkList p=L->next;
int j=1;
while(p &&p->data!=e)
{p=p->next; j++;}
if(p)//找到了该元素
return j;
else //未找到该元素
return 0;
}
插入操作
① 顺链表向后扫描寻找第i-1个结点,并检查插入操作要求的相关参数是否合理性,如果不合理,则给出相应信息并退出运行;
② 生成一个新结点;
③ 将待插入的数据元素值赋给新结点的数据域;
④ 将第i个结点的指针赋给新结点的指针域: s->next=p->next;
⑤ 修改第i-1个结点指针域中的指针,令其指向新结点: p->next=s;
4和5的顺序不可调换!
//时间复杂度为O(n)
void ListInsert(LinkList &L,int i,ElemType e)
{
// 在单链表L中第i个位置前插入值为e的结点;若插入位置不合理 则给出信息并退出运行,i的合理值为1≤i≤表长+1
LinkList p = L;
LNode s;
int j = 0;
while(p&&(j<i-1)) { // 在L中顺链向后扫描寻找第i-1个结点
p=p->next;
++j;
}
if(!p||(j>i-1)) Error(" Position Error!");
s=new LNode; // 生成新结点
s->data=e; // 将e赋给新结点的数据域
s->next=p->next; // 将第i个结点指针赋给新结点指针域
p->next=s; // 修改第i-1个结点指针域中指针
}
删除操作
① 顺链表向后扫描寻找第i-1个结点,并检查插入操作要求的相关参数是否合理性,如果不合理,则给出相应信息并退出运行;
② 设置一个指针指向待删除结点,即第i个结点,为删除后释放结点空间做准备;
③ 取出待删除结点数据域的值,为返回其值做准备;
④ 删除第i个结点,即修改第i-1个结点的指针,令其指向第i+1个结点;
⑤ 释放第i个结点的存储空间。
//时间复杂度为O(n)
void ListDelete_L(LinkList &L,int i,ElemType &e)
{
//若删除位置不合理则给出信息退出运行,i的合理值为1≤i≤表长
LinkList p = L;
int j = 0;
while((p->next)&&(j<i-1))
{
//在L中顺链向后扫描寻找第i-1个结点
p=p->next;
++j;
}
if(!(p->next)||(j>i-1))
Error(" Position Error!");
q = p->next; // 设置指针q指向被删除结点
p->next=q->next; // 删除第i个结点
delete q; // 释放第i个结点的存储空间
}
输出链表中所有元素
从第一个结点开始,顺链表向后扫描,依次输出表中每个结点数据域的值
//时间复杂度为O(n)
void TraverseList(LinkList L)
{
// 依次输出单链表L中每个结点数据域的值
LinkList p = L->next;
while(p)
{
cout<<p->data;
p=p->next;
}
}
前插法方式建立单链表
① 从空表开始,重复读入数据;
② 生成新结点;将读入数据存放到新结点的数据域中;
③ 将该新结点插入到链表的前端;
//时间复杂度为O(n)
void CreateList_H(LinkList &L,int n)
{
//按照逆序输入n个元素值,从表尾到表头逆向建立单链表L
L = new LNode;//先建立头结点
L->next=NULL; //建立一个空链表L
LinkList p;
for(i=n;i>0;--i)
{
p=new LNode; // 生成一个新结点
cin>>p->data; //读入数据到新结点数据域
p->next=L->next; // 插入新结点到L中
L->next=p; // 头结点指针指向新结点
}
}
尾插法方式建立单链表
① 从空表L开始,将新结点逐个插入到链表的尾部,尾指针r指向链表的尾结点。
② 初始时,r同L均指向头结点。每读入一个数据元素则申请一个新结点,将新结点插入到尾结点后,r指向新结点
void CreateList_L(LinkList &L,int n)
{
//正位序输入n个元素的值,建立带表头结点的单链表L
L = new LNode;
L->next=NULL;
LinkList r=L; //尾指针r指向头结点
for(int i=0;i<n;++i)
{
p=new LNode; //生成新结点
cin>>p->data; //输入元素值
p->next=NULL;
r->next=p; //插入到表尾
r=p; //r指向新的尾结点
}
}
二.循环链表
在单链表中,将最后一个结点的指针域 NULL 改为指向头结点,这样形成的链式存储结构称为单向循环链表,简称循环链表
在循环链表中,查找第一个结点的时间是 O(1),查找最后一个结点的时间是 O(n)
链表为空的条件:L->next = L;
链表尾的条件:p->next = L;
如果在循环链表中设立尾指针 Rear 而不设头指针 Head,则对第一个结点和最后一个结点的查找时间都是 O(1)。
开始结点:rear->next->next
终端结点:rear
将两个循环链表合成为一个循环链表的语句
//时间复杂度为O(1)
p = B->next->next;
B->next = A->next;
A->next = p;
三.双向链表
在循环链表中,每个结点中除了一个存放后继结点的指针域外,再增加一个指向其前驱结点的指针域,链表中有两条方向不同的链,这样形成的链式存储结构称为双向循环链表,简称双向链表。
链表样式:
- 在双向链表第一个结点之前附加一个同结构的结点,称为头结点。其数据域可不存储任何信息,也可存储诸如线性表长度等类的附加信息。
- 其前向指针域存储指向最后一个结点的指针,后向指针域存储指向第一个结点的指针。
- 指向头结点的指针称为头指针。
结构体形式
typedef struct DuLNode
{
ElemType data;// 数据域
struct DuLNode *prior; // 前向指针域
struct DuLNode *next; // 后向指针域
} DuLNode;
typedef DuLNode *DuLinkList;
空循环的双向链表
判断条件:L->next = L
插入结点操作
首先顺链向后扫描寻找第i个结点,检查插入参数i是否合理:
- 如果不合理,则给出相应信息并退出运行
- 如果合理,则生成一个新结点,并将待插数据元素值赋给其数据域
并按照如下步骤完成插入:
step1.
s->prior = p->prior;
step2.
p->prior->next = s;
step3.
s->next = p;
step4.
p->prior = s;
详细代码如下:
//时间复杂度为O(n)
//将元素值为e的结点插入到第i个结点之前
DuLNode* GetElemP_DuL(DuLinkList L,int i) {
// 1≤i≤表长+1;若表中不存在该结点,则返回NULL
DuLinkList p = L->next;
int j=1;
while((p!=L)&&(j<i))
{ // 在L中顺链向后扫描寻找第i个结点
p=p->next;
++j;
}
if(((p==L)&&(j!=i))||(j>i)) return NULL;
return p; // 返回第i个结点指针
}
void ListInsert_DuL(DuLinkList &L,int i,ElemType e)
{
//在双向链表L中第i个结点之前插入值为e的结点
//若i值不不合理则给出相应信息并退出运行
p=GetElemP_DuL(L,i); //确定双向链表L中第i个结点的指针p
if(p==NULL) Error(" Position Error!");
s=new DuLNode;
s->data=e;
s->prior=p->prior; // ①设新结点前向指针指向p结点前驱
p->prior->next=s; // ②修改p结点前驱的后向指针
s->next=p; // ③设新结点后向指针指向p结点
p->prior=s; // ④修改p结点的前向指针
}
删除结点
首先顺链向后扫描寻找第i个结点,检查删除参数i是否合理,如果不合理,则给出相应信息并退出运行;如果合理,则用e返回第i个结点数据域值;然后按照如下步骤完成删除:
① 修改第i-1个结点的后向指针,令其指向第i+1个结点;
② 修改第i+1个结点的前向指针,令其指向第i-1个结点;
③ 释放第i个结点的存储空间。
p->prior->next = p->next;
p->next->prior = p->prior;
delete p;
代码如下:
//时间复杂度为O(n)
DuLNode *GetElemP_DuL(DuLinkList L,int i)
{
//与上面相比换了一种写法 两者都可以
//返回双向链表L中第i个结点指针,1≤i≤表长;若表中不存在该结 点,则返回NULL
DuLinkList p=L;
int j=0;
while((p->next!=L)&&(j<i-1))
{// 在L中顺链向后扫描寻找 第i个结点
p=p->next;
++j;
}
if((p->next==L)||(j>i-1)) return NULL;
return p->next; //返回第i个结点指针
}
void ListDelete_DuL(DuLinkList &L,int i,ElemType &e) {// 用e返回双向链表L第i个结点数据域值并删除该结点
// 若i值不合理则给出相应信息并退出运行
p=GetElemP_DuL(L,i);//确定双向链表L中第i个结点指针p
if(p==NULL) Error(" Position Error!");
e=p->data; // 取出第i个结点数据域值
p->prior->next=p->next; // ①修改第i-1个结点后向指针
p->next->prior=p->prior; // ②修改第i+1个结点前向指针
delete p; // ③释放第i个结点空间
}
四.静态链表
数组中一个分量表示一个结点,同时使用游标(cur)代替单链表中的指针,存储指示其后继的相对地址(最后一个分量游标域值为 0),这种用数组描述的链表称为静态链表。
数组中第 0 个分量可看成头结点:
其数据域可不存储任何信息,也可存储如线性表长度等类的信息;其游标域指示静态链表第一个结点。
例:在下面的静态链表中,先将“Shi”插入到“Li”和“Zhou”之间,再删除“Zheng”
结构体类型
#define List_Size 100// 静态链表大小
typedef struct
{
ElemType data; // 数据域
int cur; // 游标,指示结点在数组中相对位置
} component;
typedef component SlinkList[List_Size+1];
在静态链表中实现线性表的操作和单链表相似,以整型游标i代替动态指针p,i=S[i].cur的操作即为指针后移(类似于单链表操作中的p=p->next)
在静态链表中查找第一个值为e的元素
如果找到,则返回其在S中的相对位置,否则返回0
//时间复杂度为O(n)
int LocateElem_SL(SlinkList S,ElemType e) {
// 在静态链表S中查找第一个值为e的元素:如果找到,则返回它在S中的位置;否则返回0
int i=S[0].cur; // 令i指示S的第一个结点
while(i&&(S[i].data!=e)) //S中顺链向后查找
i=S[i].cur;
return i;
}
五.顺序表
顺序表和链表的区别
线性结构的基本特征是:
- 在数据元素非空有限集合中:有且只有一个“第一个元素”;有且只有一个“最后一个元素”;
- 除第一个元素外,其他元素都有唯一的直接前驱;除最后一个元素之外,其他元素都有唯一的直接后继。
线性表中第 n 个元素 an 的起始地址为:loc(an)=loc(a1)+(n-1)d (1≤i≤n)
结构体定义
#define LIST_INIT_SIZE 100// 线性表存储空间的初始分配量
#define LIST_INCREMENT 10// 线性表存储空间的分配增补量
typedef struct {
ElemType *elem; // 线性表存储空间基地址
int length; // 线性表当前长度
int listsize; // 当前分配的存储容量(以ElemType为单位)
} SqList;
初始化操作
① 按照需要为线性表分配一个预定义大小的存储区域LIST_INIT_SIZE,即顺序表的最大容量,如果存储分配失败,则给出错误信息;
② 设置线性表的长度为0;
③ 设置线性表的当前存储容量为顺序表的最大容量;
//时间复杂度为O(1)
void InitList_Sq(SqList &L)
{
//构造一个最大容量为LIST_INIT_SIZE的顺序表L
L.elem=new ElemType[LIST_INIT_SIZE];
if(!L.elem) Error(" Overflow!"); // 存储分配失败
L.length=0;
L.listsize=LIST_INIT_SIZE;
}
void Error(char *s)
{
//出错信息处理函数,用于处理异常情况
cout<<s<<endl;
exit(1);
}
销毁操作
① 释放线性表中数据元素所占用的存储空间
② 设置线性表的长度为0
③ 设置线性表存储容量为0
//时间复杂度为O(1)
void DestroyList_Sq(SqList &L)
{
// 释放顺序表L中元素所占用的存储空间
delete []L.elem;
L.length=0;
L.listsize=0;
}
清空操作
重新设置线性表为空表,即令表的长度为0,但不释放掉该表所占用的存储空间
//时间复杂度为O(1)
void ClearList_Sq(SqList &L)
{
// 重置顺序表L为空表
L.length=0;
}
求表长
直接返回顺序表中的长度域值
//时间复杂度为O(1)
int ListLength_Sq(SqList L)
{
// 返回顺序表L的长度
return L.length;
}
取值操作
① 判断取数据元素值操作所要求的相关参数是否合理,如果不合理,则给出错误信息
② 返回线性表中待取数据元素的值
//时间复杂度为O(1)
//用e返回顺序表L中第i个元素值(1≤i≤L.length)
//若参数不合理则给出相应信息并退出运行
void GetElem_Sq(SqList L,int i,ElemType &e)
{
if((i<1)||(i>L.Length)) // 取元素值的参数不合理
Error(" Position Error!");
e=L.elem[i-1];
}
查找操作
① 从顺序表的第一个数据元素起,依次和给定的数据元素值进行比较,如果存在与其值相等的数据元素,则返回第1个相等元素的位序
② 如果查遍整个顺序表都没有找到与其值相等的数据元素,则返回为0
//时间复杂度为O(n),ASL = (n+1)/2
int LocateElem_Sq(SqList L,ElemType e)
{
for(i=0;i<L.length;i++)
if(L.elem[i]==e) return (i+1);//查找成功,返回序号
return 0;
}
插入操作
① 检查插入操作要求的相关参数是否合理,若不合理,则给出错误信息;
② 判断当前存储空间是否已满,如果已满,则需要系统增加空间;
③ 把顺序表中原来第n至第i个数据元素依次往后移动一个位置;
④ 把新数据元素插入在顺序表的第i个位置上;
⑤ 表长加1
//时间复杂度为O(n)
void ListInsert_Sq(SqList &L,int i,ElemType e)
{
//在顺序表L中第i个位置前插入元素e;若插入位置不合理则给出 相应信息并退出运行
//i的合理值为1≤i≤L.length+1
SqList q;
if((i<1)||(i>L.length+1)) // 插入元素的参数不合理
Error(" Position Error!");
if(L.length>=LIST_INIT_SIZE) // 若当前存储空间已满,则增加空间
Increment(L);
q=&(L.elem[i-1]); // 令指针q指向插入位置
for(p=&(L.elem[L.length–1]);p>=q;--p)
*(p+1)=*p; // 依次后移元素
*q=e; // 在L的第i个位置中插入e
++L.length; // 修正L的长度,令其增1
}
void Increment(SqList &L)
{
// 为顺序表L扩充LIST_INCREMENT个数据元素的空间
newlist=new ElemType[L.listsize+LIST_INCREMENT];
//增加LIST_INCREMENT个存储分配
if(!newlist) Error("Overflow!"); //存储分配失败
for(i=0;i<L.length;i++)
newlist[i]=L.elem[i]; //腾挪原空间中的数据到newlist中
delete []L.elem; //释放元素所占的原空间L.elem
L.elem=newlist; // 移交空间首地址
L.listsize+=LIST_INCREMENT; // 修改扩充后顺序表的最大空间
}
稍微简单版的程序:
//时间复杂度为O(n)
void ListInsert(SqList &L,int i,ElemType e)
{
//在顺序表L中第i个位置插入新的元素e,i值的合法范围是1<=i<=L.length+1
if((i<1)||(i>L.length+1)) return ERROR;
if(L.length==MAXSIZE) return ERROR;//当前存储空间已满
for(j=L.length-1;j>=i-1;j--)
L.elem[j+1]=L.elem[j];
L.elem[i-1] = e;
L.length++;
return OK;
}
删除元素
① 检查删除操作要求的相关参数是否合理,若不合理,则给出错误信息
② 将待删除数据元素的值赋给变量
③ 把顺序表中原来第i+1至第n个数据元素依次向前移一个位置
④ 修正顺序表的长度。
//时间复杂度为O(n)
void ListDelete_Sq(SqList &L,int i,ElemType &e)
{
// 删除顺序表L中第i个元素并用e返回其值;若插入位置不合理则给出信息并退出运行
// i的合理值为1≤i≤L.length
if((i<1)||(i>L.length)) // 删除元素的参数不合理
Error(" Position Error!");
for(j=i;j<=L.length-1;j++)
L.elem[j-1] = L.elem[j];
--L.length;
return OK;
}
输出所有元素
当顺序表L非空时,依次输出L中的所有数据元素
//时间复杂度为O(n)
void TraverseList_Sq(SqList L)
{
// 依次输出顺序表L中的每个数据元素
if(L.length!=0)
{
i=1; // 指示位序,初值为1
p=L.elem; // 指向L第i个元素,初始指向首元素
while(i<=L.length)
{ // 依次输出L中的元素
cout<<*p++;
i++;
}
}
}