大綱:
- 理解線性表的邏輯結構
- 掌握線性表的順序存貯結構和鏈式存貯結構;掌握線性表基本操作的實現。
- 了解線性表的應用。
線性表的定義和基本操作
-
線性表的定義
具有相同數據類型的n(n>=0)個數據元素的有限序列
線性表的特點:
- 除第一個元素外,每個元素有且僅有一個直接前驅;除最后一個元素外,每個元素有且僅有一個直接后繼
- 表中元素的個數有限
- 表中元素具有邏輯上的順序性
- 表中每個元素都是數據元素,每個元素都是單個元素
- 表中元素的數據類型都相同,即每一個元素占有相同大小的存儲空間
- 表中元素具有抽象性,即只關注于邏輯結構,不關注于元素表示什么內容
- 線性表示一種邏輯結構,表示元素之間一對一的相鄰關系;順序表和鏈表是存儲結構,表示物理結構
- 線性表的基本操作
InitList(&L)
:初始化表
Length(L)
:求表長
LocateElem(L,e)
:按值查找操作
GetElem(L,i)
:按位查找操作
ListInsert(&L,e)
:插入操作
ListDelete(&L,i,&e)
:刪除操作
PrintList(L)
:輸出操作
Empty(L)
:判空操作
DestroyList(&L)
:銷毀操作
線性表的順序表示
-
順序表的定義
用一組連續的存儲單元,依次存儲線性表中的數據元素,使得邏輯上相鄰的數據元素在物理位置上也相鄰。
結構體描述:
//數組空間靜態分配 #define MaxSize 50 //數組最大長度 typedef struct{ ElemType data[MaxSize]; //順序表的元素 int length; //順序表當前長度 }SqList; //靜態分配數組順序表的類型定義 //數組空間動態分配 #define InitSize 100 //表長度的初始定義 typedef struct{ ElemType *data; int MaxSize,length; }SeqList; //動態分配數組順序表的類型定義
- C的初始動態分配語句
L.data=(ElemType*)malloc(sizeof(ElemType)*InitSize);
- 順序表的特點是隨機訪問,并且存儲密度高。但是增刪操作需要移動大量元素
- C的初始動態分配語句
-
基本操作相關代碼
2.1 插入操作bool ListInsert(SqList &L,int i,ElemType e){ if(i<1 || i>L.length) //判斷插入范圍是否有效 return false; if(i.length>=MaxSize) //判斷存儲空間是否已滿 return false; for(int j=L.lengt;j>=i;j--) //將i之后的元素依次向后移動 L.data[j]=L.data[j-1]; L.data[i-1]=e; //在i位置上賦值,注意,i是位置序號,i-1是數組下標 L.length++; // 長度加一 return true; }
- 移動節點平均次數為n/2,時間復雜度為O(n)
2.2 刪除操作
bool ListDelete(SqList &L,int i,ElemType &e){ if(i<0 || i>L.length) //判斷刪除范圍是否有效 return false; for(int j=i;j<L.length;j++) //將i之后的值依次前移 L.data[j-1]=L,data[j]; L.length--; //長度減一 return false; }
- 移動節點平均次數(n-1)/2,時間復雜度為O(n)
2.3 按值查找
int LocateElem(SqList L,ElemType e){ //實現查找順序表中值為e的元素,查找成功則返回元素位序,否則返回0 int i; for(i=0;i<L>length;i++) if(L.data[i]==e) return i+1; //下標為i的元素值為e,其位置為i+1 return 0; }
- 移動節點平均次數為(n+1)/2,時間復雜度為O(n)
線性表的鏈式表示
單鏈表
-
單鏈表的定義
通過一組任意的存儲單元來存儲線性表中的數據元素,每個鏈表節點除了存放自身的信息之外,還要存放一個指向后繼的指針。其中data為數據域,next為指針域。
結點類型定義
typedef struct LNode{ ElemType data; struct LNode *next; }LNode,*LinkList;
-
LinkList L
==LNode *L
- 單鏈表中的元素是離散地分布在存儲空間中的,所以是非隨機存取存儲結構,想找到某個元素必須從頭遍歷
- 通常用頭指針標識一個單鏈表,此外,在單鏈表的第一個結點之前附加一個結點,稱為頭結點。頭結點中可以不加任何信息,也可以記錄表長等信息。
- 引入頭結點的優點:
- 開始結點放在頭結點的指針域中,所以鏈表的第一個位置上的操作與其他位置上的操作一致,不需要特殊處理
- 若鏈表為空,頭指針是指向頭結點的非空指針(頭結點的指針域為空),所以空表與非空表的處理統一
- 單鏈表解決了順序表需要大量連續存儲空間的缺點,但是單鏈表附加指針域,也存在浪費存儲空間的缺點
-
-
基本操作相關代碼
2.1 建立單鏈表//頭插法 LinkList CreatList1(LinkList &L){ //從表尾到表頭逆向建立單鏈表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=s; s->next=L->next; L->next=s; scanf("%d",&x); } return L; } //尾插法 LinkList CreatList2(LinkList &L){ //從表頭到表尾正向建立單鏈表L,每次均在表尾插入元素 LNode *s,*r=L; //除了s這個新結點的指針,還建立了一個指向尾結點的r指針 int x; L=(LinkList)malloc(sizeof(LNode)); L->next=NULL; scanf("%d",&x); while(x!=999){ s=(LNode*)malloc(sizeof(LNode)); //以下三條代碼為尾插法的插入細則 s->data=s; r->next=s; r=s; //r指向新的尾結點 scanf("%d",&x); } r->next=NULL; return L; }
- 頭插法建立單鏈表,讀入數據的順序與生成的鏈表中的元素的順序是相反的;尾插法建立單鏈表,讀入數據的順序與生成的鏈表中的元素的順序是相同的
- 兩種方法的時間復雜度都為O(n)
2.2 按序號查找結點值
LNode *GetElem(LinkList L,int i){ int j=1; //計數器 LNode *p=L->next; //p指向頭結點指針 if(i==0) return L; if(i<1) return NULL; while(p&&j<i){ //從第一個結點開始找,查找第i-1個結點 p=p->next; j++; } return p; //返回第i個結點的指針,如果i大于表長,p=NULL,直接返回p即可 }
- 時間復雜度為O(n)
2.3 按值查找
LNode *LocateElem(LinkList L,ElemType e){ LNode *p=L->next; while(p!=NULL&&p->data!=e) p=p->next; return p; }
- 時間復雜度為O(n)
2.4 插入結點主要代碼片段
p=GetElem(L,i-1); //查找插入位置的前驅結點 s->next=p->next; p->next=s;
- 時間復雜度為O(1)
- 單鏈表一般都是后插操作,但可以通過如下方式將前插操作轉化為后插操作
//將s結點插入到p結點之前的主要代碼片段 s->next=p->next; p->next=s; temp=p->data; //交換數據域 p->data=s->data; s->data=temp;
2.5 刪除結點操作
//刪除當前指針下一個結點 p=GetElem(L,i-1); q=p->next; p->next=q->next; free(p); //刪除當前指針所在結點 q=p->next; p->data=p->next->data; p->next=q->next; free(q);
- 第一個刪除操作時間復雜度為O(n);第二個刪除操作時間復雜度為O(1)
雙鏈表
雙鏈表是在單鏈表只有一個指向后繼結點的指針next的基礎上,增加了一個指向前驅結點的指針prior
- 單鏈表想訪問某個結點的前驅結點時,只能從頭遍歷,訪問后繼結點的時間復雜度為O(1),訪問前驅結點的時間復雜度O(n)
結點類型定義
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;
}DNode,*LinkList;
- 插入操作主要代碼片段
s->next=p->next;
p->next-prior=s;
s->prior=p;
p->next=s;
- 刪除操作主要代碼片段
p->next=q->next;
q->next->prior=p;
free(p);
循環鏈表
循環單鏈表: 在單鏈表的基礎上,表中最后一個結點的指針不是NULL,而是改為指向頭結點,整個鏈表形成一個環
- 因為沒有指針域為NULL的結點,所以,循環單鏈表的判空條件不是頭結點的指針是否為空,而是它是否等于頭指針。
- 插入,刪除操作算法與單鏈表一致,只是在表尾操作有所不同,需維持環的狀態。且,任何位置插入,刪除操作都是等價的,所以不需要判斷表尾
循環雙鏈表:在雙鏈表的基礎上,表中最后一個結點的指針不是NULL,而是改為指向頭結點,整個鏈表形成一個環
- 判空條件為頭結點的prior域和next域都等于頭結點
靜態鏈表
靜態鏈表是借助數組來描述線性表的鏈式存儲結構,結點也有數據域data和指針域next,不過這里的指針域指的是數組下標(游標)
結點類型定義
#define MaxSize //靜態鏈表的最大長度
typedef struct{
ElemType data;
int next; //下一個元素的數組下標
}SLinkList[MaxSize];
- 同順序表一樣,需要預先分配一塊連續的內存空間
- 靜態鏈表以next=-1作為其結束的標志
順序表和單鏈表的比較
- 存取方式
順序表可以順序存取,也可以隨機存取;鏈表只能順序存取 - 邏輯結構和物理結構
順序表,邏輯上相鄰的元素,物理位置上也相鄰;鏈表,邏輯上相鄰的元素,物理位置上不一定相鄰 - 查找,插入和刪除操作時間復雜度
按值查找:順序表無序時,兩者時間復雜度都是O(n);當順序表有序時,可以采用折半查找,時間復雜度為O(log?n)
按位查找:順序表支持隨機訪問,時間復雜度為O(1);鏈表平均時間復雜度為O(n)
插入,刪除的時間內復雜度見上 - 空間分配
順序表易造成空間浪費,鏈表則不會
- 實際中,應基于存儲,運算,環境的多方面考慮,恰當地選擇存儲結構