1. 程式人生 > 其它 >【資料結構】廣義表

【資料結構】廣義表

廣義表

參考:

廣義表的定義

線性表 線性表指的是n≥0個元素a1, a2, a3...的有序數列,並且線性表的元素具有原子性,即結構上是不可分割的一個整體。

廣義表(Generalized list,又稱列表) 廣義表則是線性表的一種擴充套件延伸。相對於線性表,廣義表最大的特點在於其元素既可以是原子項,也可以是另一個有不定數量的元素組成的表(廣義表)。 不難看出從廣義表的定義是遞迴的。廣義表是線性表的遞迴資料結構。

\[\text{LS}=(a_1,a_2,...a_n) \]
  • 通常用圓括號將廣義表括起來,用逗號分隔其中的元素。
  • 為了區別原子和廣義表,書寫時用大寫字母表示廣義表,用小寫字母表示原子。
  • 當 LS 非空時,稱第一個元素 \(a_1\) 為 LS 的表頭(head),其餘元素組成的表 \((a_2,a_3,...,a_n)\) 為表尾(tail)

A=( ):A是一個空表,其長度為0。

B=(b, c):B是一個長度為 2 的列表。

C=(a, (d, e, f)):C是一個長度為 2 的列表,其中第一個元素是原子 a,第二個元素是子表(d, e, f)。

D=(A, B, C):D是一個長度為 3 的列表,其中 3 個元素都是子表。

E=(a, E):E是一個長度為 2 的列表,它是一個遞迴表。

廣義表的圖形表示

廣義表的儲存結構

廣義表的儲存採用連結儲存結構

第一種儲存結構

廣義表的第一種結點結構
  • tag 標記位用於區分此節點是原子還是子表,通常原子的 tag 值為 0,子表的 tag 值為 1
  • 表結點中的 hp表頭指標tp表尾指標
    • 對於原子結點,把它看做一個子表,表頭指標 hp 指向該原子結點,表尾指標指向該原子結點後面的元素組成的子表。

因此對於廣義表 \((a,(b,c,d))\) ,儲存結構如圖所示:

  • 除非 C 是一個空表,指標 C 的值為 NULL,否則指標 C 指向的一定是 tag 值為 1 的子表結點。

廣義表的結點結構:

using Data = char;
struct GLNode
{
	/// 用於區分表結點是原子元素還是子表的標誌位
	/// =0 是原子結點;=1 是表結點
	int tag;
	union {
		Data data;///< 可能是原子結點

		/// 可能是子表
		struct
		{
			GLNode* hp;///< 指向表頭的指標域
			GLNode* tp;///< 指向表尾的指標域
		}ptr;
	};
};
  • 由於同一時間結點不是原子結點,就是表結點,因此,使用了共用體 union

第二種儲存結構

廣義表的另一套結點結構

對於廣義表 \((a,(b,c,d))\) ,儲存結構如圖所示:

廣義表的資料型別

廣義表的資料型別:

/// 廣義表
class GList
{
private:
	GLNode* first;///< 指向廣義表第一元素的指標

	/// 求輸入字串的表頭和表尾
	/// @param	head	指向字串的指標的引用
	/// @param	tail	指向字串的指標的引用
	static void getHandT(const char src[], char*& head, char*& tail);

	/// 銷燬廣義表的遞迴演算法
	/// @param	pNode	指向要銷燬的廣義表的指標的引用
	static void DeleteAll(GLNode *&pNode);

public:
	/// 拷貝廣義表的遞迴演算法
	/// @param	tar	目標廣義表指標的引用
	/// @param	src	源廣義表指標
	static void copy(GLNode *&tar, const GLNode* src);

	/// 由字串建立廣義表的遞迴演算法
	/// @param	str	輸入的字串
	/// @return	返回由字串建立的廣義表
	static GLNode* CreatGList(const char str[]);

	/// 輸出廣義表的遞迴演算法
	/// @param	要輸出的廣義表的頭指標
	static void PrintGList(GLNode* p);

	/// 求廣義表的深度的遞迴演算法
	/// @param	返回廣義表的深度
	static int GListDepth(GLNode* p);

public:

	/// 預設構造
	GList():first(nullptr) {}

	/// 建構函式
	/// 使用 GLNode* 型別的廣義表構造廣義表
	/// @param	src	源廣義表指標
	GList(const GLNode* src);

	/// 拷貝構造
	/// @param	src	源廣義表指標
	GList(const GList& src);

	/// 建構函式
	/// 使用字串構造廣義表
	/// @param	str	廣義表字符串形式
	GList(const char str[]);

	/// 輸出廣義表
	void Print(void) const
	{
		GList::PrintGList(first);
	}

	/// 清空廣義表
	void Clear(void) {
		GList::DeleteAll(first);
	}

	~GList() {
		GList::DeleteAll(first);
	}

	/// 求長度
	/// @param	返回廣義表的長度
	int Length() const;

	/// 求深度
	/// @return	返回廣義表的深度
	int Depth() const
	{
		return GList::GListDepth(first);
	}

	/// 在尾部插入一個結點
	/// @param	node	要插入的結點
	void Push_Back(GLNode* node);

	/// 在尾部插入一個表結點
	/// @param	glist	要插入的表結點
	void Push_Back(const GList& glist);

	/// 插入結點
	/// 頭插法插入節點
	/// @param	i	在第 i 個元素前面插入(i 從 1 開始)
	/// @param	node	要插入的結點
	/// @return	插入成功返回 true,插入失敗返回 false
	bool Insert(const int& i, GLNode* node);

	GLNode* GetHead() const;		///< 取廣義表表頭
	GLNode* GetTail() const;		///< 取廣義表表尾

	GLNode* GetFirst() const;		///< 取廣義表的第一個元素的子表結點
	GLNode* GetLast() const;		///< 取廣義表的最後一個元素的子表結點
	GLNode* GetElement(const int& i) const;	///< 取廣義表的第 i 個元素的子表結點

};

廣義表的重要運算

以第一種儲存結構為例。

1. 拷貝廣義表

  • 由於要修改指標的值,應採用指標傳遞(使用二級指標)或引用傳遞。這裡使用引用傳遞。
/// 拷貝廣義表的遞迴演算法
/// @param	tar	目標廣義表指標的引用
/// @param	src	源廣義表指標
static void copy(GLNode *&tar, const GLNode* src);

/// 拷貝廣義表的遞迴演算法
/// 如果是原子結點,拷貝為原子結點;如果是表結點,拷貝為表結點
void GList::copy(GLNode *&tar, const GLNode* src)
{
	// 如果源為空列表,那麼目標也為空列表
	if (src == nullptr)
	{
		tar = nullptr;
	}
	else
	{
		tar = new GLNode;
		tar->tag = src->tag;
		if (src->tag == 1)// 如為子表
		{
			GList::copy(tar->ptr.hp, src->ptr.hp);// 複製表頭
			GList::copy(tar->ptr.tp, src->ptr.tp);// 複製表尾
		}
		else// 如為原子結點
		{
			tar->data = src->data;
		}
	}
}

2. 銷燬廣義表

/// 銷燬廣義表的遞迴演算法
/// @param	pNode	指向要銷燬的廣義表的指標的引用
static void DeleteAll(GLNode *&pNode);

void GList::DeleteAll(GLNode*& pNode)
{
	if (pNode->tag == 0)
	{
		// pNode
		//   |
		//  [ ]
		delete pNode;
		pNode = nullptr;
	}
	else if (pNode->ptr.tp == nullptr)
	{
		// pNode--->( )
		//           |			  
		//        ( )/[ ]       
		GList::DeleteAll(pNode->ptr.hp);
		delete pNode;
		pNode = nullptr;
	}
	else
	{
		// pNode--->( )---( )
		//           |			   
		//        ( )/[ ]       
		GList::DeleteAll(pNode->ptr.hp);
		GList::DeleteAll(pNode->ptr.tp);
		delete pNode;
		pNode = nullptr;
	}
}

3. 由字串建立廣義表

求字串形式的廣義表的表頭和表尾

void GList::getHandT(const char str[], char*& head, char*& tail)
{
	int len = strlen(str);
	// 左括號和右括號匹配資訊標誌位
	// tag = 0,表示遍歷到的左右括號數量相等
    // tag = i,表示遍歷到的左括號數量比右括號多 i 個
	int tag = 0;
	int i;
	for (i = 0;i < len; i++)
	{
                // 遍歷完表頭
                // ((a,b,c),e,(n,f))
                //         |
                //         i
		if (str[i] == ',' && tag == 1)
			break;
		if (str[i] == '(')// ='('+1
			tag++;
		if (str[i] == ')')// =')'-1
			tag--;
	}

	if (i < len && str[i] == ',')// 如果表尾非空
	{
		// 求表頭字串
		head = new char[i];// 減去一個多餘的左括號,再 + '\0'
		for (int j = 1; j < i; j++)
		{
			head[j - 1] = str[j];
		}
		head[i - 1] = '\0';// 字串以 '\0' 結束

		// 求表尾字串
		tail = new char[len - i + 1];// 減去表頭的字串,再 + '\0'
		tail[0] = '(';
		for (int j = i + 1; j < len; j++)
		{
			tail[j - i] = str[j];
		}
		tail[len - i] = '\0';// 字串以 '\0' 結束
	}
	else
	{
		// 此時 i = 字串長度
		head = new char[i - 1];// 減去最外層的一對括號,再 + '\0'
		for (int j = 1; j < i - 1; j++)
		{
			head[j - 1] = str[j];
		}
		head[i - 2] = '\0';// 字串以 '\0' 結束

		tail = new char[3];
		strcpy_s(tail, 3, "()");// strcpy 會自動新增 '\0'
	}
}

讀取字串建立廣義表的遞迴演算法

/// 讀取字串建立廣義表的遞迴演算法
/// @param	str	輸入的字串
/// @return	返回由字串建立的廣義表
static GLNode* CreatGList(const char str[]);

GLNode* GList::CreatGList(const char str[])
{
	// 如為空字串,返回空指標
	if (str == nullptr || str[0] == '\0')
	{
		return nullptr;
	}

	// 先為一個結點分配空間
	GLNode* glist = new GLNode;
	if (strlen(str) == 1)
	{
		// 假設 src 合法,如果 strlen(str) == 1,只有可能為原子結點
		glist->tag = 0;
		glist->data = str[0];
	}
	else
	{
		glist->tag = 1;
		char* head;// 表頭字串
		char* tail;// 表尾字串
		// 求出表頭表尾字串
		GList::getHandT(str, head, tail);
		// 由表頭字串構造子表結點的表頭
		glist->ptr.hp = GList::CreatGList(head);
		if (strcmp(tail, "()") == 0)
		{
			// 如 tail = "()",表尾為空
			glist->ptr.tp = nullptr;
		}
		else
		{
			// 由表尾字串構造子表結點的表尾
			glist->ptr.tp = GList::CreatGList(tail);
		}
	}
    
	return glist;
}