C語言數據結構----鏈表

昨天寫了一篇CSDN了,由於培訓的地方網速太爛,所以導致寫好了一篇CSDN博客什麼都沒有了。只能說運氣不好了吧,把昨天那篇靜態表的博客先放下,今天先寫鏈表的。

老唐講的數據結構其實還是挺難的。

一、基本概念

1.鏈表&單鏈表n個結點鏈接成一個線性的結構叫做錶鏈表,當每個結點只包含一個指針域時,叫做單鏈表。

2.鏈表的幾個關鍵概念

(1)表頭結點,鏈表中的第一結點,包含指向第一個數據元素的指針以及鏈表自身的一些信息。

(2)數據結點,鏈表中代表數據元素的結點,包含指向下一個數據元素的指針和數據元素的信息。

(3)尾結點,鏈表中的最後一個數據結點,其的下一個元素指針爲空,也就是這個結點再沒有後繼了,所以稱爲尾結點。

在基本的鏈表插入操作中有頭插入方式和尾插入方式。操作方式圖所示:

頭插入方式:

尾插法建表:

在鏈表中,一定要注意頭的信息和頭所指向的數據結點。同時,老唐說要建立沒有頭的鏈表,我不清楚。

單鏈表的存儲結構上面已經說過了,下面是圖示:

 

 

二、頭結點,結點指針域和數據元素的具體實現(老唐的大招)

鏈表中一個數據結點的包含這個數據結點自身的數據部分以及指向下一個數據結點的指針部分。如下圖所示:

顯然,鏈表中的一個數據元素肯定是一個結構體構成的,只有這樣,纔可以保存一個數據結點中的兩個部分。其中,sum可以是任意類型的數據,p是指向下一個數據結點的指針,其中p的類型是unsigned int,因爲是地址所以剛好佔用四個字節。

這個時候老唐提出來了一個問題,爲什麼這個數據元素的地址不妨在具體的指針類型中,因爲放在具體的指針類型中我們也是通過操作後來的next來操作數據結點中的數據,把地址規定成爲unsigned int 類型,也是存放的四個字節的地址,所以是一樣,同時略顯簡潔。

老唐的第二個問題是,順序表的實現爲什麼要保存具體的指針地址而不是數據元素,我想問,假如你插入的數據結點裏面有多個數據元素那該怎麼辦?那會佔用多大的內存空間呢?所以保存地址最直接的原因就是爲了節省內存空間。

同時,假如我們想要在鏈表中插入不同數據類型的元素,那麼我們應該怎麼辦呢?你可能會說,那就插入吧,但是其實並不是那麼簡單的。

假如你插入的第一個數據結點的表示的結構體是下面這個:

struct Value
{
    LinkListNode header;
    int v;
};

那麼第二個數據結點的表示結構體是如下這個:

struct Value
{
    LinkListNode header;
    char c;
};

你每次插入都會發生數據結點指針的變化,這樣的話我們時刻都要想着這個結點指針。這個時候老唐來大招了。

針對LinkListNode* current = (LinkListNode*)sList;
 在lmain.c中,struct Value
  {
     LinkListNode header;
     int v;
  };
我們要放入鏈表裏的數據類型是上面這個結構體,但是假如我們要改變放入接結構體中的數據類型,那麼我們就要把next 這個類型作爲中間變量,這樣無論我們要放進來的是什麼變量,都被轉換成爲這樣的類型,那麼就可以隨意插入了。
  struct Value
  {
     LinkListNode header;
     int v;
  };
  這個結構體位於main.c中,這個是要插入鏈表中的結構體,這個結構體在進行插入的時候執行LinkList_Insert(list, (LinkListNode*)&v1, LinkList_Length(list));這個語句。這樣(LinkListNode*)&v1就把這個結構體的地址轉爲了LinkListNode*類型。
  typedef struct _tag_LinkListNode LinkListNode;
  struct _tag_LinkListNode
  {
      LinkListNode* next;
  };
