數據結構與算法2 -- 鏈表

前言

上一篇文章講了,數據結構包括四大邏輯結構兩大物理結構,以下:node

  1. 邏輯結構
    • 集合結構
    • 線性結構
    • 樹形結構
    • 圖形結構
  2. 物理結構
    • 順序存儲結構
    • 鏈式存儲結構

集合結構沒有太多的東西,之後須要的時候再提一下就好。咱們知道線性結構有數組、鏈表、隊列等,那麼本篇文章就來針對鏈表進行講解。數組

鏈表

鏈表在邏輯上是線性的,可是在物理存儲上倒是鏈式存儲的。所以也就被稱爲線性表的鏈式存儲。
從名字上看就能夠知道,這傢伙應該是像鐵鏈同樣,一環扣一環的,而事實上也正是如此。數據結構

話很少說,鏈表主要有如下四種:函數

  1. 單向鏈表。
  2. 單向循環鏈表。單向鏈表首尾相連。
  3. 雙向鏈表。
  4. 雙向循環鏈表。

節點

節點是鏈表中具體存儲數據的地方,能夠當作是鐵鏈上的環。
節點主要分爲兩塊,數據域和指針域。oop

單向鏈表

單向鏈表
下面是使用C語言實現的單向鏈表,以及對應的一些經常使用方法。
.h文件

#import <Foundation/Foundation.h>

#define ERROR 0
#define SUCCESS 1
typedef int hyStatus;

// 定義 節點 結構體類型
typedef struct Node {
    int data;               // 數據域
    struct Node *next;      // 指針域
}Node;
// 定義 鏈表 類型
typedef Node* LinkList;

// 建立一個鏈表
LinkList InitLinkList(void);

// 頭插法添加一個節點
hyStatus addNodeToHeader(LinkList *ll, int data);
// 尾插法添加一個節點
hyStatus addNodeToFooter(LinkList ll, int data);
// 指定位置插入一個節點
hyStatus addNodeToCustom(LinkList *ll, int data, int index);

// 刪除指定位置的節點
hyStatus delNodeWithIndex(int index, LinkList *ll);

// 查找某個數據第一次出如今哪一個節點
int checkIndexInLinkList(LinkList ll, int data);
// 查找指定index處的節點的data值
int checkDataInLinkList(LinkList ll, int index);

// 遍歷鏈表打印其中的data
void foreachLinkList(LinkList ll);

// 銷燬一個節點變量
hyStatus freeNode(Node *node);
// 銷燬一個鏈表
hyStatus freeLinkList(LinkList *ll);
複製代碼

.m文件(爲了方便就直接使用.m文件寫了,反正OC徹底支持C語言)post

#import "LinkList.h"

// 建立一個節點
Node * InitNode(int data) {
    Node *node = malloc(sizeof(Node));
    if (!node) {
        printf("建立Node失敗\n");
        return NULL;
    }
    node->data = data;
    node->next = NULL;
    return node;
}

// 頭插法添加一個節點
hyStatus addToHeader(LinkList *ll, Node *node) {
    node->next = *ll;
    *ll = node;
    return SUCCESS;
}

// 尾插法添加一個節點
hyStatus addToFooter(LinkList ll, Node *node) {
    LinkList p = ll;
    while (p->next) {
        p = p->next;
    }
    p->next = node;
    return SUCCESS;
}

// 指定位置插入一個節點
hyStatus addToCustom(LinkList *ll, Node *node, int index) {
    if (index < 1) {
        printf("index最小要爲1,表示插入到第一個位置\n");
        return ERROR;
    }
    // 若是要插入的位置是第一個
    if (index == 1) {
        node->next = *ll;
        *ll = node;
    }
    else {
        LinkList p = *ll;
        // 要插入的位置是第2到最後,找到要插入位置的前一個位置
        for (int i = 1; i < index - 1; i++) {
            if (p->next) {
                // 2----length - 1
                p = p->next;
            }
            else {
                // other
                printf("要插入的位置比鏈表長度還大\n");
                return ERROR;
            }
        }
        node->next = p->next;
        p->next = node;
    }
    return SUCCESS;
}

