線性表
線性表的定義
線性表是具有相同數據類型的\(n(n\ge 0)\)個數據元素的有限序列,其中\(n\)為表長,當\(n=0\)時線性表是一個空表。若用\(L\)命名線性表,則其一般表示為
\(a_i\)是線性表中的“第i個”元素線性表中的位序(位序從1開始,數組下標從0開始)
\(a_1\)是表頭元素;\(a_n\)是表尾元素
除第一個元素外,每個元素有且僅有一個直接前驅;除最后一個元素外,每個元素有且僅有一個直接后繼。
線性表的基本操作
InitList(&L ): //初始化表。構造一個空的線性表L,分配內存空間
DestoryList(&L ): //銷毀操作。銷毀線性表,並釋放線性表L所占用的內容空間。
ListInsert(&L,i,e): //插入操作。在表L中的第i個位置上指定元素e
ListDelete(&L,i,&e); //刪除操作。刪除表中L中第i個位置的元素,並用e返回刪除元素的值
LocateElem(L,e); //按值查找操作。在表L中查找具有給定關鍵字的元素。
GetElem(L,i); //按位查找操作。獲取表L中第i個位置的元素的值。
Length(L); //求表長。返回線性表L的長度,即L中數據元素的個數
PrintList(); //輸出操作。按前后順序輸出線性表L的所有元素值
Empty(L); //判空操作。若L為空表,則返回true,否則返回false;
線性表的順序表示
順序表的定義
順序表——用順序存儲的方式實現線性表。
//定義
//靜態分配
#define MaxSize 10 //定義最大長度
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++動態分配語句
L.data=new Elemtype[InitSize];
順序表的特定:
- 隨機訪問,即可以在\(O(1)\)s時間內找到第i個元素
- 存儲密度高,每個節點只存儲數據元素
- 拓展容量不方便(基表采用動態分配的方式實現,拓展長度的時間復雜度也比較高)
- 插入,刪除操作不方便,需要移動大量元素
順序表上的基本操作的實現
插入操作
//靜態分配下
bool ListInsert(SqList &L,int i,Elemtype e)
{
if(i<1||i>L.length+1) //判定i的范圍是否有效
return false;
if(L.length>=MaxSize) //當前存儲空間已滿,不能插入
return false;
for(int j=L.length;j>=i;--j) //后移
L.data[j]=L.data[j-1];
L.data[i-1]=e;
L.length++;
return true;
}
最好情況:在表尾插入(即\(i=n+1\)),循環0次;最好時間復雜度為\(O(1)\)
最壞情況:在表頭插入(即\(i=1\)),循環n次;最好時間復雜度為\(O(n)\)
平均情況:假設\(p_i(p_i=1/(n+1))\)是在第\(i\)個位置上插入一個結點的概率,則移動結點的平均次數為
插入算法的平均時間復雜度為\(O(n)\)
刪除操作
//靜態分配下
bool ListDelete(SqList &L,int i,Elemtype& e)
{
if(i<1||i>L.length+1) //判定i的范圍是否有效
return false;
e=L.data[i-1]; //賦值給e
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)\)
平均情況:假設\(p_i(p_i=1/n)\)是在第\(i\)個位置上刪除一個結點的概率,則移動結點的平均次數為
刪除算法的平均時間復雜度為\(O(n)\)
查找操作(按值查找)
int LocateELem(SqList L,Elemtype e)
{
int i=0;
for(i=0;i<L.Length;++i)
if(L.data[i]==0)
return i+1; · //下標為i的元素值等於e,返回其位序i+1
return 0; //退出循環,說明查找識別
}
最好情況:查找的元素就在表頭,僅需比較一次;最好時間復雜度為\(O(1)\)
最壞情況:查找的元素在表尾(或不存在)時,需比較n次;最好時間復雜度為\(O(n)\)
平均情況:假設\(p_i(p_i=1/n)\)是查找的元素在第\(i\)個位置的概率,則移動結點的平均次數為
查找算法的平均時間復雜度為\(O(n)\)
線性表的鏈式表示
單鏈表的定義
//單鏈表中的結點類型
typedef struct Lnode{
Elemtype data; //數據域
struct Lnode* next; //指針域
}Lnode,*LinkList;
要表示一個單鏈表時,只需聲明一個頭指針L,指向單鏈表的第一個結點
為了操作上的方便,在單鏈表第一個結點之前附加一個頭結點。頭節點的數據域可以不設任何信息。引入頭結點后:
- 在鏈表的第一個位置上的操作與其他位置的一致,無須進行特殊處理
- 無論鏈表為空,其頭指針都指向頭結點的非空指針(空表中頭節點的指針域為空),實現空表與非空表的處理統一。
單鏈表的基本操作
建立操作
頭插法
該方法從一個空表開始,生成一個新結點,並將讀取到的數據存放到新結點的數據域中,然后講新結點插入到當前鏈表的表頭。
//帶頭結點
LinkList List_HeadInsert(LinkList &L)
{
LNode *s;int x;
L=(LinkList)malloc(sizeof(LNode)); //創建頭結點
L->next=NULL; //初始為空鏈表
scanf("%d",&x); //輸入結點的值
whille(x!=9999) //輸入9999表示結點
{
s=(LNode*)malloc(sizeof(LNode));
s-data=x;
s-next=L->next; //將新結點插入表中,L為頭結點
L-next=s;
scanf("%d",&x);
}
return L:
}
平均時間復雜度為\(O(n)\)
頭插法應用:鏈表的逆置
尾插法
該方法將新結點插入到當前鏈表的表尾,為此必須增加一個尾指針人,使其始終指向當前鏈表的尾結點。
//帶頭結點
LinkList List_TailInsert(LinkList &L)
{
int x;+
LNode *s,*r=L; //r為表尾指針
scanf("%d",&x); //輸入結點的值
whille(x!=9999) //輸入9999表示結點
{
s=(LNode*)malloc(sizeof(LNode));
s-data=x;
r->next=s;
r=s; //r指向新的表尾結點
scanf("%d",&x);
}
r->next=NULL; //尾指針置空
return L:
}
平均時間復雜度為\(O(n)\)
查找操作
按位(序號)查找
LNode *GetElem(LinkList L,int i){ int j=1; //計數,初始為1 LNode *p=L->next; //頭結點賦值給p if(i==0) return L; //若i等於0,則返回頭結點 if(i<1) return NULL; //若i無效,則返回NUll while(p&&j<i) //第一個結點開始,查找第i個結點 { p=p->next; j++; } return p; //返回第i個結點的指針,若i大於表長,則返回NULL}
平均時間復雜度為\(O(n)\)
按值查找
LNode *LocateElem(LinkList L,ELemtype e){ LNode *p=L->next; while(p!=NULL&&p->data!=e) //第一個結點開始查找data域為e的結點 p=p->next; return p; //找到后返回該結點指針,否則返回NULL}
平均時間復雜度為\(O(n)\)
插入操作
后插操作:
p=GetElem(L,i-1); //查找插入位置的前驅結點s->next=p->next; //第二步p->next=s; //第三步
時間復雜度為\(O(n)\)
擴展:對某一結點進行前插操作
s->next=p->next; //修改指針域,不能顛倒p->next=s;temp=p->data; //交換數據域部分p->data=s=>data;s->data=temp;
時間復雜度為\(O(1)\)
刪除操作
p=GetElem(L,i-1); //查找刪除位置的前驅結點q=p->next; //令q指向被刪除結點p->next=q->next; //將*q結點從鏈中“斷開”free(q); //釋放結點的存儲空間
時間復雜度為\(O(n)\)
擴展:刪除結點*p
q=p->next; //令q指向*p的后續結點p->data=p->next->data; //和后續結點交換數據域(注意是否為最后一個結點)p->next=q->next; //將*q結點從鏈中“斷開”free(q);
時間復雜度為\(O(1)\)
雙鏈表的定義
引入前驅指針
//定義雙鏈表結點類型typedef struct DNode{ Elemtype data; //數據域 struct DNode* prior,*next; //前驅和后繼指針 }DNode,*DLinkList;
雙鏈表的基本操作
插入操作
s->next=p->next; //將結點*s插入到*p之后if(p->next!=NULL) //判斷是否為最后一個結點 p->next->prior=p;s->next=p;p->next=s;
刪除操作
//刪除*p后續結點*qp->next=q->next;q->next->priot=p;free(q);
循環鏈表的定義
循環單鏈表
表中最后一個結點的指針不是NULL,,而改為指向頭結點。
判空條件:是否等於頭結點
循環雙鏈表
頭結點的prior指針還要指向表尾結點。
當循環雙鏈表為空表是,其頭結點和prior域與next域都等於L。
具體操作自己學習實現。
靜態鏈表的定義
靜態鏈表借助數組來描述線性表的鏈式存儲結構,結點也有數據域data與指針域next;
這里的指針是結點的相對地址(數組下標),又稱游標。
靜態鏈表要預先分配一塊連續的內存空間。
typedef struct{ Elemtype data; //存儲數據元素 int next; //下一個元素的數組下標}SLinkList[MaxSize]; //定義一個結構體數組
優點:增,刪操作不需要大量移動元素
缺點:不能隨機存儲,只能從頭結點開始依次往后查找;容量固定不可變
適用場景:①不支持指針的低級語言 ②數據元素數量固定不變的場景(如操作系統的文件分配表FAT)