【数据结构:C语言版】003:链表揭秘-C语言中的数据组织艺术

        链表是一种常用的动态数据结构,它通过指针将一组零散的内存块串联在一起。与数组相比,链表插入和删除元素的操作更加高效,但随机访问性能较差。本文将深入探讨三种常见的链表类型:单向链表、双向链表和循环链表,带您领略C语言中链表的魅力。

1. 单向链表:简单而灵活的数据链

单向链表是最基本的链表类型。每个节点包含数据和指向下一个节点的指针。

实现和操作

下面是一个简单的单向链表实现,包括创建、插入、删除和打印操作:

#include 
#include 

// 定义链表节点结构
typedef struct Node {
    int data;
    struct Node* next;
} Node;

// 创建新节点
Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        printf("内存分配失败\n");
        exit(1);
    }
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

// 在链表头部插入节点
Node* insertAtBeginning(Node* head, int data) {
    Node* newNode = createNode(data);
    newNode->next = head;
    return newNode;
}

// 删除指定值的节点
Node* deleteNode(Node* head, int key) {
    Node* temp = head;
    Node* prev = NULL;

    if (temp != NULL && temp->data == key) {
        head = temp->next;
        free(temp);
        return head;
    }

    while (temp != NULL && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }

    if (temp == NULL) return head;

    prev->next = temp->next;
    free(temp);
    return head;
}

// 打印链表
void printList(Node* node) {
    while (node != NULL) {
        printf("%d -> ", node->data);
        node = node->next;
    }
    printf("NULL\n");
}

int main() {
    Node* head = NULL;

    head = insertAtBeginning(head, 3);
    head = insertAtBeginning(head, 2);
    head = insertAtBeginning(head, 1);

    printf("初始链表: ");
    printList(head);

    head = deleteNode(head, 2);

    printf("删除节点后: ");
    printList(head);

    return 0;
}

/* 运行结果:
初始链表: 1 -> 2 -> 3 -> NULL
删除节点后: 1 -> 3 -> NULL
*/

        单向链表的优点是实现简单,内存占用小。但它只能从头到尾遍历,不支持反向遍历,删除节点时需要记录前一个节点。

2. 双向链表:灵活性的代价

        双向链表在单向链表的基础上,为每个节点增加了指向前一个节点的指针。这增加了灵活性,但也增加了内存占用和实现复杂度。

实现和操作

以下是双向链表的基本实现,包括插入、删除和打印操作:

#include 
#include 

typedef struct Node {
    int data;
    struct Node* next;
    struct Node* prev;
} Node;

Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        printf("内存分配失败\n");
        exit(1);
    }
    newNode->data = data;
    newNode->next = NULL;
    newNode->prev = NULL;
    return newNode;
}

Node* insertAtBeginning(Node* head, int data) {
    Node* newNode = createNode(data);
    newNode->next = head;
    if (head != NULL) {
        head->prev = newNode;
    }
    return newNode;
}

Node* deleteNode(Node* head, int key) {
    Node* temp = head;

    while (temp != NULL && temp->data != key) {
        temp = temp->next;
    }

    if (temp == NULL) return head;

    if (temp->prev != NULL) {
        temp->prev->next = temp->next;
    } else {
        head = temp->next;
    }

    if (temp->next != NULL) {
        temp->next->prev = temp->prev;
    }

    free(temp);
    return head;
}

void printList(Node* node) {
    Node* last;
    printf("正向: ");
    while (node != NULL) {
        printf("%d -> ", node->data);
        last = node;
        node = node->next;
    }
    printf("NULL\n");

    printf("反向: ");
    while (last != NULL) {
        printf("%d -> ", last->data);
        last = last->prev;
    }
    printf("NULL\n");
}

int main() {
    Node* head = NULL;

    head = insertAtBeginning(head, 3);
    head = insertAtBeginning(head, 2);
    head = insertAtBeginning(head, 1);

    printf("初始双向链表:\n");
    printList(head);

    head = deleteNode(head, 2);

    printf("删除节点后:\n");
    printList(head);

    return 0;
}

/* 运行结果:
初始双向链表:
正向: 1 -> 2 -> 3 -> NULL
反向: 3 -> 2 -> 1 -> NULL
删除节点后:
正向: 1 -> 3 -> NULL
反向: 3 -> 1 -> NULL
*/

        双向链表的主要优点是可以双向遍历,并且可以更容易地实现某些算法。然而,它的内存占用更大,且插入和删除操作稍微复杂一些。

3. 循环链表:首尾相连的数据结构

循环链表是首尾相连的链表,可以是单向的,也可以是双向的。这里我们实现一个单向循环链表。

实现和操作

以下是单向循环链表的基本实现,包括插入、删除和打印操作:

#include 
#include 

typedef struct Node {
    int data;
    struct Node* next;
} Node;

Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        printf("内存分配失败\n");
        exit(1);
    }
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

Node* insertAtBeginning(Node* last, int data) {
    Node* newNode = createNode(data);
    if (last == NULL) {
        newNode->next = newNode; // 指向自己形成循环
        return newNode;
    }
    newNode->next = last->next;
    last->next = newNode;
    return last;
}

Node* deleteNode(Node* last, int key) {
    if (last == NULL) return NULL;

    Node* curr = last->next;
    Node* prev = last;

    do {
        if (curr->data == key) {
            if (curr == last) {
                if (curr->next == curr) {
                    free(curr);
                    return NULL;
                }
                last = prev;
            }
            prev->next = curr->next;
            free(curr);
            return last;
        }
        prev = curr;
        curr = curr->next;
    } while (curr != last->next);

    printf("节点 %d 未找到\n", key);
    return last;
}

void printList(Node* last) {
    if (last == NULL) {
        printf("链表为空\n");
        return;
    }
    Node* temp = last->next;
    do {
        printf("%d -> ", temp->data);
        temp = temp->next;
    } while (temp != last->next);
    printf("(回到开头)\n");
}

int main() {
    Node* last = NULL;

    last = insertAtBeginning(last, 3);
    last = insertAtBeginning(last, 2);
    last = insertAtBeginning(last, 1);

    printf("初始循环链表: ");
    printList(last);

    last = deleteNode(last, 2);

    printf("删除节点后: ");
    printList(last);

    return 0;
}

/* 运行结果:
初始循环链表: 1 -> 2 -> 3 -> (回到开头)
删除节点后: 1 -> 3 -> (回到开头)
*/

        循环链表的特点是没有明确的开始和结束点,非常适合需要循环处理的场景,如操作系统的资源调度。

总结

        链表是一种灵活的数据结构,适用于频繁插入和删除操作的场景。单向链表简单高效,双向链表提供了更多的灵活性,而循环链表则适合需要循环处理的情况。选择哪种链表类型取决于具体的应用需求。

        理解和掌握这些链表类型及其操作是构建更复杂数据结构和算法的基础。希望通过本文的介绍和示例,您能更好地理解和应用C语言中的链表结构。

你可能感兴趣的:(数据结构C语言版,数据结构,c语言,链表)