动态数组和链表优缺点以及代码实现(C语言)

1. 动态数组

1.1 优点

  • 查找容易(通过下标),时间复杂度为O(1)。不需要额外申请或删除空间。
  • 使用下标位置索引(index)十分高效的访问任意元素,修改快

1.2 缺点 

  • 插入、删除元素难,效率低。(需要移动大量元素以使元素空间连续)。
  • 插入操作平均需要移动n/2个元素。
  • 删除操作平均需要移动(n-1)/2个元素。
  • 扩展相对繁琐。一方面需要确保能提供更大区域的连续内存空间,另一方面需要将原有数据复制到新的顺序表中。

1.3 代码实现动态数组 

通过指针和动态内存分配来实现动态数组,常见的内存分配函数包括:malloc(),calloc(),realloc()以及free().

为了提高代码的可读性和灵活性(比如:将int类型变为double,char,struct类型),使用typedef 来为类型取一个别名,这样方便将来类型的更改!!!

typedef int element_t;
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int element_t;

typedef struct{
    element_t *data;
    size_t capacity;
    size_t size;
} DynamicArray;

// 判断结构体数组是否为NULL
bool checkIsNULL(DynamicArray *array)
{
    if (array == NULL){
        printf("结构体数组为NULL!\n");
        return false;
    }
    return true;
}

// 初始化
void initDynamicArray(DynamicArray *array, size_t initCapacity)
{
    if (!checkIsNULL(array)) return false;

    array->data = (element_t *)malloc(sizeof(element_t) * initCapacity);
    if (array->data == NULL){
        printf("动态数组分配失败!\n");
        exit(EXIT_FAILURE);
    }
    array->capacity = initCapacity;
    array->size = 0;
}

// 销毁动态分配的内存
void desDynamicArray(DynamicArray *array)
{
    if (!checkIsNULL(array)) return false;

    if (array->data == NULL){
        return;
    }

    free(array->data);
    array->data = NULL;
    array->capacity = 0;
    array->size = 0;
}

// 获取动态数组元素的个数
size_t getDynamicArrayLength(DynamicArray *array)
{
    if (!checkIsNULL(array)) return false;

    return array->size;
}

// 扩容
bool changeCapacity(DynamicArray *array, size_t newCapacity)
{
    if (!checkIsNULL(array)) return false;

    element_t *newData = (element_t *)realloc(array->data, newCapacity * sizeof(element_t));
    if (newData == NULL){
        printf("扩容失败!\n");
        return false;
    }

    array->data = newData;
    array->capacity = newCapacity;

    return true;
}

// 插入元素
void insertAt(DynamicArray *array, size_t index, element_t *element)
{
    if (!checkIsNULL(array)) return false;

    if (index < 0 || index > array->size){
        printf("不允许在 %zu 的位置插入元素!\n", index);
        return;
    }

    if (array->size == array->capacity){
        if (changeCapacity(array, array->capacity * 2) == false)
            return;
    }

    for (size_t i = array->size; i > index; i++){
        array->data[i] = array->data[i - 1];
    }

    array->data[index] = *element;
    array->size++;
}

// 在末尾插入元素
void insertEnd(DynamicArray *array, element_t *element)
{
    if (!checkIsNULL(array)) return false;

    insertAt(array, array->size, element);
}

// 打印动态数组中的所有元素
void PrintArray(DynamicArray *array)
{
    if (!checkIsNULL(array)) return false;

    for (size_t i = 0; i < array->size; i++){
        printf("%d ", array->data[i]);
    }

    printf("\n");
}

// 删除指定位置的元素
bool deletedAt(DynamicArray *array, size_t index, element_t *deletedElement)
{
    if (!checkIsNULL(array)) return false;

    if (index < 0 || index >= array->size){
        printf("不允许删除 %zu 位置的元素!\n", index);
        return false;
    }

    *deletedElement = array->data[index];

    for (size_t i = index; i < array->size - 1; i++){
        array->data[i] = array->data[i + 1];
    }

    array->size--;

    return true;
}

// 删除末尾的元素
bool deletedEnd(DynamicArray *array, element_t *deletedElement)
{
    if (!checkIsNULL(array)) return false;

    return deletedAt(array, array->size - 1, deletedElement);
}

// 修改指定位置的元素
void updateDynamicArrayElement(DynamicArray *array, size_t index, element_t *element)
{
    if (!checkIsNULL(array)) return false;

    if (index < 0 || index >= array->size){
        printf("不允许修改 %zu 位置的元素!\n", index);
        return;
    }

    array->data[index] = *element;
}

