以下内容主要参考了严蔚敏版的数据结构教材。
广义表是线性表的推广,现在假设有广义表
L
S
=
{
a
1
,
a
2
,
.
.
.
,
a
n
}
LS=\{a_1,a_2,...,a_n\}
LS={a1,a2,...,an},
a
i
(
i
=
1
,
2
,
.
.
.
,
n
)
a_i(i=1,2,...,n)
ai(i=1,2,...,n)是表中的元素,与线性表不同的是,线性表中的每一个数据元素都属于同一数据对象(或者说类),比如一个英文字符或者是同一类型的一个结构体变量。但是对于广义表中的元素
a
i
(
i
=
1
,
2
,
.
.
.
,
n
)
a_i(i=1,2,...,n)
ai(i=1,2,...,n),它既可以是同一数据对象(或者说类)
[
原
子
元
素
]
[原子元素]
[原子元素]也可以是由该数据对象(或者说类)组成的广义表
[
子
表
元
素
]
[子表元素]
[子表元素],这样来看的话广义表的定义是一个递归的定义。当广义表非空时,称其第一个元素为表头,称其剩余元素组成的表为表尾。表头可能为
[
原
子
元
素
]
[原子元素]
[原子元素]也可能是
[
子
表
元
素
]
[子表元素]
[子表元素],但是表尾一定是
[
子
表
元
素
]
[子表元素]
[子表元素]。
现在举例说明如下(数据对象为英文字符):
- A = ( ) A=() A=(),A是一个空表。
- B = ( e ) B=(e) B=(e),B是一个包含一个原子元素的广义表。
- C = ( a , ( b , c , d ) ) C=(a,(b,c,d)) C=(a,(b,c,d)),C是一个包含一个原子元素元素和一个子表元素 ( b , c , d ) (b,c,d) (b,c,d)的广义表。
- D = ( A , B , C ) D=(A,B,C) D=(A,B,C),D是一个包含三个子表元素的广义表。
- E = ( a , E ) E=(a,E) E=(a,E),E是一个包含一个原子元素和一个子表元素的广义表。子表元素就是该广义表本身,因此这是一个递归的表。
由于广义表中的元素既可以是 [ 原 子 元 素 ] [原子元素] [原子元素]也可以是 [ 子 表 元 素 ] [子表元素] [子表元素],因此很难用顺序存储结构,因此通常采用链式存储结构。以下介绍了广义表的两种相近的链式存储结构:
- 结构一,该结构中有两种节点,表节点和原子节点,如图1所示。表节点用来表示整个广义表或者广义表中的一个 [ 子 表 元 素 ] [子表元素] [子表元素]。它有三个数据域, i s A t o m isAtom isAtom(原子节点也有该数据域)用来表示该节点是否是原子节点或表节点,指针域 h e a d head head指向该广义表的表头,指针域 t a i l tail tail指向该广义表的表尾。原子节点用来表示广义表中的 [ 原 子 元 素 ] [原子元素] [原子元素],除了 i s A t o m isAtom isAtom数据域外, A t o m T y p e AtomType AtomType存储实际的原子类型的记录。图2给出了上面提到的几个广义表例子的该链式表示。
- 结构二,该结构中也有两种节点,表节点和原子节点,如图3所示。表节点用来表示整个广义表或者广义表中的一个 [ 子 表 元 素 ] [子表元素] [子表元素]。它有三个数据域, i s A t o m isAtom isAtom(原子节点也有该数据域)用来表示该节点是否是原子节点或表节点,指针域 h e a d head head指向该广义表的表头,指针域 n e x t next next指向该广义表的下一个元素。原子节点用来表示广义表中的 [ 原 子 元 素 ] [原子元素] [原子元素],除了 i s A t o m isAtom isAtom数据域外, A t o m T y p e AtomType AtomType存储实际的原子类型的记录,指针域 n e x t next next指向该广义表的下一个元素。。图4给出了上面提到的几个广义表例子的该链式表示。
从图二和图四可以看出对于空表,两种链式表示不一样,单这是课本上这么画的,我个人觉得,两种结构的空表既可以表示为图2的形式也可以表示为图4的形式。




