順序表總結
1.線性表
零個或多個元素的有限序列。或者 線性表是由n個元素組成的有限序列。
我們都知道了線性表元素之間是有序的,一一對應的。下面是線性表的兩種實現。
1.2.順序表
物理儲存:用一段地址連續的存儲單元依次存儲線性表的數據元素。既然是連續內存,我們就可以想到數組。所以我們可以用數組來實現順序表。
重點注意:既然是用數組,我們都知道數組字定義時,必須聲明數組的大小。所以這里引出兩個概念。
數組長度: 我們在定義結構體時的數組申明大小
順序表長度: 定義變量后,變量存放數據的多少。
由此可以看出,順序表的長度一定小於等於數組長度。
線性表元素的序號和存放它的數組下標之間存在對應關系:由於我們數數都是從1開始數的,線性表的定義也不能避免,起始也是1,可C語言中的數組卻是從0開始的第一個下標,於是線性表的第i個元素是要存儲在數組下標為i-1的位置。
既然順序表有長度,是不是需要一個變量來記錄這個長度?
這樣我們的順序表的結構也就出來了。
struct list
{
int data[20]; //存放數據
/*--------------------------------------*/
Int len;; // 記錄順序表的長度
};
這里要注意的是
/*------------*/前面:是可以寫多個,任意類型的數據(一般多個數據用結構體來包裹起來,所以一般都是結構體類型)。
/*------------*/后面:的是記錄順序表的長度,也就是存儲元素的個數。
1.2.1順序表插入
我們都知道順序表的存儲是用數組實現的。
所以我們在插入的時候,需要數組元素往后移動。空一個位置來進行插入。
1. 從最后一個位置開始向前遍歷到第i個位置,也就是我們需要插入的位置。分別將他們向后移動一位。這樣就會空出一個位置來。
2. 我們在第i個位置來插入這個元素。
3. 不要忘記---->順序表長度加1
圖示:
圖 5-1 插入示意圖
在圖5-1中,紅色方框表示空出來的位置。我們需要在4這個位置,插入
注意增加的元素就是我們要刪除的元素。
代碼實現:
要注意數組下標和實際位置之間的關系。我們的循環i控制的是數組的下標。
for(i = list.len; i >= x; i--)
{
list.data[i] = list.dat[i-1];
}
List.data[x-1] = ??;//這里賦值,或者scanf輸入data如果是結構體,就給其成員變量分別來賦值。
List.len++;
1.2.2 順序表的刪除
在執行刪除操作時,我們只需將數組從刪除的位置,向前移動就可以了。
1. 從刪除的位置開始,向后遍歷,直到最后一個元素位置。分別將他們向前移動一個位置。
2. 不要忘記----->順序表長度減1
圖示:
圖 5-2 刪除示意圖
在圖5-2中紅色數字表示要刪除的數字。我們把位置為5的元素刪除。
注意刪除元素后,順序表的長度減1
代碼實現:
for(i = x-1; i < list.len; i++)
{
list.data[i] = list.dat[i+1];
}
List.len--;
1.2 單鏈表
物理存儲:鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的。
鏈表由一系列結點(鏈表中每一個元素稱為結點)組成,結點可以在運行時動態生成。每個結點包括兩個部分:一個是存儲數據元素的數據域,另一個是存儲下一個結點地址的指針域。
由此我們可以總結出來鏈表的結構體這也就是所謂的節點:
struct linklist
{
Int data;
struct linklist *next;
};
typedef struct linklist * linklist; // 這里的linklist一般取名為我們實際鏈表的名稱這
在這里我們需要特別注意的是頭節點和頭指針之間的區別。當然還有最后一個節點的指針指向NULL。
頭指針:
//TODO
頭結點:
//TODO
1.2.1單鏈表的插入
圖 5 - 3 單鏈表的插入
如圖所示:要將S節點插入。此時我要注意的是不能將p 后面的元素丟失
代碼實現:
1. S -> next = P -> next; // 注意p后面的元素不能丟失,所以先讓P后的元素掛到s上去。2. P -> next = s; // 然后讓P和 s建立關系
1.2.2 單鏈表的刪除
圖 5 -4 單鏈表刪除
如圖所示:我們要刪除的是p后的一個元素。我們只要把p的next域指向他的下下個元素。
代碼實現:
p->next = p->next->next;
//TODO...
1.3 順序表和單鏈表的比較
1.3.1 順序表特點:
- 邏輯上相鄰的數據元素,物理存儲位置也相鄰,並且,順序表的存儲空間需要預先分配
- 存儲空間連續,即允許元素的隨機訪問。
- 存儲密度大,內存中存儲的全部是數據元素。
- 要訪問特定元素,可以使用索引訪問,時間復雜度為 O(1)。
- 要想在順序表中插入或刪除一個元素,都涉及到之后所有元素的移動,因此時間復雜度為 O(n)。
優點:
ü 存取速度高效,通過下標來直接存儲,隨機訪問的特點。
ü 方法簡單,各種高級語言中都有數組,容易實現。
ü 不用為表示節點間的邏輯關系而增加額外的存儲開銷。
缺點:
- 在順序表中做插入、刪除操作時,平均移動表中的一半元素,因此對n較大的順序表效率低。
- 需要預先分配足夠大的存儲空間,估計過大,可能會導致順序表后部大量閑置;預先分配過小,又會造成溢出。
1.3.2 單鏈表特點
長度不固定,可以任意增刪。
存儲空間不連續,數據元素之間使用指針相連,每個數據元素只能訪問周圍的一個元素(根據單鏈表還是雙鏈表有所不同)。
存儲密度小,因為每個數據元素,都需要額外存儲一個指向下一元素的指針(雙鏈表則需要兩個指針)。
要訪問特定元素,只能從鏈表頭開始,遍歷到該元素,時間復雜度為 O(n)。
在特定的數據元素之后插入或刪除元素,不涉及到其他元素的移動,因此時間復雜度為 O(1)。雙鏈表還允許在特定的數據元素之前插入或刪除元素。
優點:
ü 插入和刪除速度快,保留原有的物理順序,比如:插入或者刪除一個元素時,只需要改變指針指向即可
缺點:
- 要占用額外的存儲空間存儲元素之間的關系,存儲密度降低。存儲密度是指一個節點中數據元素所占的存儲單元和整個節點所占的存儲單元之比。
- 鏈表不是一種隨機存儲結構,不能隨機存取元素。
三、順序表與鏈表的優缺點切好相反,那么在實踐應用中怎樣選取存儲結構呢?通常有以下幾點考慮:
(1)順序表的存儲空間是靜態分配的,在程序執行之前必須明確規定它的存儲規模,也就是說事先對“MaxSize”要有合適的設定,設定過大會造成存儲空間的浪費,過小造成溢出。因此,當對線性表的長度或存儲規模難以估計時,不宜采用順序表。然而,鏈表的動態分配則可以克服這個缺點。鏈表不需要預留存儲空間,也不需要知道表長如何變化,只要內存空間尚有空閑,就可以再程序運行時隨時地動態分配空間,不需要時還可以動態回收。因此,當線性表的長度變化較大或者難以估計其存儲規模時,宜采用動態鏈表作為存儲結構。
但在鏈表中,除數據域外海需要在每個節點上附加指針。如果節點的數據占據的空間小,則鏈表的結構性開銷就占去了整個存儲空間的大部分。當順序表被填滿時,則沒有結構開銷。在這種情況下,順序表的空間效率更高。由於設置指針域額外地開銷了一定的存儲空間,從存儲密度的角度來講,鏈表的存儲密度小於1.因此,當線性表的長度變化不大而且事先容易確定其大小時,為節省存儲空間,則采用順序表作為存儲結構比較適宜。
(2)基於運算的考慮(時間)
順序存儲是一種隨機存取的結構,而鏈表則是一種順序存取結構,因此它們對各種操作有完全不同的算法和時間復雜度。例如,要查找線性表中的第i個元素,對於順序表可以直接計算出a(i)的的地址,不用去查找,其時間復雜度為0(1).而鏈表必須從鏈表頭開始,依次向后查找,平均需要0(n)的時間。所以,如果經常做的運算是按序號訪問數據元素,顯然順表優於鏈表。
反之,在順序表中做插入,刪除時平均移動表中一半的元素,當數據元素的信息量較大而且表比較長時,這一點是不應忽視的;在鏈表中作插入、刪除,雖然要找插入位置,但操作是比較操作,從這個角度考慮顯然后者優於前者。
(3)基於環境的考慮(語言)
順序表容易實現,任何高級語言中都有數組類型;鏈表的操作是基於指針的。相對來講前者簡單些,也用戶考慮的一個因素。
結論:
總之,兩種存儲結構各有長短,選擇哪一種由實際問題中的主要因素決定。通常“較穩定”的線性表,即主要操作是查找操作的線性表,適於選擇順序存儲;而頻繁做插入刪除運算的(即動態性比較強)的線性表適宜選擇鏈式存儲。