// 建立一個鏈表
LinkList InitLinkList() {
    // 開闢一塊能夠存放一個Node類型變量的堆空間,而且把這塊空間的地址返回
    LinkList ll = malloc(sizeof(Node));
    if (!ll) {
        printf("開闢Node空間失敗\n");
        return NULL;
    }
    ll->data = 0;
    ll->next = NULL;
    return ll;
}

// 頭插法添加一個節點
hyStatus addNodeToHeader(LinkList *ll, int data) {
    Node *node = InitNode(data);
    if (node) {
        return addToHeader(ll, node);
    }
    return ERROR;
}

// 尾插法添加一個節點
hyStatus addNodeToFooter(LinkList ll, int data) {
    Node *node = InitNode(data);
    if (node) {
        return addToFooter(ll, node);
    }
    return ERROR;
}

// 指定位置插入一個節點
hyStatus addNodeToCustom(LinkList *ll, int data, int index) {
    Node *node = InitNode(data);
    if (node) {
        return addToCustom(ll, node, index);
    }
    return ERROR;
}

// 刪除指定位置的節點
hyStatus delNodeWithIndex(int index, LinkList *ll) {
    if (index < 1) {
        printf("節點的最小位置是1, index < 1");
        return ERROR;
    }
    Node *node;
    if (index == 1) {
        if ((*ll)->next) {
            node = *ll;
            *ll = (*ll)->next;
            return freeNode(node);
        }
        else {
            return freeNode(*ll);
        }
    }
    else {
        // 不改動第一個節點,則不須要使用*ll
        /* node\*ll-->1-->2-->3 */
        node = *ll;
        Node *p = NULL;
        for (int i = 1; i < index; i++) {
            if (node->next) {
                p = node;
                node = node->next;
            }
            else {
                printf("要刪除的index比鏈表的長度還大\n");
                return ERROR;
            }
        }
        // 刪除中間節點
        p->next = node->next;
        return freeNode(node);
    }
}

// 查找某個數據第一次出如今哪一個節點
int checkDataInLinkList(LinkList ll, int data) {
    int i = 1;
    do {
        if (ll->data == data) {
            return i;
        }
        ll = ll->next;
        i++;
    } while (ll);
    return ERROR;
}

int checkIndexInLinkList(LinkList ll, int index) {
    if (index < 1) {
        printf("index不能小於1\n");
        return ERROR;
    }
    /* ll-->1-->2-->3-->4 */
    for (int i = 1; i < index; i++) {
        if (ll->next) {
            ll = ll->next;
        }
        else {
            printf("index不能大於鏈表的總長度\n");
            return ERROR;
        }
    }
    return ll->data;
}

// 遍歷鏈表打印data
void foreachLinkList(LinkList ll) {
    do {
        printf("--------%d---------\n", ll->data);
        ll = ll->next;
    } while (ll);
}

// 銷燬一個節點變量
hyStatus freeNode(Node *node) {
    node->data = 0;
    node->next = NULL;
    free(node);
    node = NULL;
    return SUCCESS;
}

// 銷燬一個鏈表
hyStatus freeLinkList(LinkList *ll) {
    Node *node;
    while ((*ll)->next) {
        // node保存當前節點,*ll指向下一個節點
        node = *ll;
        *ll = (*ll)->next;
        // 釋放node節點
        freeNode(node);
    }
    // 釋放*ll中next爲空的最後一個節點
    return freeNode(*ll);
}
複製代碼

調用,在main.m中調用的。ui

#import "LinkList.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 建立一個鏈表
        LinkList ll = InitLinkList();
        // 循環添加幾個節點
        for (int i = 1; i <= 10; i++) {
            // 頭插法
// addNodeToHeader(&ll, i);
            // 尾插法
            addNodeToFooter(ll, i);
        }
        // 遍歷鏈表
        foreachLinkList(ll);
        
        // 任意插入
        printf("=========================\n");
        addNodeToCustom(&ll, 28, 12);
        foreachLinkList(ll);
        
        // 刪除指定位置的節點
        printf("++++++++++++++++++++++++++\n");
        delNodeWithIndex(5, &ll);
        foreachLinkList(ll);
        
        // 查詢節點
        int index = checkDataInLinkList(ll, 4);
        printf("查找到的位置是:%d\n", index);
        
        int data = checkIndexInLinkList(ll, 5);
        printf("查找到的數據是:%d\n", data);
    }
    return 0;
}
複製代碼

