目录
4.5 广义表
4.5.1广义表的定义
广义表是线性表的推广,也称列表。广泛地用于人工智能等领域的表处理语言LISP语言,把广义表作为基本的数据结构,就连程序也表示为一系列的广义表。
广义表一般记作,其中可以是单个元素,也可以是一个广义表。广义表的定义是一个递归的过程,相当于单个元素可以是广义表,这个广义表内的元素又可以有广义表......广义表中的单个元素我们成为原子,广义表中的广义表我们成为子表。习惯上用大写字母表示广义表的名称,用小写字母表示原子。
下面我们列举一些广义表的例子。
1)A=( ),A是一个空表,其长度为0。
2)B=(e),B只有一个原子e,其长度为1。
3)C=(a,(b,c,d)),C的长度为2,两个元素分别为原子a和子表(b,c,d)。
4)D=(A,B,C),D的长度为3,3个元素都是广义表。显然,将子表的元素带入后,有D=(( ),(e),(a,(b,c,d)))。
5)E=(a,(E)),这是一个递归的表,其长度为2。E相当于一个无限的广义表E=(a,(a,(a,...)))。
从上述定义和例子我们可以得出广义表的如下3个结论。
1)广义表的元素可以是子表,子表的元素还可以是子表.......由此,广义表是一个多层次的结构,可以用图像形象地表示。如下图表示的是广义表D,图中以圆圈表示广义表,以方块表示原子。
2)广义表可以为其他广义表所共享。例如在上述例子中,广义表A、B和C为D的子表,则在D中可以不必列出子表的值,而是通过子表的名称来引用。
3)广义表可以是一个递归的表,即广义表也可以是其本身的一个子表。
由于广义表的结构比较复杂,其各种运算的实现也不如线性表简单,其中,最重要的两个运算如下:
1.取表头GetHead(LS):取出的表头为非空表的第一个元素,它可以是一个单原子,也可以是一个子表。
2.取表尾GetTail(LS):取出的表尾为除去表头之外,由其余元素构成的表。即表尾一定是一个广义表。
例如:
GetHead(B)=e, GetTail(B)=( ),
GetHead(D)=A, GetTail(D)=(B,C),
由于(B,C)为非空广义表,则可继续分解得到:
GetHead(B,C)=B, GetTail(B,C)=(C),
值得注意的是,广义表( )和(( ))不同,前者为空表,长度为0;后者长度为1,可分解得到其表头、表尾均为空表( )。
4.5.2 广义表的存储结构
由于广义表中的数据可以有不同的结构(或是原子,或是列表),因此难以用顺序存储结构表示,通常采用链式存储结构。常用的链式存储结构有两种,头尾链表的存储结构和扩展线性链表的存储结构。
1.头尾链表的存储结构
由于广义表中的数据元素可能为原子或广义表,由此需要两种结构的结点:一种是表结点,用以表示广义表;一种是原子结点,用以表示原子。从上面得知:若广义表不为空,则可分解成表头和表尾,因此,一对确定的表头和表尾可唯一确定广义表。一个表结点可由3个域组成:标志域、指示表头的指针域和指示表尾的指针域。而原子结点只需两个域:标志域和值域。如下图所示,其中tag是标志域,值为1时表明结点是子表,值为0时表明结点是原子。
下面我们将设计表结构的代码
【代码分析】
上面我们说到原子结点和表结点都需要一个标志域用来判断,因此我们在这里可以用到c语言中的枚举类型。然后对于广义表的两种元素:原子和子表,因为是头尾链表的存储结构,它们在表结构中是共同占有一段内存,由此,在设计其结点代码时我们可以用到c语言中的共用体结构。相当于我们每定义一个表结构,这个表里含有一个原子和子表,而子表里又含有一个原子和子表。前面几节我们已经熟悉了“结点结构”的大致内容,下面我们给出其具体代码。
【代码实现】
typedef enum{ATOM,LIST} ElemTag; //ATOM=0:原子;LIST=1:子表
typedef struct GLNode
{
ElemTag tag;
union
{
AtomType atom; //atom是原子结点的值域
struct
{
struct GLNode *hp; //指向表头的指针
struct GLNode *tp; //指向表尾的指针
}ptr; //ptr是表结点的指针域
};
} *GList;
由于比较难以理解,下面我们用上述用到的广义表图形表示来进一步说明广义表的头尾链表的存储结构。
1)除空表的表头指针为空外,对任何非空广义表,其表头指针均指向一个表结点,且该结点中的hp域指示广义表表头(或为原子结点,或为表头结点),tp域指向广义表表尾(除非表尾为空,则指针为空,否则必为表结点)。
2)容易分清列表中原子和子表所在的层次。如在广义表D中,原子a和e在同一层次上,而b、c和d在同一层次且比a和e低一层,B和C是同一层的子表。
3)最高层的表结点个数即为广义表的长度。
4)每个层次的每个结点一定属于一个广义表。
读者在理解上图时,一定要先理清广义表的结构,要注意此结构用的是头尾链表的存储结构。
2.扩展线性链表的存储结构
在这种结构中,无论是原子结点还是表结点均由三个域组成,其结点结构如下图所示:
接下来我们再用前面提到的广义表的图形表示来进一步解释该存储结构。
下面我们来总结广义表两种存储结构的不同:
首先我们要知道一个广义表可有一对确定的表头和表尾唯一确定,也就是说一个广义表的表头可以是原子也可以是一个子表,而表尾一定是一个子表。从而在头尾链表的存储结构中,原子结点一定是属于某一个广义表的,且该存储结构中的原子结点没有指针域,只能作为一个元素独立存在,且一定属于某一个广义表。而在扩展性链表的存储结构中,给原子结点增加了一个指针域,因此不再是每个原子结点独立属于一个表结点,在这里我们可以理解为同一层次的多个结点可以共同属于某一个表结点,因此该层次的多个结点可以通过指针域连接起来,所以就不再需要单独连接一个表结点。
对于两种存储结构占用内存的性能是相差无几的,需在特殊形况特殊对待。例如,当同一层次的原子结点较多的时候,我们使用后者就会节省存储空间。