線性表就是元素排成一列的結構,能夠理解爲行軍隊伍,例如一字長蛇陣c++
隊中的成員除了第一我的外,每一個人前面都有一我的;算法
隊中的成員除了最後一我的外,每一個人後面都有一我的。數組
簡單來講就是一個排成一字的隊伍。函數
線性表按順序存儲和鏈式儲存兩種,順序存儲須要一塊連續的存儲空間,即爲順序表;而鏈式存儲對於空間則沒有要求,使用指針實現,只要有空間就能儲存,元素之間用指針指向下一個元素的地址。下面按照上圖依次介紹線性表內容。指針
有一天李華出去遊玩,到了晚上找了一個酒店住下,服務員告訴李華說,您的房間號是523,因而李華拿上房卡,上了五樓,找到23號房間,住下了。code
拿這個例子來講什麼是順序表?簡單來講,酒店的房間,按號順序排列,這些房間就構成了一個順序表!線性表的順序存儲就是順序表(後面會說到鏈表,即鏈式存儲)。例如說學生上體育課時按學號一個挨一個有順序的排列也是一個順序表。blog
在計算機中,順序表的存儲須要一組連續的存儲空間(注意是連續的存儲空間,下圖爲順序表的圖示),若是要表示順序表,用咱們學過的數組好像就能夠,那麼下面來寫出數組表示順序表的方法。其中第一個存儲空間(頭節點)通常不放數據,這樣數據就和數組的下標對應上了,找起來更加方便,例如第一個數據,就是數組的array[1];頭節點也不會空着浪費,通常能夠存放數組的長度等信息。it
#define maxSize 50 //定義maxSize的值爲50 typedef struct{ //定義順序表的結構體,起名叫sqList int data[maxSize]; //用數組來分配一塊連續的存儲空間,int表示順序表元素的類型 int length; //順序表的長度 }sqList; //結構體類型名sqList
以上就是用數組定義的順序表的結構體,接下來咱們就能夠對順序表進行操做(增刪改查)!拿順序表增長元素來講,你能夠往順序表裏放元素,可是存在一個問題,在用數組表示順序表時,在定義數組時,數組的長度就定下來了且後續不可修改,那麼若是順序表存滿了元素,我還要繼續添加元素的話,就會溢出。如何解決這個問題呢?class
答案是對順序表的空間採用動態分配,上面的用數組是靜態分配的方法,問題很明顯,那麼採用動態分配的方法,就能解決掉順序表滿時繼續添加元素致使添加溢出的問題。當存儲空間不夠時,動態的分配一塊更大的空間來替換之前的存儲空間,就不會有問題了。date
那麼如何動態分配存儲空間呢?c語言中給出了一個malloc()函數,這裏用int型舉例
int *pointer; //定義一個int指針pointer pointer = (int *)malloc(n * sizeof(int));//使用malloc函數分配n個int型的空間,並用pointer指向該空間
那麼動態分配順序表的結構體定義就以下所示
#define InitSize 100 //初始化表長度 typedef struct{ ElemType *data; //動態分配數組的指針 int MaxSize,length; //數組的最大容量和當前個數(即數組長度) }SeqList;
定義了順序表以後,下面說一說順序表的特色
一、根據順序表的圖示,可知順序表能夠隨機訪問,經過地址序號和元素序號就能夠在O(1)的時間找到指定的元素
二、順序表的存儲密度高,每個節點只存儲數據
三、元素之間彼此相鄰,當插入和刪除元素時須要移動大量的元素
元素的插入位置如圖箭頭所示均爲合法操做,即插入位置 i 的取值爲 0 <= i <= L.length;若頭節點不存儲元素,則第一個箭頭的位置不能插入,i 的取值爲 1 <= i <= L.length;
假設增長的元素插入位置在 y 和 h 之間,則 h 以後的元素須要所有後移一格,若是刪除元素 h ,則 h 以後的元素須要所有前移一格,這個就是順序表的缺點,即增長刪除元素須要移動大量的元素;下面繼續說插入元素的操做,元素插入位置合法操做成功,返回 1 ;操做失敗,返回 0 ;
代碼以下
int insertElem(SqList &L,int p,int e){ //插入目標表L,插入位置p,插入元素e 【注1】 int i; if(p<0 || p>L.length || L.length==maxSize){ //插入位置錯誤或者表長達到最大值, return 0; //此時插入不成功,返回0 } for(i=L.length-1;i>=p;--i){ //插入成功的操做 L.data[i+1] = L.data[i]; //從表尾開始依次把元素後移一個位置 【注2】 L.data[p] = e; //元素後移以後將待插入元素放入空出的位置, ++(L.length); //插入完成,表長度增長1 return 1; //返回1 } }
代碼註釋中有兩個注,下面解釋一下
【注1】在方法傳入的參數中,傳入的順序表SqList &L;爲何表L前加了一個&符號?這個&符號在c++中表示引用型,具體的用法就是在操做表的時候,若是操做後的表和操做前的表不同,那麼傳入的表前需加引用型,例如增長,刪除,修改,表均發生了改變,這時就須要用引用型,而查詢操做,不改變表,就不須要加&符。
**【注2】**在插入元素對插入位置後的元素後移時,須要從後往前操做,最後的元素後移一個位置,倒數第二個元素再移動位置,依次從後往前操做,不可從前日後操做,否則前一個元素會把後一個元素覆蓋;而在刪除操做中,刪除的元素直接用後面的元素覆蓋掉就能夠完成刪除操做了,這時須要從前日後前移一格,操做就完成了,刪除後面講。
還記得咱們以前說過的時間複雜度嗎?他是衡量一個算法優劣的指標,如今咱們來分析一下該插入算法的時間複雜度。
最好狀況:當在表尾插入元素時,元素不須要移動,找到位置插入就完事了,很簡單,時間複雜度爲O(1);
最壞狀況:在表頭插入,全部表元素(n個元素)均要後移一個位置,後移的代碼語句要執行n次,時間複雜度爲O(n);
那麼在通常狀況下,在表的任意位置插入,時間複雜度是多少呢?
從表頭開始,在表頭插入,需移動 n 個元素(n爲表長);在第一個元素後插入,需移動 n-1 個元素;在第二個元素後插入,需移動 n-2 個元素;……;依次類推,在表尾插入,需移動n-n個元素,爲0,即不須要移動元素;
那麼在任意位置插入所需移動元素的平均次數即爲該算法的時間複雜度
插入移動的總次數爲 0+1+2+3+…+n = n(n+1)/2;
插入位置爲n+1個
時間複雜度爲計算結果爲n/2,故而爲O(n);
咱們在講時間複雜度時說過,時間複雜度是關於問題規模的函數,上式的結果就是一個關於 n 的函數,時間複雜度取決於表長即 n 的大小,和其前面的係數二分之一沒有關係,故而爲O(n);
關於時間複雜度計算出的結果及其大小比較以下所示,瞭解便可
刪除順序表 L 的第 i 個元素,成功返回 true ,失敗返回false;
其中i的取值爲0 <= i <= L.length
bool ListDelete(SqList &L,int i){ //刪除元素,表L要改變,故而使用引用型&符號 if(i<0 || i>L.length){ //判斷i的範圍是否合法,不合法返回false return false; } for(int j=i;j<L.length;j++){ //將第i個位置以後的元素前移 L.date[j-1] = L.data[j]; //同上文代碼 } L.length--; //刪除元素以後表長減一 return true; //操做成功返回true }
時間複雜度的計算同上
最好狀況:刪除表尾元素,無需移動元素,時間複雜度爲O(1);
最壞狀況:刪除表頭元素,除第一個元素外,其他元素均需前移,時間複雜度爲O(n);
平均狀況:刪除第i個位置
移動次數爲:0+1+2+3+…+(n-1)=n(n-1)/2;
移動的位置共有n個
故平均爲(n-1)/2,仍爲O(n);
以上是按位置刪除元素,讀者可自行寫出按值刪除元素的代碼
查詢順序表L中第一個元素值爲e的元素,並返回位序
int LocateElem(SqList L,ElemType e){ //查詢不改變表結構,不須要引用型 for(int i=0;i<L.length;i++){ if(L.data[i] == e){ return i+1; //下標爲i的元素,返回i+1 【注3】 }else return 0; } }
【注3】以前說過若是表頭元素不存儲,那麼表的下標和位序是同樣的,若是表頭存儲數據,那麼下標爲i,位序就爲i+1;
時間複雜度的計算
最好狀況:查找的元素就在表頭,比較一次就成功,時間複雜度爲O(1);
最壞狀況:查找元素在表尾,比較次數即爲表長n,時間複雜度爲O(n);
平均狀況:查找第i個位置
移動次數爲:1+2+3+…+n=n(n+1)/2;
移動的位置共有n個
故平均爲(n+1)/2,仍爲O(n);