广义表.docx

上传人:b****2 文档编号:2452803 上传时间:2023-05-03 格式:DOCX 页数:20 大小:48.87KB
下载 相关 举报
广义表.docx_第1页
第1页 / 共20页
广义表.docx_第2页
第2页 / 共20页
广义表.docx_第3页
第3页 / 共20页
广义表.docx_第4页
第4页 / 共20页
广义表.docx_第5页
第5页 / 共20页
广义表.docx_第6页
第6页 / 共20页
广义表.docx_第7页
第7页 / 共20页
广义表.docx_第8页
第8页 / 共20页
广义表.docx_第9页
第9页 / 共20页
广义表.docx_第10页
第10页 / 共20页
广义表.docx_第11页
第11页 / 共20页
广义表.docx_第12页
第12页 / 共20页
广义表.docx_第13页
第13页 / 共20页
广义表.docx_第14页
第14页 / 共20页
广义表.docx_第15页
第15页 / 共20页
广义表.docx_第16页
第16页 / 共20页
广义表.docx_第17页
第17页 / 共20页
广义表.docx_第18页
第18页 / 共20页
广义表.docx_第19页
第19页 / 共20页
广义表.docx_第20页
第20页 / 共20页
亲,该文档总共20页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

广义表.docx

《广义表.docx》由会员分享,可在线阅读,更多相关《广义表.docx(20页珍藏版)》请在冰点文库上搜索。

广义表.docx

广义表

5.4广义表的类型定义

ADTGlist{

数据对象:

D={ei|i=1,2,..,n;n≥0;

ei∈AtomSet或ei∈GList,

AtomSet为某个数据对象}

数据关系:

LR={|ei-1,ei∈D,2≤i≤n}

广义表是递归定义的线性结构,

LS=(1,2,,n)

其中:

i或为原子或为广义表

换句话说,广义表是一个多层次的线性结构。

例如:

D=(E,F)

E=(a,(b,c))

F=(d,(e))

A=()

B=(a,B)=(a,(a,(a,,)))

C=(A,D,F)

广义表的结构特点:

1)广义表中的数据元素有相对次序;

2)广义表的长度定义为最外层包含的元素个数;

3)广义表的深度定义为所含括弧的重数;

注意:

“原子”的深度为“0”;

“空表”的深度为1

4)广义表可以共享;

5)广义表可以是一个递归的表;

递归表的深度是无穷值,长度是有限值。

6)任何一个非空广义表

LS=(1,2,…,n)

均可分解为

表头Head(LS)=1和

表尾Tail(LS)=(2,…,n)

两部分,可见,非空广义表的表头可以是原子,也可以是子表,而表尾必定是一个广义表。

例如:

LS=(A,D)

Head(LS)=ATail(LS)=(D)

Head(D)=ETail(D)=(F)

Head(E)=aTail(E)=((b,c))

Head(((b,c)))=(b,c)Tail(((b,c)))=()

Head((b,c))=bTail((b,c))=(c)

Head((c))=cTail((c))=()

基本操作:

结构的创建和销毁

InitGList(&L);DestroyGList(&L);

CreateGList(&L,S);CopyGList(&T,L);

状态函数

GListLength(L);GListDepth(L);

GListEmpty(L);GetHead(L);GetTail(L);

插入和删除操作

InsertFirst_GL(&L,e);

DeleteFirst_GL(&L,&e);

遍历

Traverse_GL(L,Visit());

}ADTGList

5.5广义表的表示方法

头、尾指针的链表结构

表结点:

tag=1hptp

原子结点:

tag=0data

 

构造存储结构的分析方法:

1)空表ls=NIL

非空表

ls1

若表头为原子

0

 

2)空表ls=NIL

非空表

ls11•••1

12n

若子表为原子

0

5.6递归函数的设计方法

递归函数

一个含直接或间接调用本函数语句的函数被称之为递归函数,它必须满足以下两个条件:

1)在每一次调用自己时,必须是(在某种意义上)更接近于解;

2)必须有一个终止处理或计算的准则。

例如:

梵塔的递归函数

voidhanoi(intn,charx,chary,charz)

{

if(n==1)

move(x,1,z);

else{

hanoi(n-1,x,z,y);

move(x,n,z);

hanoi(n-1,y,x,z);

}

}

二叉树的遍历

StatusPreOrderTraverse(BiTreeT,Status(Visit)(BiTreeP)){

if(T){

if(Visit(T->data))

if(PreOrderTraverse(T->lchild))

if(PreOrderTraverse(T->rchild))returnOK;

returnERROR;

}

elsereturnOK;

}//PreOrderTraverse

