概念
線性表是零個或多個具有相同特性的數據元素組成的有限序列,該序列中所含元素的個數叫做線性表的長度挨稿,線性表有以下幾個特點:
首先是一個序列
其次是有限的
可以是有序的也可以是無序的
線性表的開始元素沒有前驅元素只有后繼元素猖任,線性表的結束元素沒有后繼元素只有前驅元素,除了開頭元素和結尾元素以外,每個元素都有且只有一個前驅元素和后繼元素
線性表的結構分為順序結構和鏈式結構
順序結構
順序表就是把線性表中的所有元素按照某種邏輯順序屉符,依次存儲到從指定位置開始的一塊連續(xù)的存儲空間析恋,重點是連續(xù)的存儲空間良哲。
數組長度和線性表的長度區(qū)別:數組長度是存放線性表的存儲空間的長度,存儲分配后這個量一般是不變的助隧,線性表的長度是線性表中數據元素的個數筑凫,隨著線性表插入和刪除操作的進行,這個量是變化的并村。
關于順序表的創(chuàng)建和初始化
#define SUCCESS 1
#define FAIL 0
typedef int elemType;
typedef int status;
typedef struct {
elemType *data;
int length;
}Sqlist;
status initList(Sqlist *list){
list->data = malloc(sizeof(elemType) * MAXSIZE);
if(!list->data) exit(FAIL);
list->length = 0;
return SUCCESS;
}
插入算法步驟
如果插入位置不合理巍实,拋出異常;
如果線性表長度大于等于數組長度哩牍,則拋出異彻睿或動態(tài)增加容量夸浅;
從最后一個元素開始向前遍歷到第i個位置,分別將它們都向后移動一個位置蚪黑;
將要插入的元素填入位置i處;
長度+1
代碼如下:
status insertData(Sqlist *list,int index,elemType data){
if((index<1) || (index>list->length+1)) return FAIL;
//存儲空間已滿
if(L->length == MAXSIZE) return FAIL;
//插入數據不在表尾,則先移動出空余位置
if(index <= list->length){
for(int j = list->length-1; j>=index-1;j--){
list->data[j+1] = L->data[j];
}
}
list->data[index-1] = data;
++list->length;//長度+1
return SUCCESS;
}
刪除步驟
如果刪除位置不合理,拋出異常;
取出刪除元素;
從刪除位置開始遍歷到最后一個元素位置纬朝,分別將它們都向前移動一個位置;
長度減1
代碼如下:
status listDelete(Sqlist *list,int i){
//線性表為空
if(list->length == 0) return FAIL;
//i值不合法判斷
if((i<1) || (i>list->length+1)) return FAIL;
for(int j = i; j < list->length;j++){
//被刪除元素之后的元素向前移動
list->data[j-1] = list->data[j];
}
//表長度-1;
list->length --;
return SUCCESS;
}
鏈式結構
鏈式結構如圖所示
鏈式存儲結構的特點是用一組任意的存儲單元存儲線性表的數據元素骄呼,這組存儲單元可以是連續(xù)的共苛,也可以是不連續(xù)的。這就意味著蜓萄,這些數據元素可以存在內存未被占用的任意位置俄讹。
在順序存儲結構中,每個數據元素只需要存儲數據元素就可以了∪频拢現在鏈式結構中患膛,處理要存儲數據元素信息之外,還要存儲它的后繼元素的存儲地址耻蛇。
在鏈表中踪蹬,為了方便定位鏈表,我們一般會在創(chuàng)建鏈表的時候臣咖,先給他一個頭節(jié)點跃捣,頭節(jié)點沒有實際內容的作用,作為一個鏈表的哨兵夺蛇,可以方便定位疚漆。
鏈表主要分成單向鏈表、單向循環(huán)鏈表刁赦、雙向鏈表娶聘、雙向循環(huán)鏈表,下面我們分別來講講甚脉。
單向鏈表
單向鏈表每一個節(jié)點丸升,都包含一個data和一個指向下一個節(jié)點的指針
在初始化單向鏈表的時候,需要注意頭節(jié)點是沒有內容的牺氨,->next下一個節(jié)點才是真正的首元節(jié)點狡耻,代碼如下:
Status InitList(LinkList *L){
*L = (LinkList)malloc(sizeof(Node));
//存儲空間分配失敗
if(*L == NULL) return ERROR;
//將頭結點的指針域置空
(*L)->next = NULL;
return OK;
}
單向鏈表插入和刪除
先來看下示意圖:
插入的主要步驟:
定義一個指針指向鏈表頭節(jié)點,初始化j從1開始猴凹;
當j < i 時夷狰,就遍歷鏈表,讓p的指針向后移動郊霎,不斷指向下一個結點沼头,j累加1;
若到鏈表末尾p為空歹篓,則說明第i個結點不存在瘫证;否則查找成功,在系統(tǒng)中生成一個空結點s
將數據元素e賦值給 s->data庄撮;
單鏈表的插入標準語句 s->next; p->next=s;
返回成功
刪除的主要步驟
定義兩個指針指p背捌、s指向鏈表頭節(jié)點,初始化i從1開始
判斷當前鏈表是否只有頭節(jié)點洞斯,如果不存在其他節(jié)點毡庆,return error,否則繼續(xù)執(zhí)行
判斷是否刪除的首元節(jié)點烙如,刪除第一個節(jié)點的時候需要注意把頭節(jié)點的指針移動到下一個節(jié)點
釋放么抗,并返回成功
代碼如下:
單向鏈表插入
Status ListInsert(LinkList *L,int i,ElemType e){
int j;
LinkList p,s;
p = *L;
j = 1;
//尋找第i-1個結點
while (p && j<i) {
p = p->next;
++j;
}
//第i個元素不存在
if(!p || j>i) return ERROR;
//生成新結點s
s = (LinkList)malloc(sizeof(Node));
//將e賦值給s的數值域
s->data = e;
//將p的后繼結點賦值給s的后繼
s->next = p->next;
//將s賦值給p的后繼
p->next = s;
return OK;
}
單向鏈表刪除
Status ListDelete(LinkList *L,int i,ElemType *e){
int j;
LinkList p,q;
p = (*L)->next;
j = 1;
//查找第i-1個結點,p指向該結點
while (p->next && j<(i-1)) {
p = p->next;
++j;
}
//當i>n 或者 i<1 時,刪除位置不合理
if (!(p->next) || (j>i-1)) return ERROR;
//q指向要刪除的結點
q = p->next;
//將q的后繼賦值給p的后繼
p->next = q->next;
//將q結點中的數據給e
*e = q->data;
//讓系統(tǒng)回收此結點,釋放內存;
free(q);
return OK;
}
單向循環(huán)鏈表初始化
先來看一下單向循環(huán)鏈表和單向鏈表的區(qū)別,單向循環(huán)鏈表圖如下
單向循環(huán)鏈表就像一個首尾相接的蛇亚铁,他的最后一個節(jié)點的next不是指向NULL蝇刀,而是指向了首元節(jié)點,以此達到循環(huán)徘溢。在初始化的時候吞琐,需要注意
初始化的時候,需要創(chuàng)建一個新的節(jié)點指向自身然爆,往里面增加節(jié)點的時候需要注意最后一個節(jié)點需要指向首元節(jié)點站粟。
#define ERROR 0
#define OK 1
#define MAXSIZE 20
typedef int Status; /* Status是函數的類型,其值是函數結果狀態(tài)代碼,如OK等 */
typedef int ElemType;/* ElemType類型根據實際情況而定曾雕,這里假設為int */
//定義結點
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node * LinkList;
Status CreateList(LinkList *L){
int item;
LinkList temp = NULL;
LinkList target = NULL;
printf("輸入節(jié)點的值奴烙,輸入0結束\n");
while(1) {
scanf("%d",&item);
if(item==0) break;
//如果輸入的鏈表是空。則創(chuàng)建一個新的節(jié)點剖张,使其next指針指向自己
(*head)->next=*head;
if(*L==NULL) {
*L = (LinkList)malloc(sizeof(Node));
if(!L) exit(0);
(*L)->data=item;
(*L)->next=*L;
} else {
//輸入的鏈表不是空的切诀,尋找鏈表的尾節(jié)點,使尾節(jié)點的next=新節(jié)點搔弄。新節(jié)點的next指向頭節(jié)點
for (target = *L; target->next != *L; target = target->next);
temp=(LinkList)malloc(sizeof(Node));
if(!temp) return ERROR;
temp->data=item;
temp->next=*L; //新節(jié)點指向頭節(jié)點
target->next=temp;//尾節(jié)點指向新節(jié)點
}
}
return OK;
}
單向循環(huán)鏈表插入和刪除
插入的主要步驟:
定義一個指針target指向鏈表頭節(jié)點趾牧,初始化j從1開始;
當j < i 時肯污,就遍歷鏈表翘单,讓target的指針向后移動,不斷指向下一個結點蹦渣,j累加1哄芜;
若到鏈表末尾target為空,則說明第i個結點不存在柬唯;否則查找成功认臊,在系統(tǒng)中生成一個空結點s
將數據元素e賦值給 temp->data = data;
單鏈表的插入標準語句 temp->next = target->next; target->next = temp;
返回成功
需要注意的是锄奢,在插入節(jié)點的時候失晴,我們需要判斷插入位置是否是第一個節(jié)點剧腻。如果是第一個節(jié)點,需要讓新的節(jié)點next指向原節(jié)點涂屁,并且頭節(jié)點指向新增加節(jié)點书在,修改尾節(jié)點。
刪除的主要步驟
定義兩個指針指temp拆又、taeget向鏈表頭節(jié)點儒旬,初始化i從1開始
判斷當前鏈表收否只有頭節(jié)點,如果不存在其他節(jié)點帖族,return error栈源,否則繼續(xù)執(zhí)行
刪除找到的節(jié)點,修改前一個節(jié)點的next指向竖般,釋放被刪除的節(jié)點
返回成功
刪除需要注意頭節(jié)點
代碼定義如下
Status ListInsert(LinkList *L, int place, int num) {
LinkList temp, target;
int i;
if (place == 1) {
//如果插入的位置為1,則屬于插入首元結點,所以需要特殊處理
//1. 創(chuàng)建新結點temp,并判斷是否創(chuàng)建成功,成功則賦值,否則返回ERROR;
//2. 找到鏈表最后的結點_尾結點,
//3. 讓新結點的next 執(zhí)行頭結點.
//4. 尾結點的next 指向新的頭結點;
//5. 讓頭指針指向temp(臨時的新結點)
temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) {
return ERROR;
}
temp->data = num;
for (target = *L; target->next != *L; target = target->next);
temp->next = *L;
target->next = temp;
*L = temp;
} else {
//如果插入的位置在其他位置;
//1. 創(chuàng)建新結點temp,并判斷是否創(chuàng)建成功,成功則賦值,否則返回ERROR;
//2. 先找到插入的位置,如果超過鏈表長度,則自動插入隊尾;
//3. 通過target找到要插入位置的前一個結點, 讓target->next = temp;
//4. 插入結點的前驅指向新結點,新結點的next 指向target原來的next位置 ;
temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) {
return ERROR;
}
temp->data = num;
for (i = 1, target = *L; target->next != *L && i != place - 1; target = target->next, i++) ;
temp->next = target->next;
target->next = temp;
}
return OK;
}
Status LinkListDelete(LinkList *L, int place) {
LinkList temp, target;
int i;
//temp 指向鏈表首元結點
temp = *L;
if (temp == NULL) return ERROR;
if (place == 1) {
if ((*L)->next == (*L)) {
(*L) = NULL;
return OK;
}
target->next = (*L)->next;
for (target = *L; target->next != *L; target = target->next) ;
temp = *L;
*L = (*L)->next;
target->next = *L;
free(temp);
} else {
for (i = 1, target = *L; target->next != *L && i != place - 1; target = target->next, i++) ;
temp = target->next;
target->next = temp->next;
free(temp);
}
return OK;
}
雙向鏈表和雙向循環(huán)鏈表
雙向鏈表相比較單向鏈表甚垦,多了一個前驅,而雙向循環(huán)鏈表的則在雙向鏈表的情況下涣雕,多了首尾的指向
初始化
對比的代碼如下
// 雙向鏈表
Status CreateList(LinkList *L) {
*L = (LinkList)malloc(sizeof(Node));
if ((*L) == NULL) return ERROR;
(*L)->prior = NULL;
(*L)->next = NULL;
return OK;
}
// 雙向循環(huán)鏈表
Status CreateList(LinkList *L) {
*L = (LinkList)malloc(sizeof(Node));
if ((*L) == NULL) return ERROR;
(*L)->prior = *L; // 這里和上面不一樣制轰,指向自己,
(*L)->next = *L;
return OK;
}
插入
雙向鏈表的插入和單向列表的插入對比胞谭,其實是相似的垃杖,不同之處在于,雙向鏈表在找到需要插入的節(jié)點后丈屹,需要先指定插入節(jié)點的prior和next调俘,然后再確定proir->next 和 next->prior,大致步驟如下
判斷插入的位置是否合法旺垒,不合法return error彩库,創(chuàng)建節(jié)點temp和指向頭節(jié)點的鏈表p,i從1開始
遍歷鏈表先蒋,讓p的指針向后移動骇钦,不斷指向下一個結點,i累加1竞漾;
若到鏈表末尾p為空眯搭,則說明第i個結點不存在;否則查找成功业岁,在系統(tǒng)中生成一個空結點temp
將數據元素e賦值給 temp->data = e鳞仙;
指定temp的前驅和后驅
替換temp的前驅的后驅,temp后驅的前驅
返回成功
需要注意的是笔时,在插入節(jié)點的時候棍好,我們需要判斷插入位置是否是第一個節(jié)點。如果是第一個節(jié)點,需要讓新的節(jié)點next指向原節(jié)點借笙,并且頭節(jié)點指向新增加節(jié)點扒怖,修改尾節(jié)點。
雙向循環(huán)鏈表步驟比雙向鏈表业稼,少了一步判斷首元節(jié)點盗痒,因為循環(huán)鏈表的尾節(jié)點就是指向首元節(jié)點。但是需要判斷尾節(jié)點的指向
代碼如下:
// 插入
Status ListInsert(LinkList *L, int place , ElemType v) {
if (place < 1) return ERROR;
LinkList temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) return ERROR;
temp->data = v;
temp->prior = NULL;
temp->next = NULL;
// 找到插入前的節(jié)點
LinkList p = *L;
// 找到插入位置 (place 位置的前一個結點)
for (int i = 1; i < place; i++) {
if (p->next == NULL) break;
p = p->next;
}
if (p->next == NULL) {
p->next = temp;
temp->prior = p;
} else {
p->next->prior = temp;
temp->next = p->next;
temp->prior = p;
p->next = temp;
}
return OK;
}
Status ListInsert(LinkList *L, int place , ElemType v) {
// 有頭節(jié)點 所以插入位置不能是<1
if (place < 1) return ERROR;
// 新節(jié)點
LinkList temp = (LinkList)malloc(sizeof(Node));
if (temp == NULL) return ERROR;
temp->data = v;
temp->prior = NULL;
temp->next = NULL;
// 找到插入前的節(jié)點
LinkList p = *L;
// 找到插入位置 (place 位置的前一個結點)
for (int i = 1; i < place; i++) {
if (p->next == NULL) break;
p = p->next;
}
// 注意:因為有頭節(jié)點盼忌,所有新節(jié)點一定是在頭節(jié)點之后,所以頭指針L掂墓,一定指向頭節(jié)點
if (p->next == NULL) {
p->next = temp;
temp->prior = p;
} else {
p->next->prior = temp;
temp->next = p->next;
temp->prior = p;
p->next = temp;
}
return OK;
}
刪除
雙向鏈表對比與單向鏈表的刪除谦纱,需要注意是不是刪除尾節(jié)點,因為尾節(jié)點的next需要為NULL君编;雙向循環(huán)鏈表的刪除對比單向循環(huán)鏈表跨嘉,仍然需要考慮是不是尾節(jié)點,因為他有下一個節(jié)點吃嘿,把下一個節(jié)點和他前一個節(jié)點建立互相指向祠乃,釋放自己。因為有頭節(jié)點的引入兑燥,所以我們不需要額外關注頭指針的指向了亮瓷。
代碼示例如下:
// 雙向鏈表刪除
Status ListDelete(LinkList *L, int place, ElemType *v) {
// 有頭節(jié)點 所以插入位置不能是<1
if (place < 1) return ERROR;
// 找到place位置的節(jié)點
LinkList p = (*L)->next; // 從首元節(jié)點開始
int i = 1
for (; i < place; i++) {
if (p->next == NULL) break;
p = p->next;
}
if (i < place) return ERROR;
*v = p->data;
if (p->next == NULL) {
p->prior->next = NULL;
free(p);
} else {
// p是任意位置的節(jié)點
// 1、將p的后一個節(jié)點指向p的前一個節(jié)點
// 2降瞳、將p的前一個節(jié)點指向p的后一個節(jié)點
// 3嘱支、釋放
p->next->prior = p->prior;
p->prior->next = p->next;
free(p);
}
return 1;
}
// 雙向循環(huán)鏈表刪除
Status ListDelete(LinkList *L, int place, ElemType *v) {
if (place < 1) return ERROR;
LinkList p = (*L)->next; // 從首元節(jié)點開始
int i = 1;
for (; i < place; i++) {
if (p->next == NULL) break;
p = p->next;
}
if (i < place) return ERROR;
*v = p->data;
// p是任意位置的節(jié)點
// 1、將p的后一個節(jié)點指向p的前一個節(jié)點
// 2挣饥、將p的前一個節(jié)點指向p的后一個節(jié)點
// 3除师、釋放
p->next->prior = p->prior;
p->prior->next = p->next;
free(p);
return 1;
}
結尾
總算把單向鏈表、單向循環(huán)鏈表扔枫、雙向鏈表汛聚、雙向循環(huán)鏈表理了一遍,但是結尾處有點匆忙短荐,下次有時間再補一下圖片和詳細優(yōu)化吧倚舀。寫博客真的好累啊(′▽`)