此文章为本人学习笔记,若有错误求指正。
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 单向链表的操作
插入元素
- 头插法:将新节点插入到链表头部。
- 尾插法:将新节点插入到链表尾部。
头插法实现
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. 总结
表作为一种基本的数据结构,其结构和操作是许多复杂算法的基础。线性表适合用于需要快速随机访问的场合,而链表则在频繁插入和删除操作中表现更为高效。通过对表的深刻理解和代码实现,能够为进一步学习和开发复杂的数据结构和算法奠定坚实的基础。