1、線性表的定義:有零個或多個數(shù)據(jù)元素組成的有序數(shù)列。
線性表是一種常用的數(shù)據(jù)結(jié)構(gòu)。在實際應用中,線性表都是以棧、隊列、字符串、數(shù)組等特殊線性表的形式來使用的。由于這些特殊線性表都具有各自的特性,因此,掌握這些特殊線性表的特性,對于數(shù)據(jù)運算的可靠性和提高操作效率都是至關重要的。
線性表是一個線性結(jié)構(gòu),它是一個含有n≥0個結(jié)點的有限序列,對于其中的結(jié)點,有且僅有一個開始結(jié)點沒有前驅(qū)但有一個后繼結(jié)點,有且僅有一個終端結(jié)點沒有后繼但有一個前驅(qū)結(jié)點,其它的結(jié)點都有且僅有一個前驅(qū)和一個后繼結(jié)點。
特征:
- 集合中必存在唯一的一個“第一元素”;
- 集合中必存在唯一的一個 “最后元素” ;
- 除了第一個元素之外,均有唯一的前驅(qū)(前件)。
- 除了最后一個元素之外,均有唯一的后繼(后件);
注意:
線性表強調(diào)是有限的,事實上無論計算機發(fā)展到多強大,它處理的元素個數(shù)是有限的。
用數(shù)學語言來定義:
若將線性表記為(a1,…,ai-1,ai,ai+1,…,an)
,則表中ai-1
領先于ai,ai
領先于ai+1
,稱ai-1
是ai
的直接前驅(qū)元素,ai+1
是ai
的直接后繼元素。當i = 1,2,…,n-1
時,ai
有且僅有一個直接后繼,當i = 2 , 3 , … , n
時,ai
有且僅有一個直接前驅(qū)。
所以線性表元素的個數(shù)n(n ≥ 0)
定義為線性表長度,當n=0
時,稱為空表。
在非空表中的每個數(shù)據(jù)元素都有一個確定的位置,如a1
是第一個數(shù)據(jù)元素,an是最后一個數(shù)據(jù)元素,ai
是第i個數(shù)據(jù)元素,稱i為數(shù)據(jù)元素ai
在線性表中的位序。
2、線性表的抽象數(shù)據(jù)類型
數(shù)據(jù)類型 : 是指一組性質(zhì)相同的值的集合及定義在此集合上的一些操作的總稱。
例如很多編程語言的整型、浮點型、字符型這些指的就是數(shù)據(jù)類型。
抽象:是指抽取出事物具有的普遍性的本質(zhì)。它要求抽出問題的特征而忽略非本質(zhì)的細節(jié),是對具體事物的一個概括。抽象是一種思考問題的方式,它隱藏了繁雜的細節(jié)。
我們對已有的數(shù)據(jù)類型進行抽象,就有了抽象數(shù)據(jù)類型。
抽象數(shù)據(jù)類型(Abstract Data Type,ADT) 是指一個數(shù)據(jù)模型及定義在該模型上的一組操作。
抽象數(shù)據(jù)類型的定義僅取決于它的一組邏輯性,而與其在計算機內(nèi)部如何表示和實現(xiàn)無關。
線性表的抽象數(shù)據(jù)類型定義如下:
ADT 抽象數(shù)據(jù)類型名
Data 數(shù)據(jù)元素之間邏輯關系的名稱
Operation 操作
endADT
ADT 線性表(List)
Data
線性表的數(shù)據(jù)對象集合為{a1,a2,....,an},每個元素的類型均為DataType。其中除了,第一個元素a1外,每一個元素有且只有一個直接前驅(qū)元素,除最后一個元素an外,每一個元素有且只有一個直接后繼元素。數(shù)據(jù)元素之間的關系是一對一的關系。
Operation
InitList(*L):初始化操作,建立一個空的線性表。
ListEmpty(L):若線性表為空,返回true,否則返回false。
ClearList(\*L):線性表清空。
GetElem(L,i,\*e):將線性表L中第i個位置元素返回給e。
LocateElem(L,e):在線性表L中查找與給定值e相等的元素,如果
查找成功,返回該元素在表中的序列號;否則,返回0表示失敗。
ListInsert(\*L,i,e):在線性表的第i個位置插入元素e。
ListDelete(\*L,i,\*e):刪除線性表L中的第i個元素,并用e返回其值
ListLength(L):返回線性表L的元素個數(shù)。<br>
endADT
對于不同的應用,線性表的操作時不同的,上述操作時最基本的,問題中設計的關于線性表的更復雜操作,完全可以用這些基本操作的組合來實現(xiàn)。
比如,要實現(xiàn)兩個線性表集合A和B的并集操作。即要使得集合A = A ∪ B,說白了,就是把存在集合B中但并不存在中的數(shù)據(jù)元素插到A中即可。
假設我們La表示集合A,Lb表示集合B
示例如下:
//將所有的在線性表Lb中但不在La中的元素插入到La中
void unionL(List *La , List Lb)
{
int La_len,Lb_len,i;
ElemType e;
La_len = ListLength(*La);
Lb_len = ListLength(*Lb);
for(i = 0 ;i ≤ Lb;i++)
{
GetElem(Lb,i,*e);//取出Lb中第i個數(shù)據(jù)元素賦給e
if(!LocateElem(*La,e))//La中不存在和e元素相同的數(shù)據(jù)元素
{
ListInsert(La,++La_len,e);//插入
}
}
}
3、線性表的順序存儲結(jié)構(gòu)
線性表有兩種物理存儲結(jié)構(gòu):順序存儲結(jié)構(gòu)和鏈式存儲結(jié)構(gòu)。
3.1、順序存儲結(jié)構(gòu)定義
線性表的順序存儲結(jié)構(gòu),指定的是用一段地址連續(xù)的存儲單元一次存儲線性表的數(shù)據(jù)元素。
3.2、順序存儲結(jié)構(gòu)方式
線性表的順序存儲方式,說白了,就是在內(nèi)存中找了一塊地方,把一定內(nèi)存空間占了,然后把相同數(shù)據(jù)類型的數(shù)據(jù)元素一次存在在里面。既然線性表的數(shù)據(jù)元素的類型都相同,所以用C語言的一維數(shù)組來實現(xiàn)順序存儲結(jié)構(gòu),即把第一個數(shù)據(jù)元素存儲到數(shù)組下表為0的位置中,接著把線性表相鄰的元素存儲在數(shù)組中相鄰的位置。
為了建立一個線性表,要在內(nèi)存中找一塊地,于是這塊地的第一個位置就非常關鍵,它是存儲空間的起始位置。
線性表中,我們估算這個線性表的最大存儲容量,建立一個數(shù)組,數(shù)組的長度就是最大存儲容量。
我們已經(jīng)有了起始位置,也有了最大的容量,于是我們可以在里面增加數(shù)據(jù)了。隨著數(shù)據(jù)的插入,我們線性表的長度開始變大,不過線性表的當前長度不能超過存儲容量,即數(shù)組的長度。
接下來看線性表順序存儲的結(jié)構(gòu)代碼:
#define MAXSIZE 20
typedef int ElemType;
typedef struct
{
ElemType data[MAXSIZE];
int length; //線性表當前長度
}SqList;
順序存儲結(jié)構(gòu)封裝需要三個屬性:
- 存儲空間的起始位置,數(shù)組data,它的存儲位置就是線性表存儲空間的存儲位置。
- 線性表的中最大存儲容量,數(shù)組的長度MaxSize。
- 線性表的當前長度:length。
注意,數(shù)組的長度與線性表的當前長度需要區(qū)分一下:數(shù)組的長度是存放線性表的存儲區(qū)間的總長度,一般初始化后不變。而線性表的當前長度是線性表中元素的個數(shù),是會變化的。
3.3、數(shù)組長度與線性表長度區(qū)別
數(shù)組長度是存放線性表的存儲空間的長度,存儲空間分配完一般是不變的。
線性表長度是線性表中元素數(shù)據(jù)的個數(shù),隨著線性表插入和刪除操作的進行,這個量是變化的。
在任意時刻,線性表的長度應該小于等于數(shù)組的長度。
3.4、地址計算方法
線性表的起始是從1開始的,可數(shù)組卻是從0開始開始第一個下標的,于是線性表中第i個元素,存儲在數(shù)組小標為i-1的位置。
用數(shù)組存儲順序表意味著要分配固定長度的數(shù)組空間,由于線性表中可以進行插入和刪除操作,因此分配的數(shù)組空間要大于等于當前線性表的長度。
由于每個數(shù)組元素,不管它是整形、實型還是字符型,它都是需要占用一定的存儲空間。
假設占用的是c個存儲單元,那么線性表中第i+1個元素的存儲位置和第i個元素的存儲位置的關系是(LOC表示獲得存儲位置的函數(shù)):
LOC(ai+1) = LOC(ai) + c;
所以對于第i個數(shù)據(jù)元素ai的存儲位置可以由a1推算出:
LOC(ai) = LOC(ai) + (i-1)*c
通過這個公式,隨時可以算出線性表中任意位置的地址,不管他是第一個還是最后一個,都是相同的事件。那么我們對每個線性表位置的存入或者取出數(shù)據(jù),對于計算機來說都是相等的時間,也就是一個常數(shù),因此我們算法中學到的時間復雜度的概念來說,它的存取時間的性能為O(1)。我們通常把具有這一特點的存儲結(jié)構(gòu)稱為隨機存取結(jié)構(gòu)。
4、順序存儲結(jié)構(gòu)的插入與刪除
4.1、獲得元素操作
對于線性的順序存儲結(jié)構(gòu)來說,我們要實現(xiàn)GetItem操作,即將線性表L中的第i個位置元素返回,其實是非常簡單的。就程序而言,只要第i個元素在下標的范圍內(nèi),就是把數(shù)組第i-1個下標值返回即可。
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status
//Status是函數(shù)的類型,其值是函數(shù)結(jié)果狀態(tài)代碼,如OK等
//初始條件:順序線性表L已經(jīng)存在,1 <=i <= ListLength(L)
//操作結(jié)果:用e反回L中第i個元素的值。
Status GeElem(SqList L,int i,ElemType *e){
if(L.length==0 || i < 1 || i >L.length){
return ERROR;
}
*e = L.data[i-1];
return OK;
}
注意這里返回值類型Status是一個整型,返回OK代表1,ERROR代表0。
4.2、插入操作
線性表的順序存儲結(jié)構(gòu)具有隨機存儲結(jié)構(gòu)的特點,時間復雜度為O(1);
如果我們要實現(xiàn)ListInsert(*L,i,e),即在線性表L中的第i個位置插入新元素e,應該如何操作?
所以插入算法操作:
- 如果插入位置不合理,拋出異常;
- 如果線性表長度大于等于數(shù)組長度,則拋出異常或動態(tài)增加數(shù)組容量;
- 從最后一個元素開始向前遍歷到第i個位置,分別將它們都向后移動一個位置。
- 將要插入元素填入位置i處
- 線性表長+1
實現(xiàn)代碼如下:
//初始條件:順序線性表L已存在,1 ≤ i ≤ ListLength(L)
//操作結(jié)果:在L的第i個位置插入新的數(shù)據(jù)元素e,L的長度加1
Status ListInsert(SqList *L,int i,ElemType e)
{
int k;
if(L->length == MAXSIZE)//當線性表已滿
return ERROR;
if(i < 1 || i >L->length + 1)//當i不在范圍內(nèi)時
{
return ERROR;
}
if(i <= L->length)//若插入數(shù)據(jù)位置不在表尾
{
for(k = L->length-1;k > i-1;k--)
{
L->data[k + 1] = L->data[k];
}
}
L->data[i - 1] = e;//將新元素插入
L->length++;
return OK;
}
4.3、刪除操作
刪除算法的思路:
- 如果刪除位置不合理,拋出異常
- 取出刪除元素
- 從刪除元素位置開始遍歷到最后一個元素位置,分別將它們向前移動一個位置
- 表長減一
實現(xiàn)代碼如下:
//初始條件:順序線性表L已經(jīng)存在,1 <= i <= ListLength(L)
//操作結(jié)果:刪除L的第i個元素,并用e返回其值,L的長度減1
Status ListDelete(SqList *L ,int i , ElemType *e)
{
int k;
if(L->length == 0)//線性表為空
return ERROR;
if(i < 1 || i > L->length)//刪除位置不正確
return ERROR;
*e = L->data[i-1];
if(i < L->length)
{
for(k = i;k < L->length;k++)
L->data[k - 1] = L->data[k];
}
L->length--;
return OK;
}
現(xiàn)在,我們來分析一下,插入和刪除的事件復雜度。
現(xiàn)在我們來看最好的情況,如果一個元素要插入到最后一個位置,或者刪除最后一個位置,此時時間復雜度為O(1),因為不需要移動元素的。
最壞的情況呢,如果元素要插入到第一個位置或者刪除第一個元素,此時時間復雜度是多少呢?那就意味著所有元素向后或者向前,所以這個時間復雜度為O(n)。
至于平均的情況,由于元素插入到第i個位置,或者刪除第i個元素,需要移動n - i個元素,每個位置插入或刪除元素的可能性是相同的,也就是位置靠前,移動元素多,位置靠后,移動元素少。最終平均移動次數(shù)和最中間那個元素的移動次數(shù)相等,為(n - 1)/ 2。
根據(jù)時間復雜度的推導,平均時間復雜度還是O(n)。
這說明:
線性表的順序存儲結(jié)構(gòu),在存、讀數(shù)據(jù)時,不管是哪個位置,時間復雜度都是O(1);而插入或刪除時,時間復雜度都是O(n)。這就說明,它比較適合元素個數(shù)不太變化,而更多是存取數(shù)據(jù)的應用。
4.4、線性表順序存儲結(jié)構(gòu)的優(yōu)缺點
優(yōu)點:
無須為表中元素之間的邏輯關系而增加額外的存儲空間
可以快速地存取表中任一位置的元素
缺點:
插入和刪除需要移動大量元素
當線性表長度變化較大時,難以確定存儲空間的容量
造成存儲空間的“碎片”
特別感謝:
魚C工作室小甲魚