註釋寫的很明確,應該就不須要過多的解釋了。
須要注意的是,單向鏈表和單向循環鏈表都沒有使用head節點。spa

單向循環鏈表

前面已經說了,單向循環鏈表就是在單向鏈表的基礎上首尾相連。以下圖: 指針

單向循環
從上圖能夠看到,單向循環鏈表的最後一個節點的指針域再也不是NULL了,而是保存了這個鏈表首節點的地址,即指向了鏈表的首節點。

代碼實現:
分文件寫有點麻煩,就所有寫到main.m文件中了。code

#import <Foundation/Foundation.h>

#define ERROR 0
#define SUCCESS 1
typedef int hyStatus;

// 定義 節點 結構體類型
typedef struct Node {
    int data;               // 數據域
    struct Node *next;      // 指針域
}Node;
// 定義 鏈表 類型
typedef Node* LinkList;

// 建立循環鏈表
LinkList createLoopLinkList(void) {
    LinkList ll = NULL;     // 始終指向鏈表頭
    LinkList r  = NULL;     // 始終指向鏈表尾
    int data;
    printf("輸入非負整數向鏈表建立節點數據,輸入負數結束建立\nlinklist = ");
    while (1) {
        scanf("%d", &data);
        if (data < 0) {
            break;
        }
        // 第一次輸入
        if (ll == NULL) {
            ll = malloc(sizeof(Node));
            // 增長程序健壯性
            if (ll == NULL) {
                exit(0);
            }
            // 賦值,改指針
            ll->data = data;
            ll->next = ll;
            r = ll;
        }
        else {
            Node *node = malloc(sizeof(Node));
            if (node == NULL) {
                break;
            }
            node->data = data;      // 數據
            node->next = r->next;   // r是尾節點,r->next指向頭節點,,,這一步是把頭節點的地址給node->next,把node的next指向頭節點
            r->next = node;         // 把node的地址給r->next,這一步是把node連到了鏈表的最後,此時node就是尾節點,r是倒數第二個節點
            r = r->next;            // 將r從新改到尾節點
        }
    }
    
    return ll;
}
// 指定位置插入一個節點
hyStatus insertNode(LinkList *ll, int data, int index) {
    if (index < 1) {
        printf("插入位置非法\n");
        return ERROR;
    }
    // 要插入的節點
    Node *node = malloc(sizeof(Node));
    if (node == NULL) {
        exit(0);
    }
    node->data = data;
    // 插入在第一個位置和插入在最後一個位置,鏈表的結構相同,但意義不一樣
    // 在最後一個位置插入時,*ll指針指向的還是頭節點
    // 在第一個位置插入時,*ll要修改指向爲當前插入的節點,此時新插入的節點是頭節點,本來的頭節點如今已是第二個了,這也是爲何參數要傳*ll的緣由
    if (index == 1) {
        // 首先,要找到最後一個節點
        Node *lastNode = *ll;
        while (lastNode->next != *ll) {
            lastNode = lastNode->next;
        }
        // 找到了最後一個節點,開始插入
        node->next = *ll;
        lastNode->next = node;
        // 改變頭節點的位置,爲新插入的node
        *ll = node;
    }
    else {
        // 若是要插入第3個位置,則須要找到第2個位置
        // 由於tNode默認就在第1個位置,因此只須要循環index-2次就可找到index前一個位置
        Node *tNode = *ll;
        for (int i = 1; i < index - 1; i++) {
            if (tNode->next == *ll) {
                printf("插入的位置比鏈表長度還大\n");
                return ERROR;
            }
            tNode = tNode->next;
        }
        node->next = tNode->next;
        tNode->next = node;
    }
    return SUCCESS;
}
// 刪除指定位置的節點
hyStatus deleteNode(LinkList *ll, int index) {
    if (index < 1) {
        printf("刪除位置非法\n");
        return ERROR;
    }
    // 刪除第一個節點
    if (index == 1) {
        // 首先,要找到最後一個節點
        Node *lastNode = *ll;
        while (lastNode->next != *ll) {
            lastNode = lastNode->next;
        }
        // 找到了最後一個節點,開始刪除第一個
        lastNode->next = (*ll)->next;
        (*ll)->data = 0;
        (*ll)->next = NULL;
        free(*ll);
        // 改變頭節點的位置,爲本來的第二個節點
        *ll = lastNode->next;
    }
    else {
        // 若是要刪除第3個位置,則須要找到第2個位置
        // 由於tNode默認就在第1個位置,因此只須要循環index-2次就可找到index前一個位置
        Node *tNode = *ll;
        for (int i = 1; i < index - 1; i++) {
            tNode = tNode->next;
            if (tNode->next == *ll) {
                printf("刪除的位置比鏈表長度還大\n");
                return ERROR;
            }
        }
        // 開始刪除
        Node *delNode = tNode->next;
        tNode->next = delNode->next;
        delNode->data = 0;
        delNode->next = NULL;
        free(delNode);
    }
    return SUCCESS;
}
// 查詢某值在鏈表中的位置
int findNode(LinkList ll, int data) {
    Node *p = ll;
    int index = 1;
    do {
        if (p->data == data) {
            return index;
        }
        p = p->next;
        index++;
    } while (p != ll);
    printf("沒有找到此節點\n");
    return ERROR;
}
// 遍歷循環鏈表
void foreachLinkList(LinkList ll) {
    Node *p = ll;
    do {
        printf("%-5d", p->data);
        p = p->next;
    } while (p != ll);
    printf("\n");
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 建立鏈表
        LinkList ll = createLoopLinkList();
        foreachLinkList(ll);
        // 插入節點
        printf("++++++++++++++++++++++++++\n");
        insertNode(&ll, 0, 1);
        foreachLinkList(ll);
        // 刪除節點
        printf("--------------------------\n");
        deleteNode(&ll, 1);
        foreachLinkList(ll);
        // 查詢節點
        printf("==========================\n");
        printf("查找到的位置是:%d\n", findNode(ll, 10));
    }
    return 0;
}
複製代碼

