数据结构(14)广义表

前言

在线性表中,结点存储的是单个元素,而在广义表中,结点可以存储单个元素,也可以存储一个广义表。当存储的是单个元素时,称其为原子;存储的是整个广义表时,称其为子表。

可见广义表是一个递归的概念,因此说广义表是线性表的推广。

当广义表非空时,称其第一个元素为表头,其余均为表尾。

广义表的存储结构

通过广义表的概念可以发现,广义表是很难用顺序存储结构来表示的,通常采用链式存储结构,每个数据元素可以用一个结点来表示。

那么如何设计这个结点的结构呢?我们知道广义表中的结点有两种类型,一种是存储数据的原子结点,另一种则是存储表的表结点。因为链式存储结构的特性,要储存一整个表,实际上只需要记录下该表的表头地址即可,其余部分用类似于next指针就能找到。

可以想到一个结点需要有以下部分:

  1. 指针域,用于保存指向下一个结点的next指针
  2. 数据域,用于存储原子结点的值或表结点的表头地址。这是两种不同类型的值,但是可以用共用体来存储
  3. 由于使用共用体来存储数据,因此需要设置一个是原子节点或表结点的标记(用于区分存储区内是什么类型的数值

img_1
这样设计出来的广义表结构示例如下:

img_2

当然,结点的结构不止一种设计方式,分别设计两个不同的结点也是可行的。

在实际代码的实现中,我们多设计了一个头结点(参考链表中的头结点),只是多使用了一个标记值标识头结点,区别不大。

img_3

广义表的初始化

同链表的初始化一样,实质上就是创建头结点的过程。

//初始化广义表
void InitGenList(GenList &gl){
	//创建头结点
	gl = (GLNode *)malloc(sizeof(GLNode));
	assert(gl != NULL);
	gl->tag = HEAD;
	gl->hp = gl->tp = NULL;
}

广义表的创建

这里的创建指将一个字符串表示的广义表用我们刚才设计的结构进行存储。

由于广义表可以”表中有表“,表内的一个元素可能是原子,也可能是子表,字符串的可能性有很多,如图所示。

img_4

基本思路是:我们读到一个元素,判断它的类型(原子或子表),然后分别处理(直接存储或递归调用创建方法)。注意括号和逗号是不存储的,只存储字符串中的数据。

现在的问题是,如何正确读完一个元素?

我们知道,在广义表中,称其第一个元素为表头,其余均为表尾。那么上面的问题就可以转换为表头和表尾的分离问题:将字符串分成表头和表尾,处理表头;然后将剩下的部分(表尾)继续分离成表头和表尾,处理表头,以此类推。

img_5

因为是字符串,所以还要对括号进行处理,实际上一进入创建方法中,就要把两头的括号给去除。

    //删除最外层括号
    strncpy(sub, str+1, n-2);
    //标志字符串结束
    sub[n-2] = '\0';

下面说明将表头和表尾分离的方法。

表头表尾分离法

刚才说到,一进入创建方法中,就需要把字符串两头的括号去除,因此在分离方法中,我们遇到的字符串是这样的:

img_6

观察可以发现,表头实际上只有三种情况:

  1. 表头为原子,则字符串开头为数字字符
  2. 表头为子表,则字符串开头为 “ ( "符号
  3. 表头为空

当表头为原子时,我们遇上 ” , “ 就可以直接分割,而当表头为子表时,需要寻找到 ” ) “ 符号后才可以分割。

因此,我们需要对首个字符进行判断,并且用标志进行记录:设用k来记录首个字符的状态,若首个字符非括号,则k = 0,此后遇到” , “就停止;若首个字符是“ ( "符号,则k = 1,此时继续遍历,遇到 ” , “ 也不停止;直到遇到 ” ) “ 符号,设置k = 0,此后遇到” , “就停止。

img_7

img_8

	int n = strlen(sub);
	//用于遍历整个字符串
	int i = 0;
	char ch = sub[0];
	//用于标记括号
	int k = 0;

	while(i<n && (ch!=',' || k!= 0)){
		if(ch == '('){
			k ++;
		}else if(ch == ')'){
			k --;
		}

		i++;
		ch = sub[i];
	}

当这个循环结束时,也有不同的情况需要处理

	if(k != 0){
		//内部括号不匹配
		printf("字符串有误!\n");
		return false;
	}else if(i < n){
    //表头表尾正常分离
		sub[i] = '\0';
		strcpy(hsub,sub);
		strcpy(sub,sub+i+1);
	}else{
		//整个表都是表头
		//"(1,2)"  hsub = "(1,2)" sub=""
		strcpy(hsub,sub);
		sub[0] = '\0';
	}

当掌握了表头表尾分离的方法之后,创建的方法也就写出来了

	if(sever(sub,hsub)){
		if(hsub[0] == '('){
			//是子表
			p->tag = CHILDLIST;
			//初始化子表
			InitGenList(p->hp);
			//递归创建
			CreateGenList(p->hp,hsub);
		}else{
			//是原子
			p->tag = ATOM;
			p->atom = atoi(hsub);
		}
	}else{
		//字符串分离有错误
		exit(0);
	}

只展示了比较核心的代码,详情请看全部代码。

求广义表的长度

求广义表的长度实际上很简单,观察可以发现,求广义表的长度也就是求如图所示”第一层“中的结点个数,并不关心子表的内容。我们通过tp指针(类似链表的next指针),依次计数,遍历结束返回结果即可。

img_9

//求广义表长度
int GenListLength(GenList &gl){

	int length = 0;
	GLNode *p = gl->tp;
	while(p != NULL){
		length ++;
		p = p->tp;
	}

	return length;
}

求广义表的深度

求广义表的深度则要复杂一些,观察可以发现,求广义表的深度实际上是在求图中总的层数。仍然需要tp来遍历,不同的是需要遍历每一层(因为每一个结点都可能是子表结点,而子表结点里又可能有子表)。

每当我们找到一个子表结点时,就同样调用求深度的方法,通过函数值返回该子表内的最大深度。(因为每一层可能有不止一个子表结点,同层的子表结点内部可能有不同的深度,因此返回的是“最大”的深度。)

img_10

//求广义表深度
int GenListDepth(GenList &gl){

	if(gl->tp == NULL){
		//空表
		return 1;
	}

	GLNode *p = gl->tp;
	int maxDepth = 0;
	int dep;

	while(p != NULL){
		if(p ->tag == CHILDLIST){
			//是子表,递归调用求深度的方法
			dep = GenListDepth(p->hp);
			if(dep > maxDepth){
				//是目前最大的深度,保存
				maxDepth = dep;
			}
		}

		p = p->tp;
	}
	//返回子表深度+自己这一层
	return maxDepth+1;
}

全部代码

注:在实现“展示广义表”的方法时,由于展示子表也需要递归调用该方法,子表之后需要输出一个 " , " 符号,而很难判断当前输出的是子表还是最后一个结点(最后一个结点则不需要输出“ , ”符号),为了省事也没有去想解决的方法,因此在输出最后的 “ ) ”号后会多输出一个“ , ”符号,自动忽略即可。
本次代码中使用到了引用,实际上属于C++的范畴了,但是一般使用的都是C++的编译器,所以问题也不大。

GenList.h

#pragma once

#include<stdio.h>
#include<string.h>
#include<malloc.h>
#include<stdlib.h>
#include<assert.h>

#define AtomType int
typedef enum {
	HEAD, //头结点
	ATOM, //原子
	CHILDLIST //子表
}ElemTag;

typedef struct GLNode{
	ElemTag tag;
	union{
		AtomType atom;
		struct GLNode *hp;
	};
	struct GLNode *tp;
}GLNode;

typedef GLNode *GenList;

//初始化广义表
void InitGenList(GenList &gl);
//创建广义表
void CreateGenList(GenList &gl, char *str);
//将表头表尾分离
bool sever(char *sub,char *hsub);

//展示广义表
void ShowGenList(GenList &gl);
//判断广义表是否为空
bool GenListEmpty(GenList &gl);
//求广义表长度
int GenListLength(GenList &gl);
//求广义表深度
int GenListDepth(GenList &gl);

GenList.cpp

#include "GenList.h"


//初始化广义表
void InitGenList(GenList &gl){
	//创建头结点
	gl = (GLNode *)malloc(sizeof(GLNode));
	assert(gl != NULL);
	gl->tag = HEAD;
	gl->hp = gl->tp = NULL;
}


//创建广义表 
void CreateGenList(GenList &gl, char *str){
	
	int n = strlen(str);
	//表尾
	char *sub = (char *)malloc(sizeof(char) * (n - 2));
	//表头
	char *hsub = (char *)malloc(sizeof(char) * (n - 2));
	assert(sub != NULL && hsub != NULL);

	//去除掉外部的括号
	strncpy(sub,str+1,n-2);
	sub[n-2] = '\0';

	GLNode *p = gl;
	while(strlen(sub) != 0){
		p = p->tp = (GLNode *)malloc(sizeof(GLNode));
		assert(p != NULL);
		p->hp = p->tp = NULL;


		//分离表头和表尾
		//"1,2,3" -> hsub="1" sub="2,3"
		//"(1,2),3,4 -> hsub="(1,2)" sub="3,4"
		if(sever(sub,hsub)){
			if(hsub[0] == '('){
				//是子表
				p->tag = CHILDLIST;
				//初始化子表
				InitGenList(p->hp);
				//递归创建
				CreateGenList(p->hp,hsub);
			}else{
				//是原子
				p->tag = ATOM;
				p->atom = atoi(hsub);
			}
		}else{
			//字符串分离有错误
			exit(0);
		}

	}
}

//将表头表尾分离
bool sever(char *sub, char *hsub){

	//""
	if(*sub == '\0'){
		hsub[0] = '\0';
		return true;
	}

	int n = strlen(sub);
	//用于遍历整个字符串
	int i = 0;
	char ch = sub[0];
	//用于标记括号
	int k = 0;

	while(i<n && (ch!=',' || k!= 0)){
		if(ch == '('){
			k ++;
		}else if(ch == ')'){
			k --;
		}

		i++;
		ch = sub[i];
	}

	if(k != 0){
		//内部括号不匹配
		printf("字符串有误!\n");
		return false;
	}else if(i < n){
		sub[i] = '\0';
		strcpy(hsub,sub);
		strcpy(sub,sub+i+1);
	}else{
		//整个表都是表头
		//"(1,2)"  hsub = "(1,2)" sub=""
		strcpy(hsub,sub);
		sub[0] = '\0';
	}
	return true;
}


//展示广义表
void ShowGenList(GenList &gl){

	GLNode *p = gl->tp;
	printf("(");
	while(p != NULL){

		if(p->tag == ATOM){
			//原子,直接输出
			printf("%d",p->atom);
			if(p->tp != NULL){
				//后面还有元素,输出一个,
				printf(",");
			}
			p = p->tp;
		}else if(p->tag == CHILDLIST){
			ShowGenList(p->hp);
			p = p->tp;
		}
	}

	printf("),");
}

//判断广义表是否为空
bool GenListEmpty(GenList &gl){
	return gl->tp == NULL;
}

//求广义表长度
int GenListLength(GenList &gl){

	int length = 0;
	GLNode *p = gl->tp;
	while(p != NULL){
		length ++;
		p = p->tp;
	}

	return length;
}

//求广义表深度
int GenListDepth(GenList &gl){

	if(gl->tp == NULL){
		//空表
		return 1;
	}

	GLNode *p = gl->tp;
	int maxDepth = 0;
	int dep;

	while(p != NULL){
		if(p ->tag == CHILDLIST){
			//是子表,递归调用求深度的方法
			dep = GenListDepth(p->hp);
			if(dep > maxDepth){
				//是目前最大的深度,保存
				maxDepth = dep;
			}
		}

		p = p->tp;
	}
	//返回子表深度+自己一层的深度
	return maxDepth+1;
}

Main.cpp

#include "GenList.h"

int main(void){

	GenList gl;

	InitGenList(gl);

	char *ga = "(1,2,3)";
	char *gb = "(1,(2,3))";
	char *gc = "(1,(2,3),4)";
	char *gd = "((1,2),3)";
	char *ge = "((1,2,3))";
	char *gf = "((()))";
	char *gg = "(1,(2,(3,(10,20),4),5),6)";
	char *gh = "(((2),1),3)";

	CreateGenList(gl, gh);
	ShowGenList(gl);
	printf("\n");

	int length = GenListLength(gl);
	printf("length = %d\n",length);

	int depth = GenListDepth(gl);
	printf("depth = %d\n",depth);

	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章