// 得到指定位置的元素
element_t getValue(DynamicArray *array, size_t index)
{
    if (!checkIsNULL(array)) return false;

    if (index < 0 || index >= array->size){
        printf("不允许获取 %zu 位置的元素!\n", index);
        return -1;
    }

    return array->data[index];
}

int main()
{
    DynamicArray array;
    initDynamicArray(&array, 4);

    element_t a = 20, b = 30;
    insertEnd(&array, &a);

    insertAt(&array, 1, &b);

    printf("当前动态数组中的元素的个数为:%d \n", getDynamicArrayLength(&array));
    PrintArray(&array);

    element_t deletedEndElement;
    bool resEnd = deletedEnd(&array, &deletedEndElement);
    if (resEnd == true)
        printf("被删除的元素为:%d\n", deletedEndElement);

    element_t deletedAtElement;
    bool resAt = deletedAt(&array, 0, &deletedAtElement);
    if (resAt == true)
        printf("被删除的元素为:%d\n", deletedAtElement);
    printf("当前动态数组中的元素的个数为:%d \n", getDynamicArrayLength(&array));
    PrintArray(&array);
    
    desDynamicArray(&array);
    return 0;
}

2. 链表

2.1 优点

  • 插入和删除操作效率高
  • 动态扩展性能更好,链表不需要像数组那样预先指定固定的大小,而是可以随时动态的增长或缩小。链表是真正的动态数据结构,不需要处理固定容量的问题。

2.2 缺点

  • 查找慢。由于链表中的结点不是连续存储的,无法像数组一样根据索引直接计算出每个结点的地址。必须从头结点开始遍历链表,直到找到目标结点,这导致了链表的随机访问效率较低。

  • 额外的存储空间。链表的每个结点都需要存储指向下一个结点的指针,这会占用额外的存储空间。所以,相比于数组,链表需要更多的内存空间来存储相同数量的数据元素。

2.3 链表的特点与实现要点总结

2.3.1 链表的基本特性

  • 内存不连续:链表的节点在内存中是离散分布的,不需要像动态数组那样占用一块连续的空间。
  • 无需扩容:由于每个节点都是独立分配的,因此链表不需要扩容操作
  • 依赖动态内存管理:链表中的每个节点都需要通过 malloc 或类似方式动态申请内

2.3.2 链表节点结构体的设计

链表的每个节点必须包含两个部分:

  • 数据域(data):用于存储当前节点的数据。
  • 指针域(next):指向下一个节点的地址。
typedef struct Node {
    int data;           // 当前节点的数据
    struct Node *next;  // 指向下一个节点的指针
} Node;

注意:

  • 结构体内部不能省略自身的类型名(如 struct Node),否则无法正确声明指向下一个节点的指针。
  • 不建议使用 void* 存储任意类型数据,除非你正在实现一个通用链表库。

2.3.3 链表结构体的设计

为了方便管理和操作链表,通常会封装一个链表结构体,包含以下两个核心字段:

  • 头节点指针(head):指向链表的第一个节点。
  • 当前节点个数(size):记录链表中有效节点的数量。
typedef struct {
    Node *head;     // 头节点指针
    size_t size;    // 当前链表中的节点数量
} LinkedList;

2.3.4 实现链表的关键操作(实现链表时,需要特别注意)

2.4 代码实现链表(单向链表) 

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int element_t;

// 定义节点
typedef struct Node
{
    element_t data;
    struct Node *next;
} Node;

// 定义链表结构体
typedef struct
{
    Node *head;
    size_t size;
} LinkedList;

void isLinkedListNull(LinkedList *list)
{
    if (list == NULL){
        printf("链表为空!\n");
        return;
    }
}

// 初始化链表
void initLinkedList(LinkedList *list)
{
    isLinkedListNull(list);

    list->head = NULL;
    list->size = 0;
}

// 销毁动态分配的内存
void destoryLinkedList(LinkedList *list)
{
    isLinkedListNull(list);

    Node *currentNode = list->head;
    while (currentNode != NULL)
    {
        Node *destoryNode = currentNode;
        currentNode = currentNode->next;

        free(destoryNode);
    }

    list->head = NULL;
    list->size = 0;
}

// 获取上一个节点
Node *getPrevNode(LinkedList *list, size_t index)
{
    if (index <= 0 || index > list->size){
        printf("索引 %zu 不被允许进行接下的操作!\n", index);
        return NULL;
    }

    Node *prevNode = list->head;
    for (int i = 1; i < index; i++){
        prevNode  = prevNode->next;
    }
    return prevNode;
}

