ListGet(myList,i,&x);
}
程序运行结果:
1234678910
2.3线性表的链式表示和实现
(1)单链表中构成链表的结点只有一个指向直接后继结点的指针域。
其结构特点:
逻辑上相邻的数据元素在物理上不一定相邻。
结点结构如图示:
指针域
数据域
next
data
或
数据域:
存储元素数值数据
指针域:
存储直接后继的存储位置
2.单链表的操作实现
(1)线性链表的结点定义如下:
typedefstructLnode
{
ElemTypedata;//定义数据域
structLnode*next;//定义指针域
}Lnode,*LinkList;//定义结点和头指针类型名
LinkListL;//定义头指针变量
1)初始化单链表
voidInit_LinkList(Lnode**L)指向指针的指针,则调用时形参可能为:
指针变量的地址或voidInit_LinkList(LinkList*L)1)初始化
voidInit_LinkList(Lnode**L)
{
//申请头结点,用head指示其地址
*head=(Lnode*)malloc(sizeof(Lnode));
//将头结点指针域置空NULL
(*head)->next=NULL;
}
//带尾结点单链表的初始化
voidInit_LinkList(LinkList*L,Lnode**tail)
{
*L=(Lnode*)malloc(sizeof(Lnode));
(*L)->next=NULL;
*tail=*L;
}
动态内存分配——malloc函数
分配一个新结点的存储空间,并用p指向该新结点,语句为:
p=(Lnode*)malloc(sizeof(Lnode));
malloc函数定义在stdlib.h头文件中。
sizeof(类型名):
求字节数运算符。
例如:
sizeof(int)运算结果为一个int类型所占用字节数。
(2)在表头插入新结点
//生成新结点,再将新结点插入到当前链表的表头上
voidInsert_LinkList(LinkList*L,DataTypex)
{
Lnode*p;
p=(Lnode*)malloc(sizeof(Lnode));
p->data=x;//生成新结点
p->next=(*L)->next;
(*L)->next=p;
}
(3)查找运算
//情况1:
给定需查找结点的序号i,找到则返回该结点地址(即指向该节点的指针),否则返回NULL。
这是一个指针函数,函数返回值为指针
Lnode*Get_LinkList(LinkListL,inti)
{
Lnode*p=L;
intj=0;
while(p!
=NULL&&j
{
p=p->next;
j++;
}
returnp;
}
调用:
LinkListL;
Lnode*p;
.....
p=Get_LinkList(L,2);
printf("No.2=%c\n",p->data);
//情况2:
按值查找。
给定值x,查找链表中元素值与之相等的结点,找到则返回该结点在链表中的位置(结点序号),否则返回0
intFind_node(LinkListL,ElemTypex)
{
Lnode*p;
intj=1;
p=L->next;//p指向首结点
while(p!
=NULL&&p->data!
=x)
{p=p->next;
j++;}
if(p)//即如果p!
=NULL
{printf("%d在链表中,是第%d个元素\n",p->data,j);
returnj;}
else
{printf("该数值不在链表里。
\n");
return0;}
}
(4)插入运算
情况1:
在已知P指针所指向的结点后插入一个元素值为x的新结点,
新结点:
s=(structLnode*)malloc(sizeof(structLnode));
s->data=x;
插入:
(1)s->next=p->next;
(2)p->next=s;
情况2:
在已知P指针所指向的结点前插入一个元素值为x的新结点:
voidInsert_front(LinkListL,Lnode*p)
{
Lnode*q=L->next;
while(q->next!
=p)
q=q->next;
//循环结束后,q指向p的前驱结点
s=(structLnode*)malloc(sizeof(structLnode));
s->data=x;
s->next=q->next;
q->next=s;
}
(5)删除结点
删除单链表中第i个结点。
如图所示:
(1)用q指向(待删的)第i个结点,p指向其前驱结点;
(2)p.next=q.next;
(6)撤消单链表Destroy(head)
voidDestroy(Lnode**head)
{Lnode*p,*p1;
p=*head;
while(p!
=NULL)
{p1=p;p=p->next;
free(p1);//free函数,释放p1所指向的结点空间
}
*head=NULL;
}
//求单链表长度
intLinkList_Length(Lnode*head)
{
Lnode*p=head;
intsize=0;
while(p->next!
=NULL)
{p=p->next;
size++;
}
returnsize;
}
//输出链表中各结点值
voidOutput_LinkList(LinkListL)
{
Lnode*p;
p=L->next;
while(p!
=NULL)
{
printf("%c\t",p->data);
p=p->next;
}
printf("\n");
}
//带尾指针的链表,将数据x插入链表尾
voidInsert_LinkList_tail(Lnode**tail,ElemTypex)
{
Lnode*p;
p=(Lnode*)malloc(sizeof(Lnode));//新结点
p->data=x;
p->next=(*tail)->next;
(*tail)->next=p;
(*tail)=(*tail)->next;//更新链尾
}
//P35,【例2-3】带头结点单链表就地逆置
voidReverse_LinkList(LinkListL)
{
Lnode*p,*q;
p=L->next;
L->next=NULL;
while(p!
=NULL)
{
q=p;
p=p->next;
q->next=L->next;//前插法将结点插入链表
L->next=q;
}
}
和顺序表相比,单链表的优点和缺点正好相反。
头指针是指向链表中第一个结点(或为头结点、或为首元结点)的指针;
头结点是在链表的首元结点之前附设的一个结点;数据域内只放空表标志和表长等信息,它不计入表长度。
首结点是指链表中存储线性表第一个数据元素a1的结点。
线性链表可由头指针唯一确定,整个链表的存取需要从头指针开始进行。
每个数据元素的存储位置都包含在其直接前驱结点的指针域中。
结论:
(1)带头结点单链表,对于无论是第一个数据元素结点,还是在其他结点,操作方法一致,无需特别处理;
(2)无论链表是否为空,处理方式也一致
因此,带头结点单链表的算法设计简单。
后面如无特别说明,线性链表均带头结点
循环单链表
循环单链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针域指向整个链表的第一个结点,从而使链表形成一个环。
它的优点是从链尾到链头比较方便。
循环单链表也有带头结点和不带头结点两种结构
2.3.3双向链表
双向链表是每个结点除后继指针域外还有一个前驱指针域,它有带头结点和不带头结点,循环和非循环结构,双向链表是解决查找前驱结点问题的有效途径。
priordatanext
在双向链表中:
设指针p指向第i个数据元素结点,则p->next指向第i+1个数据元素结点,p->next->prior仍指向第i个数据元素结点,即p->next->prior==p;同样p->prior->next==p。
prior
next
prior
next
p
(c)与p等价的指针关系
双向链表结点类型定义
typedefstructDLnode
{
ElemTypedata;
structDLnode*prior,*next;
}DLnode,*DLinkList;
线性链表应用举例
P35【例2-3】(重点)
voidReverse_LinkList(LinkListL)
{
Lnode*p,*q;
p=L->next;
L->next=NULL;
while(p!
=NULL)
{
q=p;
p=p->next;
q->next=L->next;//前插法将结点插入链表
L->next=q;
}
}
【例2-4】,升序表中,删除值介于min和max的结点
voidDelete_LinkList_minmax(LinkListL,ElemTypemin,ElemTypemax)
{
Lnode*p,*q,*r;
p=L->next;
while(p&&p->data<=min)
{
q=p;
p=p->next;
}
while(p&&p->data{
r=p;
p=p->next;
free(r);
}
q->next=p;
}
第3章栈和队列
1、栈的基本概念
(1)定义:
限定只能在固定一端进行插入和删除操作的线性表。
特点:
后进先出。
(2)允许进行插入和删除操作的一端称为栈顶,另一端称为栈底。
作用:
可以完成从输入数据序列到某些输出数据序列的转换
(3)向一个栈插入新的数据元素称为进栈、入栈或压栈,具体操作是把新的数据元素放到栈顶数据元素的上面,使之成为新的栈顶数据元素;
(4)从一个栈删除数据元素又称作出栈或弹栈,它是把栈顶数据元素删除,
栈顶
栈底
出栈
进栈
栈示意图
2.栈的基本运算
一般情况下栈具有下列基本运算:
(1)进栈,向栈顶插入一个新元素;
(2)出栈,删除栈顶元素;
(3)读栈,读取栈顶元素;
(4)置空栈,将栈置为空栈;
(5)判栈空,判断一个栈是否为空栈。
栈是一种线性表,因此有关线性表的两种存储结构同样也适用于栈。
栈的顺序存储结构简称顺序栈,利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,栈底位置是固定不变的,而栈顶位置是随着进栈和出栈操作而变化的。
1.顺序栈
顺序栈可以用C语言描述如下:
#defineMaxSize50//栈存储空间最大值
typedefstructSqStack
{
ElemTypeelem[MaxSize];//栈元素的存储空间
inttop;//栈顶指针
}SqStack;
2.顺序栈的状态及基本操作
用一维数组来存放栈,栈顶指针动态反映栈中数据元素的变化情况。
(1)空栈:
top=-1,表示栈为空,此时如果做出栈运算,会产生下溢错误;
(2)入栈:
栈顶指针top上移,递增1,元素A进栈;
(3)满栈:
所有的数据元素都已经进栈,
top=MaxSize-1表示栈已满,此时继续进栈运算,会产生上溢误;
(4)出栈:
栈顶元素F出栈,栈顶指针top下移,递减1。
3.栈的基本运算定义
(1)初始化栈Init_Stack(S)
(2)判断栈是否为空Stack_Empty(S)
(3)判断栈是否为满Stack_Full(S)
(4)进栈Push(S,x)
(5)出栈Pop(S)
(6)求栈的长度Stack_Length(S)
(7)读栈顶元素Get_Top(S)
1.顺序栈初始化:
voidInit_SqStack(SqStack*S)
{S->top=-1;
}
建立一个空栈,并初始化栈顶指针
2.顺序栈入栈
voidPush(SqStack*s,ElemTypex)
{if(s->top{s->top=s->top+1;
s->elem[s->top]=x;
}//栈未满,栈顶指针递增1,元素x进栈
else
printf("栈已满,不能入栈!
\n");
//栈满时不能入栈
}①检查栈是否已满,如果栈已满,则进行上溢错误处理;
②将栈顶指针上移一个位置,使之指向一个空闲单元;
③将新元素填入该空闲单元中。
3.出栈:
ElemTypePop(SqStack*s)
{
ElemTypex;
if(s->top!
=-1)
{x=s->elem[s->top];
s->top=s->top-1;
returnx;//栈未空,栈顶指针递减1,元素x出栈
}
else
{printf("栈为空,不能出栈!
\n");//栈空时不能出栈
return0;
}
}
①检查栈是否为空,如果为空,则进行下溢错误处理;
②将栈顶指针所指向的元素删除;
③将栈顶指针下移一个位置,递减1。
4.读栈顶元素
voidGet_Top(SqStack*s,ElemType&x)
{if(s->top!
=-1)
x=s->elem[s->top];
}
①检查栈是否为空,如果为空,则返回提示信息;
②将栈顶数据元素值赋予某一变量;
③返回栈顶数据元素值。
3.1.3栈的链式存储结构及运算
1.链栈的概念
采用链式存储结构表示的栈简称为链栈。
链栈栈顶元素就是首结点
头结点
an
an-1
a1
∧
…
s
栈底
栈顶
带头结点的链栈示意图
栈链式存储结构定义
typedefstructnode
{
ElemTypedata;
structnode*next;
}Stacknode,*LinkStack;
LinkStacks;
1.进栈运算
①申请一个新结点,将要进栈元素x的值送入该结点的data域;
②将该结点入栈,使之成为新的栈顶元素。
算法描述如下:
voidPush(LinkStacks,ElemTypee)
{
Stacknode*p;
p=(Stacknode*)malloc(sizeof(Stacknode));
p->data=e;
p->next=s->next;//入栈
s->next=p;//新结点成为新的栈顶元素
}
2.出栈运算
①检查栈是否为空,如果为空,则返回0,输出提示信息;如果不为空,执行以下:
将栈顶数据结点的data域的值由参数返回,取下栈顶结点,使其直接后继成为新的栈顶结点,将取出的栈顶结点空间放。
链栈出栈
intPop(LinkStacks,ElemType*e)
{Stacknode*p;
if(s->next==NULL)//栈空的情况
return0;
p=s->next;//p指向第一个数据结点
e=p->data;//栈顶元素通过参数返回
s=p->next;//原栈顶结点的直接后继成为新的栈顶结点
free(p);//释放原栈顶结点空间
return1;
}
3.读栈顶元素运算
①检查栈是否为空,如果为空,则返回提示信息;
②将栈顶数据元素值赋予某一变量;
③返回栈顶数据元素值。
算法描述如下:
intGet_Top(LinkStacks,ElemType*e)
{
if(s->next==NULL)//栈空时返回0
return0;
e=s->next->data;
//栈非空,将栈顶数据元素值赋予变量e
return1;
}
3.3队列
(1)定义:
只能在表的一端进行插入操作,在表的另一端进行删除操作的线性表。
一个队列的示意图如下:
(1)顺序队列:
顺序存储结构的队列。
(3)顺序队列的“假溢出”问题
顺序队列因多次入队列和出队列操作后出现的虽有存储空间但不能进行入队列操作的情况。
可采取四种方法:
1)采用顺序循环队列;(教材中的方法)
2)按最大可能的进队操作次数设置顺序队列的最大元素个数;(最差的方法)
3)修改出队算法,使每次出队列后都把队列中剩余数据元素向队头方向移动一个位置;
4)修改入队算法,增加判断条件,当假溢出时,把队列中的数据元素向对头移动,然后方完成入队操作。
(4)顺序循环队列的基本原理
把顺序队列所使用的存储空间构造成一个逻辑上首尾相连的循环队列。
当rear和front达到MaxQueueSize-1后,再前进一个位置就自动到0。
(5)顺序循环队列的队空和队满判断问题
新问题:
在顺序循环队列中,队空特征是front=rear;队满时也会是front=rear;判决条件将出现二义性!
解决方案有三:
①使用一个计数器记录队列中元素个数(即队列长度);
(教材中的方法)
判队满:
count>0&&rear==front
判队空:
count==0
②设