通過前面的學習知道,具有“一對一”邏輯關系的數據,最佳的存儲方式是使用線性表。那么,什么是線性表呢?
采用線性表將其儲存到物理空間中。
首先,用“一根線兒”把它們按照順序“串”起來,如圖 2 所示:

圖 2 數據的"線性"結構
圖 2 中,左側是“串”起來的數據,右側是空閑的物理空間。把這“一串兒”數據放置到物理空間,我們可以選擇以下兩種方式,如圖 3 所示

圖 3 兩種線性存儲結構
圖 3a) 是多數人想到的存儲方式,而圖 3b) 卻少有人想到。我們知道,數據存儲的成功與否,取決於是否能將數據完整地復原成它本來的樣子。如果把圖 3a) 和圖 3b) 線的一頭扯起,你會發現數據的位置依舊沒有發生改變。因此可以認定,這兩種存儲方式都是正確的。
將具有“一對一”關系的數據“線性”地存儲到物理空間中,這種存儲結構就稱為線性存儲結構(簡稱線性表)。
使用線性表存儲的數據,如同向 數組中存儲數據那樣,要求數據類型必須一致,也就是說,線性表存儲的數據,要么全不都是整形,要么全部都是字符串。一半是整形,另一半是字符串的一組數據無法使用線性表存儲。
將具有“一對一”關系的數據“線性”地存儲到物理空間中,這種存儲結構就稱為線性存儲結構(簡稱線性表)。
使用線性表存儲的數據,如同向 數組中存儲數據那樣,要求數據類型必須一致,也就是說,線性表存儲的數據,要么全不都是整形,要么全部都是字符串。一半是整形,另一半是字符串的一組數據無法使用線性表存儲。
順序存儲結構和鏈式存儲結構
圖 3 中我們可以看出,線性表存儲數據可細分為以下 2 種: 也就是說,線性表存儲結構可細分為順序存儲結構和鏈式存儲結構。前驅和后繼
數據結構中,一組數據中的每個個體被稱為“數據元素”(簡稱“元素”)。例如,圖 1 顯示的這組數據,其中 1、2、3、4 和 5 都是這組數據鍾的一個元素。另外,對於具有“一對一”邏輯關系的數據,我們一直在用“某一元素的左側(前邊)或右側(后邊)”這樣不專業的詞,其實線性表中有更准確的術語:
- 某一元素的左側相鄰元素稱為“直接前驅”,位於此元素左側的所有元素都統稱為“前驅元素”;
- 某一元素的右側相鄰元素稱為“直接后繼”,位於此元素右側的所有元素都統稱為“后繼元素”;

圖 4 前驅和后繼
順序表,全名順序存儲結構,是
線性表的一種,線性表用於存儲邏輯關系為“一對一”的數據,順序表自然也不例外。
不僅如此,順序表對數據的物理存儲結構也有要求。順序表存儲數據時,會提前申請一整塊足夠大小的物理空間,然后將數據依次存儲起來,存儲時做到數據元素之間不留一絲縫隙。
例如,使用順序表存儲集合
圖 1 順序存儲結構示意圖
由此我們可以得出,將“具有 '一對一' 邏輯關系的數據按照次序連續存儲到一整塊物理空間上”的存儲結構就是順序存儲結構。
通過觀察圖 1 中數據的存儲狀態,我們可以發現,順序表存儲數據同 數組非常接近。其實,順序表存儲數據使用的就是數組。
不僅如此,順序表對數據的物理存儲結構也有要求。順序表存儲數據時,會提前申請一整塊足夠大小的物理空間,然后將數據依次存儲起來,存儲時做到數據元素之間不留一絲縫隙。
例如,使用順序表存儲集合
{1,2,3,4,5}
,數據最終的存儲狀態如
圖 1 所示:

圖 1 順序存儲結構示意圖
由此我們可以得出,將“具有 '一對一' 邏輯關系的數據按照次序連續存儲到一整塊物理空間上”的存儲結構就是順序存儲結構。
通過觀察圖 1 中數據的存儲狀態,我們可以發現,順序表存儲數據同 數組非常接近。其實,順序表存儲數據使用的就是數組。
順序表的初始化
使用順序表存儲數據之前,除了要申請足夠大小的物理空間之外,為了方便后期使用表中的數據,順序表還需要實時記錄以下 2 項數據:- 順序表申請的存儲容量;
- 順序表的長度,也就是表中存儲數據元素的個數;
提示:正常狀態下,順序表申請的存儲容量要大於順序表的長度。
因此,我們需要自定義順序表,C 語言實現代碼如下
typedef struct Table{ int * head;//聲明了一個名為head的長度不確定的數組,也叫“動態數組” int length;//記錄當前順序表的長度 int size;//記錄順序表分配的存儲容量 }table;
注意,head 是我們聲明的一個未初始化的動態數組,不要只把它看做是普通的指針。
接下來開始學習順序表的初始化,也就是初步建立一個順序表。建立順序表需要做如下工作:
- 給 head 動態數據申請足夠大小的物理空間;
- 給 size 和 length 賦初值;
因此,C 語言實現代碼如下:
#define Size 5 //對Size進行宏定義,表示順序表申請空間的大小 table initTable(){ table t; t.head=(int*)malloc(Size*sizeof(int));//構造一個空的順序表,動態申請存儲空間 if (!t.head) //如果申請失敗,作出提示並直接退出程序 { printf("初始化失敗"); exit(0); } t.length=0;//空表的長度初始化為0 t.size=Size;//空表的初始存儲空間為Size return t; }
我們看到,整個順序表初始化的過程被封裝到了一個函數中,此函數返回值是一個已經初始化完成的順序表。這樣做的好處是增加了代碼的可用性,也更加美觀。與此同時,順序表初始化過程中,要注意對物理空間的申請進行判斷,對申請失敗的情況進行處理,這里只進行了“輸出提示信息和強制退出”的操作,可以根據你自己的需要對代碼中的 if 語句進行改進。
通過在主函數中調用 initTable 語句,就可以成功創建一個空的順序表,與此同時我們還可以試着向順序表中添加一些元素,C 語言實現代碼如下:
#include<stdio.h> #include<stdlib.h> #define Size 5 //定義一個順序表 typedef struct table{ int *head;//用於定義數組頭地址 int length;//記錄當前順序表的長度 int size;//記錄順序表的存儲容量 }table; //初始化順序表 table initTable() { table t; t.head = (int*)malloc(Size * sizeof(int));//動態申請存儲空間 if (!t.head) { printf("順序表初始化是被失敗"); exit(0); } t.length = 0;//空表長度初始化為0 t.size = Size;//空表可以存儲元素個數 return t; } //輸出順序表中元素函數 void display(table t) { for (int i = 0; i < t.length;i++) { printf("%d ",t.head[i]); } printf("\n"); } int main() { table t = initTable(); //向順序表中添加元素 for (int i = 1; i <= Size;i++) { t.head[i - 1] = i; t.length++; } printf("順序表中存儲元素分別是:\n"); display(t); return 0; }
程序運行結果如下:
順序表中存儲的元素分別是:
1 2 3 4 5
可以看到,順序表初始化成功。
順序表插入元素
向已有順序表中插入數據元素,根據插入位置的不同,可分為以下 3 種情況:
- 插入到順序表的表頭;
- 在表的中間位置插入元素;
- 尾隨順序表中已有元素,作為順序表中的最后一個元素;
- 將要插入位置元素以及后續的元素整體向后移動一個位置;
- 將元素放到騰出來的位置上;
{1,2,3,4,5}
的第 3 個位置上插入元素 6,實現過程如下:
- 遍歷至順序表存儲第 3 個數據元素的位置,如圖 1 所示:

圖 1 找到目標元素位置
- 將元素 3 以及后續元素 4 和 5 整體向后移動一個位置,如圖 2 所示:

圖 2 將插入位置騰出
- 將新元素 6 放入騰出的位置,如圖 3 所示:

圖 3 插入目標元素
//插入函數,其中,elem為插入的元素,add為插入到順序表的位置 table addTable(table t,int elem,int add) { //判斷插入本身是否存在問題(如果插入元素位置比整張表的長度+1還大(如果相等,是尾隨的情況),或者插入的位置本身不存在,程序作為提示並自動退出) if (add>t.length+1||add<1) { printf("插入位置有問題"); return t; } //做插入操作時,首先需要看順序表是否有多余的存儲空間提供給插入的元素,如果沒有,需要申請 if (t.length==t.size) { t.head=(int *)realloc(t.head, (t.size+1)*sizeof(int)); if (!t.head) { printf("存儲分配失敗"); return t; } t.size+=1; } //插入操作,需要將從插入位置開始的后續元素,逐個后移 for (int i=t.length-1; i>=add-1; i--) { t.head[i+1]=t.head[i]; } //后移完成后,直接將所需插入元素,添加到順序表的相應位置 t.head[add-1]=elem; //由於添加了元素,所以長度+1 t.length++; return t; }
注意,動態數組額外申請更多物理空間使用的是 realloc 函數。並且,在實現后續元素整體后移的過程,目標位置其實是有數據的,還是 3,只是下一步新插入元素時會把舊元素直接覆蓋。
順序表刪除元素
從順序表中刪除指定元素,實現起來非常簡單,只需找到目標元素,並將其后續所有元素整體前移 1 個位置即可。后續元素整體前移一個位置,會直接將目標元素刪除,可間接實現刪除元素的目的。
例如,從
圖 4 順序表刪除元素的過程示意圖
因此,順序表刪除元素的 C 語言實現代碼為:
{1,2,3,4,5}
中刪除元素 3 的過程如圖 4 所示:

圖 4 順序表刪除元素的過程示意圖
因此,順序表刪除元素的 C 語言實現代碼為:
//刪除指定元素 table deltable(table t,int del) { //add表示順序表中刪除元素的位置 if (del>t.length|| del<1) { printf("被刪除的元素有誤"); exit(0); } //刪除操作 for (int i = del; i < t.length;i++) { t.head[i - 1] = t.head[i]; } t.length--; return t; }
順序表查找元素
順序表中查找目標元素,可以使用多種查找算法實現,比如說二分查找算法、插值查找算法等。
這里,我們選擇順序查找算法,具體實現代碼為:
//查找函數,其中,elem表示要查找的數據元素的值 int selectTable(table t,int elem){ for (int i=0; i<t.length; i++) { if (t.head[i]==elem) { return i+1; } } return -1;//如果查找失敗,返回-1 }
順序表更改元素
順序表更改元素的實現過程是:
- 找到目標元素;
- 直接修改該元素的值;
順序表更改元素的 C 語言實現代碼為
//更改函數,其中,elem為要更改的元素,newElem為新的數據元素 table amendTable(table t,int elem,int newElem){ int add=selectTable(t, elem); t.head[add-1]=newElem;//由於返回的是元素在順序表中的位置,所以-1就是該元素在數組中的下標 return t; }
以上是順序表使用過程中最常用的基本操作,這里給出本節完整的實現代碼:

#include<stdio.h> #include<stdlib.h> #define Size 5 //定義一個順序表 typedef struct table{ int *head;//用於定義數組頭地址 int length;//記錄當前順序表的長度 int size;//記錄順序表的存儲容量 }table; //初始化順序表 table initTable() { table t; t.head = (int*)malloc(Size * sizeof(int));//動態申請存儲空間 if (!t.head) { printf("順序表初始化是被失敗"); exit(0); } t.length = 0;//空表長度初始化為0 t.size = Size;//空表可以存儲元素個數 return t; } //插入函數,elem:插入的元素 add:插入到順序表的位置 table addTable(table t, int elem,int add) { /*1,判斷插入本身是否存在問題 如果插入元素位置比整張表的長度+1還大,(如果相等,就是尾隨的情況) 或者插入位置本身不存在程序作為提示,自動退出*/ if (add > t.length + 1 || add < 1) { printf("插入位置有問題"); return t; } /*做插入時,首先要看順序表是否有多余的存儲空間提供給插入的元素, 如果沒有需要申請 */ if (t.length==t.size) { t.head = (int*)realloc(t.head,(t.size+1)*sizeof(int)); if (!t.head) { printf("存儲分配失敗"); exit(0); } t.size += 1; } //插入操作,需要從插入位置開始的后續元素,逐個后移 for (int i = t.length - 1; i >= add - 1;i--) { t.head[i + 1] = t.head[i]; } //后移完成后直接將所需元素,添加到順序表相應的位置 t.head[add - 1] = elem; //由於添加了元素,所以長度+1 t.length++; return t; } //刪除指定元素 table deltable(table t,int del) { //add表示順序表中刪除元素的位置 if (del>t.length|| del<1) { printf("被刪除的元素有誤"); exit(0); } //刪除操作 for (int i = del; i < t.length;i++) { t.head[i - 1] = t.head[i]; } t.length--; return t; } //查找元素,elem表示要找的數據元素的值 int findByElem(table t,int elem) { for(int i=0;i < t.length;i++){ if (t.head[i]==elem) { return i + 1; } } return -1;//如果查找失敗返回-1 } //更改順序表中某個元素 table updateTable(table t,int oldElem,int newElem) { int n = findByElem(t,oldElem);//找出old元素所在位置 t.head[n - 1] = newElem;//由於返回的是元素在順序表中的位置,所以-1就是元素在數組的下標 return t; } //輸出順序表中元素函數 void display(table t) { for (int i = 0; i < t.length;i++) { printf("%d ",t.head[i]); } printf("\n"); } int main() { table t = initTable(); //向順序表中添加元素 for (int i = 1; i <= Size;i++) { t.head[i - 1] = i; t.length++; } printf("原順序表中存儲元素順序是:\n"); display(t); table t1=addTable(t,8,2); printf("添加元素后是:\n"); display(t1); t1=deltable(t1,2); printf("刪除add位上的元素后:\n"); display(t1); int n=findByElem(t1,4); printf("查找元素4的位置:%d \n",n); t1 = updateTable(t1,1,9); printf("元素1修改為9后遍歷:\n"); display(t1); return 0; }
程序運行結果為:
總結:
順序表的短板
插入元素,時間復雜度 O(n)
插入為 第 i 個 元素,則需要移動 n - i +1 個數據元素. 需要移動 第 n 到第 i 個 元素。
均值的計算: 一共為 (n+1)(n+0) / 2 ,因為一共計算插了 n+1 個位置。則均值為 : n / 2

刪除元素,時間復雜度 O(n)
刪除 第 i 個 元素,則需要移動 n - i 個數據元素 。 需要移動 第 i+ 1 到 第 n 個 元素。
均值的計算:一共為 (n)(n-1+0) / 2 ,因為一共計算刪除 n 個位置。則均值為 : (n-1) / 2

小提示
1、一般在實際開發時,為了盡量避免移動元素的開銷,都會使用貼近硬件的API去完成內存數據的移動,而不是使用循環。例如使用memmove函數。
2、當內部數組的容量不夠時,需要重新調整數組的大小,上面的例子我們使用了realloc函數去實現,且每次增加20。然而我們必須認識到,調整大小是很銷耗資源的一個操作,因此在實際開發時,我們必須做出明智的容量增長策略。例如:Java中的ArrayList每次將容量擴展為原來的1.5倍。