2019-8-30
链表
1、表的链式存储—链表
链表:线性表采用链式方式将结点链接起来的存储结构称为链表,修改结点链接适合动态变化
单链表:链表中的每个结点只有一个指针域
①单链表的结点结构
值域(数据域) | 链域 (指针域) |
---|---|
data | next |
存储结点的数据值 | 存储数据元素的直接后继的地址 |
②链式存储结构图
- 首指针(表头指针):指向链表的第一个结点的指针变量,其值为首结点的存储地址
- 表尾结点(最后一个结点):由于最后一个数据元素没有直接后继,链域值为空(NULL)
- 链表就是表头指针和一串相继链接的结点的总称
用一组任意的存储单元(地址可以不连续的单元)存放线性表的结点
每个结点的唯一后继依靠一个结点指针维持
H ----头指针,指向第一个结点
^ ----最后一个结点的指针域为"空"(NULL)
③带头结点的单链表结构
(a)带头结点的空单链表
(b)带头结点的非空单链表
附设头结点的目的:为方便统一空表或非空表的运算处理
头结点:数据域无要求,其指针域指向第一个结点
首结点:链表中第一个元素结点
Q:链表中头指针、头结点、首结点的关系
链表头指针指向单链表开始(H)
带头结点的链表中,头指针(H)指向头结点,头结点指向首结点
无头结点的链表中,头指针(H)指向首结点
④单向链表结构
结构示例:线性表(A,B,C,D,E,F,G,H)的单链表存储结构,头指针H为31
存储地址 | 数据域 | 指针域 |
---|---|---|
1 | D | 43 |
7 | B | 13 |
13 | C | 1 |
19 | H | NULL |
25 | F | 37 |
31 | A | 7 |
37 | G | 19 |
解析:头指针H为31,指向表中地址为31的存储地址,其中31的存储地址数据元素为A,其指针域为7,指向其后继结点存储地址为7的地址,以此循环下去。这个表很好地反映了存放线性表的结点是用一组任意的存储单元(地址可以不连续的单元)。
⑤单链表类型C语言定义
typedef struct Node //结点类型定义
{
ElemType data;//定义数据域
struct Node *next;//定义指针域
}Node,*LinkList; //LinkList为结构指针类型
LinkList L; //L为单链表的头指针,也称为单链表L
LinkList与Node* 同为结构指针类型
常用LinkList类型定义指针变量
常用Node* 来定义单链表中的结点类型
⑥单链表的基本使用方式
- 头指针指向单链表
- 单链表的操作必须从头指针开始依次访问表中的结点
- 访问带头结点单链表L的首结点p,其语句为:
p=L->next
- 首结点的data部分(a1):头指针指向单链表
(L->next)->data
2、单链表的基本操作以及运算
结点的引用:
p->data:结点的值域,等价于(*p).data
p->next:结点的链域,等价于(*p).next
①单链表的建立
- 空表
Status InitList(LinkList &L)
{
L=(LinkList)malloc(sizeof(LNode)); //申请结构体类型的内存
if(!L)exit(OVERFLOW); //若申请内存失败,退出
L->next=NULL;//将头结点的next域置空,变为空指针
return OK;
}
- 头插法建立非空单链表
Status InitList(LinkList &L)
{
L=(LinkList)malloc(sizeof(LNode));//申请内存
if(!L)exit(OVERFLOW);
L->next=NULL; //置空头结点的next域
cin>>n; //n代表n个结点
for(i=1;i<=n;i++)
{
p=(LinkList)malloc(sizeof(LNode)); //每一次循环,申请新结点的内存空间
cin>>p->data;//每一次循环,输入结点的data值
p->next=L->next;//将头结点的next域赋给新结点的next域,使新结点的next域指向头结点后面的结点
L->next=p;//将指向新结点的指针赋给头结点的next域,使头指针指向头结点
}
return OK;
}
- 尾插法建立非空单链表
Status InitList(LinkList &L)
{
L=(LinkList)malloc(sizeof(LNode));
if(!L)exit(OVERFLOW);
L->next=NULL;
q=L;
cin>>n;//输入要插入的结点个数
for(i=1;i<=n;i++)
{
p=(LinkList)malloc(sizeof(LNode)); //申请新结点的内存空间,并将地址赋给指针p
cin>>p->data;//存入新结点数据域的值
q->next=p; //将指向新结点的指针赋给尾结点q的next域,使尾结点的next域指针指向新结点的地址
q=p; //将指向新结点的指针赋给指向尾结点的指针
}
return OK;
}
①单链表的插入
插入条件:要在带头结点的单链表L中的第i个数据元素之前插入一个数据元素e
算法描述:
a)在单链表中找到第i-1个结点并由指针pre指示
b)然后申请一个新结点并由指针s指示,其数据域的值为e
c)修改第i-1个结点的指针使其指向s
d)使s结点的指针域指向第i个结点
//插入关键步骤
① s->next=pre->next;
② pre->next=s;
//注意:这两个的操作步骤顺序不能改变
//表中插入
//单链表插入新结点的完整代码
Status ListInsert_L(LinkList &L,int i,ElemType e){
//在带头结点的单链表L中的第i个位置之前插入元素e
p=L;
j=0;
while(p&&j<i-1){
//寻找第i-1个结点
p=p->next;
++j;
}
if(!p||j>i-1) //
{
return ERROR;
}
s=(LinkList)malloc(sizeof(LNode));//申请一个新的存储结点
s->data=e;//置结点的值域,将读入的元素值存入新结点的值域
s->next=p->next;//将指向第i-1个结点的指针赋给s的指针域,使指向新结点e的指针域指向后继结点i
p->next=s;//将指向新结点的指针s赋给第i-1个结点的指针域,使其指向新结点e
return OK;
}
//插在表头
//head为头指针
p->next=head;//将头指针head赋给新结点的指针域,使新结点的指针域指向其后继结点
head=p;//将指向新结点的指针赋给头指针,使头指针指向新结点的地址
①求带头结点单链表长度
算法思路:沿带头结点单链表L的首元结点计数统计单链表长度
算法描述:
a)顺链头开始,计数器j的初值为0,当前指针p指向链表L的首元结点 p=L->next
b)p依次往后(计数j++)直到表尾(p=NULL)
//求带头结点的单链表L的单链表的长度,next部分是指针域
int ListLength(LinkList L)
{
Node *p;
p=L->next; //使当前p指针指向链表L的首元结点,L是头指针
j=0; //用来存放单链表的长度
while(p!=NULL)
{
p=p->next;//使指针p指向下一个结点的存储位置
j++;//从首结点开始计数到尾结点
}
return j;
}
②建立单链表
//建立空表L
InitList(LinkList *L)
{
L=(LinkList)malloc(sizeof(Node));//建立头结点
L->next=NULL;//建立空的单链表
}
注:L是指向单链表头结点的指针,用来接收单链表头指针变量的地址
③头插法建表
算法描述:已知空链表L,依次读入结点数据头插入,直到读入结束标志为止。
每插入一个结点到表头的步骤:
1、生成新结点s
2、将结点s插入到首元结点之前,即表头结点之后
Linklist CreateFromHead(LinkList L)
{
Node *s;
char c;
int flag=1;//设置一个标志,初值为1,当输入"$"时,flag为0,建表结束
while(flag)
{
c=getchar();//输入字符
if(c!='$')
{ s=(Node*)malloc(sizeof(Node));//申请新结点s
s->data=c;//赋值给新结点的数据域
s->next=L->next;//头结点的指针域赋给新结点的指针域
L->next=s;//将结点s插入链表L
}
else flag=0;
}
}
④尾插法建表
算法描述:已知空链表,设置尾指针r指向当前表尾,依次读入结点数据尾插入,直到读到结束标志时将表尾结点链域置空
1、生成新结点s
2、将结点s插入到表尾r之后,该结点作为当前表尾结点
Linklist CreateFromTail(LinkList L)
{
LinkList L;
Node *r,*s;//定义指针变量r,s
int flag=1;//设置一个标志,初值为1,当输入“$”时,flag=0,建表结束
r=L;//r指针始终动态指向链表的当前表尾,便于做尾插入,其初值指向头结点
while(flag)
{
c=getchar();
if(c!='$')
{
s=(Node*)malloc(sizeof(Node));
s->data=c;
r->next=s;
r=s;
}
else{
flag=0;
r->next=NULL;//将最后一个结点的next链域置为空,表示链表的结束
}
}
}
3、指定位置的删除
①删除表头结点
q=head;
head=head->next;
free(q);
②删除表中结点
p=q->next;
q->next=p->next;
free(p);
4、链表的特点
- 结点地址不连续
- 插入/删除不移动结点,耗时为O(1)
- 用于动态管理
- 使用指向结点(结构类型)的指针
- 执行期间,调用动态存储管理函数产生结点、回收结点
5、单向链表的构造
构造链表的通用算法
步骤1)构造“空链表”
步骤2)读入第一个元素值
步骤3)当读入的元素值不是“输入结束标记”时,循环执行步骤4~7
步骤4)申请一个新结点
步骤5)将读入的元素值存入新结点的值域
步骤6)将新结点“插在”链表中
步骤7)读入“下一个”元素值,转步骤3
步骤8)构造完毕,返回首指针
①向前插入法造表
ptr creatlinkedA()
{
ptr head,p;//定义局部变量,head,p为指针变量
int x;
head=NULL;//将表头指针置空,表示链表的初始状态
scanf("%d",&x);//读入第一个元素
while(x!=End_elm)//当读入的不是结束标记时循环,这里的End_elm是抽象定义,具体实现自己定义
{
p=(ptr)malloc(sizeof(snode));//申请一个存储结点
p->data=x;//置结点的值域,将读入的元素值存入新结点的值域
p->next=head;//插在表头处
head=p;//表头指针指向新结点
scanf("%d",&x);
}
return(head);//返回表头指针
}//ptr在C语言中没有特别的含义,既不是关键字也不是库函数的函数名。是自定义的一个变量名或函数名。
②向后插入法造表
构造链表的通用算法
步骤1)构造“空链表”
步骤2)读入第一个元素值
步骤3)当读入的元素值不是“输入结束标记”时,循环执行步骤4~7
步骤4)申请一个新结点
步骤5)将读入的元素值存入新结点的值域
步骤6)将新结点“插在”链表的表尾处
步骤7)读入“下一个”元素值,转步骤3
步骤8)构造完毕,返回首指针
ptr creatlinked_B()
{
ptr head,last,p;
int x;
head=NULL;
scanf("%d",&x);
while(x!=End_elm)
{
p=(ptr)malloc(sizeof(snode));//申请一个存储结点
p->data=x;
if(head=NULL)//空表,修改头指针和尾指针
{
p->next=head;
head=p;
last=p;
}
else//非空表,插在表尾,修改尾指针
{
last->next=p;
p->next=NULL;
last=p;
}
scanf("%d",&x);//读入下一个元素
}
return head;
}