線性表(Linear List)
基本概念
線性表是由n(n>=0)個類型相同數據元素組成的有限序列。數據元素可由若干個數據對象組成,且一個線性表中的數據元素必須屬於同一數據對象。
線性表示n個類型相同數據元素的有限序列,對n>0,除第一個元素無直接前驅,最后一個元素無直接后繼外,其余的每個數據元素只有一個直接前驅和直接后繼。
線性表的邏輯結構如圖:
線性表具有如下特點:
同一性:線性表由同類數據元素組成,每個元素必須屬於同一數據類型。
有窮性:線性表由有限個數據元素組成,表長度就是表中數據元素的個數。
線性表中相鄰數據元素之間存在着序偶關系。
線性表的順序存儲
線性表的順序存儲是指用一組地址連續的存儲單元依次存儲線性表的各個元素,使得線性表中在邏輯結構上相鄰的數據元素存儲在連續的物理存儲單元中,即通過數據元素物理存儲的連續性來反映數據元素邏輯上的相鄰關系。
采用順序存儲結構存儲的線性表通常簡稱為順序表。可將順序表歸納為:關系線性化,結點順序化。
順序存儲表示
定義
用c語言定義線性表的順序存儲結構:
typedef struct{ char data[MAXSIZE]; int len; //數據長度 }List;
那么線性表的存儲地址是如何計算的呢?
假設線性表中有n個元素,每個元素占 sizeof(ElemType)個單元,則第一個元素的地址為 LOC(A) ,則第n個元素的地址為: LOC(n) = LOC(A) + (n-1)* sizeof(ElemType) 。
順序表的存儲結構如下:
我們只是定義了順序表,還沒有創建呢!
創建
//初始化順序表 List* initList(List *L){ int num, i; char ch; //輸入順序表長度 printf("請輸入順序表長度(0<len<100): "); scanf("%d", &num); L->len = num; //輸入數據 for(i = 0; i < L->len; i++){ getchar(); printf("請輸入第 %d 個數:", i); scanf("%c", &ch); L->data[i] = ch; } return L; }
基本操作
線性表的基本操作有查找,插入,刪除。
查找
查找分為兩種:1.可以按序號查找 getAsNum(L,i) :查找線性表L中第i個數據元素。2.按內容查找 getAsContent(L, content) :查找順序表L中的和Content相等的數據元素
【算法思想】:按內容查找運算可采用順序查找,即從第一個元素開始,依次將表中元素content相比較,若想等,則查找成功,返回該元素在表中的序號;若都不想等,則查找失敗,返回-1
【算法描述】按內容查找算法
//按內容查找 int getAsContent(List L, char content){ unsigned int i = 0; while(i >= 0 || i <= L.len - 1){ //順序掃描表,找到對應元素,或者掃描完則退出 if(L.data[i] != content){ i++; }else{ break; } } if(i <= L.len - 1){ //找到則輸出並返回序號 printf("按內容查找成功,第 %d 個位置元素為 %c \n\n", i, L.data[i]); return i; }else{ //未找到 printf("查找失敗,沒有你所找的元素!\n\n"); return ERROR; } }
小結:查找時要注意判斷表中沒有你要查找的元素的情況,此算法時間復雜度為O(n)。
插入
插入操作指在第i個元素之前插入一個新的元素,這時線性表的長度就變成了n+1了。
【算法思想】:用順序表作為線性表的存儲結構時,由於結點的物理順序必須和結點的邏輯順序保持一致,因此必須將原表位置的n,n-1,……,i上的結點依次后移一個位置(此時原表移動對應得位置為n+1,n,……,i+1),空出第i個位置,然后在該位置插入新結點。注意插入的位置為表尾時的情況。
【算法描述】順序表的插入運算
int insertList(List *L, int i, char content){ int k; //插入的位置不在表的范圍 if(i < 0 || i >= L->len){ printf("插入位置不合法!\n\n"); return ERROR; } //考慮表滿的情況 if(L->len == MAXSIZE){ printf("表已滿!無法插入!\n\n"); return ERROR; }else if(i >= 0 && i <= L->len - 1){ //插入位置后的元素向后移動 for(k = L->len - 1; k >= i; k--){ L->data[k+1] = L->data[k]; } L->data[i] = content; //表長度加一 L->len++; printf("插入成功!\n\n"); print(L); return OK; } }
小結:設E為在長度為n的表中插入一元素所需移動元素的平均次數,假設為在第i個元素之前插入元素的概率,並假設在任何位置上插入的概率相等,即 , i = 1, 2, ……, n+1, 則有:
刪除
線性表的刪除操作指的是將表的第i個(1<=i<=n)個元素刪去,使長度為n的線性表變成長度為n-1的線性表
【算法思想】:類似於插入操作,用順序表作為線性表的存儲結構時,由於結點的物理順序必須和結點的邏輯順序保持一致,空出第i個位置就要將原表位置的上的結點依次前移一個位置。
【算法描述】順序表的刪除運算
//刪除 int deleteList(List *L, int i, char *content){ int k; //刪除位置不合法則推出 if(i < 0 || (i >= L->len)){ printf("刪除位置不合法!\n\n"); return ERROR; } //刪除的表已空 if(L->len == 0){ printf("表已空!\n\n"); return ERROR; }else{ *content = L->data[i]; //前移 for(k = i; k <= L->len - 2; k++){ L->data[k] = L->data[k+1]; } //刪除成功后表長度減一 L->len--; printf("刪除成功!\n\n"); print(L); return OK; } }
小結:與插入算法類似,刪除運算也要移動結點。設E為刪除一個元素所需移動元素的平均移動次數, 為 刪除第i個元素的概率,並假設在任何位置上刪除的概率相等,即 ,i=1,2,……,n 。則有:
最后的匯總代碼如下:

/* 線性表的順序存儲 基本操作:查找,插入,刪除 */ #include<stdio.h> #include<string.h> #define MAXSIZE 100 #define OK 1 #define ERROR -1 typedef struct{ char data[MAXSIZE]; int len; //數據長度 }List; List* initList(List *L); //初始化 int getAsNum(List L, int num); //按序號查找 int getAsContent(List L, char content); //按內容查找 int insertList(List *L, int i, char content); //插入,在元素之前插入 int deleteList(List *L, int i, char *content); //刪除 void print(List *L); //打印 //初始化順序表 List* initList(List *L){ int num, i; char ch; //輸入順序表長度 printf("請輸入順序表長度(0<len<100): "); scanf("%d", &num); L->len = num; //輸入數據 for(i = 0; i < L->len; i++){ getchar(); printf("請輸入第 %d 個數:", i); scanf("%c", &ch); L->data[i] = ch; } return L; } //按序號查找 int getAsNum(List L, int num){ if(num < 0 || num > L.len - 1){ printf("查找失敗,位置 %d 超過鏈表長度!\n\n", num); return ERROR; }else{ printf("按序號查找成功,第 %d 個位置元素為 %c \n\n", num, L.data[num]); return num; } } //按內容查找 int getAsContent(List L, char content){ unsigned int i = 0; while(i >= 0 || i <= L.len - 1){ //順序掃描表,找到對應元素,或者掃描完則退出 if(L.data[i] != content){ i++; }else{ break; } } if(i <= L.len - 1){ //找到則輸出並返回序號 printf("按內容查找成功,第 %d 個位置元素為 %c \n\n", i, L.data[i]); return i; }else{ //未找到 printf("查找失敗,沒有你所找的元素!\n\n"); return ERROR; } } //插入,在元素之前插入 int insertList(List *L, int i, char content){ int k; //插入的位置不在表的范圍 if(i < 0 || i >= L->len){ printf("插入位置不合法!\n\n"); return ERROR; } //考慮表滿的情況 if(L->len == MAXSIZE){ printf("表已滿!無法插入!\n\n"); return ERROR; }else if(i >= 0 && i <= L->len - 1){ //插入位置后的元素向后移動 for(k = L->len - 1; k >= i; k--){ L->data[k+1] = L->data[k]; } L->data[i] = content; //表長度加一 L->len++; printf("插入成功!\n\n"); print(L); return OK; } } //刪除 int deleteList(List *L, int i, char *content){ int k; //刪除位置不合法則推出 if(i < 0 || (i >= L->len)){ printf("刪除位置不合法!\n\n"); return ERROR; } //刪除的表已空 if(L->len == 0){ printf("表已空!\n\n"); return ERROR; }else{ *content = L->data[i]; //前移 for(k = i; k <= L->len - 2; k++){ L->data[k] = L->data[k+1]; } //刪除成功后表長度減一 L->len--; printf("刪除成功!\n\n"); print(L); return OK; } } //打印 void print(List *L){ int i; printf("===================順序表如下===================\n"); printf("共有 %d 個數據: ", L->len); for(i = 0; i < L->len; i++){ printf("%c ", L->data[i]); } printf("\n"); } int main(void){ List L; int i, num, length, flag = 1; char ch, cha; //初始化 initList(&L); print(&L); //按序號查找 printf("請輸入你要查找的元素序號:"); scanf("%d", &num); getchar(); getAsNum(L, num); //按內容查找 printf("請輸入你要查找的元素的內容:"); scanf("%c", &ch); getchar(); getAsContent(L, ch); //插入元素 printf("請輸入你要插入的內容(格式:addr_num data_char):"); scanf("%d %c", &num, &ch); getchar(); insertList(&L, num, ch); //刪除元素 printf("請輸入你要刪除的位置(格式:addr_num):"); scanf("%d", &num); getchar(); deleteList(&L, num, &cha); return 0; }
執行結果:
線性表的鏈式存儲
鏈表:鏈表使用一組任意的存儲單元來存放線性表的結點,這組存儲單元可以是連續的,也可以是非連續的,甚至是零散分布在內存的任何位置。
采用鏈式存儲結構的線性表稱為線性鏈表。從鏈接方式看,鏈表可分為單鏈表,循環鏈表,雙鏈表(也叫循環雙鏈表,雙向鏈表)。從實現角度看可分為動態鏈表和靜態鏈表。
結點:結點包括兩個域:數據域和指針域。數據域存放數據,指針域指向其他結點的地址。兩個域的數量視具體情況而定。
單鏈表和循環單鏈表有一個指針域,雙鏈表有連個指針域。
單鏈表
單鏈表中每個結點的存儲地址存放在其前驅結點的指針域中,由於線性表的第一個結點無前驅,通常設一個頭指針header指向第一個結點。
定義:
單鏈表的存儲結構如下:
typedef struct Node{ char ch; int len; //表長度 struct Node *next; }Node, *linkList;
創建
單鏈表的創建有兩種:頭插法和尾插法
和
代碼在這里:
//尾插法建立表 linkList createFromTail(linkList L){ Node *s, *r; //r指針始終指向鏈表尾部 char ch; int flag = 1; r = L; printf("尾插法建立表,請輸入數據並以'#'結束輸入:\n"); while(flag){ printf("輸入數據: "); scanf("%c", &ch); getchar(); if(ch == '#'){ //若輸入 # 則退出 flag = 0; r->next = NULL; }else{ s = (linkList)malloc(sizeof(Node)); s->ch = ch; r->next = s; r = s; (L->len)++; flag = 1; } } print(L); return L; }
基本操作
其基本操作和順序表一樣:查找,插入,刪除。
查找
這里也只講按值查找
【算法思想】:從單鏈表的頭指針指向的頭結點出發,順鏈逐個將結點值和給定值作比較。
【算法描述】
//按內容查找 linkList getAsContent(linkList L){ Node *p; char ch; int i = 1; p = L->next; printf("\n請輸入查找內容:"); scanf("%c", &ch); getchar(); //遍歷完表且未找到數據退出循環, 找到數據時退出函數 while(p != NULL){ if(p->ch == ch){ printf("按內容查找成功,第 %d 個位置的數據為 %c\n", i, p->ch); return p; } p = p->next; i++; } //未找到數據 if(p == NULL){ printf("按內容查找失敗!未在表中找到數據!\n"); } }
插入
【算法思想】:首先要找到插入位置i的前一個結點,並用指針pre指向它,然后申請新結點s,通過修改pre和s的指針域將新結點插入鏈表。
【算法描述】
//插入 linkList insertList(linkList L){ Node *pre, *s; int k, i; char ch; pre = L; k = 0; printf("\n請輸入你要插入的位置和內容(格式: address content):"); scanf("%d %c", &i, &ch); getchar(); //插入位置不可能為負 if(i <= 0){ printf("插入失敗!插入位置不合法!插入位置不能為負\n"); return NULL; } ////遍歷完表且未找到插入位置(此時i大於表的長度) 或 找到插入位置時退出函數 退出循環 while(pre != NULL && k < i - 1){ pre = pre->next; k++; } if(pre == NULL){ // 未找到插入位置(此時i大於表的長度) printf("插入失敗!插入位置不合法!插入位置超出表的長度\n"); return NULL; }else{ //找到插入位置並插入數據 s = (linkList)malloc(sizeof(Node)); s->ch = ch; s->next = pre->next; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } }
刪除
【算法思想】:通過計數方式找到刪除位置的前一個結點並用pre指針指向它,然后刪除結點並釋放空間。
【算法描述】
//刪除 linkList delList(linkList L){ Node *pre, *r; int k = 0, i; char *ch; pre = L; printf("請輸入刪除的數據的位置(格式:address):"); scanf("%d", &i); getchar(); //刪除的位置必須合法 if(i > L->len || i<= 0){ printf("刪除的位置超出了鏈表的長度!\n"); return; } // 找到刪除位置退出 while(pre->next != NULL && k < i - 1){ pre = pre->next; k++; } //刪除操作 r = pre->next; pre->next = r->next; free(r); L->len--; printf("刪除成功!\n"); print(L); return L; }
這是最后的完整算法:

/* 線性表的鏈式存儲 基本操作:查找,插入,刪除 */ #include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct Node{ char ch; int len; //表長度 struct Node *next; }Node, *linkList; void initList(linkList *L); //初始化鏈表 linkList cteateFromeHead(linkList L); //頭插法建立表 linkList createFromTail(linkList L); //尾插法建立表 linkList getAsNum(linkList L); //按序號查找 linkList getAsContent(linkList L); //按內容查找 linkList insertList(linkList L); //插入 linkList delList(linkList L); //刪除 linkList print(linkList L); //查看鏈表 //初始化鏈表 void initList(linkList *L){ (*L) = (linkList)malloc(sizeof(Node)); (*L)->len = 0; (*L)->next = NULL; } //頭插法建立表 linkList cteateFromeHead(linkList L){ Node *s; char ch; int flag = 1; printf("頭插法建立表,請輸入數據並以'#'結束輸入:\n"); while(flag){ printf("輸入數據: "); scanf("%c", &ch); getchar(); if(ch == '#'){ //若輸入 # 則退出 flag = 0; }else{ s = (linkList)malloc(sizeof(Node)); s->ch = ch; s->next = L->next; L->next = s; (L->len)++; flag = 1; } } print(L); return L; } //尾插法建立表 linkList createFromTail(linkList L){ Node *s, *r; //r指針始終指向鏈表尾部 char ch; int flag = 1; r = L; printf("尾插法建立表,請輸入數據並以'#'結束輸入:\n"); while(flag){ printf("輸入數據: "); scanf("%c", &ch); getchar(); if(ch == '#'){ //若輸入 # 則退出 flag = 0; r->next = NULL; }else{ s = (linkList)malloc(sizeof(Node)); s->ch = ch; r->next = s; r = s; (L->len)++; flag = 1; } } print(L); return L; } //按序號查找 linkList getAsNum(linkList L){ int i, j; Node *p; p = L; j = 0; printf("\n請輸入查找的序號: "); scanf("%d", &i); getchar(); //查找的序號不可能為負 if(i <= 0){ printf("輸入不合法! \n"); return NULL; } //退出情況有兩種:表遍歷完畢沒有找到數據 或 p指針指向目標結點 while((p->next != NULL) && (j < i)){ p = p->next; j++; } if(j == i){ //找到結點 printf("按序號查找成功, 序號 %d 的數據是 %c \n", i, p->ch); return p; }else{ //未找到 printf("按序號查找失敗!未在表中找到數據!\n"); return NULL; } } //按內容查找 linkList getAsContent(linkList L){ Node *p; char ch; int i = 1; p = L->next; printf("\n請輸入查找內容:"); scanf("%c", &ch); getchar(); //遍歷完表且未找到數據退出循環, 找到數據時退出函數 while(p != NULL){ if(p->ch == ch){ printf("按內容查找成功,第 %d 個位置的數據為 %c\n", i, p->ch); return p; } p = p->next; i++; } //未找到數據 if(p == NULL){ printf("按內容查找失敗!未在表中找到數據!\n"); } } //插入 linkList insertList(linkList L){ Node *pre, *s; int k, i; char ch; pre = L; k = 0; printf("\n請輸入你要插入的位置和內容(格式: address content):"); scanf("%d %c", &i, &ch); getchar(); //插入位置不可能為負 if(i <= 0){ printf("插入失敗!插入位置不合法!插入位置不能為負\n"); return NULL; } ////遍歷完表且未找到插入位置(此時i大於表的長度) 或 找到插入位置時退出函數 退出循環 while(pre != NULL && k < i - 1){ pre = pre->next; k++; } if(pre == NULL){ // 未找到插入位置(此時i大於表的長度) printf("插入失敗!插入位置不合法!插入位置超出表的長度\n"); return NULL; }else{ //找到插入位置並插入數據 s = (linkList)malloc(sizeof(Node)); s->ch = ch; s->next = pre->next; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } } //刪除 linkList delList(linkList L){ Node *pre, *r; int k = 0, i; char *ch; pre = L; printf("請輸入刪除的數據的位置(格式:address):"); scanf("%d", &i); getchar(); //刪除的位置必須合法 if(i > L->len || i<= 0){ printf("刪除的位置超出了鏈表的長度!\n"); return; } // 找到刪除位置退出 while(pre->next != NULL && k < i - 1){ pre = pre->next; k++; } //刪除操作 r = pre->next; pre->next = r->next; free(r); L->len--; printf("刪除成功!\n"); print(L); return L; } //查看鏈表 linkList print(linkList L){ Node *p; int i = 0; p = L->next; printf("==============查看鏈表數據為==============\n"); printf("共有 %d 個數據(注意序號是從頭結點0開始,即第一個數據序號為 1)\n", L->len); for(; i < L->len; i++){ printf("%c ", p->ch); p = p->next; } printf("\n\n"); return L; } int main(void){ Node *L; initList(&L); //頭插法 // cteateFromeHead(L); //尾插法 createFromTail(L); getAsNum(L); getAsContent(L); insertList(L); delList(L); return 0; }
運行結果如圖:
循環單鏈表
定義
循環鏈表是一個首尾相接的鏈表。將單鏈表最后一個結點的指針域有NULL改為指向表頭結點,就得到了單鏈形式的循環鏈表,並成為循環單鏈表。
對於循環單鏈表,若經常要在首尾兩端進行操作,則可以設一個為指針在表尾(如下圖中C)。
c語言定義如下:
typedef struct cNode{ char data; int len; //表長 struct cNode *next; }Node, *cNode;
創建
和前面一樣,這里只講尾插法創建
//尾插法建立表 cNode createFromTail(cNode L){ Node *s, *r; int i = 0, flag = 1; char data; r = L; printf("尾插法建立表,請輸入數據並以'#'結束輸入:\n"); while(flag){ printf("輸入數據: "); scanf("%c", &data); getchar(); if(data != '#'){ s = (cNode)malloc(sizeof(Node)); s->data = data; s->next = r->next; r->next = s; r = s; L->len++; }else{ printf("結束輸入...\n"); flag = 0; } } r->next = L; print(L); return L; }
基本操作
查找
//按內容查找 cNode searchAsContent(cNode L){ Node *p; char data; int i = 1; p = L->next; printf("\n請輸入查找內容:"); scanf("%c", &data); getchar(); //遍歷完表且未找到數據退出循環, 找到數據時退出函數 while(p != L){ if(p->data == data){ printf("按內容查找成功,第 %d 個位置的數據為 %c\n", i, p->data); return p; } p = p->next; i++; } //未找到數據 if(p == L){ printf("按內容查找失敗!未在表中找到數據!\n"); } }
插入
//插入 cNode insertCNode(cNode L){ Node *pre, *s; int k, i; char data; pre = L->next; k = 1; printf("\n請輸入你要插入的位置和內容(格式: address content):"); scanf("%d %c", &i, &data); getchar(); //插入位置不可能為負 if(i <= 0){ printf("插入失敗!插入位置不合法!插入位置不能為負\n"); return NULL; } ////遍歷完表且未找到插入位置(此時i大於表的長度) 或 找到插入位置時退出函數 退出循環 while(pre != L && k < i - 1){ pre = pre->next; k++; } if(pre == L){ // 未找到插入位置(此時i大於表的長度) printf("插入失敗!插入位置不合法!插入位置超出表的長度\n"); return NULL; }else{ //找到插入位置並插入數據 ,注意:pre指向插入位置的前一個結點 s = (cNode)malloc(sizeof(Node)); s->data = data; s->next = pre->next; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } }
刪除
//刪除 cNode delList(cNode L){ Node *pre, *r; int k = 0, i; pre = L; printf("請輸入刪除的數據的位置(格式:address):"); scanf("%d", &i); getchar(); //刪除的位置必須合法 if(i > L->len || i<= 0){ printf("刪除的位置超出了鏈表的長度!\n"); return; } // 找到刪除位置退出 while(pre->next != L && k < i - 1){ pre = pre->next; k++; } //刪除操作 r = pre->next; pre->next = r->next; free(r); L->len--; printf("刪除成功!\n"); print(L); return L; }
完整代碼:

/* 循環單鏈表 */ #include<stdio.h> #include<stdlib.h> #include<string.h> typedef struct cNode{ char data; int len; //表長 struct cNode *next; }Node, *cNode; cNode initCNode(cNode *L); //初始化 cNode createFromTail(cNode L); //尾插法建立表 cNode searchAsNum(cNode L); //按序號查找 cNode searchAsContent(cNode L); //按內容查找 cNode insertCNode(cNode L); //插入 cNode delList(cNode L); //刪除 cNode print(cNode L); //查看鏈表 //初始化 cNode initCNode(cNode *L){ (*L) = (cNode)malloc(sizeof(Node)); (*L)->len = 0; (*L)->next = (*L); return (*L); } //尾插法建立表 cNode createFromTail(cNode L){ Node *s, *r; int i = 0, flag = 1; char data; r = L; printf("尾插法建立表,請輸入數據並以'#'結束輸入:\n"); while(flag){ printf("輸入數據: "); scanf("%c", &data); getchar(); if(data != '#'){ s = (cNode)malloc(sizeof(Node)); s->data = data; s->next = r->next; r->next = s; r = s; L->len++; }else{ printf("結束輸入...\n"); flag = 0; } } r->next = L; print(L); return L; } //按序號查找 cNode searchAsNum(cNode L){ int i, j; Node *p; p = L->next; j = 1; printf("\n請輸入查找的序號: "); scanf("%d", &i); getchar(); //查找的序號不可能為負 if(i <= 0){ printf("輸入不合法! \n"); return NULL; } //單循環鏈表的到表尾的判斷條件是 p != L while((p != L) && (j < i)){ p = p->next; j++; } if(j == i){ //找到結點 printf("按序號查找成功, 序號 %d 的數據是 %c \n", i, p->data); return p; }else{ //未找到 printf("按序號查找失敗!未在表中找到數據!\n"); return NULL; } } //按內容查找 cNode searchAsContent(cNode L){ Node *p; char data; int i = 1; p = L->next; printf("\n請輸入查找內容:"); scanf("%c", &data); getchar(); //遍歷完表且未找到數據退出循環, 找到數據時退出函數 while(p != L){ if(p->data == data){ printf("按內容查找成功,第 %d 個位置的數據為 %c\n", i, p->data); return p; } p = p->next; i++; } //未找到數據 if(p == L){ printf("按內容查找失敗!未在表中找到數據!\n"); } } //插入 cNode insertCNode(cNode L){ Node *pre, *s; int k, i; char data; pre = L->next; k = 1; printf("\n請輸入你要插入的位置和內容(格式: address content):"); scanf("%d %c", &i, &data); getchar(); //插入位置不可能為負 if(i <= 0){ printf("插入失敗!插入位置不合法!插入位置不能為負\n"); return NULL; } ////遍歷完表且未找到插入位置(此時i大於表的長度) 或 找到插入位置時退出函數 退出循環 while(pre != L && k < i - 1){ pre = pre->next; k++; } if(pre == L){ // 未找到插入位置(此時i大於表的長度) printf("插入失敗!插入位置不合法!插入位置超出表的長度\n"); return NULL; }else{ //找到插入位置並插入數據 ,注意:pre指向插入位置的前一個結點 s = (cNode)malloc(sizeof(Node)); s->data = data; s->next = pre->next; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } } //刪除 cNode delList(cNode L){ Node *pre, *r; int k = 0, i; pre = L; printf("請輸入刪除的數據的位置(格式:address):"); scanf("%d", &i); getchar(); //刪除的位置必須合法 if(i > L->len || i<= 0){ printf("刪除的位置超出了鏈表的長度!\n"); return; } // 找到刪除位置退出 while(pre->next != L && k < i - 1){ pre = pre->next; k++; } //刪除操作 r = pre->next; pre->next = r->next; free(r); L->len--; printf("刪除成功!\n"); print(L); return L; } //查看鏈表 cNode print(cNode L){ Node *p; int i = 0; p = L->next; printf("查看鏈表數據為...\n"); printf("共有 %d 個數據(注意序號是從頭結點0開始,即第一個數據序號為 1)\n", L->len); for(; i < L->len; i++){ printf("%c ", p->data); p = p->next; } printf("\n\n"); return L; } int main(void){ Node *L; printf("===============================循環單鏈表=====================================\n"); initCNode(&L); createFromTail(L); searchAsNum(L); searchAsContent(L); insertCNode(L); delList(L); printf("===============================循環單鏈表=====================================\n"); return 0; }
運行結果:
雙向鏈表
定義
雙向鏈表的的指針域在前面說過,它有兩個指針域,一個指針域指向本結點的直接前驅,另一個則指向直接后繼
定義:
typedef struct DNode{ char data; int len; struct DNode *prior; struct DNode *next; }DNode, *DList;
創建
//尾插法創建 DList createFromTail(DList L){ DNode *s, *r; int flag = 1; char data; r = L; printf("尾插法建立表,請輸入數據並以'#'結束輸入:\n"); while(flag){ printf("輸入數據: "); scanf("%c", &data); getchar(); if(data == '#'){ //若輸入 # 則退出 flag = 0; }else{ s = (DList)malloc(sizeof(DNode)); s->data = data; s->prior = r; s->next = L; r->next = s; r = s; L->prior = r; (L->len)++; flag = 1; } } printf("結束輸入...\n"); print(L); return L; }
基本操作
查找
//按內容查找 DList searchAsContent(DList L){ DNode *p; char data; int i = 1; p = L->next; printf("\n請輸入查找內容:"); scanf("%c", &data); getchar(); //遍歷完表且未找到數據退出循環, 找到數據時退出函數 while(p != L){ if(p->data == data){ printf("按內容查找成功,第 %d 個位置的數據為 %c\n", i, p->data); return p; } p = p->next; i++; } //未找到數據 if(p == L){ printf("按內容查找失敗!未在表中找到數據!\n"); } }
插入
//插入 DList insertDList(DList L){ DNode *pre, *s; int k, i; char data; pre = L->next; k = 1; printf("\n請輸入你要插入的位置和內容(格式: address content):"); scanf("%d %c", &i, &data); getchar(); //插入位置不可能為負 if(i <= 0){ printf("插入失敗!插入位置不合法!插入位置不能為負\n"); return NULL; } ////遍歷完表且未找到插入位置(此時i大於表的長度) 或 找到插入位置時退出函數 退出循環 while(pre != L && k < i - 1){ pre = pre->next; k++; } if(pre == L){ // 未找到插入位置(此時i大於表的長度) printf("插入失敗!插入位置不合法!插入位置超出表的長度\n"); return NULL; }else{ //找到插入位置並插入數據 ,注意:pre指向插入位置的前一個結點 s = (DNode*)malloc(sizeof(DNode)); s->data = data; s->next = pre->next; pre->next->prior = s; s->prior = pre; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } }
刪除
DList delDList(DList L){ DNode *p; int k = 1, i; p = L->next; printf("請輸入刪除的數據的位置(格式:address):"); scanf("%d", &i); getchar(); //刪除的位置必須合法 if(i > L->len || i<= 0){ printf("刪除的位置超出了鏈表的長度!\n"); return; } // 找到刪除位置退出 while(p != L && k < i){ p = p->next; k++; } //刪除操作 p->next->prior = p->prior; p->prior->next = p->next; //上面兩步可以互換順序 free(p); L->len--; printf("刪除成功!\n"); print(L); return L; }
完整代碼:

/* 雙向鏈表 */ #include<stdio.h> #include<stdlib.h> typedef struct DNode{ char data; int len; struct DNode *prior; struct DNode *next; }DNode, *DList; DList initDList(DList *L); DList createFromTail(DList L); DList searchAsNum(DList L); DList searchAsContent(DList L); DList insertDList(DList L); DList delDList(DList L); DList print(DList L); //初始化 DList initDList(DList *L){ (*L) = (DList)malloc(sizeof(DNode)); (*L)->len = 0; (*L)->prior = (*L); (*L)->next = (*L); return *L; } //尾插法創建 DList createFromTail(DList L){ DNode *s, *r; int flag = 1; char data; r = L; printf("尾插法建立表,請輸入數據並以'#'結束輸入:\n"); while(flag){ printf("輸入數據: "); scanf("%c", &data); getchar(); if(data == '#'){ //若輸入 # 則退出 flag = 0; }else{ s = (DList)malloc(sizeof(DNode)); s->data = data; s->prior = r; s->next = L; r->next = s; r = s; L->prior = r; (L->len)++; flag = 1; } } printf("結束輸入...\n"); print(L); return L; } //按序號查找 DList searchAsNum(DList L){ int i, j; DNode *p; p = L; j = 0; printf("\n請輸入查找的序號: "); scanf("%d", &i); getchar(); //查找的序號不可能為負 if(i <= 0){ printf("輸入不合法! \n"); return NULL; } //退出情況有兩種:表遍歷完畢沒有找到數據 或 p指針指向目標結點 while((p->next != L) && (j < i)){ p = p->next; j++; } if(j == i){ //找到結點 printf("按序號查找成功, 序號 %d 的數據是 %c \n", i, p->data); return p; }else{ //未找到 printf("按序號查找失敗!未在表中找到數據!\n"); return NULL; } } //按內容查找 DList searchAsContent(DList L){ DNode *p; char data; int i = 1; p = L->next; printf("\n請輸入查找內容:"); scanf("%c", &data); getchar(); //遍歷完表且未找到數據退出循環, 找到數據時退出函數 while(p != L){ if(p->data == data){ printf("按內容查找成功,第 %d 個位置的數據為 %c\n", i, p->data); return p; } p = p->next; i++; } //未找到數據 if(p == L){ printf("按內容查找失敗!未在表中找到數據!\n"); } } //插入 DList insertDList(DList L){ DNode *pre, *s; int k, i; char data; pre = L->next; k = 1; printf("\n請輸入你要插入的位置和內容(格式: address content):"); scanf("%d %c", &i, &data); getchar(); //插入位置不可能為負 if(i <= 0){ printf("插入失敗!插入位置不合法!插入位置不能為負\n"); return NULL; } ////遍歷完表且未找到插入位置(此時i大於表的長度) 或 找到插入位置時退出函數 退出循環 while(pre != L && k < i - 1){ pre = pre->next; k++; } if(pre == L){ // 未找到插入位置(此時i大於表的長度) printf("插入失敗!插入位置不合法!插入位置超出表的長度\n"); return NULL; }else{ //找到插入位置並插入數據 ,注意:pre指向插入位置的前一個結點 s = (DNode*)malloc(sizeof(DNode)); s->data = data; s->next = pre->next; pre->next->prior = s; s->prior = pre; pre->next = s; L->len++; printf("插入成功!"); print(L); return L; } } //刪除 DList delDList(DList L){ DNode *p; int k = 1, i; p = L->next; printf("請輸入刪除的數據的位置(格式:address):"); scanf("%d", &i); getchar(); //刪除的位置必須合法 if(i > L->len || i<= 0){ printf("刪除的位置超出了鏈表的長度!\n"); return; } // 找到刪除位置退出 while(p != L && k < i){ p = p->next; k++; } //刪除操作 p->next->prior = p->prior; p->prior->next = p->next; //上面兩步可以互換順序 free(p); L->len--; printf("刪除成功!\n"); print(L); return L; } //查看表 DList print(DList L){ DNode *p; int i = 0; p = L->next; printf("查看鏈表數據為...\n"); printf("共有 %d 個數據(注意序號是從頭結點0開始,即第一個數據序號為 1)\n", L->len); for(; i < L->len; i++){ printf("%c ", p->data); p = p->next; } printf("\n\n"); return L; } int main(void){ DNode *L; printf("===============================雙向鏈表=====================================\n"); initDList(&L); createFromTail(L); searchAsNum(L); searchAsContent(L); insertDList(L); delDList(L); printf("===============================雙向鏈表=====================================\n"); return 0; }
運行結果:
順序表和鏈表的比較總結
在數據結構緒論中就有提及算法的性能評價,既要從時間方面考慮(時間復雜度),又要從空間方面考慮(空間復雜度)。線性表在物理結構上的存儲造成了順序表和鏈表性能上的差異:順序表使用一組連續的物理地址存儲(邏輯上通常為數組,能夠隨機存取),而鏈表的物理存儲地址是任意的(不能隨機存取)。如此一來,線性表的基本操作會受到影響。
基於時間
適合順序表的情況:因為順序表通常是用數組表示決定了順序表能夠隨機存儲,所以當線性表的操作主要是進行查找而很少做插入和刪除操作時應用順序表。
適合鏈表的情況:與順序表的情況想反,需要頻繁的進行插入或刪除操作的線性表應用鏈表。
基於空間
存儲密度,是指結點數據本身所占的存儲量和整個結點結構所占的存儲量之比。
由前面提到順序表和鏈表的物理存儲地址可知:順序表是靜態分配的,鏈表是動態分配。靜態分配的一個缺點就只,分配時空間有多大就是多大,存儲規模是確定的,若數據元素無法用完靜態分配的空間,就會造成浪費,存儲密度小,空間利用率低。而動態分配則不會有這種情況。
因此,當線性表的長度變化不大,易於事先確定其存儲規模時宜采用順序表,反之應采用鏈表。
線性表鏈式存儲方式比較
鏈表名稱/操作名稱 | 找表中首元素結點 | 找表尾元素結點 | 找p結點的前驅結點 |
帶頭結點的單鏈表L | L->next 時間耗費O(1) | 一重循環時間耗費O(n) | 無法找到 |
帶頭結點的循環單鏈表L | L->next 時間耗費O(1) | 一重循環時間耗費O(n) | 順p結點的next域可以找到,時間耗費O(n) |
帶尾指針的循環單鏈表R | R->next ->next 時間耗費O(1) | 一重循環時間耗費O(1) | 順p結點的next域可以找到,時間耗費O(n) |
到頭結點的雙向鏈表L | L->next 時間耗費O(1) | 一重循環時間耗費O(1) | p->prior就是p的前驅結點,時間耗費O(1) |