数据结构笔记一:线性表--链表
单链表 线性表的链式存储表示
特点:
逻辑上相邻的两个数据元素其存储的物理位置不要求紧邻。
顺序存取。
定义
用链式存储存储结构实现了线性结构。
一个结点存储一个数据元素。
单链表:链表的每个结点只包括一个指针域。
单链表的存取由头指针开始,头指针指向链表中第一个结点的存储位置。最后一个数据元素无直接后继,指向NULL。
//单链表的存储结构
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域
}LNode, *LinkList;
结点包括数据域和指针域。数据域存储数据元素信息,指针域存储直接后继结点的存储位置。–>各个结点间的先后关系用一个指针表示。
LNode、LinkList
LNode* 和LinkList本质上是等价的。
LinkLIst定义单链表,强调定义的是某个单链表的头指针。
LNode* 定义指向单链表中任意结点的指针变量。
单链表可以用头指针唯一确定。
//使用LNode、LinkList定义变量的含义。
LinkList L; //L为单链表的头指针
LNode *p; //p为指针。指向单链表某个结点;*p为该结点。
头结点,附设在单链表的第一个结点之前。首元结点、头结点、头指针
首元结点:链表中存储第一个数据元素的结点
头结点:指针域指向首元结点,头结点的数据域可以不存储信息也可存储附加信息。
头指针:指向链表中第一个结点。若有头结点则指向头结点,无头结点则指向首元结点。
空表判断:
//设头结点 头结点指针域置空
L->next==NULL;
//无头结点 头指针指向空
L==NULL;
初始化 InitList
//构造一个空的有头结点的单链表L
Status InitList(LinkList &L){
//生成新节点为头结点,头指针L指向头结点
L= new LNode;
//头结点的指针域置空
L->next = NULL;
return OK;
}
插入 ListInsert
按位插入
在带头结点的单链表L中第i个位置插入值为e的新结点。需要生成一个数据域为e的结点插入到单链表中。
①找到第i-1个结点,p指向该结点
②生成一个新节点* s
③* s的数据域存为e
④* s的指针域指向原来的第i个结点
⑤* p的指针域指向新节点* s
(可以看做对第i-1个结点进行了后插操作)
Status ListInsert(LinkList &L , int i , ElemType e)
{
p=L;
j=0;
while (p&& (j<i-1)){
p=p->next;
j++;
}
if(!p || j>i-1)
return ERROR;
s= new LNode;
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
时间复杂度:O(n)
p=L:L指向头结点,头结点为第0个结点
j:用来表示当前p指向的为第几个结点
指定结点的后插操作
Status InsertNextNode(LNode *p , ElemType e)
{
if(!p) return ERROR; //即p==NULL
s= new LNode;
if(!s) return ERROR; //内存分配失败
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
指定结点的前插操作
方法一:
定义链表的头结点,循环查找指定结点的直接前驱结点,对指定节点的前驱结点进行后插操作。
时间复杂度为O(n)
方法二:
申请新节点s , 利用后插法s成为p的后继节点,将p的数据域信息存入s,将所要插入的数据信息存入原来的结点p。
时间复杂度为O(1)
Status InsertPriorNode(LNode *p , ElemType e)
{
if(!p) return ERROR; //即p==NULL
s= new LNode;
if(!s) return ERROR; //内存分配失败
s->next = p->next;
p->next = s;
s->data = p->data;
p->data = e;
return OK;
}
删除 ListDelete
按位删除
删除单链表的第i个结点
①查找第i个结点,指针p指向该结点
②临时保存待删除结点的地址于p,在后续释放存储空间
③将结点*p的指针域指向待删除结点的直接后继结点
④释放待删除结点的空间
Status ListDelete(LinkList &L , int i )
{
p=L;
j=0;
//找到要删除的结点
while ((p->next)&& (j<i-1)){
p=p->next;
j++;
}
//删除结点、释放存储空间
if(!(p->next) || j>i-1)
return ERROR;
q = p->next;
p->next = q->next;
delete q;
return OK;
}
循环条件
插入算法中循环条件为(p&&(j<i-1)),也可写为((p!=NULL)&&(j<i-1))
删除算法中循环条件为((p->next)&&(j<i-1)),也可写为(((p->next)!=NULL)&&(j<i-1))
当单链表有n个结点时,合法的插入位置有n+1个,自头结点后至尾结点后都可以插入,1<=i<=n+1(头结点为第0个结点)
合法的删除位置有n个,即存储数据元素的n个结点,1<=i<=n
指定结点的删除
待删除结点为*p,指定结点时,*p的前驱结点不明,类比指定节点的插入可知有两种方法,一为查找链表得到前驱结点,时间复杂度为O(n);一为替换结点,用待删除结点的后继结点代替待删除结点被删除、释放,时间复杂度为O(1)。
下实现第二种方法。待删除结点 * p,用 *p的后继节点代替它被删除、释放,那么新的待删除结点 *q的前驱结点就是 *p ,将p->next的数据域存入 *p,改变相应的指针,释放 *q即可。
Status DeleteNode(LNode *p)
{
if(!p) return ERROR; //即p==NULL
//q指向待删除结点p的后继
LNode *q = p->next;
//待删除结点p与后继结点交换数据域
p->data = p->next->data;
//将*q结点断开
p->next = q->next;
//释放后继节点的存储空间
delete q;
return OK;
}
值得注意的是,该方法不具有普适性,当要删除最后一个结点,即指定节点为链表的尾结点,指针域为空时, “p->data = p->next->data”无法顺利实现。
按位查找/取值
给定一个结点位置序号i,找到单链表对应的结点,用参数e保存当前节点的数据域。
单链表不是随机存取结构,无法像顺序表一样随机访问,从首元结点出发,顺着next向下访问。
–>指针指向首元结点,顺着链域next依次访存;需要计数器j
①指针p指向首元结点,计数器j赋值为1
②从首元结点开始向下访存,只要p!=null(链表未结束)且没到达序号为i的结点,循环执行: p指向下一个节点,计数器+1
③退出循环时,若p为空或者计数器j>1,说明指定的i不合法。否则取之成功,此时j=i,p指向要找的第i个结点,参数e保存当前节点的数据域。
i合法取值:1<=i<=n
//带有头结点
status GetElem(LinkList L, int i, ElemType &e){
p=L->next;
j=1;
//找到第i个结点
while(p&&j<i){
p=p->next;
j++;
}
if(!p||j>i) return ERROR;
//取值 此时j=i,p指向第i个结点,不为空
e=p->data;
return OK;
}
时间复杂度:O(n)
按值查找
从链表的首元结点出发,依次将结点数据域的值与给定数值e进行比较,返回查找到的结点。
①指针p指向首元结点
②从首元结点开始依次顺着链域next向下查找,只要指向当前结点的指针p不为空,且p所指结点数据域不等于定值e,循环执行:p指向下一个结点
③返回p。查找成功,p值为找到的结点,查找失败,p值为null
//不带头结点
LNode *LocateElem(LinkList L, ElemType e){
p=L->next;
while (p&&p->data!=e){
p=p->next;
}
return p;
}
时间复杂度与e的值有关,可能第一个结点的数据域就与e相等,也可能尾结点才相等,平均时间复杂度为O(n)
LNode* 强调返回的为某个结点。
求表长
指针p依次遍历链表的各个结点,不为空时记为一个结点,计数器加一。当p为空时表示单链表结束,返回计数值。
//有头结点
int ListLength(LinkList L){
LinkList p; //注意p的类型
p=L->next; //p指向首元结点
i=0;
while(p!=NULL){
p=p->next;
i++;
}
return i;
}
销毁
指针p暂存L的首元结点,并在后续释放,头指针L沿链域后移,随着释放p的存储空间,单链表逐渐缩短,当头指针为空时表示单链表完全销毁。
//无头结点
Status DestoryList(LinkList &L){
LinkList p;
while (L){
p=L;
L=L->next;
delete p ;
}
return OK;
}
创建单链表
前插法
将新节点逐个插入链表的头部、头结点之后来创建链表。每次申请一个新节点存入数据元素值,将新节点插入到头结点之后。
最后生成的单链表中,数据存储是逆序的。
//有头结点,输入n个元素的值,创建有头结点的单链表L
void CreatList_H(LinkList &L, int n){
//先建立一个有头结点的空链表
L = new LNode;
L->next = NULL;
for(i=0;i<n;i++){
p = new LNode;
cin >> p->data;
p->next = L->next;
L->next = p;
}
}
使用之前的函数,封装
void CreatList_H(LinkList &L, int n){
//先建立一个有头结点的空链表
InitList(L)
//根据元素个数进行循环操作,在头结点后使用后插操作,创建单链表
for(i=0;i<n;i++){
cin>>e;
InsertNextNode(L, e)
}
}
后插法
将新节点逐个插入到链表的尾部。每申请一个新节点,存入相应的数据。增加一个尾指针指向链表的尾结点。
生成的单链表的数据顺序相对输入数据为顺序的。
void CreateList_R( LinkList &L, int n){
L = new LNode;
L->next = NULL; //初始化得到空表L
r=L; //尾指针指向头结点
for(i=0;i<n;i++){
p = new LNode;
cin >> p->data;
p->next = L->next;
r->next = p;
r=p; //r指向新的尾结点*p
}
时间复杂度为O(n)