一、分治法(分割求解)(DivideandConquer)

对于一个输入规模为n的函数或问题,用某种方法把输入分割成k(1

若子问题还相当大,则可以反复使用分治法,直至最后所分得的子问题足够小,以至可以直接求解为止。

在利用分治法求解时,所得子问题的类型常常和原问题相同,因而很自然地导致递归求解。

例如:

焚塔问题:

Hanoi(n,x,y,z)可以分成三个子问题:

1)Hanoi(n-1,x,z,y)

2)Move(n,‘X’‘Z’)

3)Hanoi(n-1,y,x,z)

n=1时可以直接求解

二叉树的遍历Traverse(BT)分成三个子问题:

1)Visite(RootNode)

2)Traverse(LBT)

3)Traverse(RBT)

对空树不需要遍历

分治法特别适用于结构上可以分解的结构,如:

二叉树、树、广义表等

广义表从结构上可以分解成

广义表=表头+表尾

或者

广义表=子表1+子表2+···+子表n

则常用分治法求解。

例一求广义表的深度

广义表的深度=Max{子表的深度}+1

空表的深度=1

原子的深度=0

intGlistDepth(GlistL){

//采用头尾链表存储结构,求广义表L的深度。

if(!

L)return1;//空表深度为1

if(L->tag==ATOM)return0;//原子深度为0

for(max=0,pp=L;pp;pp=pp->ptr.tp){

dep=GlistDepth(pp->ptr.hp);

//求以pp->ptr.hp为头指针的子表深度

if(dep>max)max=dep;

}

returnmax+1;

//非空表的深度是各子表的深度的最大值加1

}//GlistDepth

例二复制广义表

若ls=NIL则newls=NIL

否则

由表头ls^.hp复制得newhp

由表尾ls^.tp复制得newtp

构造结点newls,并使newls^.hp=newhp,

newls^.tp=newtp

StatusCopyGList(Glist&T,GlistL){

//采用头尾链表存储结构,由广义表L复制得到

//广义表T。

if(!

L)T=NULL;//复制空表

else{

if(!

(T=(Glist)malloc(sizeof(GLNode))))

exit(OVERFLOW);//建表结点

T->tag=L->tag;

if(L->tag==ATOM)T->atom=L->atom;

//复制单原子

else{

CopyGList(T->ptr.hp,L->ptr.hp);

//复制广义表T->ptr.hp的一个副本L->ptr.hp

CopyGList(T->ptr.tp,L->ptr.tp);

//复制广义表T->ptr.tp的一个副本L->ptr.tp

}//else

}//else

returnOK;

}//CopyGList

例三创建广义表的存储结构

根据LS=(1,2,,n)建广义表ls

若LS=()则ls=NIL

否则

构造表结点ls^

分解出第一个子串1,对应建广义表的表头ls^.hp

若剩余串非空,则构造表尾结点ls^.tp

分解出第二个子串2,对应建广义表

依次类推,直至剩余串为空串止

StatusCreateGList(Glist&L,SstringS){

//采用头尾链表存储结构,由广义表的书写形式串

//S创建广义表L。

设emp=”()”。

if(StrCompare(S,emp))L=NULL;//创建空表

else{

if(!

(L=(Glist)malloc(sizeof(GLNode))))

exit(OVERFLOW);//建表结点

if(StrLength(S)==1){

L->tag=ATOM;L->atom=S

}//创建单原子广义表

else{

L->tag=LIST;p=L;

SubString(sub,S,2,StrLength(S)-2);//脱外层括号

do{//重复建n个子表

sever(sub,hsub);//从sub中分离出表头串hsub

CreateGList(p->ptr.hp,hsub);q=p;

if(!

StrEmpty(sub){//表尾不空

if(!

(p=(GLNode*)malloc(sizeof(GLNode))))

exit(OVERFLOW);

p->tag=LIST;q->ptr.tp=p;

}

}while(!

StrEmpty(sub));

q->ptr.tp=NULL;

}//else

}//else

returnOK;

}//CreateGList

二、后置递归法(Postponingthework)

假如某个问题的的求解过程可以分成若干步进行,并且当前这一步的解可以直接求得,则先求出当前这一步的解,对于余下的问题,若问题的性质和原问题类似,则又可递归求解。

显然,递归的终结状态应该是,当前的问题只需一步便可求得,对原问题而言,则是走到了求解的最后一步。

例一删除单链表中所有元素为x的结点

分析:

1)单链表是一种顺序结构,必须从第一个结点起,逐个检查每个结点的数据元素;

