数据结构与算法—表(List)

此文章为本人学习笔记,若有错误求指正。

1. 引言

表(List)是一种基本的数据结构,它是存储多个数据元素的集合。这些元素按照线性顺序排列,每个元素都有唯一的序号(index)。由于表的有序性,可以通过序号随机访问元素,这使得表在计算机科学中广泛应用。

2. 基本概念

2.1 元素与序号

  • 元素(Element):表中的每个数据项称为元素。
  • 序号(Index):每个元素在表中的位置由一个非负整数表示,通常从0开始。
  • 前驱(Predecessor):在表中,元素a1是元素a2的前驱。
  • 后继(Successor):元素a3是元素a2的后继。

3. 抽象数据类型(ADT)

表的抽象数据类型包括数据字段和数据操作两部分。

  • 3.1 数据字段

  • 存储结构:表的存储结构可以是连续的(如线性表)或链式的(如链表)。
  • 长度:表的长度是其包含的元素个数。

3.2 数据操作

  • 插入(Insert):在表的任意位置插入新元素。
  • 删除(Delete):从表中删除指定位置的元素。
  • 查找(Search):根据元素的值或序号查找元素。
  • 更新(Update):修改表中指定位置的元素值。

4. 线性表

线性表是一种存储结构为连续的表,通常基于数组实现。它是一个长度动态增长的数组,允许快速的随机访问。

4.1 线性表的特点

索引访问:O(1)的时间复杂度,可以通过序号快速访问任意元素。

插入与删除:在任意位置插入或删除元素的时间复杂度为O(n)。

查找元素:需要遍历整个表,时间复杂度为O(n)。

4.2 线性表的操作实现

插入元素

插入元素需要先找到插入位置,然后将该位置及其后的元素向后移动一位,最后将新元素插入到指定位置。

void insert(int arr[], int n, int index, int value) {
    for (int i = n; i > index; i--) {
        arr[i] = arr[i-1];
    }
    arr[index] = value;
}

删除元素

删除元素时,需要将被删除元素后的所有元素依次前移,覆盖被删除的元素。

void delete(int arr[], int n, int index) {
    for (int i = index; i < n-1; i++) {
        arr[i] = arr[i+1];
    }
}

5. 链表

链表是存储结构为链式的表,使用链表可以在内存不连续的情况下动态分配空间,适合元素频繁插入和删除的情况。

5.1 链表的类型

  • 单向链表(Singly Linked List):每个节点只有一个指向下一个节点的指针。
  • 双向链表(Doubly Linked List):每个节点有两个指针,分别指向前驱和后继节点。
  • 循环链表(Circular Linked List):链表的最后一个节点指向表头,形成一个环。

5.2 链表的节点结构

单向链表节点结构

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

双向链表节点结构

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

5.3 单向链表的操作

插入元素

  1. 头插法:将新节点插入到链表头部。
  2. 尾插法:将新节点插入到链表尾部。

头插法实现

void insertAtHead(struct Node** head, int data) {
    struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = *head;
    *head = newNode;
}

尾插法实现

void insertAtTail(struct Node** head, int data) {
    struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = NULL;
    if (*head == NULL) {
        *head = newNode;
        return;
    }
    struct Node* temp = *head;
    while (temp->next != NULL) {
        temp = temp->next;
    }
    temp->next = newNode;
}

删除元素

删除操作需要找到要删除的节点,并调整前驱节点的指针指向被删除节点的后继节点。

void deleteNode(struct Node** head, int key) {
    struct Node* temp = *head;
    struct Node* prev = NULL;

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

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

    if (temp == NULL) return;

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

5.4 双向链表的操作

插入元素

双向链表的插入操作类似于单向链表,但需要同时调整前驱和后继节点的指针。

头部插入节点

void insertAtHead(struct Node** head, int data) {
    struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = *head;
    newNode->prev = NULL;
    if (*head != NULL) {
        (*head)->prev = newNode;
    }
    *head = newNode;
}

尾部插入节点

void insertAtTail(struct Node** head, int data) {
    struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
    newNode->data = data;
    newNode->next = NULL;
    if (*head == NULL) {
        newNode->prev = NULL;
        *head = newNode;
        return;
    }
    struct Node* temp = *head;
    while (temp->next != NULL) {
        temp = temp->next;
    }
    temp->next = newNode;
    newNode->prev = temp;
}

删除元素

删除双向链表中的节点时,需要调整前驱和后继节点的指针。

void deleteNode(struct Node** head, struct Node* del) {
    if (*head == NULL || del == NULL) return;

    if (*head == del) *head = del->next;

    if (del->next != NULL) del->next->prev = del->prev;

    if (del->prev != NULL) del->prev->next = del->next;

    free(del);
}

5.5 循环链表的操作

循环链表与普通链表的区别在于,最后一个节点的后继指向头节点,这使得链表可以从任意节点开始遍历。

插入元素

插入元素时,除了调整新节点的指针外,还需要调整最后一个节点的指针。

void insertAtTail(struct Node** head, int data) {
    struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));
    newNode->data = data;
    if (*head == NULL) {
        *head = newNode;
        newNode->next = *head;
        return;
    }
    struct Node* temp = *head;
    while (temp->next != *head) {
        temp = temp->next;
    }
    temp->next = newNode;
    newNode->next = *head;
}

删除元素

删除元素时,需要特别注意调整最后一个节点的指针。

void deleteNode(struct Node** head, int key) {
    if (*head == NULL) return;

    struct Node *temp = *head, *prev;
    if (temp->data == key && temp->next == *head) {
        *head = NULL;
        free(temp);
        return;
    }

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

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

    if (temp->next->data == key) {
        prev = temp->next;
        temp->next = prev->next;
        free(prev);
    }
}

6. 总结

表作为一种基本的数据结构,其结构和操作是许多复杂算法的基础。线性表适合用于需要快速随机访问的场合,而链表则在频繁插入和删除操作中表现更为高效。通过对表的深刻理解和代码实现,能够为进一步学习和开发复杂的数据结构和算法奠定坚实的基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值