數(shù)據(jù)結(jié)構(gòu)和算法之一——線性表_3_鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)_1_單鏈表

1. 鏈表的定義

線性表的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)的特點(diǎn)是用一組任意的存儲(chǔ)單元存儲(chǔ)線性表的數(shù)據(jù)元素,這組存儲(chǔ)單元可以存在內(nèi)存中未被占用的任意位置。

(比起順序存儲(chǔ)結(jié)構(gòu)每個(gè)數(shù)據(jù)元素只需要存儲(chǔ)一個(gè)位置就可以了。現(xiàn)在鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)中,除了要存儲(chǔ)數(shù)據(jù)元素信息外,還要存儲(chǔ)它的后繼元素的存儲(chǔ)地址(指針)。也就是說除了存儲(chǔ)其本身的信息外,還需存儲(chǔ)一個(gè)指示其直接后繼的存儲(chǔ)位置的信息。)

我們把存儲(chǔ)數(shù)據(jù)元素信息的域稱為數(shù)據(jù)域,把存儲(chǔ)直接后繼位置的域稱為指針域。指針域中存儲(chǔ)的信息稱為指針或鏈。這兩部分信息組成數(shù)據(jù)元素稱為存儲(chǔ)映像,稱為結(jié)點(diǎn) (Node) 。

n 個(gè)結(jié)點(diǎn)鏈接成一個(gè)鏈表,即為線性表 (a1, a2,a3, ..., an) 的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)。因?yàn)榇随湵淼拿總€(gè)結(jié)點(diǎn)中只包含一個(gè)指針域,所以叫做單鏈表。見如下圖:

單鏈表

2. 頭指針和頭結(jié)點(diǎn)

2.1 頭指針:

1)頭指針是指鏈表指向第一個(gè)結(jié)點(diǎn)的指針,若鏈表有頭結(jié)點(diǎn),則是指向頭結(jié)點(diǎn)的指針。

2)頭指針具有標(biāo)識(shí)作用,所以常用頭指針冠以鏈表的名字(指針變量的名字)。

3)無論鏈表是否為空,頭指針均不為空。

4)頭指針是鏈表的必要元素。

2.2 頭結(jié)點(diǎn)

1)頭結(jié)點(diǎn)是為了操作的統(tǒng)一和方便而設(shè)立的,放在第一個(gè)元素的結(jié)點(diǎn)之前,其數(shù)據(jù)域一般無意義(但也可以用來存放鏈表的長度)。

2)有了頭結(jié)點(diǎn),對(duì)在第一元素結(jié)點(diǎn)前插入結(jié)點(diǎn)和刪除第一結(jié)點(diǎn)起操作與其它結(jié)點(diǎn)的操作就統(tǒng)一了。

3)頭結(jié)點(diǎn)不一定是鏈表的必須要素。

3. 單鏈表的存儲(chǔ)結(jié)構(gòu)

3.1我們?cè)?C 語言中可以用結(jié)構(gòu)指針來描述單鏈表。見例1.

//例1
typedef struct Node
{
ElemType data; // 數(shù)據(jù)域
struct Node* Next; // 指針域
} Node;
typedef struct Node* LinkList;
(注:我們看到結(jié)點(diǎn)由存放數(shù)據(jù)元素的數(shù)據(jù)域和存放后繼結(jié)點(diǎn)地址的指針域組成。)

假設(shè) p 是指向線性表第 i 個(gè)元素的指針,則該結(jié)點(diǎn) ai 的數(shù)據(jù)域我們可以用 p->data 的值是一個(gè)數(shù)據(jù)元素,結(jié)點(diǎn) ai 的指針域可以用 p->next 來表示, p->next 的值是一個(gè)指針。那么 p->next 指向 ai+1 的指針。

4. 單鏈表的讀取

獲得鏈表第 i 個(gè)數(shù)據(jù)的算法思路:

1)聲明一個(gè)結(jié)點(diǎn) p 指向鏈表第一個(gè)結(jié)點(diǎn),初始化 j 從1 開始;

2)當(dāng) j<i 時(shí),就遍歷鏈表,讓 p 的指針向后移動(dòng),不斷指向一下結(jié)點(diǎn), j+1 ;

3)若到鏈表末尾 p 為空,則說明第 i 個(gè)元素不存在;

4)否則查找成功,返回結(jié)點(diǎn) p 的數(shù)據(jù)。

