C語言資料結構----迴圈連結串列
阿新 • • 發佈:2019-01-25
主要講解釋迴圈連結串列的一些定義和具體的操作。
一、基本定義:
1.單鏈表的侷限:不可以迴圈。
2.迴圈連結串列的定義:將單鏈表中最後一個元素的next指向第一個元素。
3.迴圈連結串列擁有單鏈表的所有操作。
4.迴圈連結串列的插入和單鏈表插入的差別:
單鏈表的插入是
node->next = NULL; header->next = node;
迴圈連結串列的插入是:node->next
= node->next; header->next = node;
5.單鏈表改制成迴圈連結串列需要注意的地方
(1)在插入函式中,需要增加一個if判斷,判斷length是否為0,如果length為0,那麼要把node->next
= node;
(2)合法性的檢測時候不必要再檢測pos是否小於length。只需要檢測pos是否大於等於0。
(3)刪除其他元素和單鏈表是一樣的,特殊地方在刪除第一個元素(具體的看程式中的程式碼註釋),同時合法性 的檢測中也不再需要pos的位置是否小於length的檢測了。
二、基本定義(迴圈連結串列)
1.遊標:在迴圈連結串列中定義一個當前指標,這個當前指標被稱作為遊標。遊標是迴圈連結串列重要的一部分。
2.迴圈連結串列不同於單鏈表的操作:
(1)獲取當前遊標指向的資料元素
可以直接指定某一個數據元素,而不需要尋找我們要刪除的元素的下標。
(2)將遊標重置指向連結串列中的第一個元素
(3)將遊標移動指向連結串列中的下一個元素
(4)直接指定刪除連結串列中的某一個元素
三、單鏈表改寫成為迴圈連結串列的基本操作
1.在插入函式中安全性的檢測不必要再判斷pos是否超過了list->length,因為這個時候連結串列是迴圈連結串列,所以沒有具體的長度限制,不過我們要加入的判定是list->node != NULL;這個的目的是要判斷插入的元素不可以為空。
具體實現程式碼如下:
int ret = (sList != NULL) && (pos >= 0) && (node != NULL);
2.在插入函式中for迴圈的程式碼如下:
for(i=0; (i<pos) && (current->next != NULL); i++) { current = current->next; }
這裡要詳細的說一下,首先這個current,這個current是由CircleListNode* current = (CircleListNode*)sList;這個語句得來的。目的是為了定義一個臨時變數。
針對for迴圈的條件current->next != NULL;這裡對current->next的判斷,假如當pos=0的時候,那麼將不會指向for迴圈。當pos比0大的時候,首先要找出pos位置,也就是i > pos的作用,假如我們的pos很大,但是我們的連結串列長度只有 20個,那麼就要進行一下這個判斷,這樣就直接插在連結串列的末尾,這就是current->nex的作用。
然後再來說一下這個for迴圈的執行過程吧,這裡深刻的體會到了一個程式設計師查數的時候習慣性的從0開始的原因了,這種思維要鍛鍊,需要大量的程式碼來喂。
for迴圈是從i= 0開始執行的,假如i = 3,那麼for迴圈執行了三次,i= 0, i = 1, i = 2這裡的current可以當成一箇中間變數,第一步,head裡的next給了current->next;第二步,第0個元素的next給了current,也就是head->next->next;第三步把第1個元素的next給了current,也就是head->next->next->next;這樣這個時候current是指向第2個元素的next;而我們要的是第3個元素,所以最後的ret返回的應該是找的current->next指向的內容。也就是第2個數據結點的指標。
這樣接下來的兩個常規性的插入操作也就可以很容易的理解了。
node->next = current->next;
current->next = node;
我們上面說的都是常規性的插入,而且因為是迴圈連結串列,所以當我們使用尾插法的時候也不需要考慮太多的因素。
那麼當我們從連結串列的頭部開始插入元素的時候假如連結串列不是空的,那麼我們可以執行上面的過程就可以成功插入元素了,但是假如我們的連結串列在我們插入之前一個元素都沒有呢?我們插入元素後必須要執行node ->next = node;這個操作的目的就是在連結串列剛剛形成的時候就把迴圈構成。
具體的程式碼如下:
if( sList->length == 0 )
{
/*構建出迴圈部分,是這個圈形成*/
sList->slider = node;
node->next = node;
}
同時這裡我還有一個疑問:我想問的是(i<pos) && (current->next != NULL)這一句不是已經判斷了插入的元素是否為第一個元素了麼?這兩個判斷不衝突麼?其實這兩個判斷並不衝突,第一個判斷current->next != NULL的主要作用是:這裡對current->next的判斷,假如當pos=0的時候,那麼將不會指向for迴圈。而第二個判斷的主要作用是:當插入第一個元素的時候就將連結串列形成,也就是在插入第一個元素的時候就將尾元素指向第一個元素,而且每一次第一個元素的位置都是在不斷改變的。
具體的插入函式程式碼如下:
int CircleList_Insert(CircleList* list, CircleListNode* node, int pos) // O(n)
{
TCircleList* sList = (TCircleList*)list;
int ret = (sList != NULL) && (pos >= 0) && (node != NULL);
int i = 0;
if( ret )
{
CircleListNode* current = (CircleListNode*)sList;
for(i=0; (i<pos) && (current->next != NULL); i++)
{
current = current->next;
}
/*常規性的插入操作*/
node->next = current->next;
current->next = node;
if( sList->length == 0 )
{
/*構建出迴圈部分,是這個圈形成*/
sList->slider = node;
node->next = node;
}
sList->length++;
}
return ret;
}
頭插法建表的示意圖:
同時,頭插法建表還存在著問題,因為假如我們的連結串列中有了一個或者若干個資料節點,那麼這一個或者若干個資料節點就會形成一個迴圈連結串列,然後外面由連結串列頭在連線,當我們使用頭插法插入一個數據節點的時候,我們只執行了把資料節點插入到頭節點和原來的連結串列第一個資料節點之間,這樣我們的連結串列再一次變成了Q型,所以我們需要加入一個條件性的檢測。在插入函式中加入的條件性檢測程式碼如下:
if (current == (CircleListNode*)sList)
{
CircleListNode* last = CircleList_Get(sList, sList->length-1);
last->next = node;
}
假如不加入這個安全性的檢測,那麼我們在列印連結串列的時候會發現打印出來的是一個死迴圈的連結串列。
3.在get函式還有兩點需要注意
(1)get這個函式的返回值是一個指向我們想要獲取的資料節點的指標。
(2)get函式的最後有一條:ret = current->next,我們的整體程式碼如下:
CircleListNode* CircleList_Get(CircleList* list, int pos) // O(n)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
int i = 0;
/*判定同上*/
if( (sList != NULL) && (pos >= 0) )
{
CircleListNode* current = (CircleListNode*)sList;
for(i=0; i<pos; i++)
{
current = current->next;
}
ret = current->next;
}
return ret;
}
在for迴圈中我們已經找到了一個current。,那麼我們為什麼還要進行ret = current ->next。這個一又回到了老問題了,這個時候的current其實是pos-1個(因為pos是從0開始的) 而我們要找的是pos個,所以實際返回值ret應該是ret = current->next;而且這個pos的值其實是單鏈表中的pos,所以在使用pos進行查詢元素的時候查詢的一定是按順序儲存的位置,不要去想著迴圈
4.在刪除連結串列元素的函式中,首先定義了兩個CircleListNode*型別,first和last,主要是為了對刪除第一個元素時候的判定(CircleListNode*)CircleList_Get(sList, sList->length - 1);這是為了判定最後一個元素的位置然後在接下來的步驟裡更改最後一個元素所指向的元素。
程式碼如下:
CircleListNode* first = sList->header.next;
CircleListNode* last = (CircleListNode*)CircleList_Get(sList, sList->length - 1);
同時,在老唐的程式碼中他對刪除的元素是否是第一個元素進行了判定,
if( first == ret )
{
sList->header.next = ret->next;
last->next = ret->next;
}
如果是第一個元素,那麼就頭指向原來第一個元素的指向,同時,把最後一個元素指向原來第一個元素的指向。last->next = ret->next;這一句是多餘的,因為current->next = ret->next;已經完成了這個功能。而sList->header.next = ret->next;這一句是必要的。
當我們沒有進行迴圈直接刪除第一元素的時候,為了保證連結串列仍然是一個完整的連結串列,而且是正規的(尾指向頭),那麼我們需要進行如上判定。
當我們沒有進行迴圈而刪除第一個元素的時候,而且刪除這個元素以後連結串列中將不再又元素,那麼我們需要進行如下的判定和操作:
if( sList->length == 0 )
{
sList->header.next = NULL;
sList->slider = NULL;
}
以上的情況可以總結為常規刪除,如圖:
當我們迴圈一週以後,而且要刪除的元素的位置恰好是第一個元素,這個時候我們需要進行如下判定:
if (last != NULL)
{
slist->header.next = ret->next;
}
這樣刪除元素的示意圖如下所示:
在刪除函式中,當程式每次執行刪除函式的時候我們都要首先進行程式遍歷,以找到程式的last,但是實際上我們程式中使用到last的地方只有兩處,這樣的話我們可以對程式進行修改,提高刪除函式的執行效率。
四、關於遊標
1.定義:遊標可以理解為一個當前指標。我們可以通過這個指標來遍歷連結串列中的所有元素。
2.結構示意圖如下:
3.遊標的操作其實還是依賴著連結串列的其他操作的,比如說你要通過遊標來刪除連結串列中的一個元素,這個時候要首先找到要刪除元素在連結串列中的位置,這個時候也就是遊標在遊走,然後在呼叫連結串列實現函式中的delete函式進行刪除,如果要進行連續刪除或者在距離遊標較近的範圍內進行刪除這樣操作遊標會比較快,如果要刪除連結串列靠近尾部的元素或者距離遊標比較遠的,其實遊標的執行效率並不一定比普通的連結串列刪除元素操作快太多的。關於遊標的其他操作我們在下面的程式中體現。
五、迴圈連結串列的具體程式碼
1.連結串列的實現程式碼
#include <stdio.h>
#include <malloc.h>
#include "CircleList.h"
/*
構建一個連結串列資訊
包括:連結串列的next,遊標和長度
*/
typedef struct _tag_CircleList
{
CircleListNode header;
CircleListNode* slider;
int length;
} TCircleList;
/*
連結串列建立函式
返回值是為連結串列結構體申請的空間大小
*/
CircleList* CircleList_Create() // O(1)
{
/*
為連結串列的結構體分配空間
這個空間可以在連結串列的銷燬函式中釋放
*/
TCircleList* ret = (TCircleList*)malloc(sizeof(TCircleList));
if( ret != NULL )
{
/*開始進行初始化操作*/
ret->length = 0;
ret->header.next = NULL;
/*迴圈連結串列剛剛建立,所以遊標為空*/
ret->slider = NULL;
}
return ret;
}
/*
連結串列的銷燬操作
引數是已經建立了的連結串列指標
*/
void CircleList_Destroy(CircleList* list) // O(1)
{
free(list);
}
/*
連結串列的清空操作,引數是已經建立了的連結串列的指標
*/
void CircleList_Clear(CircleList* list) // O(1)
{
/*進行強制型別轉換,把CircleList* list型別的指標轉為TCircleList* sList*/
TCircleList* sList = (TCircleList*)list;
/*把連結串列的結構體恢復到初始狀態*/
if( sList != NULL )
{
sList->length = 0;
sList->header.next = NULL;
sList->slider = NULL;
}
}
/*
獲取連結串列長度函式
函式返回值為連結串列的長度
*/
int CircleList_Length(CircleList* list) // O(1)
{
TCircleList* sList = (TCircleList*)list;
/*如果連結串列為空,那麼函式的返回值將返回-1,即報錯*/
int ret = -1;
if( sList != NULL )
{
//將連結串列長度賦值給返回值
ret = sList->length;
}
return ret;
}
/*
在連結串列中插入一個元素
函式的返回值是一個int型變數,主要用來判斷此函式是否執行成功
函式的引數:(1)已經建立了的連結串列指標(2)要插入的資料節點(從上層傳遞過來的是一個地址)
(3)要插入的位置
*/
int CircleList_Insert(CircleList* list, CircleListNode* node, int pos) // O(n)
{
TCircleList* sList = (TCircleList*)list;
int ret = (sList != NULL) && (pos >= 0) && (node != NULL);
/*初始化一個變數,for迴圈中使用*/
int i = 0;
if( ret )
{
CircleListNode* current = (CircleListNode*)sList;
/*
for(i=0; (i<pos) && (current->next != NULL); i++)
{
current = current->next;
}
/*常規性的插入操作*/
node->next = current->next;
current->next = node;
if( sList->length == 0 )
{
/*構建出迴圈部分,是這個圈形成*/
sList->slider = node;
node->next = node;
}
sList->length++;
}
return ret;
}
CircleListNode* CircleList_Get(CircleList* list, int pos) // O(n)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
int i = 0;
/*判定同上*/
if( (sList != NULL) && (pos >= 0) )
{
CircleListNode* current = (CircleListNode*)sList;
for(i=0; i<pos; i++)
{
current = current->next;
}
/*最後的返回值是current->next所對應的元素*/
ret = current->next;
}
return ret;
}
CircleListNode* CircleList_Delete(CircleList* list, int pos) // O(n)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
int i = 0;
if( (sList != NULL) && (pos >= 0) )
{
CircleListNode* current = (CircleListNoe*)sList;
CircleListNode* first = sList->header.next;
CircleListNode* last = (CircleListNode*)CircleList_Get(sList, sList->length - 1);
for(i=0; i<pos; i++)
{
current = current->next;
}
ret = current->next;
current->next = ret->next;
sList->length--;
if( first == ret )
{
sList->header.next = ret->next;
last->next = ret->next;
}
/*
如果要刪除的元素恰好是遊標所指向的元素
那麼遊標指向要刪除元素的後一個元素。
*/
if( sList->slider == ret )
{
sList->slider = ret->next;
}
/*
判斷連結串列是否為空
(1)復原頭結點的指標
(2)遊標重置復原為空
*/
if( sList->length == 0 )
{
sList->header.next = NULL;
sList->slider = NULL;
}
}
return ret;
}
/*
刪除迴圈連結串列中的一個元素
*/
CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node) // O(n)
{
/*必要的安全性檢測*/
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
int i = 0;
if( sList != NULL )
{
/*還是定義移動*/
CircleListNode* current = (CircleListNode*)sList;
for(i=0; i<sList->length; i++)
{
/*尋找node在連結串列中的邏輯位置*/
if( current->next == node )
{
ret = current->next;
break; //這個時候返回得到i的值
}
/*移動指向*/
current = current->next;
}
if( ret != NULL )
{
/*進行刪除操作*/
CircleList_Delete(sList, i);
}
}
return ret;
}
/*
將遊標重新指向連結串列的第一個元素
引數:建立連結串列返回的得到的連結串列指標。
返回遊標的指標。
*/
CircleListNode* CircleList_Reset(CircleList* list) // O(1)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
/*合法性的檢測*/
if( sList != NULL )
{
sList->slider = sList->header.next;
ret = sList->slider;
}
/*返回值用來判斷我們的重置操作是否成功*/
return ret;
}
/*返回當前遊標指向的資料元素*/
CircleListNode* CircleList_Current(CircleList* list) // O(1)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
if( sList != NULL )
{
ret = sList->slider;
}
return ret;
}
/*移動遊標,把當前遊標指向下一個元素*/
CircleListNode* CircleList_Next(CircleList* list) // O(1)
{
TCircleList* sList = (TCircleList*)list;
CircleListNode* ret = NULL;
/*
這個時候遊標也不可以為空
如果遊標為空,那麼程式將會崩潰
*/
if( (sList != NULL) && (sList->slider != NULL) )
{
/*
把原來遊標指向的資料元素賦值給ret
這裡的ret被當作一箇中間變數
*/
ret = sList->slider;
/*把遊標的指向指向下一個元素*/
sList->slider = ret->next;
}
return ret;
}
連結串列實現的.h檔案
#ifndef _CIRCLELIST_H_
#define _CIRCLELIST_H_
typedef void CircleList;
typedef struct _tag_CircleListNode CircleListNode;
struct _tag_CircleListNode
{
CircleListNode* next;
};
CircleList* CircleList_Create();
void CircleList_Destroy(CircleList* list);
void CircleList_Clear(CircleList* list);
int CircleList_Length(CircleList* list);
int CircleList_Insert(CircleList* list, CircleListNode* node, int pos);
CircleListNode* CircleList_Get(CircleList* list, int pos);
CircleListNode* CircleList_Delete(CircleList* list, int pos);
CircleListNode* CircleList_DeleteNode(CircleList* list, CircleListNode* node);
CircleListNode* CircleList_Reset(CircleList* list);
CircleListNode* CircleList_Current(CircleList* list);
CircleListNode* CircleList_Next(CircleList* list);
#endif
3.測試程式
#include <stdio.h>
#include <stdlib.h>
#include "1.h"
struct Value
{
CircleListNode header;
int v;
};
int main(int argc, char *argv[])
{
int i = 0;
CircleList* list = CircleList_Create();
struct Value v1;
struct Value v2;
struct Value v3;
struct Value v4;
struct Value v5;
struct Value v6;
struct Value v7;
struct Value v8;
v1.v = 1;
v2.v = 2;
v3.v = 3;
v4.v = 4;
v5.v = 5;
v6.v = 6;
v7.v = 7;
v8.v = 8;
CircleList_Insert(list, (CircleListNode*)&v1, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v2, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v3, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v4, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v5, 5);
CircleList_Delete(list, 0);
for(i=0; i < CircleList_Length(list); i++)
{
struct Value* pv = (struct Value*)CircleList_Get(list, i);
printf("%d\n", pv->v);
}
printf("\n");
while( CircleList_Length(list) > 0 )
{
struct Value* pv = (struct Value*)CircleList_Delete(list, 0);
printf("%d\n", pv->v);
}
printf("\n");
CircleList_Insert(list, (CircleListNode*)&v1, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v2, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v3, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v4, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v5, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v6, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v7, CircleList_Length(list));
CircleList_Insert(list, (CircleListNode*)&v8, CircleList_Length(list));
for(i=0; i<CircleList_Length(list); i++)
{
struct Value* pv = (struct Value*)CircleList_Next(list);
printf("列印連結串列中的內容:%d\n", pv->v);
}
printf("\n");
/*測試重置遊標部分*/
struct Value* youbiao = (struct Value*) CircleList_Reset(list);
printf ("重置遊標後,遊標指向的內容為:%d\n", youbiao->v);
while (CircleList_Length(list) > 0)
{
/*刪除遊標所指向的元素*/
struct Value* pv = NULL;
for(i=1; i<3; i++)
{
CircleList_Next(list);
}
pv = (struct Value*)CircleList_Current(list);
printf("經過for迴圈後遊標指向的內容為:%d\n", pv->v);
CircleList_DeleteNode(list, (CircleListNode*)pv);
for(i=0; i<CircleList_Length(list); i++)
{
/*
這個時候遊標指向被刪除元素的下一個元素也就是4
所以列印順序是4567812
*/
struct Value* pv = (struct Value*)CircleList_Next(list);
printf("列印連結串列中的內容:%d\n", pv->v);
}
}
CircleList_Destroy(list);
return 0;
}