線性表
線性表的定義
定義:零個或多個數據元素的有限序列。
一個有順序的序列。第一個元素無前驅,最後一個元素無後繼。其他每個元素都只有一個前驅和一個後繼。
線性表的元素個數n定義爲線性表的長度n,當n=0的時候爲空表。在非空的數據表中每個元素的位置都是固定的。
線性表的抽象數據類型
ADT | 線性表 抽象數據類型 |
---|
DATA:線性表的數據對象的集合是a1,a2,…,an,每個數據的數據類型是DataType.
Operation
InitList(*L): 初始化操作,建立一個空的線性表。
ListEmpty(L): 若線性表爲空返回true否則就返回false
ClearList(*L):將線性表清空
GetElem(L,i,*e):把數據表中第i個元素返回給e
LocateElem(L,e):在線性表中查找與給定的e相等的元素,如果查找成功返回該元素在表中的序列號;否則返回0表示失敗
ListInsert(*L,i,e):在線性表中的第i個位置插入新元素e
ListDelete(*L,i,e):刪除線性表中的第i個位置元素,並用e返回其值
ListLength(L):返回線性表L的元素個數
下面舉一個簡單的例子:
把兩個線性表進行合併把僅存在集合B的元素插入到集合A:
void unionL(List *La,List Lb)
{
int La_len,Lb_len,i;
ElemType e;
La_len=ListLength(*La);
Lb_len=ListLength(Lb);
for(i=1;<Lb_len;i++)
{
GetElem(Lb,i,&e);//依次取出元素存入e
if(!LocateElem(*La,e));//如果有與e相同的元素
ListInsert(La,++La_len,e);//在La的末尾插入此相同元素
}
}
3.4線性表的順序存儲結構
3.4.2順序存儲的定義
線性表有兩種物理存儲結構,一種是順序存儲,一種是鏈式存儲結構;
順序存儲結構就是用一串地址連續的存儲單元依次存放線性表的數據元素。
3.4.2順序存儲方式
順序結構的存儲方式就是在內存中找了一塊區域把一串相同數據類型的數據元素存在這塊空地裏面。
順序表存儲結構代碼:
#define MAXSIZE 20
typedef int ElemType ;
typedef struct{
ElemType data[MAXSIZE];
int length;
}SqList;
這存儲空間有三個屬性
- 存儲空間的其實位置:數組data
- 存儲空間的最大長度:MAXSIZE
- 線性表的當前長度:length
3.4.3數據長度與線性表的長度區別·
數組的長度和線性表的長度,其中數組的長度在一開始就被定義了,是一個不變的量,而線性表的長度隨着數據的插入和刪除會相應的增加和減小。一般來說線性表的長度要小於等於數組的長度。
3.4.4地址的計算方法
線性表的順序是從一開始的,但是我們定義的數組是從0開始的下標。就是說現線性表種的第i個元素對應的是數組下標爲i-1的。在存儲器中每個存儲單元都有自己的編號,這個編號就叫做地址。這舉個例子來說明如何算當前的地址:
假設每個數據元素佔c個字節那麼線性表中第i個數據元素的地址和第i+1個數據元素地址之間有如下關係:(LOC表示獲取當前存儲位置的函數)
LOC(ai+1)=LOC(ai)+c
那麼第i個元素的位置就可以由第一個公式推算出:
LOC(ai)=LOC(a1)+(i-1)*c
由最後這個公式你可以很快的算出任何一個元素的地址,不管是第一個還是最後一個,都是相同的時間,所以對線性表的每個元素經行讀取和刪除操作的時候,時間是一樣的。在算法中的概念就是算法的時間複雜度是O[1]這一存儲結構由被稱爲隨機存儲結構。
3.5順序存儲結構的插入與刪除
3.5.1獲取元素操作
對於線性表的順序存儲結構我們進行GetElem操作:把順序表中的第i個元素返回。在c語言中就是把數組下標爲i-1的元素返回:
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
Status GetElem(SqList L,int i,ElemType *e)
{
if((L.length==0)||i<1||i>L.length)
return ERROR;
*e =L.data[i-1];
reuturn OK;
}
3.5.2插入操作
實現向線性表中插入一個元素,就是ListInsert(*L,i,e),向線性表的第i個位置插入元素e。
插入的思路:
- 如果插入位置不合理,就拋出異常
- 如果線性表長度大於數組長度就拋出異常或者動態增加內存
- 從最後一個位置向前遍歷到第i個位置,把他們都向後移動一個位
- 將要插入的元素放入第i個位置
- 表長加一
Status ListInsert(Sqlist *L,int i, ElemType e)
{
int k;
if(L->length==MAXSIZE)
return ERROR;
if (i<1||I>L->length-1)
return ERROR;
if(i<=L->length)
{
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;
}
}
3.5.3刪除操作
刪除的算法思路:
- 如果刪除位置不合理就拋出異常
- 取出刪除元素
- 從刪除的元素後每個元素向前加一
- 表長減一
具體代碼如下:
Status ListDelete(Sqlist *L,int i,ElemType *e)
{
int k;
if(L->length==0)
rreturn ERROR;c
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;
}
線性存儲的優缺點
線性表的順序存儲結構的優點與缺點:
-
優點
可以快速的存取表種任意位置的元素
無需位表示表中元素之間的存儲關係增加額外的空間 -
缺點
插入和刪除操縱要移動大量的元素
當線性表長度發生變化時難以確定存儲空間的容量
容易造成存儲空間的碎片化
線性表的鏈式存儲結構
線性表的鏈式存儲結構定義
定義:用一組任意的存儲空間來存儲線性表的數據元素,存儲單元可以是線性的,也可以是不線性的。(這些數據元素可以存在任意未被分配的內存空間中)
爲了表示每個元素與其後繼元素之間的關係除了存儲數據本身的信息外,還需要一個存儲一個指示其直接後繼元素的信息。
存儲元素信息的域成爲數據域
存儲直接後繼位置的域稱爲指針域
指針域中存儲的信息叫做指針或者鏈
這兩部分的信息組成數據元素ai的存儲映像,叫做結點
n個這樣的結點鏈接成鏈表,成爲線性表的鏈式存儲結構,由於每個結點的指針域中只包含一個指針信息,所以稱爲單鏈表。
對線性表來說肯定有頭和尾
頭:我們把鏈表中第一個結點的存儲地址當作頭指針
尾:我們規定線性表的最後一個結點的指針位NULL空指針。
爲了表示方便,我們常常在第一個結點前加上一個頭結點,頭結點不包含任何的數據,指針域包含一個頭指針指向第一個結點
3.6.4線性表的鏈式存儲結構的代碼實現
單鏈表,在c語言中用結構體就能進行很好的構建:
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;//定義LinkList
可以清楚的看到,結點由存放數據元素的數據與存放下一個結點地址的指針與構成。
假設p是指向線性表的第i個元素的指針,則該節點的的數據可以用p->data來表示
而下一個節點的數據表示爲:p->next->data;
3.7單鏈表的讀取
在線性表的順序存儲結構中,由於元素是按順序存在數組裏面的所以很容易知道任意一個元素在線性表中位置。但是當線性表是鏈式存儲結構,要想訪問第i個元素就必須從一開始查找。算法思路:
- 申明一個指針p指向鏈表的第一個結點,初始化j從第1開始
- j小於i的時候指針就不斷向後遍歷
- 若到鏈表末尾p爲空,則說明第i個結點不存在
- 否則查找成功,並且返回p指針的數據
具體實現代碼如下:
//用e返回L中第i個元素的值
Status GetElem (LinkList L,int e,ElemType *e)
{
int j;
LinkList p;
p=L->next;
j=1;
while(p&&j<i)//指針不爲空且j始終小於i
{
p=p-next;
j++;
}
if(!p||j>1)
return ERROR;
*e =p->data;
return OK;
}
此算法的時間複雜度取決於i的大小,最壞情況時間複雜度爲O[n],核心爲工作指針後移
3.8單鏈表的插入與刪除
3.8.1單鏈表的插入
單鏈表的插入,假設要,存儲單元e的結點爲s,要實現結點p,p->next和s之間的邏輯關係變化,只需要把s結點插入到p和p->next中,不許需要改變其他結點。
s->next=p->next;p->next=s;
讓p的後繼結點改成s的後繼結點再把結點s變成p的後繼結點。
- 指針p指向鏈表頭結點,初始化j從i開始
- 當j’<i的時候就遍歷鏈表。讓p的指針向後移動,不斷到下一結點,j++
- 若鏈表末尾p爲空則說明第i個結點不存在
- 否則查找成功,在系統中生成一個空結點s
- 將數據元素e賦值給s->data
- 單鏈表的插入標準語句s->next=p->next,p->next=s;
- 返回成功
代碼實現如下:
Status ListInsert(ListType *L,int i,ElemType e)
{
int j;
LinkList p,s;
p=*L;
j=1;
while(p&&j<i)
{
p=p->next;
++j;
}
if(!p||j>i)
{
s=(LinkList)malloc(sizeof(Node))//爲新生成的結點申請內存空間
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}
}
3.8.2單鏈表的刪除
這裏要做的就是把p->next=p->next->next現在用q代表p->next;
q=p->next;p->next=q->next;
就是把p的後繼結點變成p的後繼結點的後繼結點;
- 申明指針p指向表頭指針,j初始化爲1
- j<i就遍歷整個鏈表
- 若p指向空,則第i個結點不存在
- 否則查找成功,將預刪除的結點p->next賦值給q
- 單鏈表刪除元素標準語句p->next=q->next;
- 把q結點中的數據賦值給e作爲返回
- 釋放q結點
- 返回成功
代碼如下:
Status ListDelete()
{
int j;
LinkList p,q;
p=*L;
j=1;
while(p->next&&j<i)
{
p=p->next;
j++;
}
if(!(p->next)||j>i)
return ERROR;
q=p->next;
p->next=q->next;
*e =q->data;
free(q);
return OK;
}
由此可以看出,如果對插入刪除操作比較多的時候,用鏈表結構會比較有又是,如果只是查找某個元素的話,線性表的順序存儲結構會方便很多。
3.9單鏈表的整體創建
回顧之前的可得:線性表的順序存儲結構就是數組的創建,爲線性表的鏈式存儲結構就是一種動態結構,對於每個鏈表來說所佔據的空間大小和位置是不需要預先劃分的,根據需要即使生成。單鏈表創立的整體思路如下:
- 聲明一個指針p和計數器標量i
- 初始化一個空鏈表
- 讓L的頭結點指向NULL,建立一個代頭結點的空鏈表
- 循環:
生成一新結點賦值給p
隨機生成一數字賦值給p的數據域p->data
將p插入到頭節點與前一新節點之間
具體實現代碼如下:
void CreateListHead(LinkList *L,int n)
{
LinkList p;
int i;
srand(time(0));
*L=(LinkList)malloc(sizeof(Node));
(*L)->next =NULL;
for(i-0;i<n;i++)
{
p=(LinkList)malloc(sizeof(Node));
p->data =rand()%100+1;//隨機生成數字
p->next =(*L)->next;
(*L)->next=p;
}
}