(注:由于這個(gè)算法的時(shí)間復(fù)雜度取決于 i 的位置,當(dāng)i=1 時(shí),則不需要遍歷,而 i=n 時(shí)則遍歷 n-1 次才可以。因此最壞情況的時(shí)間復(fù)雜度為 O(n) 。見例2)

//例2.
/* 初始條件:順序線性表L已存在,1<=i<=ListLength(L) /
/
操作結(jié)果:用e返回L中第i個(gè)數(shù)據(jù)元素的值 */
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;
}

5. 單鏈表的插入

單鏈表的插入

單鏈表第 i 個(gè)數(shù)據(jù)插入結(jié)點(diǎn)的算法思路:(見例3.)

1)聲明一結(jié)點(diǎn) p 指向鏈表頭結(jié)點(diǎn),初始化 j 從 1 開始;

2)當(dāng) j<1 時(shí),就遍歷鏈表,讓 p 的指針向后移動(dòng),不斷指向下一結(jié)點(diǎn), j 累加 1 ;

3)若到鏈表末尾 p 為空,則說明第 i 個(gè)元素不存在;

4)否則查找成功,在系統(tǒng)中生成一個(gè)空結(jié)點(diǎn) s ;

5)將數(shù)據(jù)元素 e 賦值給 s->data ;

6)單鏈表的插入剛才兩個(gè)標(biāo)準(zhǔn)語句;

7)返回成功。

//例3.
/* 初始條件:順序線性表L已存在,1<=i<=ListLength(L) /
/
操作結(jié)果:在L中第i個(gè)位置之前插入新的數(shù)據(jù)元素e,L的長度加1 /
Status ListInsert(LinkList * L, int i, ElemType e) /
注:這里的LinkList表示的是“ struct Node* LinkList”*/
{
int j;
LinkList p, s;
p = *L;
j = 1;
while( p && j<i ) // 用于尋找第i個(gè)結(jié)點(diǎn)
{
p = p->next;
j++;
}
if( !p || j>i )
{
return ERROR;
}
s = (LinkList)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
(注意:必須先“s->next = p->next;”, 再“p->next = s;”。 因?yàn)槿绻葓?zhí)行“ p->next = s”的話“ p->next ”會(huì)先被覆蓋為 “s” 的地址,那么” s->next = p->next “其實(shí)就等于 “s->next = s” 了。)

6. 單鏈表的刪除

單鏈表的刪除

單鏈表第 i 個(gè)數(shù)據(jù)刪除結(jié)點(diǎn)的算法思路:(見例4)

1)聲明結(jié)點(diǎn) p 指向鏈表第一個(gè)結(jié)點(diǎn),初始化 j=1 ;

2)當(dāng) j<1 時(shí),就遍歷鏈表,讓 P 的指針向后移動(dòng),不斷指向下一個(gè)結(jié)點(diǎn), j 累加1 ;

3)若到鏈表末尾 p 為空,則說明第 i 個(gè)元素不存在;

4)否則查找成功,將欲刪除結(jié)點(diǎn) p->next 賦值給 q ;

5)單鏈表的刪除標(biāo)準(zhǔn)語句 p->next = q->next ;

6)將 q 結(jié)點(diǎn)中的數(shù)據(jù)賦值給 e ,作為返回;

7)釋放 q 結(jié)點(diǎn)。

//例4
/* 初始條件:順序線性表L已存在,1<=i<=ListLength(L) /
/
操作結(jié)果:刪除L的第i個(gè)數(shù)據(jù)元素,并用e返回其值,L的長度-1 */
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;
}

7. 單鏈表的整表創(chuàng)建

7.1 概念:鏈表的各個(gè)元素分布在在內(nèi)存各個(gè)角落的,他的增長也是動(dòng)態(tài)的。對(duì)于鏈表來說,它所占用空間的大小和位置是不需要預(yù)先分配劃定的,可以根據(jù)系統(tǒng)的情況和實(shí)際的需求即時(shí)生成。

創(chuàng)建單鏈表的過程是一個(gè)動(dòng)態(tài)生成鏈表的過程,從“空表”的初始狀態(tài)起,依次建立各元素結(jié)點(diǎn)并逐個(gè)插入鏈表。所以單鏈表整表創(chuàng)建的算法思路如下:

1)聲明一結(jié)點(diǎn) p 和計(jì)數(shù)器變量 i ;

2)初始化一空鏈表 L ;

3)讓 L 的頭結(jié)點(diǎn)的指針指向 NULL ,即建立一個(gè)帶頭結(jié)點(diǎn)的單鏈表;

4)循環(huán)實(shí)現(xiàn)后繼結(jié)點(diǎn)的賦值和插入。

7.2 單鏈表創(chuàng)建

單鏈表整表創(chuàng)建有兩種方式:1)頭插法;2)尾插法

1)頭插法

頭插法從一個(gè)空表開始,生成新結(jié)點(diǎn),讀取數(shù)據(jù)存放到新結(jié)點(diǎn)的數(shù)據(jù)域中,然后將新結(jié)點(diǎn)插入到當(dāng)前鏈表的表頭上,直到結(jié)束為止。簡單來說,就是把新加進(jìn)的元素放在表頭后的第一個(gè)位置:(見例5)

(1)先讓新節(jié)點(diǎn)的 next 指向頭節(jié)點(diǎn)的next;(例5還提供一個(gè)版本是沒有頭結(jié)點(diǎn),即第一個(gè)節(jié)點(diǎn)即為數(shù)據(jù)節(jié)點(diǎn))

(2)然后讓頭節(jié)點(diǎn)的 next 指向新節(jié)點(diǎn)。(沒有頭結(jié)點(diǎn)的話,頭指針直接指向最后一個(gè)插入的數(shù)據(jù)節(jié)點(diǎn)。)

//例5
/#include<stdio.h>
/#include<stdlib.h>
typedef void Status;
typedef int Elemtype;
typedef struct node
{
Elemtype data;
struct node *next;
}Node;
Status Visit(Node *node)
{
if(node)
{
printf("%d", node->data);
}
}
Status Traversal(Node **node)
{
Node *target;
target = *node;
while(target)
{
Visit(target);
target = target->next;
}
}
Status InitLinkList(Node * * node)
{
*node = (Node )malloc(sizeof(Node));
( * node)->next = NULL;
}
Status CreateLinkList(Node * * node, int i) //有頭結(jié)點(diǎn)版本
{
Node * temp;
int j;
( * node)->data = i; //頭結(jié)點(diǎn)存儲(chǔ)鏈表長度
for(j = 1; j <= i; j++)
{
(temp) = (Node * )malloc(sizeof(Node));
(temp)->data = j;
(temp)->next = (
node)->next;
( * node)->next = (temp);
}
}
Status CreateLinkList2(Node **node, int i) //無頭結(jié)點(diǎn)版本
{
Node temp;
int j;
if(i >= 1)
{
( * node)->data = 1;
for(j = 2; j <= i; j++)
{
temp = (Node * )malloc(sizeof(Node));
temp->data = j;
temp->next = (
node);
( * node) = temp;
}
}
else
{
printf("The LinkList is empty!");
}
}
Status Clear(Node *node)
{
Node * target, temp;
target = ( * node)->next;
while(target)
{
//temp = target->next;
//free(target);
//target = temp; //上下兩種都可以
temp = target;
target = target->next;
free(temp);
}
( * node)->next = NULL; //保留頭結(jié)點(diǎn)
}
void main()
{
Node * node;
InitLinkList(&node);
CreateLinkList(&node, 10);
Traversal(&node);
printf("\n");
//InitLinkList(&node); //可以采用初始化鏈表方式重新創(chuàng)建鏈表node
Clear(&node); //也可以采用清空原鏈表方式
CreateLinkList2(&node, 11); /
無頭結(jié)點(diǎn)版本即將頭結(jié)點(diǎn)數(shù)據(jù)域直接覆蓋為首節(jié)點(diǎn)的數(shù)據(jù)。
/
Traversal(&node);
}
//輸出:
10 10 9 8 7 6 5 4 3 2 1 //有頭結(jié)點(diǎn)
11 10 9 8 7 6 5 4 3 2 1 //無頭結(jié)點(diǎn)

(注意:空鏈表必須有頭結(jié)點(diǎn),因?yàn)橛泄?jié)點(diǎn)才為鏈表;而非空鏈表可以有頭結(jié)點(diǎn),也可以沒有頭結(jié)點(diǎn)。)

2)尾插法

尾插法就是將新元素按照先來后到的順序依次插入鏈表的尾部,見例6.