雙向鏈表

在上面的單向鏈表中能夠發現一個問題,就是若是想要知道某個節點的地址,那麼就必須先找到它的前一個節點,經過前一個節點的next才能獲取這個節點的地址。同時咱們也不能經過當前節點得到前一個節點的地址,在操做過程當中就會感受很麻煩。

而接下來要說的雙向鏈表就很好的解決了這種問題,單向鏈表的節點是隻保存了數據和下一個節點的地址,那麼能不能把前一個節點的地址也保存起來呢?答案是確定的。具體樣式以下圖:

雙向鏈表
節點結構再也不是隻有兩部分了,而是有了三部分:數據域、前驅、後繼。

  1. 數據域用來存儲數據。
  2. 前驅保存前一個節點的地址。
  3. 後繼保存下一個節點的地址。

head節點

在貼代碼以前先說一下head節點,經過上面的單向鏈表代碼能夠知道,不管是在插入節點仍是刪除節點,都不可避免的須要判斷要插入的位置是否是第一個?要刪除的節點是否是第一個節點?由於在進行插入和刪除時,第一個節點和其餘的節點須要進行不一樣的操做。

那麼如何讓第一個節點和其餘節點操做相同呢?這裏就引入了頭節點(head節點)的概念。其實頭節點應該在鏈表建立的時候默認建立好,可是不讓它存儲數據。這時第一個節點實質上是第二個節點,由於真正的第一個節點是head節點,這樣就保證了插入和刪除節點的時候第一個節點和其餘節點進行的操做相同。

光說不練假把式,看代碼:

#import <Foundation/Foundation.h>

#define ERROR 0
#define SUCCESS 1
typedef int hyStatus;

