數據結構(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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章