線性表的邏輯結構
- 定義:線性表是具有相同數據類型的n(n≥0)個數據元素的有限序列。其中n為表長。當n=0時 線性表是一個空表
- 特點:線性表中第一個元素稱為表頭元素;最后一個元素稱為表尾元素。
除第一個元素外,每個元素有且僅有一個直接前驅。
除最后一個元素外,每個元素有且僅有一個直接后繼。
線性表的順序存儲結構
-
線性表的順序存儲又稱為順序表。
它是用一組地址連續的存儲單元(比如C語言里面的數組),依次存儲線性表中的數據元素,從而使得邏
輯上相鄰的兩個元素在物理位置上也相鄰。 -
建立順序表的三個屬性:
1.存儲空間的起始位置(數組名data)
2.順序表最大存儲容量(MaxSize)
3.順序表當前的長度(length)
#define Maxsize 50 //定義線性表的最大長度
typedef int Elemtype // 假定表中的元素類型是整型
typedef struct{
Elemtype data [maxsize]; // 順序表中的元素
int lengh; // 順序表的類型定義
}Sqlist;
- 其實數組還可以動態分配空間,存儲數組的空間是在程序執行過程中通過動態存儲分配語句分配
typedef int Elemtype // 假定表中的元素類型是整型
typedef struct{
Elemtype *data; // 指示動態分配數組的指針
int Maxsize,lengh; // 數組的最大容量和當前個數
}Sqlist;
//C語言的動態分配語句為
#define InitSize 100
SeqList L;
L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize);
/*
注意:動態分配並不是鏈式存儲,同樣還屬於順序存儲結構,
只是分配的空間大小可以在運行時決定
*/
- 總結:
- 1.順序表最主要的特點是隨機訪問(C語言中基於數組),即通過首地址和元素序號可以在O(1)的時間內找到指定的元素。
- 2.順序表的存儲密度高,每個結點只存儲數據元素。無需給表中元素花費空間建立它們之間的邏輯關系(因為物理位置相鄰特性決定)
- 3.順序表邏輯上相鄰的元素物理上也相鄰,所以插入和刪除操作需要移動大量元素。
順序表的操作
-
1.插入
- 算法思路:
- 1.判斷i的值是否正確
- 2.判斷表長是否超過數組長度
- 3.從后向前到第i個位置,分別將這些元素都向后移動一位
- 4.將該元素插入位置i 並修改表長
- 代碼
- 算法思路:
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.lenth;j>i;j--)
L.data[j] = L.data[j-1];
L.data[i-1] = e;
L.length++;
return true;
}
-
分析:
- 最好情況:在表尾插入(即i=n+1),元素后移語句將不執行,時間復雜度為O(1)。
- 最壞情況:在表頭插入(即i=1),元素后移語句將執行
n次,時間復雜度為O(n)。 - 平均情況:假設pi(pi=1/(n+1) )是在第i個位置上插入
一個結點的概率,則在長度為n的線性表中插入一個結
點時所需移動結點的平均次數為
-
2.刪除
- 算法思路:
- 1.判斷i的值是否正確
- 2.取刪除的元素
- 3.將被刪元素后面的所有元素都依次向前移動一位
- 4.修改表長
- 代碼
- 算法思路:
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;
}
-
分析
- 最好情況:刪除表尾元素(即i=n),無須移動元素,時間復雜度為O(1)。
- 最壞情況:刪除表頭元素(即i=1),需要移動除第一個元素外的所有元素,時間復雜度為O(n)。
- 平均情況:假設pi(pi=1/n)是刪除第i個位置上結點的概率,則在長度為n的線性表中刪除一個結點時所需移動結點的平均次數為
線性表的鏈式存儲結構
- 線性表的鏈式存儲是指通過一組任意的存儲單元來存儲線性表中的數據元素。
typedef struct LNode{ // 定義單鏈表節點類型
Elemtype data; // 數據域
struct LNode *next; // 指針域
}LNode,*LinkList;
// 因為每個節點只有一個指針指向下一個結點,故又稱單鏈表
- 頭結點和頭指針的區別?
- 不管帶不帶頭結點,頭指針始終指向鏈表的第一個結點,而頭結點是帶頭結點鏈表中的第一個結點,結點內通常不存儲信息
- 為什么要設置頭結點?
- 1.處理操作起來方便 例如:對在第一元素結點前插入結點和刪除第一結點起操作與其它結點的操作就統一了
- 2.無論鏈表是否為空,其頭指針是指向頭結點的非空指針,因此空表和非空表的處理也就統一了。
單鏈表的操作
-
1.頭插法建立單鏈表:
- 建立新的結點分配內存空間,將新結點插入到當前鏈表的表頭
- 代碼
LinkList CreatList(LinkList &L){
LNode *s; // 輔助指針
int x;
L = (LinkList)malloc(sizeof(LNode)); // 創建頭結點
L->next = Null; // 創建為空鏈表
scanf("%d",&x)
while(x!=999){
s=(LNode*)malloc(sizeof(LNode)); // 創建新結點
s->data = x;
s->next = L->next;
L->next = s; // 將新結點插入表中
scanf("%d",&x); // 讀入下一個結點值
}
return L;
}
-
2.尾插法建立單鏈表:
- 建立新的結點分配內存空間,將新結點插入到當前鏈表的表尾
- 代碼
LinkList CreatList(LinkList &L){
int x;
L = (LinkList)malloc(sizeof(LNode)); // 創建頭結點
LNode *s,*r=L; // 輔助指針,r為表尾指針,指向表尾
scanf("%d",&x)
while(x!=999){
s=(LNode*)malloc(sizeof(LNode)); // 創建新結點
s->data = x;
r->next = s;
r=s; // r指針指向新的表尾結點
scanf("%d",&x); // 讀入下一個結點值
}
r->next = Null; //表尾指針指空
return L;
}
-
3.按序號查找結點
- 在單鏈表中從第一個結點出發,順指針next域逐個往下搜索,直到找到第i個結點為止,否則返回最后一個結點指針域NULL。
- 代碼
LNode *GetElem(LinkList l,int i){ //創造結構體指針,一個指針直接遍歷鏈表
int j = 1;
LNode *p = L->next;
if(i==0) return L;
if(i<1) return Null;
while(p&&j<1){
p = p->next;
j++;
}
return p;
}
-
4.按值查找結點
- 從單鏈表第一個結點開始,由前往后依次比較表中各結點數據域的值,若某結點數據域的值等於給定值e,則返回該結點的指針;若整個單鏈表中沒有這樣的結點,則返回NULL。
- 代碼
LNode *LocateElem(LinkList L,ElemType e){ //創造結構體指針,一個指針直接遍歷鏈表
LNode *p = L->next;
while(p!=Null&&p->data!=e)
p = p->next;
return p;
}
-
5.插入
-
插入操作是將值為x的新結點插入到單鏈表的第i個位置上。先檢查插入位置的合法性,然后找到待插入位置的前驅結點,即第i−1個結點,再在其后插入新結點。
-
算法思路:
1.取指向插入位置的前驅結點的指針
① p=GetElem(L,i-1);
2.令新結點s的指針域指向p的后繼結點
② s->next=p->next;
3.令結點p的指針域指向新插入的結點s
③ p->next=s;
-
-
6.刪除
-
刪除操作是將單鏈表的第i個結點刪除。先檢查刪除位置的合法性,然后查找表中第i−1個結點,即被刪結點的前驅結點,再將其刪除。
-
算法思路:
1.取指向刪除位置的前驅結點的指針 p=GetElem(L,i-1);
2.取指向刪除位置的指針 q=p->next;
3.p指向結點的后繼指向被刪除結點的后繼 p->next=q->next
4.釋放刪除結點 free(q);
-
雙鏈表
- 定義
typedef struct LNode{ // 定義單鏈表結點類型
ElemType data; // 數據域
struct LNode *next; // 指針域
}LNode,*LinkList;
typedef struct DNode{ // 定義雙鏈表結點類型
ElemType data; // 數據域
struct DNode *prior,*next; // 前驅和后繼指針
}DNode,*DinkList;
-
1.插入:(方法不唯一)
① s->next=p->next;
② p->next->prior=s;
③ s->prior=p;
④ p->next=s; -
2.刪除:
① p->next=q->next;
② q->next->prior=p;
③ free(q);
循環鏈表&&靜態鏈表
- 循環單鏈表:循環單鏈表和單鏈表的區別在於,表中最后一個結點的指針不是NULL,而改為指向頭結點,從而整個鏈表形成一個環
-
循環雙鏈表:類比循環單鏈表,循環雙鏈表鏈表區別於雙鏈表就是首尾結點構成環
- 當循環雙鏈表為空表時,其頭結點的prior域和next域都等於Head。
-
靜態鏈表:靜態鏈表是用數組來描述線性表的鏈式存儲結構。
定義:
#define MaxSize 50
typedef int ElemType
typedef struct{
ElemType data;
int next;
}SLinkList[MaxSize]
- 數組第一個元素不存儲數據,它的指針域存儲第一個元素所在的數組下標。鏈表最后一個元素的指針域值為-1。
- 例子: