【資料結構】廣義表
阿新 • • 發佈:2022-12-01
廣義表
參考:
廣義表的定義
線性表 線性表指的是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;
}