2)从另一角度看,链表又是一个递归结构,

若head是线性链表(a1,a2,,an)的头指针,

则head^.next是线性链表(a2,,an)的头指针。

voidDelete_LinearList(Link&L,Elemx){

//删除单链表中所有元素为x的结点

if(!

L->next){

fp=L->next;

if(fp->data==x){

L->next=fp->next;

free(fp);

Delete_LinearList(L,x);

}

elseDelete_LinearList(fp,x);

}

}//Delete_LinearList

例二删除广义表中所有元素为x的原子结点

分析:

广义表和线性表比较:

相似处:

都是顺序结构

不同处:

广义表的数据元素可能还是个广义表

voidDelete_GL(Glist&L,AtomTypex){

//删除广义表中所有元素为x的原子结点

if(!

L->ptr.tp){

first=L->ptr.tp;head=first->ptr.hp;

if((head->tag==Atom)&&

(head->atom==x)){

L->ptr.tp=first->ptr.tp;free(head);

free(first);Delete_GL(L,x);

}

else{

if(head->tag==LIST)

Delete_GL(head,x);

Delete_GL(first,x);

}

}

}//Delete_GL

例三求n个数(的不同排列)可能构成的全部序列

算法:

设序列的初始状态为空,之后从1至n逐个插入,插入的位置不同则构成不同的序列,对k,则有k个插入位置。

一般情况下的问题提法为:

假设已知含1至k-1个元素的一个排列,则需要继续从k至n逐个插入,以构成n个元素的序列。

这当前的一步是插入元素k,它可有k个插入位置。

例如,对于已经形成的序列:

1,2,,k-1,可以得到如下k个序列:

k,1,2,,k-1

1,k,2,,k-1

1,2,k,,k-1

1,2,,k-1,k

则对于已经形成的(k-1)!

个不同排列的序列,在插入k之后,得到k!

个序列。

下一步的问题就变成:

已知含1至k个元素的一个排列,需要从k+1至n逐个插入,以构成n个元素的序列。

若k=n,则表明已求得全部解。

逻辑结构采用线性表表示序列

voidPermute(List&L,intn,intk){

//已知线性表L中已含k-1个元素,本函数将

//从k至n的元素依次插入到表中不同位置,

//以求得n个元素的所有不同排列,并输出之

for(i=1;i<=k;++i){

ListInsert(L,i,k);//元素k插入为第i个元素

if(k==n)ListTraverse(L,Print);//遍历输出

elsePermute(L,n,k+1);//继续从k+1起插入

ListDelete(L,i,e);//删除表中当前第i个元素

}

}//Permute

主函数:

InitList(L);

Permute(L,n,1)

以n=3为例,看图解的算法执行过程

算法执行前的初态

插入“1”之后的状态1

2112

3212312133121321

上图树中每个结点表示线性表在程序执行过程中的一个状态(如:

根结点表示算法执行前的状态;叶子结点表示插入“3”之后的状态),故称之为“状态空间树”或“递归树”。

其中,叶子结点表示“解”的状态,即“递归终结”的状态,所有非叶结点称为“活动结点”,表示求得部分解之后的状态,亦可称之为“可扩展的结点”,意为:

算法所作是“扩展”非叶结点以求得问题的一种解。

算法Permute(L,n,k)的执行过程可以看成是先根遍历这棵状态空间树,访问结点的操作是“将第k个元素插入到第i棵子树根的第i个元素之前”,然后检查是否是叶子结点,若是,则输出,否则继续依次遍历各棵子树。

注意,这棵状态空间树在“遍历”过程中动态生成和销毁,访问结点的操作实际上是生成这棵树的根结点,而遍历结束返回时,删除这棵树。

因此,问题求解的核心是分析确定状态空间树的结构,即:

从可扩展的结点扩展出的子树的状态。

在本算法的状态空间树中,第k层(遍历前的初态设为0层)的结点为插入第k个元素生成的子树根结点,因此可有k个。

类似问题很多,但对于某些问题,其状态空间树中的非叶结点不一定都是可扩展的结点,而是在求得问题的部分解之后,尚需进行检验,这类问题就是通常用“回朔法”求解的问题

三、回溯法

回溯法是一种穷举方法。

其基本思想为:

假定一个问题的解能够表示成n元组

(x1,x2,,xn)

其中xi取值于集合Si,n元组的一个子组

(x1,x2,,xi)i

称为部分解,应满足一定的约束条件。

若在已求得的满足约束条件的部分解中,添加值集Si+1中的xi+1之后,仍然满足约束条件,则添加xi+1后得到一个新的部分解(x1,x2,,xi+1),之后继续添加xi+2并检查之。