// 插入数据
void insertAt(LinkedList *list, size_t index, element_t element)
{
    isLinkedListNull(list);

    if (index < 0 || index > list->size){
        printf("不允许在 %zu 位置插入结点!\n", index);
        return;
    }

    Node *newNode = (Node *)malloc(sizeof(Node));
    if (newNode == NULL){
        printf("动态内存分配失败!\n");
        exit(EXIT_FAILURE);
    }
    newNode->data = element;
    newNode->next = NULL;

    if (index == 0){
        // 将新节点指向头节点的地址
        newNode->next = list->head;

        list->head = newNode;
    }
    else{
        // 获取上一个节点
        Node *prevNode = getPrevNode(list, index);
        newNode->next = prevNode->next;
        prevNode->next = newNode;
    }
    
    list->size++;
}

// 尾端插入节点
void insertEnd(LinkedList *list, element_t element)
{
    isLinkedListNull(list);

    insertAt(list, list->size, element);
}

// 获取当前的节点
Node *getCurrentNode(LinkedList *list, size_t index)
{   
    if (index >= list->size){
        printf("索引 %zu 不被允许进行接下的操作!\n", index);
        return NULL;
    }

    Node *currentNode = list->head;
    for (int i = 1; i <= index; i++){
        currentNode  = currentNode->next;
    }
    return currentNode;
}

// 指定位置删除数据,并且将删除的数据保存
void deletedAt(LinkedList *list, size_t index, element_t *deletedElement)
{
    isLinkedListNull(list);

    if (index < 0 || index >= list->size){
        printf("不允许在 %zu 位置删除节点!\n", index);
        return;
    }

    Node *deletedNode;
    Node *prevNode;

    if (index == 0){
        deletedNode = list->head;
        list->head = list->head->next;
    }
    else{
        prevNode = getPrevNode(list, index);
        deletedNode = prevNode->next;
        prevNode->next = prevNode->next->next;
    }

    *deletedElement = deletedNode->data;
    list->size--;
}

// 在尾端删除节点
void deletedEnd(LinkedList *list, element_t *element)
{
    deletedAt(list, list->size -  1, element);
}

// 修改指定位置节点的数据
void modifyNode(LinkedList *list, size_t index, element_t element)
{
    isLinkedListNull(list);

    if (index < 0 || index >= list->size){
        printf("不允许在 %zu 位置修改节点!\n", index);
        return;
    }

    Node *currentNode = getCurrentNode(list, index);
    currentNode->data = element;
}

// 获取指定位置的节点的数据值
element_t getNodeData(LinkedList *list, size_t index)
{
    Node *currentNode = getCurrentNode(list, index);

    return currentNode->data;
}

// 获取当前链表的节点个数
size_t getSize(LinkedList *list)
{
    isLinkedListNull(list);

    return list->size;
}

// 打印所有节点数据
void Print(LinkedList *list)
{
    Node *currentNode = list->head;

    for (int i = 0; i < list->size; i++){
        printf("当前节点的地址:%p, " 
               "当前节点的数据为:%d, " 
               "当前节点指向的地址为:%p \n",
                currentNode, currentNode->data, currentNode->next);
        
        currentNode = currentNode->next;
    }   
    printf("\n");
}

int main()
{
    LinkedList list;
    // 初始化
    initLinkedList(&list);

    // 插入数据
    insertAt(&list, 0, 100);
    insertAt(&list, 1, 200);
    insertAt(&list, 2, 300);
    insertAt(&list, 3, 400);
    insertAt(&list, 4, 500);

    // 尾端插入数据
    insertEnd(&list, 600);
    size_t size = getSize(&list);
    printf("当前链表的节点的个数为:%zu \n", size);

    Print(&list);

    // 删除节点
    element_t element;
    deletedAt(&list, 0, &element);
    printf("被删除的节点为:%d \n", element);

    // 删除尾端节点
    deletedEnd(&list, &element);
    printf("被删除的节点为:%d \n", element);

    size = getSize(&list);
    printf("当前链表的节点的个数为:%zu \n", size);
    Print(&list);

    // 获取指定位置节点的数据
    element_t getElement = getNodeData(&list, 2);
    printf("当前节点的数据为:%d \n", getElement);
    Print(&list);

    // 修改指定位置节点的数据
    modifyNode(&list, 2, 200000);
    Print(&list);

    // 销毁动态分配的内存
    destoryLinkedList(&list);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值