class GenListNode
{
public:
GenListNode(bool atom = false, char data = '\0', GenListNode* h = nullptr, GenListNode* t = nullptr)
{
isAtom = atom;
u.character = data;
u.ptr.head=h;
u.ptr.tail = t;
}
friend class GenList;
private:
bool isAtom;
union
{
char character;
struct
{
GenListNode* head;
GenListNode* tail;
}ptr;
}u;
};
class GenList
{
public:
GenList(GenListNode* h)
{
front=h;
}
private:
GenListNode* front;
};
class GenListNode
{
public:
GenListNode(bool atom=false,char data='\0',GenListNode* sub=nullptr)
{
isAtom=atom;
u.character =data;
u.subListHead =sub;
}
friend class GenList;
private:
bool isAtom;
union
{
char character;
GenListNode* subListHead;
}u;
GenListNode* nextGenListNode;
};
class GenList
{
public:
GenList(GenListNode* h)
{
front = h;
}
private:
GenListNode* front;
};
----------------------------分割线----------------------------------------------
现在我们来用广义表来表示
m
m
m元多项式。一个
m
m
m元多项式的每一项最多有m个变元和一个系数,如果对
m
m
m元多项式的每一项不管其实际变元个数的多少都分配
m
+
1
m+1
m+1的数据项来存储一个系数和
m
m
m个变元的指数,当多项式中每一项的实际变元个数偏少时会造成空间的极大浪费。如果按照
m
m
m元多项式的每一项的实际变元个数来分配空间的话会造成算法处理以及存储的不便。因此
m
m
m元多项式不适用与顺序表的存储结构。
现在有一个三元多项式
P
(
x
,
y
,
z
)
=
x
10
y
3
z
2
+
2
x
6
y
3
z
2
+
3
x
5
y
2
z
2
+
x
4
y
4
z
+
6
x
3
y
4
z
+
2
y
z
+
15
=
(
(
x
10
+
2
x
6
)
y
3
+
3
x
5
y
2
)
z
2
+
(
(
x
4
+
6
x
3
)
y
4
+
2
y
)
z
+
15
P(x,y,z)=x^{10}y^3z^2+2x^6y^3z^2+3x^5y^2z^2+x^4y^4z+6x^3y^4z+2yz+15=((x^{10}+2x^6)y^3+3x^5y^2)z^2+((x^4+6x^3)y^4+2y)z+15
P(x,y,z)=x10y3z2+2x6y3z2+3x5y2z2+x4y4z+6x3y4z+2yz+15=((x10+2x6)y3+3x5y2)z2+((x4+6x3)y4+2y)z+15。
我们令:
- A ( x , y ) = ( x 10 + 2 x 6 ) y 3 + 3 x 5 y 2 A(x,y)=(x^{10}+2x^6)y^3+3x^5y^2 A(x,y)=(x10+2x6)y3+3x5y2, B ( x , y ) = ( x 4 + 6 x 3 ) y 4 + 2 y B(x,y)=(x^4+6x^3)y^4+2y B(x,y)=(x4+6x3)y4+2y,则有:
- P ( x , y , z ) = A ( x , y ) × z 2 + B ( x , y ) × z + 15 P(x,y,z)=A(x,y)\times z^2+B(x,y)\times z+15 P(x,y,z)=A(x,y)×z2+B(x,y)×z+15
我们令:
- C ( x ) = x 10 + 2 x 6 C(x)=x^{10}+2x^6 C(x)=x10+2x6, D ( x ) = 3 x 5 D(x)=3x^5 D(x)=3x5, E ( x ) = x 4 + 6 x 3 E(x)=x^4+6x^3 E(x)=x4+6x3,则有:
- A ( x , y ) = C ( x ) y 3 + D ( x ) y 2 A(x,y)=C(x)y^3+D(x)y^2 A(x,y)=C(x)y3+D(x)y2, B ( x , y ) = E ( x ) y 4 + 2 y B(x,y)=E(x)y^4+2y B(x,y)=E(x)y4+2y
从以上内容我们可以看出3元多项式
P
(
x
,
y
,
z
)
P(x,y,z)
P(x,y,z)可以分解为变元
z
z
z的多项式,其中
A
(
x
,
y
)
A(x,y)
A(x,y)和
B
(
x
,
y
)
B(x,y)
B(x,y)都是二元多项式。
A
(
x
,
y
)
A(x,y)
A(x,y)和
B
(
x
,
y
)
B(x,y)
B(x,y)可以分解为变元
y
y
y的多项式,其中
C
(
x
)
、
D
(
x
)
、
E
(
x
)
C(x)、D(x)、E(x)
C(x)、D(x)、E(x)都是一元多项式。因此任何一个
m
m
m元多项式都可以分解为
m
m
m个变元中的一个变元的多项式,这个多项式的系数可能为实数,也可能是剩下的
m
−
1
m-1
m−1个变元的
m
−
1
m-1
m−1元多项式。递归的这些作为系数的
m
−
1
m-1
m−1元多项式又可以分解为
m
−
1
m-1
m−1个变元中的一个变元的多项式,这个多项式的系数可能为实数,也可能是剩下的
m
−
2
m-2
m−2个变元的
m
−
2
m-2
m−2元多项式。递归的分解直到作为系数的多项式为一元多项式为止。
基于
m
m
m元多项式的这种分解特性,可以采用类似广义表的第二中存储结构的存储结构来表示
m
m
m元多项式,如图5所示。

