日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【数据结构链表】之五单链表

發布時間:2025/3/14 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【数据结构链表】之五单链表 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一:鏈表基礎

1、鏈表基礎----------------摘錄自百度百科

鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的。鏈表由一系列結點(鏈表中每一個元素稱為結點)組成,結點可以在運行時動態生成。每個結點包括兩個部分:一個是存儲數據元素的數據域,另一個是存儲下一個結點地址的指針域。 相比于線性表順序結構,操作復雜。由于不必須按順序存儲,鏈表在插入的時候可以達到O(1)的復雜度,比另一種線性表順序表快得多,但是查找一個節點或者訪問特定編號的節點則需要O(n)的時間,而線性表和順序表相應的時間復雜度分別是O(logn)和O(1)。 使用鏈表結構可以克服數組鏈表需要預先知道數據大小的缺點,鏈表結構可以充分利用計算機內存空間,實現靈活的內存動態管理。但是鏈表失去了數組隨機讀取的優點,同時鏈表由于增加了結點的指針域,空間開銷比較大。鏈表最明顯的好處就是,常規數組排列關聯項目的方式可能不同于這些數據項目在記憶體或磁盤上順序,數據的存取往往要在不同的排列順序中轉換。鏈表允許插入和移除表上任意位置上的節點,但是不允許隨機存取。鏈表有很多種不同的類型:單向鏈表,雙向鏈表以及循環鏈表。鏈表可以在多種編程語言中實現。像Lisp和Scheme這樣的語言的內建數據類型中就包含了鏈表的存取和操作。程序語言或面向對象語言,如C,C++和Java依靠易變工具來生成鏈表。 ? 2、鏈表特點 線性表的鏈式存儲表示的特點是用一組任意的存儲單元存儲線性表的數據元素(這組存儲單元可以是連續的,也可以是不連續的)。因此,為了表示每個數據元素與其直接后繼數據元素 之間的邏輯關系,對數據元素 來說,除了存儲其本身的信息之外,還需存儲一個指示其直接后繼的信息(即直接后繼的存儲位置)。由這兩部分信息組成一個"結點",表示線性表中一個數據元素。線性表的鏈式存儲表示,有一個缺點就是要找一個數,必須要從頭開始找起,十分麻煩,不能像數組那樣隨機訪問。。 對于非線性的鏈表,可以參見相關的其他數據結構,例如樹、圖。另外有一種基于多個線性鏈表的數據結構:跳表,插入、刪除和查找等基本操作的速度可以達到O(nlogn),和平衡二叉樹一樣。 其中存儲數據元素信息的域稱作數據域(設域名為data),存儲直接后繼存儲位置的域稱為指針域(設域名為next)。指針域中存儲的信息又稱做指針或鏈。 由分別表示,,…,的N 個結點依次相鏈構成的鏈表,稱為線性表的鏈式存儲表示,由于此類鏈表的每個結點中只包含一個指針域,故又稱單鏈表或線性鏈表。 ? 3、循環鏈表、雙鏈表、單鏈表和順序表 循環鏈表是與單鏈表一樣,是一種鏈式的存儲結構,所不同的是,循環鏈表的最后一個結點的指針是指向該循環鏈表的第一個結點或者表頭結點,從而構成一個環形的鏈。 循環鏈表的運算與單鏈表的運算基本一致。所不同的有以下幾點: (1)在建立一個循環鏈表時,必須使其最后一個結點的指針指向表頭結點,而不是象單鏈表那樣置為NULL。此種情況還使用于在最后一個結點后插入一個新的結點。 (2)在判斷是否到表尾時,是判斷該結點鏈域(指針域)的值是否是表頭結點,當鏈域值等于表頭指針時,說明已到表尾。而非象單鏈表那樣判斷鏈域值是否為NULL。 雙向鏈表 雙向鏈表其實是單鏈表的改進。 當我們對單鏈表進行操作時,有時你要對某個結點的直接前驅進行操作時,又必須從表頭開始查找。這是由單鏈表結點的結構所限制的。因為單鏈表每個結點只有一個存儲直接后繼結點地址的鏈域,那么能不能定義一個既有存儲直接后繼結點地址的鏈域,又有存儲直接前驅結點地址的鏈域的這樣一個雙鏈域結點結構呢?這就是雙向鏈表。 在雙向鏈表中,結點除含有數據域外,還有兩個鏈域,一個存儲直接后繼結點地址,一般稱之為右鏈域;一個存儲直接前驅結點地址,一般稱之為左鏈域。 ? 4、鏈表包含以下特征:(1).由n個節點離散分配;(2).每個節點通過指針鏈連接(3)每一個節點由一個前驅節點和一個后驅節點(4).首節點沒有前驅節點,尾節點沒有后驅節點;滿足上面的4條,我們就稱為鏈表;鏈表既然由很多個節點,那節點又由什么組成?節點由兩個部分組成,一是數據域,用來存放有效數據;二是指針域,用來指向下一個節點 二、單鏈表 1、 ? ? ? ? ? ? 2、節點的構建 typedef ?struct Node { int data;//數據域 struct Node *pNext;//指針域 ??定義一個結構體指針,指向下一次個與當前節點數據類型相同的節點?? }Node,*pNode; 3、鏈表的創建

? ?在創建鏈表之前,我們需要需要了解一下專業術語:

? ?首節點:存放第一個有效數據的節點;

? ?尾節點:存放最后一個有效數據的節點;

? ?頭節點:頭節點的數據類型與首節點的數據類型相同,并且頭節點是首節點前面的那個節點,并不存放有效數據;頭節點的存在只是為了方便鏈表的插入和刪除等操作。

? ?頭指針:指向頭節點的指針;

? ?尾指針:指向尾節點的指針;

參考博客:http://blog.csdn.net/lpp0900320123/article/details/20356143 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

1、數據元素的存儲結構形式有兩種:順序存儲和鏈式存儲。
順序存儲結構:是把數據元素存放在地址連續的存儲單元里,其數據間的邏輯關系和物理關系是一致的。
鏈式存儲結構:是把數據元素存放在任意的存儲單元里,這組存儲單元可以是連續的,也可以是不連續的。
很顯然,這樣說的話鏈式存儲結構的數據元素存儲關系并不能反映其邏輯關系,因此需要用一個指針存放數據元素的地址,這樣子通過地址就可以找到相關聯數據元素的位置。
2、數據元素之間的關系:
(1)集合關系
(2)線形關系
(3)樹形關系
(4)圖形關系
3、
算法是解決特定問題求解步驟的描述,在計算機中表現為指令的有限序列,并且每條指令表示一個或多個操作。對于給定的問題,是可以有多種算法來解決的。
算法具有五個基本特征:輸入、輸出、有窮性、確定性和可行性。
(1)輸入
算法具有零個或多個輸入。
盡管對于絕大多數算法來說,輸入參數都是必要的。但是有些時候,像打印“I love fishc.com”,就不需要啥參數啦。
(2)輸出
算法至少有一個或多個輸出。
算法是一定要輸出的,不需要它輸出,那你要這個算法來干啥?
輸出的形式可以是打印形式輸出,也可以是返回一個值或多個值等。

(3)有窮性
指算法在執行有限的步驟之后,自動結束而不會出現無限循環,并且每一個步驟在可接受的時間內完成。
一個永遠都不會結束的算法,我們還要他來干啥?

(4)確定性
算法的每一個步驟都具有確定的含義,不會出現二義性。
算法在一定條件下,只有一條執行路徑,相同的輸入只能有唯一的輸出結果。
算法的每個步驟都應該被精確定義而無歧義。

(5)可行性
算法的每一步都必須是可行的,也就是說,每一步都能夠通過執行有限次數完成。

好的算法應該具備時間效率高和存儲量低的特點。

4、
算法效率一般是指算法的執行時間,判斷一個算法的效率時,函數中的常數和其他次要項常常可以忽略,而更應該關注主項(最高項)的階數。
5、
算法時間復雜度的定義:在進行算法分析時,語句總的執行次數T(n)是關于問題規模n的函數,進而分析T(n)隨n的變化情況并確定T(n)的數量級。
算法的時間復雜度,也就是算法的時間量度,記作:T(n)= O(f(n))。
它表示隨問題規模n的增大,算法執行時間的增長率和f(n)的增長率相同,稱作算法的漸近時間復雜度,簡稱為時間復雜度。其中f(n)是問題規模n的某個函數。

一般情況下,隨著輸入規模n的增大,T(n)增長最慢的算法為最優算法。

6、怎么推導大O階呢?
用常數1取代運行時間中的所有加法常數。
在修改后的運行次數函數中,只保留最高階項。
如果最高階項存在且不是1,則去除與這個項相乘的常數。
得到的最后結果就是大O階。

[線性階]

一般含有非嵌套循環涉及線性階,線性階就是隨著問題規模n的擴大,對應計算次數呈直線增長。

int i , n = 100, sum = 0;

for( i=0; i < n; i++ )
{
sum = sum + i;
}

上面這段代碼,它的循環的時間復雜度為O(n),因為循環體中的代碼需要執行n次。

[平方階]

剛才是單個循環結構,那么嵌套呢?

int i, j, n = 100;

for( i=0; i < n; i++ )
{
for( j=0; j < n; j++ )
{
printf(“I love FishC.comn”);
}
}

n等于100,也就是說外層循環每執行一次,內層循環就執行100次,那總共程序想要從這兩個循環出來,需要執行100*100次,也就是n的平方。所以這段代碼的時間復雜度為O(n^2)。

那如果有三個這樣的嵌套循環呢?

沒錯,那就是n^3啦。所以我們很容易總結得出,循環的時間復雜度等于循環體的復雜度乘以該循環運行的次數。


剛剛我們每個循環的次數都是一樣的,如果:

int i, j, n = 100;

for( i=0; i < n; i++ )
{
for( j=i; j < n; j++ )
{
printf(“I love FishC.comn”);
}
}

分析下,由于當i=0時,內循環執行了n次,當i=1時,內循環則執行n-1次……當i=n-1時,內循環執行1次,所以總的執行次數應該是:
n+(n-1)+(n-2)+…+1 = n(n+1)/2

大家還記得這個公式吧?恩恩,沒錯啦,就是搞死先生發明的算法丫。
那咱理解后可以繼續,n(n+1)/2 = n^2/2+n/2
用我們推導大O的攻略,第一條忽略,因為沒有常數相加。第二條只保留最高項,所以n/2這項去掉。第三條,去除與最高項相乘的常數,最終得O(n^2)。

[對數階]

對數,屬于高中數學內容啦,對于有些魚油可能對這玩意不大理解,或者忘記了,也沒事,咱分析的是程序為主,而不是數學為主,不怕。

int i = 1, n = 100;

while( i < n )
{
i = i * 2;
}

由于每次i*2之后,就距離n更近一步,假設有x個2相乘后大于或等于n,則會退出循環。
于是由2^x = n得到x = log(2)n,所以這個循環的時間復雜度為O(logn)。

7、

頭指針

頭指針是指鏈表指向第一個結點的指針,若鏈表有頭結點,則是指向頭結點的指針。

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

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

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

頭結點

頭結點是為了操作的統一和方便而設立的,放在第一個元素的結點之前,其數據域一般無意義(但也可以用來存放鏈表的長度)。

有了頭結點,對在第一元素結點前插入結點和刪除第一結點起操作與其它結點的操作就統一了。

頭結點不一定是鏈表的必須要素。

單鏈表圖例:

單鏈表圖例

?

空鏈表圖例:

空鏈表圖例

獲得鏈表第i個數據的算法思路:

聲明一個結點p指向鏈表第一個結點,初始化j從1開始;

當j<i時,就遍歷鏈表,讓p的指針向后移動,不斷指向一下結點,j+1;

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

否則查找成功,返回結點p的數據。

?

單鏈表的插入

?

我們先來看下單鏈表的插入。假設存儲元素e的結點為s,要實現結點p、p->next和s之間邏輯關系的變化,大家參考下圖思考一下:

單鏈表的插入

?

我們思考后發覺根本用不著驚動其他結點,只需要讓s->next和p->next的指針做一點改變。

s->next = p->next;

p->next = s;

?

我們通過圖片來解讀一下這兩句代碼。

單鏈表的插入

?

那么我們考慮一下大部分初學者最容易搞壞腦子的問題:這兩句代碼的順序可不可以交換過來?

先 p->next = s;

再 s->next = p->next;

?

大家發現沒有?如果先執行p->next的話會先被覆蓋為s的地址,那么s->next = p->next其實就等于s->next = s了。

所以這兩句是無論如何不能弄反的,這點初學者一定要注意咯~

?

單鏈表第i個數據插入結點的算法思路:

聲明一結點p指向鏈表頭結點,初始化j從1開始;

當j<1時,就遍歷鏈表,讓p的指針向后移動,不斷指向下一結點,j累加1;

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

否則查找成功,在系統中生成一個空結點s;

將數據元素e賦值給s->data;

單鏈表的插入剛才兩個標準語句;

返回成功。

?代碼示例:

單鏈表的刪除

?

現在我們再來看單鏈表的刪除操作。

單鏈表的刪除

?

假設元素a2的結點為q,要實現結點q刪除單鏈表的操作,其實就是將它的前繼結點的指針繞過指向后繼結點即可。

那我們所要做的,實際上就是一步:

可以這樣:p->next = p->next->next;

也可以是:q=p->next; p->next=q->next;

?

單鏈表第i個數據刪除結點的算法思路:

聲明結點p指向鏈表第一個結點,初始化j=1;

當j<1時,就遍歷鏈表,讓P的指針向后移動,不斷指向下一個結點,j累加1;

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

否則查找成功,將欲刪除結點p->next賦值給q;

單鏈表的刪除標準語句p->next = q->next;

將q結點中的數據賦值給e,作為返回;

釋放q結點。

?

效率PK

?

我們最后的環節是效率PK,我們發現無論是單鏈表插入還是刪除算法,它們其實都是由兩個部分組成:第一部分就是遍歷查找第i個元素,第二部分就是實現插入和刪除元素。

從整個算法來說,我們很容易可以推出它們的時間復雜度都是O(n)。

?

再詳細點分析:如果在我們不知道第i個元素的指針位置,單鏈表數據結構在插入和刪除操作上,與線性表的順序存儲結構是沒有太大優勢的。

但如果,我們希望從第i個位置開始,插入連續10個元素,對于順序存儲結構意味著,每一次插入都需要移動n-i個位置,所以每次都是O(n)。

?

而單鏈表,我們只需要在第一次時,找到第i個位置的指針,此時為O(n),接下來只是簡單地通過賦值移動指針而已,時間復雜度都是O(1)。

顯然,對于插入或刪除數據越頻繁的操作,單鏈表的效率優勢就越是明顯啦~

?三:單鏈表的頭插法和尾插法:指當鏈表新增一個節點是插入到每一個之前還是之后。 ?頭插法是插入到第一個節點(有可能是頭結點,也有可能是首節點)之前,尾插法是插入到最后一個節點之后。 頭插法是新增節點總是插在頭部,以帶頭節點鏈表為例,鏈表頭指針是Head,新增節點p,則: p->next=Head->next; Head->next=p 如果是不帶頭結點的鏈表對應: p->next=Head; Head=p; 而尾插法是將新增節點插在鏈表尾部 for(t=Head;t->next;t=t->next); ? ? ? ? ? ? ? ? ? ? ? ? ? //結束時t指向尾節點 p->next=NULL; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//進行插入 t->next=p; ?

轉載于:https://www.cnblogs.com/wycBlog/p/7145931.html

總結

以上是生活随笔為你收集整理的【数据结构链表】之五单链表的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。