//例6
/#include<stdio.h>
/#include<stdlib.h>
typedef void Status;
typedef int Elemtype;
typedef struct node
{
Elemtype data;
struct node *next;
}Node;
Status Visit(Node *node)
{
if(node)
{
printf("%d ", node->data);
}
}
Status Traversal(Node **node)
{
Node *target;
target = *node;
while(target)
{
Visit(target);
target = target->next;
}
}
Status InitLinkList(Node **node)
{
*node = (Node *)malloc(sizeof(Node));
( * node)->next = NULL;
}
Status CreateLinklist(Node **node, int i) //有頭結(jié)點(diǎn)
{
Node *target, *temp;
int j;
target = *node;
target->data = i;
for(j = 1; j <= i; j++)
{
temp = (Node *)malloc(sizeof(Node));
temp->data = j;
target->next = temp;
target = target->next;
}
}
Status CreateLinklist2(Node **node, int i) //無頭結(jié)點(diǎn)
{
Node *target, *temp;
int j;
target = *node;
for(j = 1; j <= i; j++)
{
if(j == 1)
{
target->data = j; //頭結(jié)點(diǎn)直接被覆蓋為首節(jié)點(diǎn)
}
else
{
temp = (Node *)malloc(sizeof(Node));
temp->data = j;
target->next = temp;
target = target->next;
}
}
}
void main()
{
Node *node;
InitLinkList(&node);
CreateLinklist(&node, 10);
Traversal(&node);
printf("\n");
InitLinkList(&node);
CreateLinklist2(&node, 11);
Traversal(&node);
}
//輸出:
10 1 2 3 4 5 6 7 8 9 10 //有頭結(jié)點(diǎn)
1 2 3 4 5 6 7 8 9 10 11 //無頭結(jié)點(diǎn)

7. 單鏈表的整表刪除

單鏈表整表刪除的算法思路如下:(見例7.)

1)聲明結(jié)點(diǎn) p 和 q ;

2)將第一個(gè)結(jié)點(diǎn)賦值給 p ,下一結(jié)點(diǎn)賦值給 q ;

3)循環(huán)執(zhí)行釋放 p 和將 q 賦值給 p 的操作。

//例7
Status Clear(Node **node)
{
Node * p, * q;
p = ( * node)->next;
while(p)
{
q= p->next;
free(p);
p = q;
}
( * node)->next = NULL; //保留頭結(jié)點(diǎn)
}
(可見實(shí)例5中的Clear函數(shù))

8. 單鏈表結(jié)構(gòu)與順序存儲(chǔ)結(jié)構(gòu)優(yōu)缺點(diǎn)

分別從存儲(chǔ)分配方式、時(shí)間性能、空間性能三方面來做對(duì)比。

8.1 存儲(chǔ)分配方式:

1)順序存儲(chǔ)結(jié)構(gòu)用一段連續(xù)的存儲(chǔ)單元依次存儲(chǔ)線性表的數(shù)據(jù)元素。

)2)單鏈表采用鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),用一組任意的存儲(chǔ)單元存放線性表的元素。

8.2 時(shí)間性能:

1)查找

? 順序存儲(chǔ)結(jié)構(gòu) O(1)

? 單鏈表 O(n)

2)插入和刪除

? 順序存儲(chǔ)結(jié)構(gòu)需要平均移動(dòng)表長一半的元素,時(shí)間復(fù)雜度為 O(n)

? 單鏈表在計(jì)算出某位置的指針后,插入和刪除時(shí)間僅為 O(1)

8.3 空間性能:

1)順序存儲(chǔ)結(jié)構(gòu)需要預(yù)分配存儲(chǔ)空間,分大了,容易造成空間浪費(fèi),分小了,容易發(fā)生溢出。

2)單鏈表不需要分配存儲(chǔ)空間,只要有就可以分配,元素個(gè)數(shù)也不受限制。

8.4 綜上所述對(duì)比,我們得出一些經(jīng)驗(yàn)性的結(jié)論:

1)若線性表需要頻繁查找,很少進(jìn)行插入和刪除操作時(shí),宜采用順序存儲(chǔ)結(jié)構(gòu)。

2)若需要頻繁插入和刪除時(shí),宜采用單鏈表結(jié)構(gòu)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,527評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,656評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,853評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,103評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,487評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,815評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容