- 鏈表是由結點構成的,關鍵是定義結點
- C語言程序設計上兩大特例:①鏈表節點的定義②遞歸函數的定義。這兩個違反了先定義再使用。
一、鏈表的分類
3.靜態鏈表:各結點在程序中定義,不是臨時開闢的(不是用malloc函數或者calloc函數動態開闢的空間),始終佔着內存不放。
struct student { int number; //數據區域 struct student *next; // 指針區域 }; //定義結構體類型 int main() { struct student a,b,c,*head,*p; //定義結點、頭指針、和進行遍歷的指針 a.number=1; b.number=2; c.number=3; head=&a; a.next=&b;b.next=&c;c.next=NULL; p=head; do { printf("d",p->number); //進行遍歷 p=p->next; } while(p!NULL);
}
二、鏈表的基本操作
4.動態鏈表的創建
(1)創建鏈表:從無到有建立起一個鏈表,即往空鏈表中一次插入若干結點,並保持結點之間的前驅和後繼關係。顧名思義,鏈表就像一條鐵環串成的鏈子,每個鐵環是結點,連接下一個鐵環,也可以理解爲老師帶領手拉手小朋友排隊,頭指針是老師,小朋友與小朋友之間拉着手。
第一次看的時候沒有看懂,應該在紙上把鏈表的圖畫一畫就可以明白。
struct num { int number; struct num *next; }; int icount; //全局變量標記結點的順序 struct *create() { struct *pHead=NULL,*pEnd,*pNew; icount=0; pNew=pEnd=(struct*)malloc(sizeof(struct num)); scanf("%d",&pNew->number); while(pNew->number!=0) //while(pNew->number>0)作用相同 { icount++; if(icount==1) //判斷是否是第一次加入結點也可以用if(pHead==NULL)進行判斷 { pNew->next=pHead; //使指針指向爲空,也可以pNew->next=NULL pEnd=pNew; pHead=pNew; } else { pNew->next=NULL; pEnd->next=pNew; pEnd=pNew; } pNew=(struct*)malloc(sizeof(struct num));//再次分配結點的內存空間,爲下一個結點插入做準備 scanf("%d",&pNew->number); } free(pNew); return pHead; }
5.單鏈表的遍歷輸出
(2)檢索操作:按給定的結點引導或檢索條件,查找某個結點。如果找到指定的結點則稱爲檢索成功;否則檢索失敗。自己理解是從鏈表的開頭按照順序一個一個檢查,然後找到自己想要找到的結點,如果找到則檢索成功,如果沒找到就是檢索失敗。
void print(struct num *pHead) { struct num *pTemp; int n=1; //表示鏈表中結點的序號 pTemp=pHead; while(pTemp!=NULL) { printf("%d",pTemp->num); pTemp=pTemp->next; //遍歷的關鍵,移動臨時指針到下一個結點 n++; } }
6.單鏈表的插入
(3)插入操作:在結點N(i)和N(i+1)之間插入一個新的結點N,使線性表的長度增加1,且N(i)和N(i+1)的邏輯關係發生變化:
插入前N(i)是N(i+1)的前驅,N(i+1)是N(i)的後繼;插入後新的結點N成爲N(i)的後繼,N(i+1)的前驅。
插入原則:
①插入操作不應破壞原鏈接關係
②插入的結點應該在它該在的位置(按照順序排好,如果不要求順序,則可以任意插入)
實現方法:
應該有一個插入位置的查找子過程
共有3中插入情況:
①插入結點在鏈表最前端
struct num *Insert(struct num *pHead) { struct num *pNew; pNew=(struct*)malloc(sizeof(struct num)); pNew->next=pHead; //新結點指針指向原來的首結點 pHead=pNew; //頭指針指向新節點 icuont++; //增加鏈表節點數量 return pHead; }
②插入節點在鏈表中間
struct num *Insert(struct num *pHead,int n) { struct num *p=pHead,*PNew; //兩個指針,*P是插入結點的前一個結點 while(p&&p->number!=n) //查找要插入的位置 p=p->next; //下移到下一個結點處 pNew=(struct*)malloc(sizeof(struct num)); scanf("%d",&pNew->number); pNew->next=p->next ; //新結點指向下一個結點 p->next=pNew; // 上一個結點指向新結點 icount++; //鏈表長度增加 return pHead; }
③插入節點在鏈表尾端
struct num *Insert(struct num *pHead) { struct num *p=pHead,*pNew; while(p&&p->next!=NULL) p=p->next; pNew=(struct*)malloc(sizeof(struct num)); scanf("%d",&pNew->number); p->next=pNew; // 尾結點指向新結點 pNew->next=NULL; //新結點指向空指針,變爲尾結點 icount++; return pHead; }
7.單鏈表的刪除
(4)刪除操作:刪除結點N(i)後,使線性表的長度減1,且N(i-1)N(i)N(i+1)的邏輯關係發生變化:
刪除前,N(i)是N(i+1)的前驅,N(i-1)的後繼;刪除後,N(i-1)成爲N(i+1)的前驅,N(i+1)成爲N(i-1)的後繼。
刪除的原則:
不改變原來排列的順序,只是從鏈表中分離開來,撤銷原來的鏈接關係。
兩種情況:
①要刪的結點是頭指針所指的結點則直接操作;
②不是頭結點,要依次往下找。另外考慮:空表和找不到要刪除的結點
需要兩個臨時指針:
P1:判斷指向的結點是不是要刪除的結點(用於尋找);
P2:始終指向P1前面的一個結點;
void Delete(struct num *pHead,int n) { int i; struct num *pTemp; //臨時指針,用於查找所要刪除的結點 struct num *pPre; //表示要刪除的結點前的結點 pTemp=pHead; //得到鏈表的頭結點 for(i=1;i<n;i++) { pPre=pTemp; //pPre跟進pTemp pTemp=pTemp->next; //pTemp前進下移到下一個結點 } pPre->next=pTemp->next; // 將要刪除的結點兩邊的結點鏈接 free(pTemp); icount--; //減少鏈表中結點的個數 }
三、循環鏈表
單鏈表最後一個結點的指針指向NULL,循環鏈表的最後一個結點的指針指向鏈表頭結點,首尾相連,形成數據鏈。
與單鏈表的不同點:
(1)鏈表的建立:單鏈表需要創建一個頭結點,專門存放第一個結點的地址,單鏈表的尾指針的指針域指向NULL,;而循環鏈表的建立,不需要專門的頭結點,讓最後一個結點的指針域指向鏈表的頭結點即可。
(2)鏈表表尾的判斷:單鏈表判斷結點是否爲表尾結點,只需判斷結點的指針域是否爲NULL,如果是,則爲尾結點,否則不是。而循環鏈表判斷是否尾結點,則是判斷該節點的指針域是否指向鏈表頭結點。
四、雙向鏈表
雙向兩鏈表也是基於單鏈表,單鏈表有一個頭結點,一個尾結點,雙鏈表有兩個指針域,一個指向左邊一個指向右邊,
一個存儲直接後繼結點地址,一般稱爲右鏈域,一個存儲直接前驅結點地址,一般成爲左鏈域。
13.雙向循環鏈表創建
struct num { int number; struct num *rlink; struct num *llink; }; struct num *creat(int n) { struct num *p,*h,*s; //h代表頭結點,S代表新結點,p則儲存新結點的前驅 int i; if(h=(struct*)malloc(sizeof(struct num))==NULL) //判斷開闢空間是否成功 exit(0); h->llink=NULL; //頭結點初始化 h->rlink=NULL; //頭結點初始化 p=h; for(i=0;i<n;i++) { if(s=(struct*)malloc(sizeof(struct num))==NULL) //開闢新結點 exit(0); p->rlink=s; //新結點前驅向右指向新結點 printf("請輸入第%d個人的姓名",i+1); scanf("%d",s->number); //輸入新結點裏的信息 s->llink=p; //新結點向左指向前驅 s->rlink=NULL; //新結點向右指向空 p=s; //將創建好的新結點作爲下一個新結點的前驅 } h->llink=s; //頭結點向左指向最後一個結點 p->rlink=h; //最後一個結點向右指向頭結點,連接完成 return(h); }
14.雙向鏈表查找
從表頭結點往後依次比較各結點數據域的值,若是該特定值。則返回結點的指針,否則繼續往後查,直到表尾
struct num *search(struct num *pHead,int n) { struct num *p; //行走指針,一個一個結點比較 int n; //要得到的那個數 p=pHead->rlink; //從第一個結點開始 while(p!=h) //當P走完一圈 { y=p->number; if(n==y) //進行比較 return(p); else p=p->rlink; //繼續遍歷 } }
15.雙向列表插入
雙向鏈表插入結點和單鏈表插入結點方法基本相同,只是指針域要分左右,比如要在p,q之間插入結點s,只需把p右鏈域指向s,s的左鏈域指向p,s的右鏈域指向q,q的左鏈域指向s即可
void Intset(struct num *p) { int n; struct num *s; if(s=(struct*)malloc(sizeof(struct num))==NULL) //開闢空間 exit(0); scanf("%d",&n); s->number=n; s->rlink=p->rlink; //將新結點的向右指向後一個結點 p->rlink=s; //前一個結點指向新結點 s->llink=p; //新結點指向前一個結點 (s->rlink)->llink=s; //後一個結點向左指向新結點 }
16.雙鏈表的刪除
雙鏈表的刪除和單鏈表的刪除也類似,將要刪除的鏈表跳過即可,需要注意的是在刪除的時候分清左右鏈域
比如:s、p、q三個連續結點,要刪除p,只需將s的右鏈域指向q,q的左鏈域指向s,並釋放p結點就可以了
void del(struct num *p) { (p->rlink)->llink=p->llink;//要刪除結點P的後繼向左指向P的前驅 (p->llink)->rlink=p->rlink;//要刪除結點P的前驅向右指向P的後繼 free(p); //將P開闢的空間釋放 }
以上是鏈表的一些操作,如果以上內容有錯誤,歡迎大家指出,謝謝大家