表结点用来表示系数为多项式的项,原子结点用来表示系数为实数的项。表结点的 h e a d head head域指向系数多项式的第一项,原子结点的 c o e f f i c i e n t s coefficients coefficients域表示系数为实数的项的系数。 e x p o n e n t exponent exponent域表示多项式的项的指数, n e x t next next域指向多项式的下一项。除了与广义表的第二中存储结构的以上区别外,具体在构建 m m m元多项式的链式存储结构时,还有以下两种区别。
- 对于初始的待表示的多项式,比如三元多项式 P ( x , y , z ) P(x,y,z) P(x,y,z)可以先将其看做是某个多项式(只含有一项)的某一项的系数多项式,该广义表的头指针指向的表结点就代表该系数多项式,但是因为初始多项式并不是某个多项式的一项的系数多项式,其指数域为初始多项式中变元的个数,
- 在表示每一层多项式的表中,会增加一个表头结点,其指数域为该层多项式的变元的数组索引,第三个数据域没有含义。
三元多项式 P ( x , y , z ) P(x,y,z) P(x,y,z)的广义表的链表结构如图6所示。

class MPNode
{
public:
MPNode(bool atom = false, int exp=0,int coe = 0, MPNode* sub = nullptr)
{
isAtom = atom;
exponent = exp;
u.coefficients = coe;
nextMPNode = sub;
}
friend class MPolynomial;
private:
bool isAtom;
int exponent;
union
{
int coefficients;
MPNode* subListHead;
}u;
MPNode* nextMPNode;
};
class MPolynomial
{
public:
MPolynomial(MPNode* h=nullptr)
{
front = h;
}
private:
MPNode* front;
};
----------------------------分割线----------------------------------------
广义表的递归算法:
- 求广义表的深度:广义表的深度定义为广义表中括弧的重数,例如广义表
G
L
=
(
(
)
,
(
e
)
,
(
a
,
(
b
,
c
,
d
)
)
)
GL=((),(e),(a,(b,c,d)))
GL=((),(e),(a,(b,c,d)))外向里有三重括弧,因此该广义表的深度为3(图7)。这里在求广义表的深度的时候使用的是广义表的第一种存储结构。求广义表的深度的算法为递归算法:
- 递归算法的终结状态为遇到原子节点或者是空表,原子节点的深度为0,空表的深度为1。
- 递归算法迭代访问同一层次的每一个节点并对 h e a d head head指针递归调用求得每一个字表的深度,它们中的值的最大值加一就是当前广义表的深度。
- 复制广义表:复制广义表的算法也是递归算法。这里在复制广义表的时候使用的也是广义表的第一种存储结构。(广义表
G
L
=
(
a
,
(
b
,
c
,
d
)
)
GL=(a,(b,c,d))
GL=(a,(b,c,d))的递归调用过程如图8所示)第一种存储结构将广义表看做表头和表尾,在复制的时候:
- 首先复制当前节点的原子表节点标志位。
- 然后分别递归复制表节点的表头和表尾,直到遇到空节点或原子节点的时候终止递归调用。
class GenListNode
{
public:
GenListNode(bool atom = false, char data = '\0', GenListNode* h = nullptr, GenListNode* t = nullptr)
{
isAtom = atom;
u.character = data;
u.ptr.head = h;
u.ptr.tail = t;
}
GenListNode* getHead()
{
return u.ptr.head;
}
GenListNode* getTail()
{
return u.ptr.tail;
}
void setFlag(bool value)
{
isAtom = value;
}
void setAtom(char value)
{
u.character = value;
}
void setHead(GenListNode* value)
{
u.ptr.head = value;
}
void setTail(GenListNode* value)
{
u.ptr.tail = value;
}
void copyNode(GenListNode* &ptr)
{
ptr->setFlag(isAtom);
if (isAtom)
{
ptr->setAtom(u.character);
}
else
{
if (u.ptr.head != nullptr)
{
GenListNode* temp = new GenListNode();
u.ptr.head->copyNode(temp);
ptr->setHead(temp);
}
else
{
ptr->setHead(nullptr);
}
if (u.ptr.tail != nullptr)
{
GenListNode* temp = new GenListNode();
u.ptr.tail->copyNode(temp);
ptr->setTail(temp);
}
else
{
ptr->setTail(nullptr);
}
}
return ;
}
int depth()
{
if (isAtom)
{
return 0;
}
int max = 0;
int temp = 0;
if (u.ptr.head!=nullptr)
{
max = u.ptr.head->depth();
}
GenListNode* ptr= u.ptr.tail;
for (;ptr;ptr=ptr->getTail())
{
if (ptr->getHead()==nullptr)
{
temp = 1;
}
else
{
temp = ptr->getHead()->depth();
}
if (temp>max)
{
max = temp;
}
}
return max + 1;
}
private:
bool isAtom;
union
{
char character;
struct
{
GenListNode* head;
GenListNode* tail;
}ptr;
}u;
};
class GenList
{
public:
GenList(GenListNode* h=nullptr)
{
front = h;
}
void setFront(GenListNode* value)
{
front = value;
}
int depth()
{
if (front== nullptr)
{
return 1;
}
else
{
return front->depth();
}
}
int copyGList(GenList &newGL)
{
if (front == nullptr)
{
return 1;
}
else
{
GenListNode* temp = new GenListNode();
front->copyNode(temp);
newGL.setFront(temp);
}
}
private:
GenListNode* front;
};
--------------------------------------------------------------------------------------------------
下面要说的是广义表的创建。这里采用的也是以上介绍的第一种存储结构并且假设原子类型为英文字符。那么一般的广义表将如
G
L
=
(
a
,
(
b
,
c
,
d
)
)
GL=(a,(b,c,d))
GL=(a,(b,c,d))这样的形式,该广义表写成字符串的形式就是"(a,(b,c,d))"。创建广义表的函数原型为
v
o
i
d
c
r
e
a
t
e
G
L
i
s
t
(
G
e
n
L
i
s
t
N
o
d
e
∗
&
f
r
o
n
t
,
s
t
r
i
n
g
L
S
t
r
)
void \ createGList(GenListNode* \ \& \ front,string\ LStr)
void createGList(GenListNode∗ & front,string LStr),其中参数
L
S
t
r
LStr
LStr就是广义表的字符串形式,参数
f
r
o
n
t
front
front可以看成是广义表的头指针。该函数的作用就是根据广义表的字符串表示来构建其链表结构。
函数
i
n
t
s
e
v
e
r
(
s
t
r
i
n
g
&
s
t
r
,
s
t
r
i
n
g
&
h
s
t
r
)
int\ sever(string\ \& str,string\ \&hstr)
int sever(string &str,string &hstr)为辅助函数。它的作用是取出字符串
s
t
r
str
str中第一个逗号前的所有字符构成的字符串并赋值给
h
s
t
r
hstr
hstr并将剩下的除第一个逗号外的所有字符组成的字符串赋值给字符串
s
t
r
str
str。
这里的算法为递归算法且与上面求广义表的深度的算法的递归过程类似(把广义表看成是含有n个并行的同一级别的子表,对n个子表进行递归)。广义表
G
L
=
(
a
,
(
b
,
c
,
d
)
)
GL=(a,(b,c,d))
GL=(a,(b,c,d))的递归建表过程如图9所示。
int sever(string & str,string &hstr)
{
int len = str.length();
string subString;
int i = 0;
int k = 0;
do
{
subString = str.substr(i,1);
if (subString =="(")
{
++k;
}
else if (subString == ")")
{
--k;
}
++i;
} while ((i < len) && ((subString != ",") || (k != 0)));
cout << "i=" << i << endl;
if (i < len)
{
hstr = str.substr(0,i-1);
str = str.substr(i,len-i);
}
else
{
hstr = str;
str = "";
}
return 1;
}
void createGList(GenListNode* &front,string LStr)
{
GenListNode* ptr = nullptr;
GenListNode* q = nullptr;
GenListNode* temp = nullptr;
string subStr = "";
string hStr = "";
if (LStr=="")
{
front = nullptr;
}
else
{
front =new GenListNode;
if (LStr.length==1)
{
front->setFlag(true);
front->setAtom(LStr[0]);
}
else
{
front->setFlag(false);
ptr = front;
subStr = LStr.substr(1,LStr.length()-2);
do
{
sever(subStr, hStr);
createGList(temp,hStr);
ptr->setHead(temp);
temp = nullptr;
q = ptr;
if (!subStr.empty())
{
ptr = new GenListNode;
ptr->setFlag(false);
q->setTail(ptr);
}
} while (!subStr.empty());
q->setTail(nullptr);
}
}
return;
}
//简单测试程序
int main()
{
string LStr = "(a,(b,(c,d)))";
GenListNode* front = nullptr;
GenList GL;
GenList GLCopy;
createGList(front, LStr);
GL.setFront(front);
GL.copyGList(GLCopy);
cout<<GL.depth()<<endl;
cout << GLCopy.depth();
}