數據結構與算法(五)—— 廣義表

        廣義表是線性表的推廣,又稱列表。線性表的元素僅限於原子項,即每個數據元素只能是一個數或一個記錄,如果放鬆對線性表元素的這種限制,允許它們自身具有結構,由此就產生了廣義表的概念。

一、廣義表的定義

        廣義表是n(n>=0)個元素的有限序列,其中每個元素是原子項或者是一個廣義表,通常記作GL=(a1,a2,a3,...,an)。GL是廣義表的名字,n是它的長度。爲了區分原子項和廣義表,在書寫上習慣用大寫字母表示廣義表,用小寫字母來表示原子。通常用圓括號將廣義表括起來,用逗號分隔其中的元素。當廣義表非空時,稱第一個元素a1是GL的表頭(head),其他元素組成的表稱爲GL的表尾(tail)。

下面列舉一些廣義表的例子:

(1) A=() —— A是一個空表,其長度爲零;

(2) B=(a) —— B是一個只有一個原子的廣義表,其長度爲1;

(3) C=(a,(b,c)) —— C是一個長度爲2的廣義表,第一個元素是原子,第二個元素是子表;

(4) D=(A,B,C) = ((),(a),(a,(b,c))) —— D是一個長度爲3的廣義表,其中三個元素均爲子表;

(5) E=(C,d) = ((a,(b,c)),d) —— E是一個長度爲2的廣義表,第一個元素是子表,第二個元素是原子;

(6) F=(e,F) = (e,(e,(e,...))) —— F是一個遞歸的表,它的長度爲2,第一個元素是原子,第二個元素是表自身,展開後它是一個無限的廣義表。

        一個表展開後所含括號的層數稱爲廣義表的深度。例如,表A、B、C、D、E的深度分別爲1、1、2、3、3,而表F的深度爲無窮大。 

二、廣義表的基本運算

對上面舉例的廣義表進行運算:

(1) 空表A沒有表頭也沒有表尾,length(A) = 0,depth(A) = 1

(2) head(B) = a,tail(B) = (),length(B) = 1,depth(B) = 1

(3) head(C) = a,tail(C) = ((b,c)),length(C) = 2,depth(C) = 2

(4) head(D) = A,tail(D) = (B,C),length(D) = 3,depth(D) = 3

(5) head(E) = C,tail(E) = (d),length(E) = 2,depth(E) = 3

(6) head(F) = e,tail(F) = (F),length(F) = 2,depth(F) = ∞

三、廣義表的存儲結構

        由於廣義表中的元素本身又可以具有結構,是一種帶有層次的非線性結構,因此難以用順序存儲結構表示,通常採用鏈式存儲結構,每個元素可用一個結點表示。每個結點由三個域構成,其中tag是一個標誌位,用來區分當前結點是原子還是子表,當tag爲1時,該結點是子表,第二個域爲slink,用以存放子表的地址;當tag爲0時,該結點是原子結點,第二個域爲data,用以存放元素值。next域是用來存放與本元素同一層的下一個元素結點的地址,當該元素爲最後一個元素時,next的值爲NULL。

廣義表結點的代碼結構描述如下:

//定義廣義表的數據結構
typedef struct GList{
	NodeTag tag; //用以區分是原子結點還是子表結點
	union{
		DataType data; //用以存放原子結點值,其類型由用戶自定義
		GList *slink; //指向子表的指針
	};
	GList *next; //指向下一個表結點
} *GListPtr;

四、廣義表基本運算的實現

1、建立廣義表的存儲結構

        基本思想:在廣義表表達式中,遇到"("時遞歸構造子表,否則構造原子結點;遇到逗號時遞歸構造後續廣義表,直到表達式字符串輸入結束。

代碼如下:

GListPtr GreateGList(GListPtr gl){
	char c;
	scanf("%c", &c);
	if(c != ' '){
		gl = (GListPtr)malloc(sizeof(GListPtr));
		if(c == '('){
			gl->tag = list;
			gl->slink = GreateGList(gl->slink); //遞歸構造子表結點
		}else{
			gl->tag = atom; //構造原子結點
			gl->data = c;
		}
	}else{
		gl = NULL;
	}
	scanf("%c", &c);
	if(gl != NULL){
		if(c == ','){
			gl->next = GreateGList(gl->next); //構造後續廣義表
		}else{
			gl->next = NULL; //遇到其他符號,如")"或";"時,無後續表
		}
	}
	return gl;
}

2、輸出廣義表

        基本思想:若遇到tag=1的結點,則是一個子表的開始,先打印輸出一個"("號,如果該子表爲空,則輸出一個空格,否則遞歸調用輸出該子表,子表打印輸出完後,再打印一個")"號;若遇到tag=0的結點,則直接輸出其數據域的值。若還有數據元素,則遞歸調用打印後續每個元素,直到遇到next域爲NULL。

代碼如下:

void PrintGList(GListPtr gl){
	if(gl != NULL){
		if(gl->tag == list){
			printf("(");
			if(gl->slink == NULL){
				printf("");
			}else{
				PrintGList(gl->slink); //遞歸調用輸出子表
			}
		}else{
			printf("%c", gl->data); //輸出結點數據域值
		}

		if(gl->tag == list){
			printf(")");
		}

		if(gl->next != NULL){
			printf(",");
			PrintGList(gl->next); //遞歸調用輸出下一個節點
		}
	}
}

3、廣義表的查找

        基本思想是:當tag=0時爲原子結點,如果是要找的結點,則查找成功;否則,若還有後續元素,則遞歸調用本過程查找後續元素,直到next域爲NULL。若遇到tag=1的結點,則遞歸調用本過程在該子表中查找,若還有後續元素,則遞歸調用本過程查找後續每個元素,直到next域爲NULL。

代碼如下:

int FindGList(GListPtr gl,DataType t){
	int mark = 0;
	if(gl != NULL){
		if(gl->tag == 0 && gl->data == t){
			mark = 1;
		}else {
			if(gl->tag == 1){
				mark = FindGList(gl->slink,t);
			}else{
				mark = FindGList(gl->next,t);
			}
		}
	}
	return mark; //若查找成功返回1
}

 

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