線性表在采用不同的存儲結構時,它的描述方法是不一樣的,那么它的基本操作實現方法也截然不同。下面來看線性表在順序存儲下,也就是順序表的每一個基本操作的具體實現方法以及編寫方法。
插入操作
還是上一個例子,一群朋友去吃火鍋。此時有一個女生過來了,她叫小紅。小紅是小綠的女朋友,當然想和小綠坐在一起,但是小綠旁邊是沒有空位置的。所以小綠麻煩他旁邊的朋友小黃和小黑依次移動了一個位置。這樣就空出了一個位置,小紅就可以過來坐下了。這樣就完成了一個類似順序表插入的過程。
代碼實現
知道了順序表插入的大致過程,來看它用程序語言是如何編寫的。首先畫出了一個順序表
其中數據元素是連續存放的。接着標出了存放該順序表數據元素的數組下標。注意一下,數組下標是從 0 開始的,而順序表的元素標號是從 1 開始的。接着用 C++ 寫出了該插入操作的函數體:
bool ListInsert(SqList &L, int i, ElemType e) {
if(i<1 || i>L.length+1)
return false;
if(L.length >= MaxSize)
return false;
for(int j=L.length; j>=i; j--)
L.data[j] = L.data[j-1];
L.data[i-1] = e;
L.length++;
return true;
}
它的返回值類型是一個布爾類型,表示如果插入成功會返回一個 True ,插入失敗會返回一個 False。它有三個參數,分別是一個引用類型的順序表 L,這里為什么用引用類型?因為在函數體內部進行操作,其實是作用一個局部變量上的,不會真正作用到這個參數上,如果不用引用類型,是不能把插入操作真正作用到傳入的順序表上的。接着是一個整型變量 i,它表示的是插入的位置,要注意在采用插入操作時,往往是前插法,這是一個默認規定。還有一點要注意的就是,i 對應的是順序表的表號而非數組的下標。最后一個參數是所插入的數據元素 e。
首先第一個條件 i<1 || i>L.length+1
,順序表的元素標號是從 1 開始的,i 表示的是插入的位置,如果插入的位置是 0 或者更小,又或者比 L.length+1
還大,都是不合法的。第二個條件判斷的是插入的數組是否有足夠空間去插入,因為數組不可以增加容量,一旦滿了就沒用新的空間去提供插入了。循環語句中申請了變量 j 初始化為順序表的長度,j 進行減一操作,一直到 j=i
的時候中止循環。循環體中 L.data[j] = L.data[j-1]
的意思就是把每一個數據元素向后移了一位,一直移動到 ai 移動到原先 ai+1 的位置。 執行完了所有的循環,空出了一個位置用於插入,L.data[i-1] = e
就是把要插入的元素放到該位置。注意,在 i 的位置,元素對應的下標是 i-1。最后將順序表的長度加一。
時間復雜度
再來看一下這個程序的時間復雜度,當循環次數最少時,它就是最好的時間復雜度,什么時候循環次數最少呢?發現當在順序表的尾部,也就是 L.length+1
的時候,這時候不用進行元素的移動,它的循環次數是 0,所以它的最好時間復雜度是 O(1) ,因為它的語句頻度是常數級別的。
接着來看平均時間復雜度,平均時間復雜度是在所有情況等概率的情況下,計算它的期望。這里的等概率,一定是合法的情況。所以 i 一共有 n+1 個合法位置,n 個數據元素,加上第 n 個數據元素之后的那個位置,都可以進行插入。所以每一個插入等概率的情況下,它的概率是:
接着根據概率的知識,可以用每一個位置的概率乘以它循環的次數,它是從尾部一直循環到 i ,所以循環了 n-i+1
次,根據等差數列求和公式可以計算:
計算結果與 n 同階無窮大,所以它的平均時間復雜度為 O(n) 。
最壞就是循環次數最多,即每一個數據元素都要向后移動一位,也就是插入在第一個位置時是最壞的情況,所以要循環 n 次,所以最壞情況下的時間復雜度是 O(n) 。
刪除操作
還是一群小伙伴在等候區等待吃火鍋,這時小綠被他的女朋友叫回去一起學習數據結構,那么小綠要走了,小綠走了之后就空出了一個位置。在等候區中間是不可以有空的位置的,所以說小黃和小黑兩個人會依次向前移一位。
這也就是刪除操作的大致過程。接着來看它的代碼實現。
代碼實現
bool ListDelete(SqList &L, int i, ElemType &e) {
if(i<1 || i>L.length)
return false;
e = L.data[i-1]
for(int j=i; j<L.length; j++)
L.data[j-1] = L.data[j];
L.length--;
return true;
}
函數的返回值依舊是一個布爾類型。它有三個參數,分別是一個引用類型的順序表 L,一個整型變量 i 表示刪除的位置,一個引用類型的數據元素 e ,這里使用引用類型也是因為在函數體內部進行操作,是作用在一個局部變量上的,不會真正作用到這個參數 e 上,所以說使用引用類型來將所刪除的這個數據元素真正的返回到函數外部。
第一個條件是判斷刪除的位置是否合法。接着 e = L.data[i-1]
是在保存要刪除的數據元素,將 i
位置的數據元素,對應下標 i-1
,賦值給 e
。然后循環從 i
開始,每次進行加一操作,一直循環到 L.length-1
為止,也就是從 i
一直循環到 n-1
,將 ai 賦值給 ai+1 ,也就是將數組下標為 i
的賦值給 i-1
的,這樣就將 ai 之后的所有數據元素都向前移了一位。接着將順序表的長度減一。
時間復雜度
最好的時間復雜度是刪除最后一個元素,它的最好時間復雜度是 O(1) 。
所有合法的刪除位置有 n 個,因為有 n 個數據元素。所以每一個合法位置的概率都是 1/n
,它的循環次數是從 i 循環到 L.length-1
,所以是 n-i
次,所以它的期望是 (n-1)/2
,它與 n 同階無窮大,所以它的平均時間復雜度為 O(n) 。
刪除第一個數據元素循環的次數最多,此時需要循環 n-1 次,與 n 同階無窮大,因此最壞時間復雜度為 O(n) 。
按值查找
需要找到等候區的小綠,等候區是這樣依次坐下的。於是依次進行查找,從第一個位置開始,一直找到第三個位置,發現他是小綠。
這樣的查找過程,就與按值查找的函數十分相似。
代碼實現
int Locate(SqList &L, ElemType e) {
int i;
for(i=0; i<L.length; i++)
if(L.data[i] == e)
return i+1;
return 0;
}
這個函數的返回值是一個整型變量,它表示返回的是一個順序表的下標。兩個參數分別是查找的順序表以及查找的值。所有對輸入參數進行修改的操作,都使用引用類型。所有進行查找的操作都不會使用引用類型。
首先是一個整型變量 i ,它用來存放最終的輸出結果。接着是一個循環,變量 i 從下標 0,也就是第一個元素開始,每一次循環進行加一操作,一直循環到 L.length-1 ,也就是下標 n-1 的位置,一共執行了 n 次。循環體的內容是一個判斷語句,判斷此時數據元素是否為要找的那個元素,如果相等返回當前順序表的下標,也就是 i+1。最后 return 0 表示循環結束了,還沒有找到對應的這個值的位置,那么就說明順序表當中是沒有該值的,此時就返回一個 return 0 表示查找失敗。
時間復雜度
最好的時間復雜度依舊是循環的次數最少,也就是第一個數據元素就是要找的那個值,所以最好的時間復雜度為 O(1) 。
一共有 n 個元素,查找概率是 1/n
,查找次數是 n-i
次,它的期望是 (n+1)/2
,它與 n 同階無窮大,所以它的平均時間復雜度為 O(n) 。
最后一個元素被找到是最壞的情況,循環了 n 次,因此最壞時間復雜度為 O(n) 。