1. 程式人生 > 實用技巧 >聊聊 HTTP 常見的請求方式

聊聊 HTTP 常見的請求方式

C語言的連結串列基本操作。

為何需要連結串列?

學習 C 語言的開始,我們最早接觸的資料型別是陣列,它有兩個特點:

  1. 順序儲存所有的元素,連續佔用記憶體空間;
  2. 建立陣列時需要事先知道儲存元素的個數,確定陣列的長度;
  3. 查詢元素複雜度是 O(1)O(1),刪除和插入的複雜度是 O(n)O(n)。

陣列元素非常適合元素個數基本保持不變的場景,特別便於查閱操作,但是如果資料儲存的元素個數經常發生變化,操作起來複雜度就很高,因此急需一種資料結構便於插入和刪除,連結串列就很合適。

概念

一個連結串列的示意圖如下所示

連結串列在記憶體中不是連續的順序儲存的,連結串列包括表頭,表尾和中間結點。

  • 中間結點包括資料域和指標域
    • 資料域用於儲存當前結點的資料,通常是資料型別
    • 指標域指示下一個結點在記憶體中的地址,是一個指標
  • 表頭是特殊的中間結點,它的資料域可以包括表的長度或者什麼都沒有,但是指標域必須指向單鏈表的第一個中間結點的位置
  • 表尾也是特殊的中間結點,資料域可以儲存資料,因為已經沒有後繼結點,指標域為空

優勢

單鏈表和陣列相比的優勢體現在如下兩個方面:

  1. 空間儲存更靈活,單鏈表不需要連續佔用記憶體空間,因為每個結點都知道它的後繼結點在記憶體中的位置,所以不需要連續儲存;
  2. 空間申請更靈活,可以使用動態記憶體分配,在程式執行時候按需申請記憶體大小,避免陣列申請過大造成的浪費。
  3. 空間釋放和增加更靈活,刪除一個元素僅僅需要將該元素的前置元素指標指向它的後繼結點,釋放該結點即可,增加元素只需要更改指標指向即可,複雜度是 O(1)O(1)

但是,單鏈表的劣勢也很明顯,單鏈表中的元素僅僅知道後繼結點的位置,如果需要查詢某個元素,只能從頭結點開始一個一個地去遍歷,而不像陣列直接給出下標一步就能定位到,因此單鏈表的查詢複雜度是 O(n)O(n)。

程式碼示例

假定單鏈表的資料元素的資料型別是ElemType,那單鏈表可以定義如下

typedef struct Node {
    ElemType data;
    struct Node *next;
}Node;
typedef struct Node * LinkList;

特別注意,有個特點,單鏈表有一個指向自身的指標,表示下一個結點next的位置。單鏈表的常用操作包括:建立,查詢,插入,刪除,清除。

建立列表

建立有兩種方法,頭插法和尾插法,第一種是保持新插入的元素始終在表的第一個元素,第二個保持新插入的元素始終在表的最後一個元素。

/*頭插法建立連結串列*/
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;       
    }
}

尾插法程式碼如下

/*尾插法建立連結串列*/
void CreateListTail(LinkList *L, int n){
    LinkList p,r;
    int i;
    srand(time(0));
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;
    
    r = *L;
    for(i = 0; i < n; i++){  
      p = (LinkList)malloc(sizeof(Node));
      p->data = rand() % 100 + 1;
      r->next = p;
      r = p;       
    }
    
    r->next = NULL;
}

查詢元素

工作的基本原理就是工作指標後移,這個技巧也是單鏈表操作的基礎和關鍵。

/* 單鏈表已經存在,用e返回L中的第i個元素的值*/
Status GetElem(LinkList L, int i, ElemType *e)
{
    int j = 1;    
    LinkList p = L->next;
    
    while(p && j < i) //p不為空並且j還沒有和i相等時,繼續往後查詢
    {
        p = p->next; //一直指向下一個結點,順藤摸瓜
        ++j;
    }
    
    if(!p || j > i)
        return ERROR;
    
    *e = p->data;
    return OK;
}

插入元素

/* 單鏈表已經存在,在第i個元素位置插入新的元素e,L的表長加1*/
Status ListInsert(LinkList *L, int i, ElemType e)
{
    int j = 1;    
    LinkList p = *L->next, s;
    
    while(p && j < i) //p不為空並且j還沒有和i相等時,繼續往後查詢
    {
        p = p->next; //一直指向下一個結點,順藤摸瓜
        ++j;
    }
    
    if(!p || j > i)
        return ERROR;
    
    s = (LinkList)malloc(sizeof(Node));
    
    s->data = e;
    s->next = p->next;
    p->next = s;
    return OK;
}

刪除元素

/* 單鏈表已經存在,刪除連結串列L的第i個元素,並用e返回其值,表長減1*/
Status ListInsert(LinkList *L, int i, ElemType *e)
{
    int j = 1;    
    LinkList p = *L->next, q;
    
    while(p && j < i) //p不為空並且j還沒有和i相等時,繼續往後查詢
    {
        p = p->next; //一直指向下一個結點,順藤摸瓜
        ++j;
    }
    
    if(!p || j > i)
        return ERROR;
    
    q = p->next;
    p->next = q->next;
    *e = q->data;
    free(q);    
    return OK;
}

刪除整表

/*順序連結串列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;
}

參考資料