typedef struct _Node {
    int data;
    struct _Node *frontNode;     // 前驅
    struct _Node *nextNode;      // 後繼
} Node;

typedef Node* LinkList;         // 雙向鏈表

// 單向鏈表中沒有使用`head`節點,在這個雙向鏈表中就使用一次`head`節點吧
// 建立一個鏈表
LinkList createLinkList() {
    // 新建立的這個節點就是`head`節點
    LinkList ll = malloc(sizeof(Node));
    // 默認建立成功了,就不考慮開闢空間失敗了😄
    ll->data = 0;       // 這裏就用來保存節點的數量好了,反正閒着也是閒着
    ll->frontNode = NULL;
    ll->nextNode  = NULL;
    
    // 添加節點
    Node *lastNode = ll;      // 記錄最後一個節點
    int data = 0;
    printf("請輸入節點數據,負數表示輸入結束:\n");
    while (1) {
        scanf("%d", &data);
        if (data < 0) {
            break;
        }
        // 建立節點
        Node *node = malloc(sizeof(Node));
        node->data = data;
        node->nextNode  = NULL;
        // 將節點添加到鏈表後面
        node->frontNode = lastNode;
        lastNode->nextNode = node;
        lastNode = node;
        // 鏈表的節點數量++
        ll->data++;
    }
    
    return ll;
}
// 插入一個節點
hyStatus insertNode(LinkList *ll, int data, int index) {
    if (index < 1 || index > (*ll)->data + 1) {
        printf("插入的位置不合法\n");
        return ERROR;
    }
    // 建立一個節點
    Node *node = malloc(sizeof(Node));
    node->data = data;
    node->frontNode = NULL;
    node->nextNode  = NULL;
    
    // 開始插入,由於有head節點的存在,插入第1個位置和其餘位置沒太大區別
    Node *p = *ll;
    for (int i = 1; i < index; i++) {
        // 爲了保險起見,仍是加上這個判斷吧
        if (p->nextNode) {
            p = p->nextNode;
        }
    }
    // 開始插入節點
    if (p->nextNode) {
        node->nextNode = p->nextNode;
        p->nextNode->frontNode = node;
    }
    p->nextNode = node;
    node->frontNode = p;
    // 鏈表長度++
    (*ll)->data++;
    
    return SUCCESS;
}
// 刪除一個節點
hyStatus deleteNode(LinkList *ll, int index) {
    if (index < 1 || index > (*ll)->data) {
        printf("刪除的位置不合法\n");
        return ERROR;
    }
    Node *p = *ll;
    for (int i = 1; i < index; i++) {
        if (p->nextNode) {
            p = p->nextNode;
        }
    }
    // 開始刪除
    Node *delNode = p->nextNode;        // 保存要刪除的那個節點
    p->nextNode = delNode->nextNode;
    // 若刪除的是最後一個節點,是沒有nextNode的
    if (delNode->nextNode) {
        delNode->nextNode->frontNode = p;
    }
    
    // 開始釋放要刪除的節點
    delNode->data = 0;
    delNode->nextNode = NULL;
    delNode->frontNode = NULL;
    // 鏈表長度--
    (*ll)->data--;
    
    return SUCCESS;
}
// 查詢節點
int findNode(LinkList ll, int data) {
    Node *p = ll;
    for (int i = 1; i <= ll->data; i++) {
        // 由於p最開始是指向header節點的,先向後移一個節點
        if((p = p->nextNode)) {
            // 判斷是否找到
            if (p->data == data) {
                return i;
            }
        }
        else {
            printf("沒有找到\n");
            return 0;
        }
    }
    printf("沒有找到\n");
    return 0;
}
// 遍歷鏈表
void foreachLinkList(LinkList ll) {
    Node *p = ll->nextNode;     // 第一個節點
    while (p) {
        printf("%-5d", p->data);
        p = p->nextNode;
    }
    printf("\n");
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 建立鏈表
        LinkList ll = createLinkList();
        foreachLinkList(ll);        // 遍歷鏈表
        
        // 插入節點
        printf("+++++++++++++++++++++++++++++++++++\n");
        insertNode(&ll, 0, 4);
        foreachLinkList(ll);
        
        // 刪除節點
        printf("-----------------------------------\n");
        deleteNode(&ll, 4);
        foreachLinkList(ll);
        
        // 查詢節點
        printf("===================================\n");
        printf("查詢的位置是:%d\n", findNode(ll, 7));
    }
    return 0;
}
複製代碼

