代码面试之广义表

简介: 广义表的基本概念 广义表(Lists,又称列表)是线性表的推广。线性表定义为n>=0个元素a1,a2,a3,…,an的有限序列。线性表的元素仅限于原子项,原子是作为结构上不可分割的成分,它可以是一个数或一个结构,若放松对表元素的这种限制,容许它们具有其自身结构,这样就产生了广义表的概念。

广义表的基本概念

广义表(Lists,又称列表)是线性表的推广。线性表定义为n>=0个元素a1,a2,a3,…,an的有限序列。线性表的元素仅限于原子项,原子是作为结构上不可分割的成分,它可以是一个数或一个结构,若放松对表元素的这种限制,容许它们具有其自身结构,这样就产生了广义表的概念。

     广义表是n (n>=0)个元素a1,a2,a3,…,an的有限序列,其中ai或者是原子项,或者是一个广义表。通常记作LS=(a1,a2,a3,…,an)。LS是广义表的名字,n为它的长度。若ai是广义表,则称它为LS的子表。

抽象数据类型广义表的定义如下:

ADT Glist

{

   数据对象: D={ei | i=1,2,..,n;n>=0 ;  eiÎAtomSet 或ei ÎGlist,

                                     AtomSet为某个数据对象}

   数据关系:R1={< ei-1, ei > | ei-1 , ei ÎD,2<=i<=n}

   基本操作:

       InitGList( &L);

             操作结果:创建空的广义表L。

       CreateGList(&L,S);

             初始条件:S是广义表的书写形式串。

             操作结果:由S创建广义表L。

       DestroyGList(&L);

            初始条件:广义表L存在。

            操作结果:销毁广义表L。

CopyGList( &T,L);

            初始条件:广义表L存在。

            操作结果:由广义表L复制得到广义表T。

       GListLength(L);

            初始条件:广义表L存在。

            操作结果:求广义表L的长度,即元素个数。

       GListDepth(L);

            初始条件:广义表L存在。

            操作结果:求广义表L的深度。

       GListEmpty (L);

            初始条件:广义表L存在。

            操作结果:判定广义表L是否为空。

       GetHead(L);

            初始条件:广义表L存在。

            操作结果:取广义表L的头。

GetTail( &T,L);

            初始条件:广义表L存在。

            操作结果:取广义表L的尾。

       InsertFirst_GL(&L,e);

            初始条件:广义表L存在。

            操作结果:插入元素e作为广义表L的第一元素。

       DeleteFirst_GL(&L,&e);

            初始条件:广义表L存在。

            操作结果:删除广义表L的第一元素,并用e返回其值。

       Traverse_GL (L,visit());

            初始条件:广义表L存在。

            操作结果:遍历广义表L,用函数visit处理每个元素。

通常用圆括号将广义表括起来,用逗号分隔其中的元素。为了区别原子和广义表,书写时用大写字母表示广义表,用小写字母表示原子。若广义表LS(n>=1)非空,则a1是LS的表头,其余元素组成的表(a2,…an)称为LS的表尾。

    显然广义表是递归定义的,这是因为在定义广义表时又用到了广义表的概念。广义表的例子如下:

(1)A=()——A是一个空表,其长度为零。

(2)B=(e)——表B只有一个原子e,B的长度为1。

(3)C=(a,(b,c,d))——表C的长度为2,两个元素分别

         为原子a和子表(b,c,d)。

(4)D=(A,B,C)——表D的长度为3,三个元素

        都是广义表。显然,将子表的值代入后,

         则有D=(( ),(e),(a,(b,c,d)))。

(5)E=(E)——这是一个递归的表,它的长度为2,E相当于一个无限的广义表E=(a,(a,(a,(a,…)))).

从上述定义和例子可推出广义表的三个重要结论:

(1)广义表的元素可以是子表,而子表的元素还可以是子表,。由此,广义表是一个多层次的结构,可以用图形象地表示。P108

 

(2)广义表可为其它表所共享。例如在上述例(4)中,广义表A,B,C为D的子表,则在D中可以不必列出子表的值,而是通过子表的名称来引用。

 

(3)广义表的递归性。

     综上所述,广义表不仅是线性表的推广,也是树的推广。

由表头、表尾的定义可知:任何一个非空广义表其表头可能是原子,也可能是列表,而其表尾必定是列表。

          gethead(B)=e         gettail(B)=(  )

          gethead(D)=A        gettail(D)=(B,C)

 

      由于(B,C)为非空广义表,则可继续分解得到:

         gethead(B,C)=B         gettail(B,C)=(C)

  

    注意广义表( )和( ( ) )不同。前者是长度为0的空表,

对其不能做求表头的和表尾的运算;而后者是长度为1的非空表(只不过该表中唯一的一个元素是空表)。对其可进行分解,得到表头和表尾均为空表( )。

 

广义表的存储结构

由于广义表(a1,a2,a3,…an)中的数据元素可以具有不同的结构,(或是原子,或是广义表),因此,难以用顺序存储结构表示,通常采用链式存储结构,每个数据元素可用一个结点表示。

    由于广义表中有两种数据元素,原子或广义表,因此,需要两种结构的结点:一种是表结点,用以表示列表;一种是原子结点,用以表示原子。 

  若列表不空,则可分解成表头和表尾;反之,一对确定的表头和表尾可唯一确定列表。由此,一个表结点可由三个域组成:标志域、指示表头的指针域和指示表尾的指针域;而原子结点只需两个域:标志域和值域。

1、仅有表结点由三个域组成:

    标志域、指示表头的指针域和指示表尾的指针域;而原子域只需两个域:标志域和值域。

 

头尾链表存储表示

  1. typedef enum {ATOM,LIST } ElemTag;  //ATOM==0:表示原子,LIST==1:表示子表  
  2. typedef struct GLNode {  
  3.     ElemTag  tag;  //公共部分,用以区分原子部分和表结点  
  4.     union {       //原子部分和表结点的联合部分  
  5.       AtomType  atom; //atom是原子结点的值域,AtomType由用户定义  
  6.       struct { struct GLNode *hp, *tp;} ptr;  
  7.              // ptr是表结点的指针域,ptr.hp 和ptr.tp分别指向表头和表尾  
  8.     };  
  9. } *Glist;  //广义表类型  


示例如图:

 

这种存储结构的三个特点:

1。除空表的表头指针为空外,对任何非空列表,其表头指针均指向一个表结点,且该结点中的hp域指示列表表头,tp域指向列表表尾(除非表尾为空,则指针为空,否则必为表结点);

2。容易分清列表中原子和子表所在层次。如在列表D中,原子e和a在同一层次上,而b、c和d在同一层次且比e和a低一层,B和C是同一层的子表;

3。最高层的表结点个数即为列表的长度。

 

2、表结点和原子结点均由三个域组成:标志域、指示表头的指针域和指示表尾的指针域;原子结点的三个域为:标志域、值域和指示表尾的指针域。

 

其类型定义如下:

扩展线性链表存储表示

  1. Typedef enum { ATOM,LIST} ElemTag;                                
  2.     //ATOM==0:表示原子,LIST==1:表示子表  
  3. Typedef struct GLNode {  
  4.     ElemTag    tag;  //公共部分,用以区分原子部分和表结点  
  5.     union {  //原子部分和表结点的联合部分  
  6.         AtomType    atom;  //原子结点的值域  
  7.         struct GLNode  *hp;  //表结点的表头指针  
  8.         };  
  9.         struct GLNode    *tp;    
  10.                 //相当于线性链表的next,指向下一个元素结点  
  11. } *Glist;  //广义表类型Glist 是一种扩展的线性链表  


示例如图:

 

 

广义表基本操作的实现

我们以头尾表示法存储广义表,讨论广义表的有关操作的实现。由于广义表的定义是递归的,因此相应的算法一般也都是递归的。

⒈广义表的取头、取尾

GList Head(GList ls)
{
if ls->tag = = 1
then p = ls->hp;
return p;
}
算法5.6

GList Tail(GList ls)
{
if ls->tag = = 1
then p = ls->tp;
return p;
}
算法5.7

⒉建立广义表的存储结构

int Create(GList *ls, char * S)
{ Glist p; char *sub;
if StrEmpty(S) *ls = NULL;
else {
if (!(*ls = (GList)malloc(sizeof(GLNode)))) return 0;
if (StrLength(S) = = 1) {
(*ls)->tag = 0;
(*ls)->data = S;
}
else {
(*ls)->tag = 1;
p = *ls;
hsub =SubStr(S,2,StrLength(S)-2);
do {
sever(sub,hsub);
Create(&(p->ptr.hp), sub);
q = p;
if (!StrEmpty(sub)){
if (!(p = (GList)malloc(sizeof(GLNode)))) return 0;;
p->tag = 1;
q->ptr.tp = p;
}
}while (!StrEmpty(sub));
q->ptr.tp = NULL;
}
}
return 1;
}
算法5.8

int sever(char *str, char *hstr)
{
int n = StrLength(str);
i= 1; k = 0;
for (i = 1, k = 0; i <= n || k != 0; ++i)
{
ch=SubStr(str,i,1);
if (ch = = '(') ++k;
else if (ch = = ')') --k;
}
if (i <= n)
{
hstr =SubStr(str,1,i-2);
str= SubStr(str,i,n-i+1);
}
else {
StrCopy(hstr,str);
ClearStr(str);
}
}

⒊以表头、表尾建立广义表

int Merge(GList ls1,GList ls2, Glist *ls)
{
if (!(*ls = (GList)malloc(sizeof(GLNode)))) return 0;
*ls->tag = 1;
*ls->hp = ls1;
*ls->tp = ls2;
return 1;
}
算法5.10

4.求广义表的深度

 
int Depth(GList ls)
{
if (!ls)
return 1; /*空表深度为1*/
if (ls->tag = = 0)
return 0; /*单元素深度为0*/
for (max = 0,p = ls; p; p = p->ptr.tp) {
dep = Depth(p->ptr.hp); /*求以p->ptr.hp 尾头指针的子表深度*/
if (dep > max) max = dep;
}
return max+1; /*非空表的深度是各元素的深度的最大值加1*/
}
算法5.11

 

⒌复制广义表

int CopyGList(GList ls1, GList *ls2)
{
if (!ls1) *ls2 = NULL; /*复制空表*/
else {
if (!(*ls2 = (Glist)malloc(sizeof(Glnode)))) return 0; /*建表结点*/
(*ls2)->tag = ls1->tag;
if (ls1->tag = = 0) (*ls2)->data = ls1->data; /*复制单元素*/
else {
CopyGList(&((*ls2)->ptr.hp), ls1->ptr.hp); /*复制广义表ls1->ptr.hp 的一个副本*/
CopyGList(&((*ls2)->ptr.tp) , ls1->ptr.tp); /*复制广义表ls1->ptr.tp 的一个副本*/
}
}
return 1;
}

 

广义表的二叉树实现的形式

输入二叉树的广义表形式,将其转变为二叉树形式,至于怎样输入广义表需要程序规定。

定义包含头文件文件t11.h中

#include"stdio.h"
#include"string.h"
#include"ctype.h"
#include"malloc.h"
#include"stdlib.h"  //atoi(),exit();
#include"io.h"      //eof()
#include"math.h"

#define  TRUE  1
#define  FALSE  0
#define  OK   1
#define  ERROR 0
typedef int Status;
typedef int Boolean;
定义数据结构头文件gercs.h中
#define INIT_STACK 100
#define INIT_STACK_ADD  20
typedef struct node            //  二叉树的数据结构定义
{
    char data;
 struct node *lchild;
 struct node *rchild;
}*Bitree,pBitree;
typedef struct 
{
    char *bottom;
 char *top;
 int stacksize;
}Sqstack;
struct Binode
{
 Bitree lp;
};
struct Sqstack1
{
 Binode *bottom;
 Binode *top;
 int stacksize;
};
typedef struct
{
     Binode *front;
  Binode *rear;
  int queuesize;
}*linkqueue,queue;
  
Sqstack W;
定义包含实现函数功能模块gercs.cpp中
void inittree(Bitree &T)
{
 T=NULL;
}
void initqueue(queue &Q)
{
 Q.front=(Binode*)malloc(INIT_STACK*sizeof(Binode));
 Q.rear=Q.front;
 Q.queuesize=INIT_STACK;
}
void initstack(Sqstack &L)
{
 L.bottom=(char*)malloc(INIT_STACK*sizeof(char));
 if(!L.bottom)
 {
  printf("内存分配失败!!");
  exit(0);
 }
 L.top=L.bottom;
 L.stacksize=INIT_STACK;
 
}
void initstack1(Sqstack1 &L)
{
 L.bottom=(Binode*)malloc(INIT_STACK*sizeof(Binode));
 if(!L.bottom)
 {
  printf("内存分配失败!!");
  exit(0);
 }
 L.top=L.bottom;
 L.stacksize=INIT_STACK;
 
}
void enqueue(queue &Q,Bitree T)
{
 if(Q.rear-Q.front > Q.queuesize)
 {
  Q.front=(Binode*)realloc(Q.front,(Q.queuesize,INIT_STACK_ADD)*sizeof(Binode));
  Q.rear=Q.front+Q.queuesize;
  Q.queuesize+=INIT_STACK_ADD;
 }
 (Q.rear++)->lp=T;
}
void push(Sqstack &L,char ch)
{
    if(L.top-L.bottom > L.stacksize)
 {
  L.bottom=(char*)realloc(L.bottom,(L.stacksize+INIT_STACK_ADD)*sizeof(char));
  L.top=L.bottom+L.stacksize;
  L.stacksize+=INIT_STACK_ADD;
 }
 *(L.top++)=ch;
}
void push1(Sqstack1 &L,Bitree ch)
{
    if(L.top-L.bottom > L.stacksize)
 {
  L.bottom=(Binode*)realloc(L.bottom,(L.stacksize+INIT_STACK_ADD)*sizeof(Binode));
  L.top=L.bottom+L.stacksize;
  L.stacksize+=INIT_STACK_ADD;
 }
 (L.top++)->lp=ch;
}
Status pop(Sqstack &L,char &e)
{
 if(L.bottom == L.top)
  return ERROR;
 else
 {
  e=*(--L.top);
  return OK;
 }
}
Status pop1(Sqstack1 &L,Bitree &e)
{
 if(L.bottom == L.top)
  return ERROR;
 else
 {
  e=(--L.top)->lp;
  return OK;
 }
}
Status emptystack(Sqstack L)
{
 if(L.bottom == L.top)
  return OK;
 else
  return ERROR;
}
Status emptystack1(Sqstack1 L)
{
 if(L.bottom == L.top)
  return OK;
 else
  return ERROR;
}
Sqstack shuru(Sqstack &S)
{
 Sqstack R;
 initstack(R);
 char ch,ch1;
 printf("输入广义表二叉树形式:");
 ch=getchar();
 while(10 != ch)
 {
  if(ch >= 'a' && ch <= 'z')//|| ch >= 'A' && ch <='Z')
  {
   push(S,ch);
   ch1=getchar();
   if(')' == ch1)
   {
    push(S,10);
    push(S,10);
   }
   ch=ch1;
  }
  else
  {
            ch1=getchar();
   if(',' == ch && ',' == ch1)
    push(S,10);
   ch=ch1;
  }
 }
 
 while(!emptystack(S))
 {
  pop(S,ch);
  //printf("%-3c",ch);
  push(R,ch);
 }
 return R;
}
void Createtree(Bitree &T)           // 先序创建二叉树
{
 // printf("进入了!");
 char ch;
 pop(W,ch);
 // printf("ch=%c  ",ch);
 if(ch == 10)                           //  如果是回车键置为空
  T=NULL;
 else
 {
  T=(Bitree)malloc(sizeof(pBitree));
  if(!T)
  {
   printf("分配失败!");
   exit(0);
  }
  T->data=ch;
  Createtree(T->lchild);
  Createtree(T->rchild); 
 }
 // getchar();
}                               // 创建二叉树结束
void xianxu(Bitree T,Sqstack1 &S)   //  先序非递归遍历,采用的是保存右孩子的地址通过栈实现
{
 printf("先序非递归遍历输出:\n");
    if(T)
 {
  if(T->rchild)
   push1(S,T->rchild);    //  专门压入右孩子指针地址
  printf("%-3c",T->data);
  T=T->lchild;            //  更新指针
 }else
  exit(0);
 while(T || !emptystack1(S))     //  循环终止条件
 {
  if(T!= NULL)             //  始终作为一个点存在加以判断,一种情况是一直指向左孩子,另一种就是出栈弹出的右孩子
  {
   printf("%-3c",T->data);    //  输出节点的数据域
   if(T->rchild != NULL)    // 该节点存在右孩子,将右孩子入栈
    push1(S,T->rchild);
   T=T->lchild;       //  更新指针,始终往左走
  }
  else
   pop1(S,T);  
 }
}
void cengci(Bitree T,queue &Q)
{
    if(!T)
 {
  printf("树为空!");
  exit(0);
 }
 Binode *P,*S;
 S=P=Q.front;
 enqueue(Q,T);
 while(P != Q.rear)
 {
  if(P->lp->lchild)
   enqueue(Q,P->lp->lchild);
  if(P->lp->rchild)
   enqueue(Q,P->lp->rchild);
  P++;
 }
 printf("层次遍历为:\n");
 while(S < Q.rear)
 {
  printf("%-3c",S->lp->data);
  S++;
 }
}
 
最后就是定义主函数函数调用了,包含于头文件main_gercs.cpp中
#include"t11.h"
#include"gercs.h"
#include"gercs.cpp"
void main()
{
 // char ch;
 Bitree S;
    queue U;
 Sqstack1 Q;
    initstack(W);
 initstack1(Q);
 inittree(S);
 initqueue(U);
 W=shuru(W);
 /* while(!emptystack(W))
 {
 pop(W,ch);
 printf("%-3c",ch);
 
    }*/
 Createtree(S);
 xianxu(S,Q);
 printf("\n");
 cengci(S,U);
    printf("\n");
 
 
}

  编译运行之后:

相关文章
|
4月前
|
Java 编译器 C++
【Java基础面试一】、为什么Java代码可以实现一次编写、到处运行?
这篇文章解释了Java能够实现“一次编写,到处运行”的原因,主要归功于Java虚拟机(JVM),它能够在不同平台上将Java源代码编译成的字节码转换成对应平台的机器码,实现跨平台运行。
【Java基础面试一】、为什么Java代码可以实现一次编写、到处运行?
|
4月前
|
存储 缓存 Java
面试问Spring循环依赖?今天通过代码调试让你记住
该文章讨论了Spring框架中循环依赖的概念,并通过代码示例帮助读者理解这一概念。
面试问Spring循环依赖?今天通过代码调试让你记住
|
4月前
|
JavaScript 前端开发 程序员
JS小白请看!一招让你的面试成功率大大提高——规范代码
JS小白请看!一招让你的面试成功率大大提高——规范代码
|
7月前
|
缓存 监控 算法
Python性能优化面试:代码级、架构级与系统级优化
【4月更文挑战第19天】本文探讨了Python性能优化面试的重点,包括代码级、架构级和系统级优化。代码级优化涉及时间复杂度、空间复杂度分析,使用内置数据结构和性能分析工具。易错点包括过度优化和滥用全局变量。架构级优化关注异步编程、缓存策略和分布式系统,强调合理利用异步和缓存。系统级优化则涵盖操作系统原理、Python虚拟机优化和服务器调优,需注意监控系统资源和使用编译器加速。面试者应全面理解这些层面,以提高程序性能和面试竞争力。
86 1
Python性能优化面试:代码级、架构级与系统级优化
|
6月前
|
存储 算法 Java
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
|
7月前
|
数据采集 数据挖掘 Python
最全妙不可言。写出优雅的 Python 代码的七条重要技巧,2024年最新被面试官怼了还有戏吗
最全妙不可言。写出优雅的 Python 代码的七条重要技巧,2024年最新被面试官怼了还有戏吗
|
7月前
|
数据采集 XML 程序员
最新用Python做垃圾分类_python垃圾分类代码用key和format,5年经验Python程序员面试27天
最新用Python做垃圾分类_python垃圾分类代码用key和format,5年经验Python程序员面试27天
最新用Python做垃圾分类_python垃圾分类代码用key和format,5年经验Python程序员面试27天
|
7月前
|
数据采集 机器学习/深度学习 人工智能
最新用python代码画爱心,来自程序猿的浪漫~_python画爱心代码(1),2024年最新面试简历模板免费
最新用python代码画爱心,来自程序猿的浪漫~_python画爱心代码(1),2024年最新面试简历模板免费
最新用python代码画爱心,来自程序猿的浪漫~_python画爱心代码(1),2024年最新面试简历模板免费
|
7月前
|
Python
2024年Python最新刷爆全网的动态条形图,原来5行Python代码就能实现!,2024年最新Python面试必问的HashMap
2024年Python最新刷爆全网的动态条形图,原来5行Python代码就能实现!,2024年最新Python面试必问的HashMap
2024年Python最新刷爆全网的动态条形图,原来5行Python代码就能实现!,2024年最新Python面试必问的HashMap
|
7月前
|
前端开发 JavaScript 编译器
【面试题】 你知道怎么调试线上代码吗?
【面试题】 你知道怎么调试线上代码吗?