這個時候,我們要插入的節點已經轉爲了上面這個結構體類型。又因爲LinkListNode* next這樣當我們操作要插入數據結點的時候,我們可以直接操作next來實現。同時,我們也可以任意的換結構體裏的成員類型,因爲我們都要把它的地址轉爲LinkListNode*這個類型,進而通過next 指針來操作原來的結構體地址。這應該就是老唐所說的數據封裝。
這裏面涉及一個東西,那就是當一個大結構體被強制類型轉換爲一個小類型的結構體的時候,是
直接卡下來的,但是這個時候需要滿足的條件是:嵌套的結構體必須要放在大結構體的前方。
這個時候操作卡下來的結構體的前四個字節的地址就是在操作原來的結構體,也就是我們要插入的數據結點。

上面的東西我覺得真心很繞,不過,你就記住一個原則,老唐之所以這麼做的原因就是想通過這種方法,把next變成一箇中間變量,這樣,它不論上層要操作鏈表的程序傳進來的是什麼值,我們都可以通過操作next這個中間變量來操作鏈表中的數據結點,也就是說可以在鏈表中進行數據結點的操作了。

三、代碼部分

1.鏈表操作的.c文件

#include <stdio.h>
#include <malloc.h>
#include "LinkList.h"



//由於是複用表,所以應該改變的是地址,而一個地址爲4位,所以一個unsigned int
//剛剛好,也就是向順序表裏插入地址。
typedef unsigned int TSeqListNode;
typedef struct _tag_LinkList
{
    LinkListNode header;
    int length;
} TLinkList;

LinkList* LinkList_Create() // O(1)
{
		//這裏是給TLinkList這個結構體開闢空間
    TLinkList* ret = (TLinkList*)malloc(sizeof(TLinkList));
    
    //判定是否給TLinkList結構體分配了空間
    if( ret != NULL )
    {
        ret->length = 0;
        //ret是TLinkList這個結構體成員的指針
        //這個指針指向的header是這個結構體的一個內容
        //這個內容是一個結構體,這個內容結構體的的一個內容next賦值爲NULL。
        //這裏是將指向下一個節點的指針賦值爲空。
        ret->header.next = NULL;
    }
    
    return ret;
}

void LinkList_Destroy(LinkList* list) // O(1)
{
		//在malloc.h中聲明
    free(list);
}

void LinkList_Clear(LinkList* list) // O(1)
{
		//進行強制類型轉換
    TLinkList* sList = (TLinkList*)list;
    
    //判定傳入的值是否有效
    if( sList != NULL )
    {
        sList->length = 0;
        sList->header.next = NULL;
    }
}

/*獲取鏈表的長度*/
int LinkList_Length(LinkList* list) // O(1)
{
    TLinkList* sList = (TLinkList*)list;
    /*開始定義爲-1,如果傳進來的指針爲空,則返回-1*/
    int ret = -1;
    
    if( sList != NULL )
    {
        ret = sList->length;
    }
    
    return ret;
}

int LinkList_Insert(LinkList* list, LinkListNode* node, int pos) // O(n)
{ 
    TLinkList* sList = (TLinkList*)list;
    
    int ret = (sList != NULL) && (pos >= 0) && (node != NULL);
    int i = 0;
    
    if( ret )
    {
    		//這一步其實是在把頭指針轉化爲LinkListNode*類型,並把鏈表頭的地址賦值爲current.
    		//這裏也可以說是定義current指針,讓我們的current指向我們的頭結點。
        LinkListNode* current = (LinkListNode*)sList;
    		//判斷是否到達單鏈表尾結點的地方    
        for(i=0; (i<pos) && (current->next != NULL); i++)
        {
            current = current->next;
        }
        
        /*
        		這裏是把要插入的節點位置的下一個結點的地址賦值給了要插入的
        		同時,把node這個大類型的指針指向了原來current->next應該指向的位置。
        */
        
        //先把指向的指針進行移交
        node->next = current->next;
        //再把新內容的值賦給原來應該指向這個位置的指針
        current->next = node;
        //通過上面這兩步,完成了一個鏈表不斷,同時還完成了插入一個新的結點的步驟。
        
        sList->length++;
    }
    
    return ret;
}

