資料結構之鏈式表
阿新 • • 發佈:2018-12-09
繼順序表之後,我們需要討論一下資料結構的鏈式表。順序表的特點是邏輯上相鄰的元素在物理位置上也相鄰,通常我們用陣列來表示這種儲存結構,這種儲存結構簡單,易於理解。但是同時必須看到它的缺點,那就是在作
插入和刪除操作時,需要移動大量的資料
,這必然造成效率低下。因此,產生了鏈式表,鏈式表不需要邏輯上相鄰的元素在物理位置上也相鄰,因此效率大大提高。另一方面,連結串列不需要像順序表那樣對空間進行預先的分配規劃,而可以由程式設計師根據需求即時生成並且適時釋放,因而這種方式實際上可以更為高效的利用緊缺的儲存空間,這也是它被廣泛使用的原因。也正是如此,連結串列比順序表理解起來要更加複雜。
鏈式表主要分為線性連結串列,靜態連結串列,迴圈連結串列和雙向連結串列。一開始一定要搞清楚每一種連結串列的資料結構定義。 鏈式表的特點是用一組任意的儲存單元儲存線性表的資料元素,因為這些儲存單元在物理上不一定是連續的,所以每一個數據元素除了包含資料資訊外,還需要包含至少其它元素的位置資訊。我們稱這樣的儲存單元為 結點,節點由資料域和指標域組成。 線性連結串列的節點中只包含一個指標域,因此又成為單鏈表。單鏈表最後一個結點的指標域為NULL.這個和迴圈連結串列是不同的。 單鏈表的資料結構可以定義為 typedef struct LNode{ ElemType data;
struct LNode *next;
}LNode,*LinkList;
一般來說,單鏈表都包含一個頭節點,頭節點可以不儲存任何資訊,也可以儲存諸如線性表的長度等附加資訊。 靜態連結串列 是用陣列來進行描述的連結串列這種方法便於在不設“指標”型別的高階程式設計語言中來使用。
靜態連結串列的資料結構定義如下:
#define MAXSIZE 1000
typedef struct{
ElemType data;
int cur;
}component,SLinkList[MAXSIZE];
這種方式仍然需要事先分配一個較大的空間,但是仍然具有線性連結串列的在插入刪除等操作方面的優點。不過個人感覺沒有指標那麼靈活,用的不是很多,不作為優先選擇。 迴圈連結串列 的特點是表中最後一個結點的指標域指向頭節點,整個連結串列形成一個環。因此,從表中的任意一個結點出發均可以找到表中的其他節點。
迴圈連結串列的資料結構和雙向連結串列是相同的。
由於以上的連結串列結構只有一個指示直接後繼的指標域,因此從某個結點出發只能往後查詢其它結點,如果查詢該之前的節點,則只能從表頭查起,效率顯然很低。為了克服這種缺點,出現了雙向連結串列。
雙向連結串列具有連個指標域,一個指向直接後繼,一個指向直接前驅。
雙向連結串列的資料結構定義如下:
typedef struct DulNode{
ElemType data;
struct DulNode *prior;
struct DulNode *next;
}DulNode, *DuLinkList;
注意,雙向連結串列也有迴圈表,並且有兩個環。
由於連結串列在空間上的合理利用和插入、刪除操作上的有點,在很多場合都作為首先的儲存結構。但是由於實際應用的不同,我們必須要 合理的定義和使用連結串列的儲存結構,做到靈活處理,而不能生搬硬套。 以下是根據實際應用定義的線性連結串列的型別和抽象資料型別的實現,可以作為參考。 線性連結串列的資料結構型別定義:
在過程中碰到的問題都是關於地址的,連結串列中最重要的也是地址問題。非常重要的一點是,因為記憶體是由我們自己分配和管理的,如果我們不進行釋放,那麼記憶體中的資料是不會改變的。我們是按照 地址來操作該地址所對應的記憶體空間中的內容。
鏈式表主要分為線性連結串列,靜態連結串列,迴圈連結串列和雙向連結串列。一開始一定要搞清楚每一種連結串列的資料結構定義。 鏈式表的特點是用一組任意的儲存單元儲存線性表的資料元素,因為這些儲存單元在物理上不一定是連續的,所以每一個數據元素除了包含資料資訊外,還需要包含至少其它元素的位置資訊。我們稱這樣的儲存單元為 結點,節點由資料域和指標域組成。 線性連結串列的節點中只包含一個指標域,因此又成為單鏈表。單鏈表最後一個結點的指標域為NULL.這個和迴圈連結串列是不同的。 單鏈表的資料結構可以定義為 typedef struct LNode{ ElemType data;
struct LNode *next;
}LNode,*LinkList;
一般來說,單鏈表都包含一個頭節點,頭節點可以不儲存任何資訊,也可以儲存諸如線性表的長度等附加資訊。 靜態連結串列
ElemType data;
int cur;
}component,SLinkList[MAXSIZE];
這種方式仍然需要事先分配一個較大的空間,但是仍然具有線性連結串列的在插入刪除等操作方面的優點。不過個人感覺沒有指標那麼靈活,用的不是很多,不作為優先選擇。 迴圈連結串列
由於連結串列在空間上的合理利用和插入、刪除操作上的有點,在很多場合都作為首先的儲存結構。但是由於實際應用的不同,我們必須要 合理的定義和使用連結串列的儲存結構,做到靈活處理,而不能生搬硬套。 以下是根據實際應用定義的線性連結串列的型別和抽象資料型別的實現,可以作為參考。 線性連結串列的資料結構型別定義:
typedef struct LNode
{
ElemType data;
struct LNode *next;
}LNode,*Link, *Position;
typedef struct
{
Link head,tail;
int len;
}LinkList;
/*
Description: allocate a node which is pointed by p and the value of the node is e.
*/
Status MakeNode(Link &p, ElemType e)
{
p = (Link)malloc(sizeof(LNode));
if(p == NULL)
{
return ERROR;
}
p->data = e;
p->next = NULL;
return OK;
}
/*
Description: construct a vacant linear list which only has a head node.
*/
Status InitList(LinkList &L)
{
int e = 0;
MakeNode(L.head,e);
L.len = 0; // the length of a Linear List is zero.
L.tail = L.head;
return OK;
}
/*
Description: release the space pointed by p.
*/
Status FreeNode(Link &p)
{
free(p);
return OK;
}
/*
Description: destroy Linear List L and it doesn't exist anymore.
*/
Status DestroyList(LinkList &L)
{
int i;
Link p;
Link s;
for(i=0;i<L.len;i++)
{
p = L.head->next;
s = p->next;
FreeNode(p);
L.head->next = s;
}
FreeNode(L.head);
return OK;
}
/*
Description: Make the List become a vacant list and release the space.
*/
Status ClearList(LinkList &L)
{
DestroyList(L);
InitList(L);
return OK;
}
/*
Description: if h pointed to the head node, insert node s before the first node.
*/
Status InsFirst(Link h, Link s)
{
s->next = h->next;
h->next = s;
return OK;
}
/*
Description: delete the first node in the Linear List and return the address by p.
*/
Status DelFirst(Link h, Link &p)
{
Link s;
s = h->next;
h->next = s->next;
s->next = NULL;//remember to modify point s->next, as s has already lose relation with the primary list.
p = s;
return OK;
}
/*
Description: Link the nodes beginning with s to the end of L and modify the tail of L.
use point p to trace s.
*/
Status Append(LinkList &L, Link s)
{
Link p = NULL;
L.tail->next = s;// this is very important, we must link s to L.
p = s;
s = s->next;
while(s)
{
p = s;
s = s->next;
}
L.tail = p;
return OK;
}
/*
Description: remove the tail of Linear List L and return the tail address by q, modify the tail of Linear L.
*/
Status Remove(LinkList &L, Link &q)
{
Link s;
q = L.tail;
s = L.head->next;
while(s->next != L.tail)
{
s = s->next;
}
FreeNode(L.tail);
s->next = NULL;
L.tail = s;
return OK;
}
/*
Description: add the node pointed by s to the position that is previous to the node pointed by p in the Linear List L.
modify the point p and make it pointed to the new inserted node.
*/
Status InsBefore(LinkList &L, Link &p, Link s)
{
Link k;
k = L.head->next;
while(k->next != p)
{
k = k->next;
}
k->next = s;
s->next = p;
p = s;
return OK;
}
/*
Description: Insert the node into the position after the node pointed by p. Modify the point p.
*/
Status InsAfter(LinkList &L, Link &p, Link s)
{
s->next = p->next;
p->next = s;
p = s;
return OK;
}
/*
Description: renew the element pointed by p with e.
*/
Status SetCurElem(Link &p, ElemType e)
{
p->data = e;
return OK;
}
/*
Description: return the element pointed by p.
*/
ElemType GetCurElem(Link p)
{
return p->data;
}
/*
Description: if List L is a vacant list, return true, or return false.
*/
Status ListEmpty(LinkList L)
{
if(L.len == 0)
{
return OK;
}
else
{
return ERROR;
}
}
/*
Description: return the number of the element in the Linear List.
*/
int GetListLength(LinkList L)
{
return L.len;
}
/*
Description: Get the position of the head node in the Linear List.
*/
Position GetHead(LinkList L)
{
return L.head;
}
/*
Description: Get the position of the last node in the Linear List.
*/
Position GetLast(LinkList L)
{
return L.tail;
}
/*
Description: Get the position which is previous to the node pointed by p.
*/
Position PriorPos(LinkList L, Link p)
{
Link k;
k = L.head;
while(k->next != p && k->next != NULL)
{
k = k->next;
}
if(k->next == p)
{
return k;
}
else
{
return NULL;
}
}
/*
Description: Get the position next to the node pointed by p.
*/
Position NextPos(LinkList L, Link p)
{
if(p->next != NULL)
{
return p->next;
}
else
{
return NULL;
}
}
/*
Description: return the position of the node i in the Linear List using p and return OK. if i is invalid, return ERROR.
*/
Status LocatePos(LinkList L, int i, Link &p)
{
int j = 1;
Link k = L.head->next;
if(i<1 || i>L.len)
{
return ERROR;
}
while(k!= NULL && j != i)
{
j++;
k = k->next;
}
p = k;
return OK;
}
/*
Description: return the position of the node whose element fits the compare relationship with element e in the list.
*/
Position LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType))
{
Link k;
k = L.head->next;
while(!compare(k->data,e) && k!= NULL)
{
k = k->next;
}
if(k == NULL)
{
return NULL;
}
return k;
}
/*
Description: use the function visit() to process every element in the List. If visit() returns ERROR, then the function return
ERROR.
*/
Status ListTraverse(LinkList L, Status(*visit)(ElemType))
{
Link s;
s = L.head->next;
while((visit(s->data)) && (s!=NULL))
{
s = s->next;
}
if(s == NULL)
{
return OK;
}
else
{
return ERROR;
}
}
以下是一個測試程式:
已知連結串列La,Lb中的元素按照非遞減的順序排列,歸併La,Lb到新的連結串列Lc,使Lc中的元素也按照非遞減的順序排列。
int compare(ElemType a, ElemType b)
{
if(a <= b)
{
return ERROR;
}
else
{
return OK;
}
}
/*
Description: insert the elment e into the position i in the List
*/
Status ListInsert_L(LinkList &L, int i, ElemType e)
{
Link h,s;
if(!LocatePos(L,i-1,h))
{
return ERROR;
}
if(!MakeNode(s,e))
{
return ERROR;
}
InsFirst(h,s);
return OK;
}
/*
Description: Merge La and Lb into List Lc. The elements in Lc grow from small to big.
*/
Status MergeList_L(LinkList &La, LinkList &Lb, LinkList &Lc, int(*compare)(ElemType, ElemType))
{
Link ha = NULL;
Link hb = NULL;
Link pa = NULL;
Link pb = NULL;
Link q = NULL;
int a,b;
if(!InitList(Lc))
{
return ERROR;
}
ha = GetHead(La);
hb = GetHead(Lb);
pa = NextPos(La,ha);
pb = NextPos(Lb,hb);
while(pa && pb)
{
a = GetCurElem(pa);
b = GetCurElem(pb);
if((*compare)(a,b) <= 0)
{
DelFirst(ha,q);
Append(Lc,q);
pa = NextPos(La,ha);
}
else
{
DelFirst(hb,q);
Append(Lc,q);
pb = NextPos(Lb,hb);
}
}
if(pa)
{
Append(Lc,pa);
}
else
{
Append(Lc,pb);
}
FreeNode(ha);
FreeNode(hb);
return OK;
}
void main(void)
{
int i = 0;
int ea = 6;
int eb = 4;
Link p = NULL;
Link s = NULL;
LinkList La,Lb,Lc;
La.head = NULL;
La.tail = NULL;
Lb.head = NULL;
Lb.tail = NULL;
//Creat the list La,Lb.
InitList(La);
InitList(Lb);
for(i=0;i<ListLength;i++)
{
MakeNode(p,ea++);
Append(La,p);
La.len++;
}
for(i=0;i<ListLength;i++)
{
MakeNode(p,eb++);
Append(Lb,p);
Lb.len++;
}
p = La.head->next;
while(p!=NULL)
{
cout << p->data;
p = p->next;
}
cout<< endl;
p = Lb.head->next;
while(p!=NULL)
{
cout << p->data;
p = p->next;
}
cout<< endl;
MergeList_L(La,Lb,Lc,compare);//the function name can be used as the parameter to transfer.
s = Lc.head->next;
while(s != NULL)
{
printf("%d",s->data);
s = s->next;
}
}
在過程中碰到的問題都是關於地址的,連結串列中最重要的也是地址問題。非常重要的一點是,因為記憶體是由我們自己分配和管理的,如果我們不進行釋放,那麼記憶體中的資料是不會改變的。我們是按照 地址來操作該地址所對應的記憶體空間中的內容。