雙向循環鏈表

圖有點醜,可是相信看完了上面那些,理解這個仍是很輕鬆的。

雙向循環
簡而言之,首節點的前驅保存了尾節點的地址。尾節點的後繼保存了首節點的地址。這樣就構成了雙向循環。

對於使用了head節點來講,上圖中的第一個節點就是head節點,第二個節點纔是需求上須要使用的第一個節點。

雙向循環鏈表代碼實現:

#import <Foundation/Foundation.h>

#define ERROR 0
#define SUCCESS 1
typedef int hyStatus;

typedef struct _Node {
    int data;
    struct _Node *frontNode;     // 前驅
    struct _Node *nextNode;      // 後繼
} Node;

typedef Node* LinkList;         // 雙向鏈表

// MARK: - 單向鏈表和單向循環鏈表都沒有使用head,那麼在雙向鏈表和雙向循環鏈表中就都使用head好了,😂
// 建立鏈表
LinkList createLinkList() {
    // MARK: - 所有複製過來,把首尾相連就行了
    // 新建立的這個節點就是`head`節點
    LinkList ll = malloc(sizeof(Node));
    // 默認建立成功了,就不考慮開闢空間失敗了😄
    ll->data = 0;       // 這裏就用來保存節點的數量好了,反正閒着也是閒着
    ll->frontNode = NULL;
    ll->nextNode  = NULL;
    
    // 添加節點
    Node *lastNode = ll;      // 記錄最後一個節點
    int data = 0;
    printf("請輸入節點數據,負數表示輸入結束:\n");
    while (1) {
        scanf("%d", &data);
        if (data < 0) {
            break;
        }
        // 建立節點
        Node *node = malloc(sizeof(Node));
        node->data = data;
        node->nextNode  = NULL;
        // 將節點添加到鏈表後面
        node->frontNode = lastNode;
        lastNode->nextNode = node;
        lastNode = node;
        // 鏈表的節點數量++
        ll->data++;
    }
    
    // 首尾相連
    lastNode->nextNode = ll;
    ll->frontNode = lastNode;
    
    return ll;
}
// 插入一個節點
hyStatus insertNode(LinkList *ll) {
    int data = 0, index = 0;
    // 輸入位置
    printf("請輸入插入節點的位置:");
    scanf("%d", &index);
    printf("\n");
    // 判斷輸入的位置是否合法
    if (index < 1 || index > (*ll)->data + 1) {
        printf("插入的位置不合法\n");
        return ERROR;
    }
    // 輸入數據
    printf("請輸入插入節點的數據:");
    scanf("%d", &data);
    printf("\n");
    
    // 建立節點
    Node *node = malloc(sizeof(Node));
    node->data = data;
    node->nextNode = NULL;
    node->frontNode = NULL;
    
    // 找位置
    Node *p = *ll;
    for (int i = 1; i < index; i++) {
        // 由於index通過判斷,是合法的,因此不用考慮p->nextNode循環的問題
        p = p->nextNode;
    }
    // 開始插入
    node->nextNode = p->nextNode;
    p->nextNode->frontNode = node;
    p->nextNode = node;
    node->frontNode = p;
    // 長度++
    (*ll)->data++;
    
    return SUCCESS;
}
// 刪除一個節點
hyStatus deleteNode(LinkList *ll) {
    int index = 0;
    printf("請輸入要刪除節點的位置:");
    scanf("%d", &index);
    printf("\n");
    // 判斷輸入的位置是否合法
    if (index < 1 || index > (*ll)->data) {
        printf("刪除的位置不合法\n");
        return ERROR;
    }
    // 開始刪除節點
    Node *p = *ll;
    for (int i = 1; i < index; i++) {
        // 由於index通過判斷,是合法的,因此不用考慮p->nextNode循環的問題
        p = p->nextNode;
    }
    // 開始刪除
    Node *delNode = p->nextNode;
    p->nextNode = delNode->nextNode;
    delNode->nextNode->frontNode = p;
    
    // 釋放刪除的節點
    delNode->data = 0;
    delNode->frontNode = NULL;
    delNode->nextNode  = NULL;
    free(delNode);
    
    // 鏈表長度--
    (*ll)->data--;
    return SUCCESS;
}
// 查詢循環鏈表
int findNode(LinkList ll) {
    int data = 0;
    printf("請輸入要查詢的數據:");
    scanf("%d", &data);
    printf("\n");
    
    // 開始查詢
    Node *p = ll->nextNode;
    int index = 1;
    while (p != ll) {
        if (p->data == data) {
            return index;
        }
        p = p->nextNode;
        index++;
    }
    printf("沒有找到這個節點\n");
    return ERROR;
}
// 遍歷循環鏈表
void foreachLinkList(LinkList ll) {
    Node *p = ll->nextNode;
    int index = 1;
    while (p != ll) {
        printf("%-5d", p->data);
        p = p->nextNode;
        index++;
    }
    printf("\n");
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 建立鏈表
        LinkList ll = createLinkList();
        foreachLinkList(ll);
        
        // 插入節點
        printf("++++++++++++++++++++++++++++\n");
        insertNode(&ll);
        foreachLinkList(ll);
        
        // 刪除節點
        printf("----------------------------\n");
        deleteNode(&ll);
        foreachLinkList(ll);
        
        // 查找節點
        printf("============================\n");
        printf("查找數據的位置是:%d\n", findNode(ll));
        
    }
    return 0;
}
複製代碼

