1. 程式人生 > >線性表、棧、佇列的鏈式儲存結構

線性表、棧、佇列的鏈式儲存結構

一、順序儲存結構與鏈式儲存結構的區別

       順序儲存就是從記憶體中取出一段連續地址的空間,將資料依次連續的儲存在這段空間中。而鏈式儲存結構是指資料儲存在記憶體中的地址是離散的,以資料節點為單位,每個節點包括資料域和指標域。

二、鏈式結構資料的建立

       棧(Stack)是限定僅在表尾進行插入和刪除操作的線性表;佇列(Queue)是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表。換句話說,棧和佇列只是一種特殊的線性表。鏈式儲存結構的資料單位是節點。節點由資料域和指標域組成,對於單鏈表,指標僅指向後一個數據單元(後繼)的地址,而對於雙向連結串列來說,每個節點的指標域還包括指向前一個數據單元的地址。以單鏈表的資料節點為例:

typedef struct Node
{
    ElemType data;
    struct Node *next;
}Node;
typedef struct Node *LinkList;

建立了資料節點之後,定義一個指向節點指標,開始對線性表進行操作。

對於棧和佇列,仍需要以節點為單位,定義相應的鏈棧和鏈佇列。
typedef struct StackNode
{
    int data;
    struct StackNode *next;
}StackNode, *LinkStackPointer;

typedef struct LinkStack
{
    LinkStackPointer top;
    int count;
}LinkStack;
對於鏈棧,*LinkStackPointer為指向結構體節點的指標。然後,在LinkStack中定義top指標和棧的容量。佇列與棧的不同在於佇列需要在兩端分別進行插入和刪除操作,所以在鏈佇列中需要分別定義頭和尾(指標)。
typedef struct QNode
{
   int data;
   struct QNode *next;
}QNode,*QueuePointer;

typedef struct
{
   QueuePtr front,rear;
}LinkQueue;

三、鏈式結構資料的插入

建立資料節點與鏈棧、鏈佇列之後,需要進行資料的插入。對於線性表的鏈式儲存結構來說,插入資料的核心操作就是從記憶體中獲取一個節點大小的空間,並初始化一個節點指標指向該資料段;同時,將需要插入的資料寫入該資料段,並且將插入位置的上一節點的指標域賦值給該資料段的指標域,即將該資料段的後繼指向插入位置的原後繼;最後,將插入位置的上一節點的指標域指向該資料段。因為鏈式儲存結構在插入資料時,不需要移動原資料節點的位置,所以時間複雜度為O(1)。
int ListInsert(LinkList *L,int i,int e)
{ 
	int j;
	LinkList p,s;
	p = *L;   
	j = 1;
	while (p && j < i)
	{
		p = p->next;
		++j;
	} 
	if (!p || j > i) 
		return ERROR;
	s = (LinkList)malloc(sizeof(Node));
	s->data = e;  
	s->next = p->next;  
	p->next = s;
	return OK;
}
此處,LinkList本身為指向資料節點的指標,插入函式的形式引數中,第一個形參為指標的指標,指向儲存資料為節點地址的地址。i為需要插入資料的位置,e為待插入的資料。 對於鏈棧來說,插入操作也稱為壓棧(相反的,刪除操作也稱為彈棧),壓棧與彈棧都線上性表尾進行操作。
int Push(LinkStack *S,int e)
{
        LinkStackPtr s=(LinkStackPointer)malloc(sizeof(StackNode)); 
        s->data=e; 
        s->next=S->top;
        S->top=s; 
        S->count++;
        return OK;
}
其插入原理與線性表一致。對於鏈佇列來說,插入操作只能在表尾進行。即將從記憶體中獲取的資料段連結到表尾即可。
int EnQueue(LinkQueue *Q,int e)
{ 
	QueuePtr s=(QueuePointer)malloc(sizeof(QNode));
	if(!s)
		exit(OVERFLOW);
	s->data=e;
	s->next=NULL;
	Q->rear->next=s;
	Q->rear=s;
	return OK;
}

四、鏈式結構資料的刪除

與插入操作相反的原理,刪除操作只需要找到需要刪除的位置,將需要被刪除的節點的指標域(後繼)賦值給該節點的上一節點的指標域,然後將該節點所佔的記憶體空間釋放(還給系統,讓系統排程)即可。
int ListDelete(LinkList *L,int i,int *e) 
{ 
	int j;
	LinkList p,q;
	p = *L;
	j = 1;
	while (p->next && j < i)
	{
            p = p->next;
            ++j;
	}
	if (!(p->next) || j > i) 
	    return ERROR;
	q = p->next;
	p->next = q->next;
	*e = q->data;
	free(q); 
}
其中,通過指標p指向線性表,形參i為刪除資料節點的位置,指標e指向被刪除的資料。找到刪除位置後,通過指標q持有p->next,然後將p->next賦值為該節點的下一個節點地址,即將該節點地址排除在整個線性表之外,然後釋放q所指定的記憶體空間。 出棧操作仍然需要在表尾進行。即將棧頂指標下移一位,指向後一節點,然後釋放棧頂節點。
int Pop(LinkStack *S,int *e)
{ 
        LinkStackPointer p;
        if(StackEmpty(*S))
                return ERROR;
        *e=S->top->data;
        p=S->top;
        S->top=S->top->next;
        free(p);
        S->count--;
        return OK;
}
出對操作需要在隊頭進行,即佇列資料插入的另一端。其原理與以上所述線性表一致。
int DeQueue(LinkQueue *Q,int *e)
{
	QueuePointer p;
	if(Q->front==Q->rear)
		return ERROR;
	p=Q->front->next;
	*e=p->data;
	Q->front->next=p->next;
	if(Q->rear==p)	
		Q->rear=Q->front;
	free(p);
	return OK;
}
需要注意的是,如果隊頭與隊尾重合,刪除後需要將rear指向頭結點。

五、鏈式結構資料的遍歷

對於順序儲存的資料結構來說,其隨機訪問某個元素的時間複雜度為O(1),而其插入與刪除操作需要移動大量資料(線性表尾除外)。而對於鏈式儲存的資料結構,插入和刪除都不需要移動其他資料節點,時間複雜度為O(1),然後其隨機訪問需要從連結串列頭根據後續指標一一訪問,直到找到需要的那個資料節點,時間複雜度較高,如果被訪問元素恰好落在表尾,則需要遍歷所有的資料節點。
int ListTraverse(LinkList L)
{
    LinkList p=L->next;
    while(p)
    {
        visit(p->data);
        p=p->next;
    }
    printf("\n");
    return OK;
}
如上所示,每次迴圈都將p指向p的下一個節點。在上述的插入操作中,需要在位置為i的地方插入資料,則需要依次迴圈至被查詢的位置。 鏈棧與鏈佇列的遍歷與連結串列一致,只是初始指標的表示不同,對於鏈棧,初始指標p為LinkStack.top,對於鏈佇列,初始指標p為LinkQueue.front(即從隊頭遍歷至隊尾)。 List是最基本的資料容器,很多其他的資料容器都是對其進行某些方面的限定而得到的特殊情況。List的儲存結構分為最基本的順序儲存結構和鏈式儲存結構,由於儲存機理的不同,其特性差異較大,因此,需要根據應用的場合選取不同的儲存結構。在高階語言中,List、Stack、Set、Map、Queue等都已封裝好,只是在初始化的時候,根據需要選取不同的例項,靈活運用不同的資料結構,將會給程式帶來極大的優化體驗。 (注:部分原始碼來自網際網路)