三、連結串列(實踐)
阿新 • • 發佈:2018-12-05
絮絮叨叨
如何輕鬆寫連結串列的程式碼?
- 有決心並付出精力
- 理解指標或引用的含義
- 將某個變數賦值給指標,實際上就是將這個變數的地址賦值給指標,或者反過來說,指標中儲存了這個變數的記憶體地址,指向了這個變數,通過指標就能找到這個變數。
- 警惕指標丟失和記憶體洩漏
- 利用哨兵(頭結點)簡化實現難度
- 重點留意邊界條件處理
- 如果連結串列為空時,程式碼是否能正常工作?
- 如果連結串列只包含一個結點時,程式碼是否能正常工作?
- 如果連結串列只包含兩個結點時,程式碼是否能正常工作?
- 程式碼邏輯在處理頭結點和尾結點的時候,是否能正常工作?
- 舉例畫圖,輔助思考
一、資料結構
1、單向連結串列
- 結點包括:資料域 + 指標域
- 資料域:儲存資料元素的值
- 指標域(鏈域):儲存下一個結點地址或者指向其後繼結點的指標
// 定義結點 Node
typedef struct Node{
ElemType data;
struct Node * next;
} Node;
// 定義指向結點 Node 型別物件的指標 LinkList
typedef struct Node *LinkList;
2、雙向連結串列
- 結點包括:資料域 + 左指標域(prev) + 右指標域(next)
struct DNode{
int data;
DNode * prev;
DNode * 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; // 宣告指標p
p = 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的話,上面等價於:
q = p->next;
p->next=q->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;
}