線性表,有順序儲存結構 和鏈式儲存結構兩種,常見實現為一維陣列和單鏈表。
效能對比查詢
插入和刪除
順序:o(1)
順序:o(n)
單鏈表:o(n)
單鏈表:o(1)
顯然,順序儲存結構適用於查詢,鏈式儲存結構適合於插入和刪除操作,各有優缺點。
上述兩種都比較簡單,實現本文不介紹,略提一下優缺點。
接下來介紹其他的鍊錶結構
用陣列描述的鍊錶叫做靜態鍊錶。
關鍵:陣列的每個下標對應乙個data和cur,data為資料域,而cur相當於單鏈表中的next指標,存放後繼元素在陣列中的下標。
因為有兩個域,所以需用結構體實現:
#define maxsize 1000
typedef
struct
staticlinklist[maxsize]
;
用陣列模擬鍊錶,要解決的問題顯然就是如何獲得未被使用的陣列元素,因為不像鍊錶一樣,直接建立加到next域就完成。
將未被使用的陣列元素稱為備用鍊錶
因此,對陣列第乙個和最後乙個元素作特殊元素處理,不用於存放資料。
將陣列第乙個元素的cur存放備用鍊錶的第乙個結點下標;
而最後乙個元素的cur存放第乙個有數值的元素的下標,等同於單鏈表中的頭結點
基於上述知識,下面開始說操作的核心
int i;
for(i=
0; i1; i++
) space[i]
.cur = i+1;
space[maxsize-1]
.cur =0;
/* 目前靜態鍊錶為空,最後乙個元素的cur為0 */
/* 若備用空間鍊錶非空,則返回分配的結點下標,否則返回0 */
intmalloc_ssl
(staticlinklist space)
獲得了可用元素下標後,插入操作就有點類似單鏈表的插入操作了
在l中第i個元素之前插入新的資料元素e
/* 首先找到第i個元素 */
int k, l;
int k = maxsize -1;
/* 注意k首先是最後乙個元素的下標 ,即l[k].cur 此時指向第乙個元素*/
for(l =
1; l <= i -
1; l++
)/* 找到第i個元素之前的位置 */
k = l[k]
.cur;
j =
malloc_ssl
(l);
/* 獲得空閒分量的下標 */
if(j)
/*返回值非0,即備用鍊錶非空*/
最後兩行**
l[j]
.cur = l[k]
.cur;
l[k]
.cur = j;
其實就等同於
j->next = k->next;
k->next = j ;
用陣列模擬鍊錶,除了刪除外,還要把結點**到備用鍊錶中
刪除第i個元素
1:先找到第i個元素前的位置,**同前插入,不贅述,同樣用k指替
2:刪除
j = l[k]
.cur;
l[k]
.cur = l[j]
.cur;
同理,等同於單鏈表中的
j = k->next;
k->next = j->next;
3:**空間至備用鍊錶
space[j]
.cur = space[0]
.cur;
/* 先把第乙個元素的cur值賦給要刪除的分量cur */
space[0]
.cur = j;
/* 再把要刪除的分量下標賦值給第乙個元素的cur */
因為space[
0]指向備用鍊錶的第乙個下標,**空間有兩種做法:(1
)迴圈整個備用鍊錶至末尾,把被刪除結點新增到末尾,耗時o
(n)(
2)直接插入到備用鍊錶首部,耗時o(1)
顯示(2)是更優選
總的來說,靜態鍊錶只是為沒有指標的高階語言設計的一種實現單鏈表的方法,其優缺點和單鏈表無異。插入刪除省去移動大量元素,但也失去了隨機訪問的特性。
將單鏈表中終端結點的指標由空指標改為指向頭結點,就使整個單鏈表形成乙個環,稱為單迴圈鍊錶,簡稱迴圈鍊錶。
(順帶一提,頭結點並不是必要的,只是為了使空鍊錶與非空煉表處理一致,通常設乙個頭結點,因此以下說法都建立在有頭結點下。)
在單鏈表中,我們是用頭指標指向頭結點,雖然訪問第乙個結點為o(1),但訪問最後乙個結點卻需要遍歷一次,需要o(n)。
而在迴圈鍊錶中,我們捨棄頭指標,而採用指向終端結點的尾指標(用rear代表),將rear指向頭結點,那麼訪問終端結點以及開始結點(rear->next->next)都為o(1)
需要注意的是,無論是哪種鍊錶結構,頭結點至多只有乙個,
/* 假定a、b鍊錶,尾指標分別為reara、rearb,把b鍊錶合成到a鍊錶中 */
p = reara->next;
q = rearb->next;
/* p、q分別指向a、b表的頭結點 */
reara->next = q ->next /* 注意q->next指向的是b表的第乙個結點,而不是頭結點 */
rearb->next = p;
/* 令b表的尾指標指向a表的頭結點 */
free
(q);
/* 此時b表的頭結點已無用,釋放 */
/* 此時合成的鍊錶中只剩下a表的頭結點 */
在單鏈表的每個結點中,再設定乙個指向其前驅結點的指標域,就成為了雙向鍊錶。
與直接後驅指標next相對,直接前驅指標為prior
prior的用法:
eg:某個結點p的前驅的後繼 = p = p的後繼的前驅
即 p->next->prior = p = p->prior->next;
eg:將s結點插入到p,p->next 之間
(1)s->prior = p;
(2)s->next = p->next;
(3)p->next->prior = s;
(4)p->next = s;
最後(4) p->next = s,必須在最後才執行,
因為(2)(3)中都用到了p->next,若先執行(4),(2)(3)步驟都會出錯。
所以我更推薦以下做法,一開始先直接暫存p->next,在(2)(3)操作時需要用到p->next時,直接通過 t 去訪問,這樣就算(4)放錯位子的話也不會出錯
t = p->next;
s->prior = p;
s->next = t;
t->prior = s;
p->next = s;
同單鏈表刪除相比,增加了後繼前驅的設定。
p->prior->next = p->next;
p->next->prior = p->prior;
而刪除則不像插入,**順序不會導致出錯,此處反而不推薦像插入那樣去暫存某個next或prior,因為反而會導致**繁瑣。
雙向鍊錶增加了前驅指標,好處不必多說,代價就是**變複雜。
說白了,就是空間換時間。
資料結構 鍊錶常見題目
include bool insert last list ls,data data tmp next node return true void display list ls printf n list createlist 建立鍊錶 ls head next null 空鍊錶 return l...
資料結構 鍊錶及常見操作
鍊錶最常見的結構有單鏈表,雙鏈表和迴圈鍊錶 單鏈表只有乙個方向,結點只需要乙個額外的空間指向後面的結點,只能從前往後進行遍歷查詢資料 而雙向鍊錶可以向前和向後查詢資料,相對比較靈活,但是雙向鍊錶需要額外的兩個空間來儲存後繼結點和前驅結點的位址 儲存同樣多的資料,雙向鍊錶需要比單鏈表占用更多的空間。1...
常見鍊錶問題
給定單向鍊錶的頭指標和乙個要刪除的節點的值,定義乙個函式刪除該節點。返回刪除後的鍊錶的頭節點。示例 1 輸入 head 4,5,1,9 val 5 輸出 4,1,9 解釋 給定你鍊錶中值為 5 的第二個節點,那麼在呼叫了你的函式之後,該鍊錶應變為 4 1 9.示例 2 輸入 head 4,5,1,9...