好了雙向循環鏈表到這也已經說完了,可能會感受這篇文章草草了事,可是我實在是以爲這些沒什麼東西,細心思考應該都能實現這些功能。因此大部分文字都是用來解釋一些名詞,概念等。

至於邏輯思路,一切盡在代碼中,一切盡在註釋中。

總結

這篇文章主要講了線性表的鏈式存儲結構------鏈表。 包括了單向鏈表、單向循環鏈表、雙向鏈表、雙向循環鏈表以及他們對應的建立鏈表、插入節點、刪除節點、查詢數據等功能。

C語言函數------傳值和傳地址

之因此說這個,是由於我忽然想到了代碼中用到了一些傳地址的地方,可能對於一些C語言基礎不是太好的讀者來講有點迷茫。

首先我相信你們應該都知道C語言函數的傳參,實質是把變量的空間拷貝一份傳進函數實現。如:

// 將傳進來的參數+1
void sumOne(int a) {
    a = a + 1;
}

int x = 10;
// 在調用這個方法的時候,實質是把x拷貝一份,而後把拷貝的變量傳進函數實現
sumOne(x);
複製代碼

由於是把變量拷貝了一份傳到方法實現,而且這個拷貝出來的變量的做用域就只在sumOne函數內。所以在sunOne函數內的a = a + 1;並不會改變函數外部的x的值。

若是我想讓函數改變外部變量的值怎麼辦?很簡單,傳地址。以下:

// 將傳進來的參數+1
void sumOne(int *a) {
    // a是地址,*a表明經過地址a找到對應的那塊空間
    *a = *a + 1;
}

int x = 10;
int *x1 = &x;       // 將x的地址賦值給指針變量x1
// 這裏調用方法的時候會將指針變量x1拷貝一份傳進函數實現
// 即將x的地址拷貝一份傳進函數實現
sumOne(x1);
複製代碼

這樣,就能夠達到在函數內部修改外部變量值的效果了。

傳地址的用法可不只僅是這樣,咱們知道在OC中有block的存在來解決回調的需求,那麼C語言的回調函數須要怎麼搞?
沒錯,就是使用這個傳地址,只不過傳的地址是回調函數的函數地址,就這麼簡單。

本文地址

相關文章
相關標籤/搜索