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;
}

&spm=1001.2101.3001.5002&articleId=149281540&d=1&t=3&u=6e9e97fee03142f498b45703150c799d)
85

被折叠的 条评论
为什么被折叠?



