數據結構與算法學習筆記02_2(線性表)
2、線性表的鏈式存儲結構
單鏈表
除了存儲其本身的信息外,還需存儲一個指示其直接後繼的存儲位置的信息。
我們把存儲數據元素信息的域稱爲數據域,把存儲直接後繼位置的域稱爲指針域。鏈表中的第一個結點的存儲位置叫做頭指針,最後一個結點指針爲空(NULL)。
頭指針與頭結點的異同
1、頭指針是指鏈表指向第一個結點的指針,若鏈表有頭結點,則是指向頭結點的指針。
2、頭指針是鏈表的必要元素。
3、頭結點是爲了操作的統一和方便而設立的,放在第一個元素的結點之前,其數據域一般無意義(但也可以用來存放鏈表的長度)。
4、頭結點不一定是鏈表的必須要素。
實現:
typedefstructNode
{
ElemType data; // 數據域
struct Node*Next; // 指針域
} Node;
typedef struct Node*LinkList;
單鏈表的讀取:
獲得鏈表第i個數據的算法思路:
聲明一個結點p指向鏈表第一個結點,初始化j從1開始;
當j<i時,就遍歷鏈表,讓p的指針向後移動,不斷指向一下結點,j+1;
若到鏈表末尾p爲空,則說明第i個元素不存在;
否則查找成功,返回結點p的數據。
實現:
status GetElem(LinkList L, int i, ElemType* e){
int j;
LinkList p;
p = L->next;
j = 1;
while (p&&j<i)
{
p = p->next;
++j;
}
if (!p||j>i)
{
return ERROR;
}
*e = p->data;
return Ok;
}
由於單鏈表的結構中沒有定義表長,所以不能實現知道要循環多少次,因此也就不方便使用for來控制循環。
單鏈表的插入
我們思考後發覺根本用不着驚動其他結點,只需要讓s->next和p->next的指針做一點改變。
s->next= p->next;
p->next= s;
這兩句是無論如何不能弄反的。
單鏈表第i個數據插入結點的算法思路:
聲明一結點p指向鏈表頭結點,初始化j從1開始;
當j<1時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一結點,j累加1;
若到鏈表末尾p爲空,則說明第i個元素不存在;
否則查找成功,在系統中生成一個空結點s;
將數據元素e賦值給s->data;
單鏈表的插入剛纔兩個標準語句;
返回成功。
實現:
Status ListInsert(LinkList* L, int i, ElemType 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 = (Listlist)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next = s;
return Ok;
}
單鏈表的刪除
單鏈表第i個數據刪除結點的算法思路:
聲明結點p指向鏈表第一個結點,初始化j=1;
當j<1時,就遍歷鏈表,讓P的指針向後移動,不斷指向下一個結點,j累加1;
若到鏈表末尾p爲空,則說明第i個元素不存在;
否則查找成功,將欲刪除結點p->next賦值給q;
單鏈表的刪除標準語句p->next = q->next;
將q結點中的數據賦值給e,作爲返回;
釋放q結點。
實現:
Status ListDelete(LinkList *L, int i, ElemType *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);
return Ok;
}
效率分析:
無論是單鏈表插入還是刪除算法,它們其實都是由兩個部分組成:第一部分就是遍歷查找第i個元素,第二部分就是實現插入和刪除元素。從整個算法來說,我們很容易可以推出它們的時間複雜度都是O(n)。
再詳細點分析:
如果在我們不知道第i個元素的指針位置,單鏈表數據結構在插入和刪除操作上,與線性表的順序存儲結構是沒有太大優勢的。
但如果,我們希望從第i個位置開始,插入連續10個元素,對於順序存儲結構意味着,每一次插入都需要移動n-i個位置,所以每次都是O(n)。
而單鏈表,我們只需要在第一次時,找到第i個位置的指針,此時爲O(n),接下來只是簡單地通過賦值移動指針而已,時間複雜度都是O(1)。
顯然,對於插入或刪除數據越頻繁的操作,單鏈表的效率優勢就越是明顯啦~