LinkListNode* LinkList_Get(LinkList* list, int pos) // O(n)
{
		//進行一步強制類型轉化
    TLinkList* sList = (TLinkList*)list;
    LinkListNode* ret = NULL;
    int i = 0;
    
    if( (sList != NULL) && (0 <= pos) && (pos < sList->length) )
    {
        LinkListNode* current = (LinkListNode*)sList;
        
        //對目標位置開始遍歷,當i = pos的時候遍歷結束。
        for(i=0; i<pos; i++)
        {
            current = current->next;
        }
        
        ret = current->next;
    }
    
    return ret;
}

LinkListNode* LinkList_Delete(LinkList* list, int pos) // O(n)
{
    TLinkList* sList = (TLinkList*)list;
    LinkListNode* ret = NULL;
    int i = 0;
    
    if( (sList != NULL) && (0 <= pos) && (pos < sList->length) )
    {
        LinkListNode* current = (LinkListNode*)sList;
        
        for(i=0; i<pos; i++)
        {
            current = current->next;
        }
        
        ret = current->next;
        current->next = ret->next;
        
        sList->length--;
    }
    
    /*
    	這個返回值的含義是假如在主函數中malloc了一個數據結點的空間,
      那麼在刪除這個結點的時候必須把這個結點的指針返回給主函數來進行free.
    */
    return ret;
}

2.鏈表的.h文件

#ifndef _LINKLIST_H_
#define _LINKLIST_H_

typedef void LinkList;
typedef struct _tag_LinkListNode LinkListNode;
struct _tag_LinkListNode
{
    LinkListNode* next;
};

LinkList* LinkList_Create();

void LinkList_Destroy(LinkList* list);

void LinkList_Clear(LinkList* list);

int LinkList_Length(LinkList* list);

int LinkList_Insert(LinkList* list, LinkListNode* node, int pos);

LinkListNode* LinkList_Get(LinkList* list, int pos);

LinkListNode* LinkList_Delete(LinkList* list, int pos);

#endif

3.測試代碼:

#include <stdio.h>
#include <stdlib.h>
#include "LinkList.h"

/* run this program using the console pauser or add your own getch, system("pause") or input loop */

struct Value
{
    LinkListNode header;
    int v;
};

int main(int argc, char *argv[]) 
{
    int i = 0;
    LinkList* list = LinkList_Create();
    
    /*要插入鏈表中的成員*/
    struct Value v1;
    struct Value v2;
    struct Value v3;
    struct Value v4;
    struct Value v5;
    
    v1.v = 1;
    v2.v = 2;
    v3.v = 3;
    v4.v = 4;
    v5.v = 5;
    
    /*頭插法建立鏈表,這個時候的插入的元素慧順序的向後移動*/
    LinkList_Insert(list, (LinkListNode*)&v1, LinkList_Length(list));
    LinkList_Insert(list, (LinkListNode*)&v2, LinkList_Length(list));
    LinkList_Insert(list, (LinkListNode*)&v3, LinkList_Length(list));
    LinkList_Insert(list, (LinkListNode*)&v4, LinkList_Length(list));
    LinkList_Insert(list, (LinkListNode*)&v5, LinkList_Length(list));
    
    for(i=0; i<LinkList_Length(list); i++)
    {
        struct Value* pv = (struct Value*)LinkList_Get(list, i);
        
        printf("%d\n", pv->v);
    }
    
    while( LinkList_Length(list) > 0 )
    {
        struct Value* pv = (struct Value*)LinkList_Delete(list, 0);
        
        printf("%d\n", pv->v);
    }
    
    LinkList_Destroy(list);
    
    return 0;
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章