轉載請注明出處,部分內容引自百度百科、譚浩強《C程序設計》、蝸牛君的奮斗史大神的博客
前置知識: C語言入門
數組黨的福音(本蒟蒻學鏈表時不會指針,然而好像所有人都拿指針寫)
首先,我們需要知道什么是鏈表
百度百科
看不懂勿噴(畢竟百度百科也不是用來讓人看懂的)
我們可以從中得出鏈表的特性:
鏈表是一種物理存儲單元上非連續、非順序的存儲結構
提取主謂賓:鏈表是存儲結構。我認為這就是鏈表的本質——一種數據結構。
那么非連續、非線性有什么含義呢?這表明鏈表的內存是不連續的,前一個元素存儲地址的下一個地址中存儲的不一定是下一個元素。鏈表通過一個指向下一個元素地址的引用將鏈表中的元素串起來。
鏈表分為三類:單向鏈表、雙向鏈表、循環鏈表
1,單向鏈表
單向鏈表是最簡單的鏈表形式。我們將鏈表中最基本的數據稱為節點(node),每一個節點包含了數據塊和指向下一個節點的指針,鏈表有一個頭指針變量,圖中以head表示。可以看出,head指向第一個元素,第一個元素又指向第二個元素……直到最后一個元素,該元素不再指向其他元素,它稱為表尾,它的地址部分為空,鏈表到此結束。
可以看到,要找鏈表中的某一元素,必須先找到上一個元素,根據它提供的下一個元素的地址才能找到下一個元素。如果不提供頭指針,則整個鏈表都無法訪問。鏈表如同一條鐵鏈一樣,一環扣一環,中間是不能斷開的。
為了理解什么是鏈表,打一個通俗的比方:幼兒園的老師帶領孩子們出來散步,老師牽着第一個小孩的手,第一個小孩的另一只手牽着第二個孩子……這就是一個鏈,最后一個孩子有一只手空着,他是鏈尾。要找到這個隊伍,必須先找到老師,然后順序找到每一個孩子。
變量聲明:
const int maxn=1010; struct node{ //point即指針,data就是需要維護的數據
int point,data; }a[maxn]; int head,cnt; //head即頭指針,cnt即內存池計數器
建立:
head=++cnt; //把head設為沒有實際意義的哨兵
a[head].data=0;
插入(插入數據now到第k個元素之后):
add(++k,now); //進入,因為計算時考慮了哨兵,所以進入時++k
void add(int k,int now) { for(int i=head;i;i=a[i].point) //從頭指針開始遍歷鏈表
if(!(--k)) //到達插入位置
{ a[++cnt].point=a[i].point; //將新插入節點的指針指向插入位置的后繼
a[i].point=cnt; //將前驅節點的指針指向新插入節點
a[cnt].data=now; break; } }
刪除(刪除第k個元素):
del(++k); //進入
void del(int k) { for(int i=head;i;i=a[i].point) if((--k)==1) //找到前驅
{ a[i].point=a[a[i].point].point; //將前驅的指針指向后繼
break; } }
遍歷鏈表:
for(int i=a[head].point;i;i=a[i].point) cout<<a[i].data<<endl;
2,雙向鏈表
顧名思義,雙向鏈表就是有兩個方向的鏈表。同單向鏈表不同,在雙向鏈表中每一個節點不僅存儲指向下一個節點的指針,而且存儲指向前一個節點的指針。它的優點是訪問、插入、刪除更方便。但“是以空間換時間”。
變量聲明:
struct node{ int pre,nxt,data; //前驅和后繼
}a[maxn]; int head,cnt;
插入:
void add(int k,int now) { for(int i=head;i;i=a[i].nxt) if(!(--k)) { a[++cnt].nxt=a[i].nxt; //這兩個和單鏈表一樣
a[i].nxt=cnt; a[a[cnt].nxt].pre=cnt; //將后繼的前驅更新為新插入節點
a[cnt].pre=i; //將新插入節點的前驅設為其前驅
a[cnt].data=now; break; } }
刪除:
void del(int k) { for(int i=head;i;i=a[i].nxt) if(!(--k)) { a[a[i].pre].nxt=a[i].nxt; a[a[i].nxt].pre=a[i].pre; break; } }
3,循環鏈表
(1),單向循環鏈表
最后一個節點的指針指向頭結點
(2),雙向循環鏈表
最后一個節點的指針指向頭結點,且頭結點的前驅指向最后一個結點
代碼實現就不再給出,相信大家已經能夠實現。