第二章 線性表
文章目錄
2.1 線性表的概念及抽象數據類型定義
定義:線性表(Linear List)是由 n (n ≥ 0)個類型相同的數據元素a1, a2, …, an組成的有限序列, 記做( a1, a2…, ai-1, ai,ai+1, …, an) 。
解釋:數據元素之間是一對一的關係, 即每個數據元素最多有一個直接前驅和一個直接後繼 。
特點:
- 同一性:數據元素類型相同。
- 有窮性:有限。
- 有序性:線性表中相鄰數據元素之間存在着序偶關係<ai, ai+1> 。
ADT:
ADT LinearList{
數據元素: D={ai| ai∈ D0, i=1, … , n, n ≥ 0, D0爲某一數據對象}
關係: S ={ <ai, ai+1> | ai, ai+1∈ D0, i=1,2, …,n-1}
基本操作:
(1) InitList(L) 操作前提: L爲未初始化線性表。
操作結果:將L初始化爲空表。
(2) DestroyList(L) 操作前提:線性表L已存在。
操作結果:將L銷燬。
(3) ClearList(L) 操作前提:線性表L已存在 。
操作結果:將表L置爲空表。
………
}ADT LinearList
2.2 線性表的順序存儲
2.2.1 線性表的順序存儲結構
順序存儲結構的定義
線性表的順序存儲是指用一組地址連續的存儲單元依次存儲線性表中的各個元素,使得線性表中在邏輯結構上相鄰的數據元素存儲在相鄰的物理存儲單元中,通過數據元素物理存儲的相鄰關係來反映數據元素之間邏輯上的相鄰關係,採用順序存儲結構的線性表通常稱爲順序表。(數組)
順序存儲結構的C語言定義
#define maxsize = 線性表可能達到的最大長度
typdef struct
{
Elemtype elem[maxsize];//線性表佔用的數組空間
int last;//記錄最後一個元素在數組elem中的位置(這裏是個下標值,空表爲-1)
}SeqList;
用圖像表示:
2.2.2 線性表順序存儲結構上的基本運算
一、查找
- 按照序號查找
- 按照內容查找
三步:判斷合法—找—返回值(或者沒找到)
二、插入
找到插入的地方,之後的元素依次向後移動。
四步:判斷插入是否合法—表是否滿了—移動—插入
int InsList(SeqList * L,int i,ElemType e)
{
int k;
if( (i<1) || (i>L->last+2) ) /*首先判斷插入位置是否合法*/
{
printf(“插入位置i值不合法” );
return(ERROR);
}
if(L -> last >= maxsize - 1)
{
printf(“表已滿無法插入” );
return(ERROR);
}
for(k = L -> last; k >= i-1; k--) /*爲插入元素而移動位置*/
L->elem[k+1]=L->elem[k];
L->elem[i-1] = e; /*在C語言中數組第i個元素的下標爲i-1*/
L->last++;
return 0;
} /*算法時間複雜度爲O(n),平均時間複雜度E(n) = n/2*/
三、刪除操作
刪了過後後面的前移,返回刪除值
int DelList(SeqList *L,int i,ElemType *e)
/*在順序表L中刪除第i個數據元素, 並用指針參數e返回其值*/
{
int k;
if((i<1)||(i>L->last+1))
{
printf(“刪除位置不合法! ” );
return(ERROR);
}
*e = L->elem[i-1]; /* 將刪除的元素存放到e所指向的變量中*/
for(k=i;i<=L->last;k++)
{
L->elem[k-1] = L->elem[k]; /*將後面的元素依次前移*/
L->last--;
return(OK);
}/*時間複雜度爲O(n),平均時間複雜度爲E(n)=(n-1)/2
四、線性表合併操作
五、線性表順序儲存結構的優缺點
-
優點:
無需爲表示節點間的邏輯關係而怎加額外的存儲空間;
可以隨機的存取表中任意一個元素。
-
缺點
插入刪除不方便效率低
只能靜態存儲
2.3 線性表的鏈式存儲
2.3.1單鏈表
基本思路圖示:
定義一個單鏈表的存儲結構描述:
typedef struct Node
{
ElemType data;
struct Node *next;
}Node, * LinkList;
//Node 是一個存儲結構,LinkList是指針類型
2.3.2單鏈表上的基本運算
一、初始化單鏈表
InitList(LinkList *L)
{
*L = (LinkList) malloc(size of (Node));
(*L)->next=Null;
}
二、操作
1.頭插法
Linklist CreateFromHead (LinkList L)
{
Node *s; int flag=1;//設置一個標誌,當輸入"$"時,flag改爲0,建表結束。
char c;
L = (Linklist)malloc(sizeof(Node));//頭節點分配空間
L->next = NULL;
while(flag)
{
c=getchar();
if(c != '$')
{
s = (Node*)malloc(sizeof(Node));
s->data = c;
s->next = L->next;
L->next = s;
}
else
flag = 0;
}
}
2.尾插法
Linklist CreateFromTail (LinkList L)
{
Node *s, *r;//r指向鏈表的表尾
int flag=1;//設置一個標誌,當輸入"$"時,flag改爲0,建表結束。
char c;
L = (Linklist)malloc(sizeof(Node));//頭節點分配空間
L->next = NULL;
while(flag)
{
c=getchar();
if(c != '$')
{
s = (Node*)malloc(sizeof(Node));
s->data = c;
r->next = s;
r = s;
}
else
{
flag = 0;
r->next = NULL;
}
}
}
3.查找
按照結點查找
Node* Get(LinkList L,int i)
{
Node *p;
p = L;
int j = 0; //掃描計數器
while((p = p->next != NULL) && (j < i))
{
p = p->next;
j++
}
if(i = j)
return p;
else
return NULL;
}
按照內容查找
Node* Get(LinkList L,ElemType key)
{
Node *p;
p = L->next;
while(p !=NULL)
if (p->data != key)
p = p->next;
else break;
retrun p;
}
4.插入
void InsList(LinkList L, int i, Elemtype e)
{
Node *pre, *s;
pre = L;
int k = 0;
//首先找到第i-1個元素的位置,並且用*pre指向它
while(pre != NULL && k < i-1)
{
pre = pre->next;
k++;
}
//然後插入
if(k != i-1)
{
printf("插入位置不可用!");
return ERROR;
}
s = (Node*)malloc(sizeof(Node));
s->data = e;
s->next = pre->next;
pre->next = s;
return OK;
}
5.刪除
void Dellist(LinkList L, int i, ElemType *e)//刪除第i個節點並且保存其值到*e中
{
Node *p, *r;
p = L;
int k = 0;
while (p->next != NULL && k < i-1)
{
p = p->next;
k++;
}
if(k != i-1)
{
printf("刪除節點不合理");
return ERROR;
}
r = p->next;
p -> next = r->next;
free(r);
return OK;
}
6.求鏈表長度
遍歷一遍搞個計數器就完事。
7.單鏈表合併
LinkList MergeLinkList(LinkList LA, LinkList LB)
{
Node *pa, *pb, *r;
LinkList LC;
//講LC初始化爲空表。pa,pb分別指向兩個單鏈表LA,LB的第一個結點,r初始值爲LC
pa = LA->next;
pb = LB->next;
LC = LA;
LC->next = NULL;
r = LC;
//產生新表
while(pa != NULL && pb != NULL)
{
if(pa->data <= pb->data)
{
r->next = pa;
r = pa;
pa = pa->next;
}
else
{
r->next = pb;
r = pb;
pb = pb->next;
}
}
//當某個表讀完
if(pa)
r->next = pa;
else
r->next = pb;
//釋放內存返回(注意:只用釋放B表,LA的頭節點還在使用)
free (LB);
return LC;
}
2.3.3循環鏈表
一、初始化循環鏈表
什麼是循環鏈表
最後一個節點的指針域不是指向NULL而是指向頭節點。這樣子形成了一個圈圈。
初始化
InitCList(LinkList *CL)
{
*CL = (LinkList) malloc(size of (Node));
(*CL)->next = *CL;
}
二、操作
1.建立循環單鏈表
void CreateCLinkList(LinkList CL)//CL是已經初始化的頭指針
{
Node *rear, *s;
char c;
rear = CL;
c = getchar();
while(c != "$")
{
s = (Node*)malloc(sizeof(Node));
s->data = c;
rear->next = s;
rear = s;
c = getchar();
}
rear->next = CL;//最後一個節點指向頭節點
}
2.合併循環單鏈表
和合並鏈表相似,但是尾部不是指向NULL
而是頭節點。
2.3.4雙向鏈表
一、定義
typedef struct DNode
{
ElemType data;
struct DNode *prior, *next;
}DNode, *DoubleList;
兩個相鄰結點滿足如下關係:
p->prior->next == p;
p == p->next->prior;
二、操作
1,插入
算法思想:
書上是以1,2,3,4的順序插入的,然而我覺得1,3,2,4更加合理。
2,刪除
算法思想:
利用p將兩邊指針修改然後free就好了
2.3.5靜態鏈表
靜態鏈表是提前申請一些空間,用遊標(cursor)來標記位置,相當於用結構數組在模擬指針。一般用於不支持malloc(); free();
的語言。
2.4 一元多項式的表示及相加
2.5 順序表與鏈表綜合比較
1.基於空間考慮
- 順序表的存儲空間是靜態分配的,太大浪費空間太小容易爆炸。
- 線性表長度變化大,難以估計其存儲規模時,採用動態鏈表作爲存儲結構較好。
- 當線性表長度變化不大、易於事先確定其大小時,爲了節約空間宜用順序表。
2.基於時間考慮
- 線性表如果主要是查找,會比鏈表快。
- 頻繁刪除增加用鏈表。
- 插入刪除在首位的可採用帶守尾指針的單循環鏈表。
3.一個表格
2.6 總結與例題
2.6.1知識點
-
線性表特徵
-
線性表存儲方式
- 順序儲存(順序表)
- 鏈式存儲(鏈表)
-
鏈表操作特點
-
順序鏈操作技術
p = L;
p = p->next;
-
指針保留
在對第i個結點插入、刪除時,需要保留第i-1個結點的地址,pre
-
判斷表尾
非循環鏈表:p->next == NULL;
循環鏈表:p->next == head;
-