若对于所有取值于集合Si+1的xi+1都不能得到新的满足约束条件的部分解(x1,x2,,xi+1),则从当前子组中删去xi,回溯到前一个部分解(x1,x2,,xi-1),重新添加那些值集Si中尚未考察过的xi,看是否满足约束条件。

如此反复进行,直至求得满足约束条件的问题的解,或者证明问题无解。

例一皇后问题求解

设四皇后问题的解为(x1,x2,x3,x4),其中xiS={1,2,3,4}

则对于每个满足约束条件的可扩充结点至多可能有四棵子树,因此它的递归状态空间树是一棵四叉树。

一般情况,对于nn的棋盘,每个满足约束条件的可扩充结点至多可能有n棵子树,即它的递归状态空间树是一棵n叉树。

voidTrial(inti,intn){

//进入本函数时,在n×n棋盘前i-1行已放置了

//互不攻击的i-1个棋子。

现从第i行起继续为后

//续棋子选择合适位置。

当i>n时,求得一个合法

//布局,并输出之。

if(i>n)输出棋盘的当前布局;

//n为4时,即为4皇后问题

elsefor(j=1;j<=n;++j){

在第i行第j列放置一个棋子;

if(当前布局合法)Trial(i+1,n);

移走第i行第j列的棋子;

}

}//Trial

例二出栈序列问题

问题:

1至n的n个整数依次入栈,可能得到的出栈序列是什么?

分析:

对于栈的某个状态,只可能派生出两个状态:

一是,继续有下一个元素入栈,其前提是尚有元素还没有入栈;

二是,栈顶元素出栈,其前提是栈不空。

根据由此所画出的递归状态空间树可得下列算法:

voidS(Stack&S1,Stack&S2,Stack&S3){

//已知三个栈的初始状态为:

S2和S3为空栈,

//栈S1中从栈顶到栈底依次存放元素1至n,

//本函数利用三个栈求得元素1至n经入栈到

//出栈可能得到的所有排列。

//递归的终结状态是S1栈和S2栈均为空栈。

ifStackEmpty(S1)&&StackEmpty(S2)

StackTraverse(S3,Print);//输出一种排列

else{

if(!

StackEmpty(S1)){

Pop(S1,e);Push(S2,e);

S(S1,S2,S3);

Pop(S2,e);Push(S1,e);

}

if(!

StackEmpty(S2)){

Pop(S2,e);Push(S3,e);

S(S1,S2,S3);

Pop(S3,e);Push(S2,e);

}

}

}//S

回溯法算法的一般形式如下:

voidB(inti){

//假设已经得到满足约束条件的部分解

//(x1,x2,,xi-1),本函数从xiSi起继续探索,

//直至求得整个解为止。

if(i>n)输出一组解(x1,x2,,xn);

elsewhile(!

Empty(Si)){

从Si中取得xi的一个值viSi;

if((x1,x2,,xi)满足约束条件)B(i+1)

从Si中删除vi;

}

}//B

综合几点:

1)对于含有递归特性的问题,最好设计递归形式的算法。

但也不要单纯追求形式,应在算法设计的分析过程中“就事论事”。

例如,在利用分割求解设计算法时,子问题和原问题的性质相同;或者,问题的当前一步解决之后,余下的问题和原问题性质相同,则自然导致递归求解。

2)实现递归函数,目前必须利用“栈”。

一个递归函数必定能改写为利用栈实现的非递归函数;反之,一个用栈实现的非递归函数可以改写为递归函数。

需要注意的是递归函数递归层次的深度决定所需存储量的大小。

3)分析递归算法的工具是递归树,从递归树上可以得到递归函数的各种相关信息。

例如:

递归树的深度即为递归函数的递归深度;递归树上的结点数目恰为函数中的主要操作重复进行的次数;若递归树蜕化为单支树或者递归树中含有很多相同的结点,则表明该递归函数不适用。

4)递归函数中的尾递归容易消除。

例如:

先序遍历二叉树可以改写为:

StatusPreOrderTraverse(BiTreeT,Status(Visit)(BiTreeP)){

While(T){

if(Visit(T->data))

if(PreOrderTraverse(T->lchild))

T=T->rchild;

elsereturnERROR;

}

returnOK;

}//PreOrderTraverse

5)可以用递归方程来表述递归函数的时间性能。

例如:

假设解n个圆盘的梵塔的执行时间为T(n)

则递归方程为T(n)=2T(n-1)+C,T(0)=0

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2