三、链表(实践)
絮絮叨叨
如何輕松寫鏈表的代碼?
- 有決心并付出精力
- 理解指針或引用的含義
- 將某個變量賦值給指針,實際上就是將這個變量的地址賦值給指針,或者反過來說,指針中存儲了這個變量的內存地址,指向了這個變量,通過指針就能找到這個變量。
- 警惕指針丟失和內存泄漏
- 利用哨兵(頭結點)簡化實現難度
- 重點留意邊界條件處理
- 如果鏈表為空時,代碼是否能正常工作?
- 如果鏈表只包含一個結點時,代碼是否能正常工作?
- 如果鏈表只包含兩個結點時,代碼是否能正常工作?
- 代碼邏輯在處理頭結點和尾結點的時候,是否能正常工作?
- 舉例畫圖,輔助思考
一、數據結構
1、單向鏈表
- 結點包括:數據域 + 指針域
- 數據域:存儲數據元素的值
- 指針域(鏈域):存儲下一個結點地址或者指向其后繼結點的指針
2、雙向鏈表
- 結點包括:數據域 + 左指針域(prev) + 右指針域(next)
二、基本操作實例
1、單鏈表的讀取
(1)獲取鏈表第 i 個數據結點的算法思路:
- 聲明一個指針 p 指向鏈表的第一個結點,初始化 j 從 1 開始;
- 當 j < i 時,遍歷鏈表,p 不斷指向下一個結點, j++;
- 若到鏈表末尾 p 為空, 則說明第 i 個元素不存在;
- 否則查找成功,返回結點 p 的數據。
(2)實現
由于單鏈表的結構中沒有定義表長,所以不能事先知道要循環多少次,因此不方便用for循環來控制循環。==》while循環
# define OK 1 # define ERROR 0 # define TRUE 1 # define FALSE 0 typdef int Status; //Status是函數的類型,其值為函數結果狀態碼,eg:OK等 /*初始條件:順序線性表L已存在,1≤i≤ListLength(L)*/ /*操作結果: 用e返回L中第i個數據元素的值*/ Status GetElem( Node *L, int i, Elemtype *e){ int j;LinkList p; // 聲明指針pp = L->next; //讓p指向鏈表L的第一個節點j = 1;while(p && j<i) //當p不為空 或 j 小于i時,繼續循環{p = p->next;++j;}if(!p || j>i )return ERROR;*e = p->data;return OK; }2、插入結點(單向鏈表)
s->next = p->next;
p->next = s
(1)第 i 個數據插入結點的算法思路:
- 聲明一個指針p指向鏈表的第一個結點,初始化j=1;
- 當 j < i 時,遍歷鏈表,讓指針 p 向后移動,不斷指向下一結點,++j;
- 若到鏈表末尾 p 為空,說明第 i 個元素不存在;
- 否則查找成功,在系統中生成一個空結點 s;
- 將數據元素 e 賦值給 s->data;
- 單鏈表的插入標準語句:s->next = p->next; p->next = s
- 返回成功
(2)實現
/*初始條件:順序線性表L已存在,1≤i≤ListLength(L)*/ /*操作結果:在L中第i個位置之前插入新的數據元素e,L的長度加1*/ Status ListInsert(LinkList *L, int i, ElemType e) {int j;LinkList p,s;p = *L;j = 1;while(p && j<i) //尋找第i個結點{p = p->next;++j;}if(!p || j>i ) // 第i個結點不存在return ERROR;s = (LinkList)malloc(sizeof(Node)); //生成新的結點s->data = e;s->next = p->next; //將p的后繼結點賦值給s的后繼p->next = s; //將s賦值給p的后繼return OK; }3、刪除結點(單向鏈表)
p->next = p->next->next
用q取代p->next的話,上面等價于:
(1)第 i 個數據刪除結點的算法思路:
- 聲明一個指針p指向鏈表的第一個結點,初始化 j 從1開始;
- 當 j<i 時,遍歷鏈表,讓指針p向后移動,不斷,指向下一結點,++j;
- 若到鏈表末尾p為空,說明第i個結點不存在;
- 否則查找成功,將欲刪除的結點p->next賦值給q;
- 單鏈表的刪除標準語句:p->next = q->next;
- 將q結點中的數據賦值給e,作為函數的返回值
- 釋放q結點
- 返回成功
(2)實現
#include<stdio.h>Status ListDelete(LinkList *L, int i, ElemType *e) {int j;LinkList p,q;p = *L;j = 1;while(p->next && j<i){ //遍歷查找第i個結點p = p->next;++j;}if(!(p->next) || j>i)return ERROR;q = p->next;p->next = q->next;*e = q->data;free(q);return OK; }4、單鏈表的整表創建
單鏈表的整表創建過程就是一個動態生成鏈表的過程。由“空表”的初始狀態,依次建立各元素結點,并逐個插入鏈表。
(1)算法思路(頭插法):
- 聲明一結點 p 和 計數器變量 i;
- 初始化一空鏈表 L;
- 讓 L 的頭結點的指針指向NULL,即建立一個帶頭結點的單鏈表;
- 循環:
- 生成一個新結點賦值給 p;
- 隨機生成一個數組賦值給 p 的數據域 p->data;
- 將 p 插入到頭結點與前一新結點之間。
(2)實現
頭插法
/* 隨機產生n個元素的值,建立帶頭結點的單鏈表L */ void CreateListHead(LinkList *L, int n) { LinkList p;int i;srand(time(0)); //初始化隨機種子*L = (LinkList)malloc(sizeof(Node));(*L)->next = NULL; //建立一個帶頭結點的單鏈表for(i = 0; i < n; i++){p = (LinkList)malloc(sizeof(Node)); //生成新結點p->data = rand()%100 + 1;p->next = (*L)->next;(*L)->next = p;} }尾插法
/* 隨機產生n個元素的值,建立帶頭結點的單鏈表L */ void CreateListHead(LinkList *L, int n) { LinkList p, r;int i;srand(time(0)); //初始化隨機種子*L = (LinkList)malloc(sizeof(Node));r = *L; // *r 指向尾部的結點for(i = 0; i < n; i++){p = (LinkList)malloc(sizeof(Node)); //生成新結點p->data = rand()%100 + 1;r->next = p;r = p;}r->next=NULL; }5、單鏈表的整表刪除
(1)算法思路
- 聲明結點結點 p 和 q;
- 將第一個結點賦值給 p;
- 循環:
- 將下一結點賦值給 q;
- 釋放 p;
- 將 q 賦值給 p;
(2)實現
/*初始條件:順序線性表L已存在,操作結果:將L充值為空表*/ Status CLearList(LinkList *L) { LinkList p,q;p = (*L)->next;while(p){q = p->next;free(p);p = q;}(*L)->next = NULL;return OK; }三、常見操作
1、單鏈表反轉
法一:反向遍歷鏈表就類似于事先遍歷的節點后輸出,即“先進后出”,那么可以將鏈表遍歷存放于棧中,其后遍歷棧依次彈出棧節點,達到反向遍歷效果。
//1.stack void printLinkedListReversinglyByStack(Node *head){stack<Node* > nodesStack;Node* pNode = head;//遍歷鏈表while (pNode != NULL) {nodesStack.push(pNode);pNode = pNode->next;}while (!nodesStack.empty()) {pNode=nodesStack.top();printf("%d\t", pNode->value);nodesStack.pop();} } //2.遞歸 void printLinkedListReversinglyRecursively(Node *head){if (head!=NULL) {if (head->next!=NULL) {printLinkedListReversinglyRecursively(head->next);}printf("%d\t", head->value);} }2、鏈表中環的檢查,獲取連接點,計算環的長度
判斷鏈表是否有環路,獲取連接點,計算環的長度
此題很有意思,具體詳細請參考:http://www.cnblogs.com/xudong-bupt/p/3667729.html
判斷是否含有環:slow和fast,slow指針每次走一步,fast指針每次走兩步,若是鏈表有環,fast必能追上slow(相撞),若fast走到NULL,則不含有環。
//判斷是否含有環 bool containLoop(Node* head){if (head==NULL) {return false;}Node* slow = head;Node* fast = head;while (slow!=fast&&fast->next!=NULL) {slow = slow->next;fast = fast->next->next;}if (fast==NULL) {return false;}return true; }判斷環的長度:在相撞點處,slow和fast繼續走,當再次相撞時,slow走了length步,fast走了2*length步,length即為環得長度。
//獲得環的長度 int getLoopLength(Node* head){if (head==NULL) {return 0;}Node* slow = head;Node* fast = head;while (slow!=fast&&fast->next!=NULL) {slow = slow->next;fast = fast->next->next;}if (fast==NULL) {return 0;}//slow和fast首次相遇后,slow和fast繼續走//再次相遇時,即slow走了一圈,fast走了兩圈int length = 0;while (slow!=fast) {length++;slow = slow->next;fast = fast->next->next;}return length; }環得連接點:slow和fast第一次碰撞點到環的連接點的距離=頭指針到環的連接點的距離,此式可以證明,詳見上面鏈接。
//獲得環的連接點 Node* getJoinpoit(Node* head){if (head==NULL) {return NULL;}Node* slow = head;Node* fast = head;while (slow!=fast&&fast->next!=NULL) {slow = slow->next;fast = fast->next->next;}if (fast==NULL) {return NULL;}Node* fromhead = head;Node* fromcrashpoint = slow;while (fromcrashpoint!=fromhead) {fromhead = fromhead->next;fromcrashpoint = fromcrashpoint->next;}return fromhead; }3、找出中間節點
用slow和fast指針標記,slow每次走一步,fast每次走兩步,當fast到尾節點時,slow就相當于總長度的一半,即在中間節點。
//找出中間節點 Node* findMidNode(Node* head){Node* slow = head;Node* fast = head;while (fast->next != 0&&fast->next->next!=0) {slow = slow->next;fast = fast->next->next;}return slow; }4、找出倒數第k個節點
用slow和fast指針標記,fast指針事先走k步,然后slow和fast同時走,當fast到達末節點時,slow在fast的前k個節點,即為倒數第k個節點。
//找出倒數第k個節點 Node* findKNode(Node* head,int k){Node *temp1 = head;Node *temp2 = head;while (k-->0) {if(temp2 == NULL){return NULL;}temp2 =temp2->next;}while (temp2->next != NULL&&temp2->next->next!=NULL) {temp1 = temp1->next;temp2 = temp2->next->next;}return temp1; }總結
- 上一篇: 三、链表(Linked List)(原理
- 下一篇: 四、栈