靜態鏈表
一、存儲結構
#define MAXSIZE 1000
typedef struct
{
ElemType data; // 數據
int cur; // 游標(Cursor)
} Component, StaticLinkList[MAXSIZE];
靜態鏈表的存儲結構.jpg
其中,有兩對元素是特殊的:
第一對:
- 下標為0的元素,其數據不存放東西;
- 其游標是指向第一個沒有存放數據的下標,此處為下標為5的元素。
第二對:
- 下標為maxsize-1的元素(即最后一個元素),其數據也不存放東西;
- 其游標指向第一個有數據的元素,此處是指向下標為1的元素。
其余:
- 除第一個和最后一個外,每一個元素的游標都是指向它的下一個元素;即游標為下一個元素的下標。
二、初始化靜態鏈表
對靜態鏈表進行初始化相當于初始化數組:
Status InitList(StaticLinkList space)
{
int i;
for( i=0; i < MAXSIZE-1; i++ )
space[i].cur = i + 1;
space[MAXSIZE-1].cur = 0;
return OK;
}
總結:
- 我們對數組的第一個和最后一個元素做特殊處理,他們的data不存放數據。
- 我們通常把未使用的數組元素稱為備用鏈表。
- 數組的第一個元素,即下標為0的那個元素的cur就存放備用鏈表的第一個結點的下標。
- 數組的最后一個元素,即下標為MAXSIZE-1的cur則存放第一個有數值的元素的下標,相當于單鏈表中的頭結點作用。
三、靜態鏈表的插入操作
在A后邊插入B:
插入操作.png
插入操作示范.png
代碼實現:
1.獲得空閑分量的下標:
int Malloc_SLL(StaticLinkList space)
{
int i = space[0].cur;
if( space[0].cur )
space[0].cur = space[i].cur;
// 把它的下一個分量用來作為備用。
return i;
}
2.插入操作
/* 在靜態鏈表L中第i個元素之前插入新的數據元素e */
Status ListInsert( StaticLinkList L, int i, ElemType e )
{
int j, k, l;
k = MAX_SIZE - 1; // 數組的最后一個元素
if( i<1 || i>ListLength(L)+1 )
{
return ERROR;
}
j = Malloc_SLL(L); //空閑分量的下標
if( j )
{
L[j].data = e;
for( l=1; l <= i-1; l++ )
{
k = L[k].cur;
}
L[j].cur = L[k].cur;
L[k].cur = j;
return OK;
}
return ERROR;
}
四、靜態鏈表的刪除操作
如圖,要刪除下標為2的元素:
刪除操作.jpg
代碼實現:
/* 刪除在L中的第i個數據元素 */
Status ListDelete(StaticLinkList L, int i)
{
int j, k;
if( i<1 || i>ListLength(L) )
{
return ERROR;
}
k = MAX_SIZE - 1;
for( j=1; j <= i-1; j++ )
{
k = L[k].cur; // k1 = 1, k2 = 5
}
j = L[k].cur; // j = 2
L[k].cur = L[j].cur;
Free_SLL(L, j);
return OK;
}
/* 將下標為k的空閑結點回收到備用鏈表 */
void Free_SLL(StaticLinkList space, int k)
{
space[k].cur = space[0].cur;
space[0].cur = k;
}
/* 返回L中數據元素個數 */
int ListLength(StaticLinkList L)
{
int j = 0;
int i = L[MAXSIZE-1].cur;
while(i)
{
i = L[i].cur;
j++;
}
return j;
}
五、靜態鏈表的優缺點總結
優點:
- 在插入和刪除操作時,只需要修改游標,不需要移動元素,從而改進了在順序存儲結構中的插入和刪除操作需要移動大量元素的缺點。
缺點:
- 沒有解決連續存儲分配(數組)帶來的表長難以確定的問題。
- 失去了順序存儲結構隨機存取的特性。
總的來說,靜態鏈表其實是為了給沒有指針的編程語言設計的一種實現單鏈表功能的方法。
盡管我們可以用單鏈表就不用靜態鏈表了,但這樣的思考方式是非常巧妙的,應該理解其思想,以備不時之需。
題目1:快速找到未知長度單鏈表的中間節點。
方法一:
首先遍歷一遍單鏈表以確定單鏈表的長度L。
然后再次從頭節點出發循環L/2次找到單鏈表的中間節點。
算法復雜度為:O(L+L/2)=O(3L/2)。
方法二:利用快慢指針
快慢指針原理:
設置兩個指針*search、*mid都指向單鏈表的頭節點。
其中* search的移動速度是*mid的2倍。
當*search指向末尾節點的時候,mid正好就在中間了。
這也是標尺的思想。
代碼實現:
Status GetMidNode(LinkList L, ElemType *e)
{
LinkList search, mid;
mid = search = L;
while (search->next != NULL)
{
//search移動的速度是 mid 的2倍
if (search->next->next != NULL)
{
search = search->next->next;
mid = mid->next;
}
else
{
search = search->next;
}
}
*e = mid->data;
return OK;
}
題目2:實現隨機生成20個元素的鏈表,快速查找中間結點的值并顯示
#include "stdio.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status; /* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef int ElemType; /* ElemType類型根據實際情況而定,這里假設為int */
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList; /* 定義LinkList */
Status visit(ElemType c)
{
printf("%d ",c);
return OK;
}
/* 初始化順序線性表 */
Status InitList(LinkList *L)
{
*L=(LinkList)malloc(sizeof(Node)); /* 產生頭結點,并使L指向此頭結點 */
if(!(*L)) /* 存儲分配失敗 */
{
return ERROR;
}
(*L)->next=NULL; /* 指針域為空 */
return OK;
}
/* 初始條件:順序線性表L已存在。操作結果:返回L中數據元素個數 */
int ListLength(LinkList L)
{
int i=0;
LinkList p=L->next; /* p指向第一個結點 */
while(p)
{
i++;
p=p->next;
}
return i;
}
/* 初始條件:順序線性表L已存在 */
/* 操作結果:依次對L的每個數據元素輸出 */
Status ListTraverse(LinkList L)
{
LinkList p=L->next;
while(p)
{
visit(p->data);
p = p->next;
}
printf("\n");
return OK;
}
/* 隨機產生n個元素的值,建立帶表頭結點的單鏈線性表L(尾插法) */
void CreateListTail(LinkList *L, int n)
{
LinkList p,r;
int i;
srand(time(0)); /* 初始化隨機數種子 */
*L = (LinkList)malloc(sizeof(Node)); /* L為整個線性表 */
r=*L; /* r為指向尾部的結點 */
for (i=0; i < n; i++)
{
p = (Node *)malloc(sizeof(Node)); /* 生成新結點 */
p->data = rand()%100+1; /* 隨機生成100以內的數字 */
r->next=p; /* 將表尾終端結點的指針指向新結點 */
r = p; /* 將當前的新結點定義為表尾終端結點 */
}
r->next = NULL; /* 表示當前鏈表結束 */
// 創建有環鏈表
//r->next = p;
}
Status GetMidNode(LinkList L, ElemType *e)
{
LinkList search, mid;
mid = search = L;
while (search->next != NULL)
{
//search移動的速度是 mid 的2倍
if (search->next->next != NULL)
{
search = search->next->next;
mid = mid->next;
}
else
{
search = search->next;
}
}
*e = mid->data;
return OK;
}
int main()
{
LinkList L;
Status i;
char opp;
ElemType e;
int find;
int tmp;
i=InitList(&L);
printf("初始化L后:ListLength(L)=%d\n",ListLength(L));
printf("\n1.查看鏈表 \n2.創建鏈表(尾插法) \n3.鏈表長度 \n4.中間結點值 \n0.退出 \n請選擇你的操作:\n");
while(opp != '0')
{
scanf("%c",&opp);
switch(opp)
{
case '1':
ListTraverse(L);
printf("\n");
break;
case '2':
CreateListTail(&L,20);
printf("整體創建L的元素(尾插法):\n");
ListTraverse(L);
printf("\n");
break;
case '3':
//clearList(pHead); //清空鏈表
printf("ListLength(L)=%d \n",ListLength(L));
printf("\n");
break;
case '4':
//GetNthNodeFromBack(L,find,&e);
GetMidNode(L, &e);
printf("鏈表中間結點的值為:%d\n", e);
//ListTraverse(L);
printf("\n");
break;
case '0':
exit(0);
}
}
}