1、鏈表
(1)概念
- 結點在存儲器中的位置是任意的,即邏輯上相鄰的數據元素在物理上不一定相鄰
- n 個結點由指針鏈組成一個鏈表。它是線性表的鏈式存儲映像,稱為線性表的鏈式存儲結構
(2)結點組成
- 數據域:存儲元素數值數據
- 指針域:存儲直接后繼結點的存儲位置
(3)單鏈表、雙鏈表與循環鏈表:
- 結點只有一個指針域的鏈表,稱為單鏈表或線性鏈表
- 有兩個指針域的鏈表,稱為雙鏈表。雙向鏈表能夠克服單鏈表查詢前驅結點必須從表頭出發的問題,雙向鏈表有兩個指針域分別指向自己的前驅結點和后繼結點,查找的時間復雜度為O(1)
- 首尾相接的鏈表稱為循環鏈表,從循環鏈表中的任何一個結點的位置都可以找到其他所有結點,而單鏈表做不到
(4)頭指針、頭結點和首元結點
- 頭指針是指向鏈表中第一個結點的指針
- 首元結點是指鏈表中存儲第一個數據元素a1的結點
- 頭結點是在鏈表的首元結點之前附設的一個結點,數據域內只放空表標志和表長等信息。頭結點的數據域可以為空,也可存放線性表長度等附加信息,但此結點不能計入鏈表長度值。
(5)在鏈表中設置頭結點有什么好處
- 便於首元結點的處理:首元結點的地址保存在頭結點的指針域中,所以在鏈表的第一個位置上的操作和其它位置一致,無須進行特殊處理
- 便於空表和非空表的統一處理:無論鏈表是否為空,頭指針都是指向頭結點的非空指針,因此空表和非空表的處理也就統一了
(6)特點
- 結點在存儲器中的位置是任意的,即邏輯上相鄰的數據元素在物理上不一定相鄰
- 訪問時只能通過頭指針進入鏈表,並通過每個結點的指針域向后掃描其余結點,所以尋找第一個結點和最后一個結點所花費的時間不等
(7)優缺點
優點:
- 數據元素的個數可以自由擴充
- 插入、刪除等操作不必移動數據,只需修改鏈接指針,修改效率較高
缺點:
- 存儲密度小
- 存取效率不高,必須采用順序存取,即存取數據元素時,只能按鏈表的順序進行訪問
(8)結構定義
typedef struct Lnode { ElemType data; //數據域 struct LNode *next; //指針域 }LNode,*LinkList; // *LinkList為Lnode類型的指針
(9)初始化
Status InitList_L(LinkList &L){ L=new LNode; //生成新結點作頭結點,用頭指針L指向頭結點。 L->next=NULL; //頭結點的指針域置空 return OK; }
2、鏈表的基本操作
(1)銷毀
Status DestroyList_L(LinkList &L) { LinkList p; while(L) { p=L; L=L->next; delete p; } return OK; }
(2)清空
Status ClearList(LinkList & L){ // 將L重置為空表 LinkList p,q; p=L->next; //p指向第一個結點 while(p) //沒到表尾 {
q=p->next;
delete p;
p=q;
} L->next=NULL; //頭結點指針域為空 return OK; }
與銷毀不同,清空的時候會保留頭結點
(3)求表長
p=L->next; i=0; while(p)
{i++;
p=p->next;
}
int ListLength_L(LinkList L){ //返回L中數據元素個數 LinkList p; p=L->next; //p指向第一個結點 i=0; while(p){//遍歷單鏈表,統計結點數 i++; p=p->next;
} return i; }
(4)判斷是否為空
int ListEmpty(LinkList L) { //若L為空表,則返回1,否則返回0 if(L->next) //非空 return 0; else return 1; }
3、對鏈表的操作
(1)添加元素:在L中第i個元素之前插入數據元素e
Status ListInsert_L(LinkList &L,int i,ElemType e){ p=L;j=0; while(p&&j<i−1){p=p->next;++j;} //尋找第i−1個結點 if(!p||j>i−1)return ERROR; //i大於表長 + 1或者小於1 s=new LNode; //生成新結點s s->data=e; //將結點s的數據域置為e s->next=p->next; //將結點s插入L中 p->next=s; return OK; }//ListInsert_L
時間復雜度:O(1)
(2)刪除元素:將線性表L中第i個數據元素刪除
Status ListDelete_L(LinkList &L,int i,ElemType &e){ p=L;j=0; while(p->next &&j<i-1){ //尋找第i個結點,並令p指向其前驅 p=p->next; ++j; } if(!(p->next)||j>i-1) return ERROR; //刪除位置不合理 q=p->next; //臨時保存被刪結點的地址以備釋放 p->next=q->next; //改變刪除結點前驅結點的指針域 e=q->data; //保存刪除結點的數據域 delete q; //釋放刪除結點的空間 return OK; }//ListDelete_L
時間復雜度:O(1)
(3)查找:在線性表L中查找值為e的數據元素
int LocateELem_L (LinkList L,Elemtype e) { //返回L中值為e的數據元素的位置序號,查找失敗返回0 p=L->next; j=1; while(p &&p->data!=e) {p=p->next; j++;} if(p) return j; else return 0; }
時間復雜度:O(n)
4、單鏈表的創建
(1)前插法
void CreateList_F(LinkList &L,int n){ L=new LNode; L->next=NULL; //先建立一個帶頭結點的單鏈表 for(i=n;i>0;--i){ p=new LNode; //生成新結點 cin>>p->data; //輸入元素值 p->next=L->next;L->next=p; //插入到表頭 } }//CreateList_F
- 生成新結點
- 將讀入數據存放到新結點的數據域中
- 將該新結點插入到鏈表的前端
(2)尾插法
void CreateList_L(LinkList &L,int n){ //正位序輸入n個元素的值,建立帶表頭結點的單鏈表L L=new LNode; L->next=NULL; r=L; //尾指針r指向頭結點 for(i=0;i<n;++i){ p=new LNode; //生成新結點 cin>>p->data; //輸入元素值 p->next=NULL; r->next=p; //插入到表尾 r=p; //r指向新的尾結點 } }//CreateList_L
- 從一個空表L開始,將新結點逐個插入到鏈表的尾部,尾指針r指向鏈表的尾結點
- 初始時,r同L均指向頭結點。每讀入一個數據元素則申請一個新結點,將新結點插入到尾結點后,r指向新結點
5、線性表和鏈表的比較
(1)空間
- 存儲空間:順序表需要預先分配,會導致空間閑置或溢出現象,鏈表動態分配,不會出現存儲空間閑置或溢出現象
- 存儲密度:線性表等於1,鏈表小於1
(2)時間
- 取數據:順序表是隨機存取,時間復雜度為O(1),鏈表是順序存取,時間復雜度為O(n)
- 插入刪除:順序表需要移動元素,時間復雜度為O(n),鏈表不需要,時間復雜度為O(1)
(3)適用范圍
線性表
表長變化不大,且能事先確定變化的范圍
很少進行插入或刪除操作,經常按元素位置序號訪問數據元素
鏈表
長度變化較大
頻繁進行插入或刪除操作