1.数据结构基本概念
数据结构是计算机中存储、组织数据的方式,旨在提高数据的访问和操作效率。它是实现高效算法和程序设计的基石。
目标:通过思维导图了解数据结构的知识点,并掌握。
1.1逻辑结构
逻辑结构主要四种类型:
集合:结构中的数据元素之间除了同属于一个集合的关系外,别无其他关系。
线性结构:结构中的数据元素之间存在一个对一个的关系,如线性表。
树形结构:结构中的元素之间存在一个对多个的关系,像二叉树等。
图状结构或网状结构:结构中的数据元素之间存在多个对多个的关系。
1.2物理结构
物理结构主要两种类型的特点:
顺序存储结构:数据元素在存储器中是顺序存放的,逻辑上相邻的数据元素在物理位置上也相邻。
链式存储结构:数据元素在存储器中的位置是任意的,逻辑上相邻的数据元素在物理位置上不一定相邻,通过指针来表示元素之间的关系。
1.3抽象数据类型(ADT)
抽象数据类型是指一个数学模型以及定义在该模型上的一组操作。它将数据和操作封装在一起,使用者只需要关心它的功能,而不需要了解其内部实现细节。
在数据结构设计中,ADT有助于提高程序的模块化和可维护性。
可以简单理解成:接口,封装,模块化。
1.4数据结构常用的关键字及函数
数据结构核心必须要掌握常用的关键字,常用函数的原型以及内存的资源分配,对内存的操作时需要注意:内存申请,内存使用的状态,内存的释放,避免数据溢出而导致的程序异常,这里我们就简单介绍下struct、union、typedef 。
1.4.1数据结构常用关键字及函数定义
1.4.2数据结构体及联合体解析
1.4.2.1 struct解析
struct代码示例:
//struct原型定义的格式为:struct 结构体名 { 成员列表 }; #include <stdio.h> // 引入标准输入输出库//1. struct Point定义了一个名为 Point 的结构体struct Point {int x; // int x;:结构体成员 x,用于存储点的 x 坐标int y; // int y;:结构体成员 y,用于存储点的 y 坐标
};int main() {//2. 声明结构体变量:
// struct Point p1;:声明一个 Point 类型的变量 p1,用于存储一个点的坐标struct Point p1; //3. 初始化结构体 p1 的成员变量 p1.x = 10; // p1.x = 10;:将 p1 的 x 坐标设置为 10p1.y = 20; // p1.y = 20;:将 p1 的 y 坐标设置为 20//4. 访问并打印 p1 的坐标
//p1.x 和 p1.y 分别访问 p1 的 x 坐标和 y 坐标printf("Point p1: (%d, %d)\n", p1.x, p1.y); // 输出点 p1 的坐标return 0; // 程序结束
}
1.4.2.2 union解析
union代码示例:
/*
1.union原型:
2.UnionName是union的名称
3.type1, type2, ..., typen是union中成员的数据类型,member1, member2, ..., membern是union的成员名称*/union UnionName {type1 member1;type2 member2;// ...typen membern;
};#include <stdio.h>
#include <string.h>//1. 定义一个 union
// union Data 定义了一个名为 Data 的 union,及三个成员:一个整数 i,一个浮点数 f,一个字符数组 str
// union 中的所有成员共享同一块内存空间,因此 union 的大小等于其最大成员的大小union Data {int i; // 整数成员float f; // 浮点数成员char str[20]; // 字符数组成员
};int main() {//2. 定义 union 变量及声明:
// union Data data;:声明一个 Data 类型的变量 data,用于存储不同类型的数据union Data data;//3. 访问 union 成员:data.i = 10; // data.i = 10;:将 data 的整数成员 i 设置为 10printf("data.i: %d\n", data.i); // 使用 printf 函数输出 data.i 的值//4. 修改 union 成员data.f = 220.5; // 将浮点数成员 f 设置为 220.5printf("data.f: %f\n", data.f); // 输出浮点数成员 f 的值//5. 再次访问 union 成员
由于 union 成员共享同一块内存空间,初始化 f 会覆盖之前的 i 的值。printf("data.i: %d\n", data.i); // 输出整数成员 i 的值,可以看到它已经被覆盖//6. 使用 str 成员strcpy(data.str, "hello glang"); // 将字符串 "Hello glang" 复制到字符数组成员 strprintf("data.str: %s\n", data.str); // 输出字符数组成员 data.str 的值//7. 再次访问 union 成员
// 由于 union 成员共享同一块内存空间,初始化 str 会覆盖之前的 i 和 f 的值printf("data.i: %d\n", data.i); return 0;
}
1.4.2.3 typedef 解析
代码示例:
/*
typedef的基本语法existing_type是已存在的数据类型,new_type_name是你为这个数据类型定义的新名称typedef existing_type new_type_name;
*///1. 为基本数据类型int定义别名Integer
typedef int Integer;//2. 为基本数据类型float定义别名Real
typedef float Real;//3. 为指向int类型的指针定义别名IntPtr
typedef int *IntPtr;//4. 为指向char指针的指针(即char**)定义别名StringArray,可用于表示字符串数组
typedef char **StringArray;//5. 为包含10个int元素的数组定义别名ArrayOfInt
typedef int ArrayOfInt[10];//6. 为匿名结构体定义别名Point,该结构体包含两个int类型的成员x和y
typedef struct {int x; // x坐标int y; // y坐标
} Point;//7. 为指向函数的指针定义别名Operation,该函数接受两个int参数并返回一个int值
typedef int (*Operation)(int, int);//8. Integer a = 10; // 等同于 int a = 10;//9. Real b = 20.5f; // 等同于 float b = 20.5f;//10. IntPtr p = &a; // 等同于 int *p = &a;//11. StringArray strings = {"Hello", "World"}; // 等同于 char *strings[] = {"Hello", "World"};//12. ArrayOfInt numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; // 等同于 int numbers[10] = {1, 2,
3, 4, 5, 6, 7, 8, 9, 0};//13. Point p1 = {1, 2}; // 等同于 struct {int x; int y;} p1 = {1, 2};//14. 假设有一个函数add,它接受两个int参数并返回它们的和int add(int x, int y) {return x + y;
}Operation op = add; // 等同于 int (*op)(int, int) = add;int sum = op(5, 3); // 调用add函数,等同于 int sum = add(5, 3);
1.4.2.4 struct、union、typedef 联合应用
代码示例:
# include <stdio.h>
# include <string.h>// 定义一个结构体,用于表示二维平面上的一个点
struct Point {// 点的 x 坐标int x;// 点的 y 坐标int y;
};// 定义一个联合体,用于表示不同类型的数据
union Data {// 整数成员int i;// 浮点数成员float f;// 字符数组成员char str[20];
};// 使用 typedef 为结构体和联合体定义别名,方便后续使用
typedef struct Point Point;
typedef union Data Data;int main() {// 使用别名定义结构体变量Point p1;// 设置 x 坐标为 10p1.x = 10;// 设置 y 坐标为 20p1.y = 20;// 输出点 p1 的坐标printf("Point p1: (%d, %d)\n", p1.x, p1.y);// 使用别名定义联合体变量Data data;// 设置整数成员 i 的值为 10data.i = 10;// 输出整数成员 i 的值printf("data.i: %d\n", data.i);// 设置浮点数成员 f 的值为 220.5data.f = 220.5;// 输出浮点数成员 f 的值printf("data.f: %f\n", data.f);// 设置字符数组成员 str 的值为 "C Programming"strcpy(data.str, "C Programming");// 输出字符数组成员 str 的值printf("data.str: %s\n", data.str);// 再次访问联合体成员 i 和 f,由于联合体的特性,这些值可能已经被覆盖printf("data.i after str: %d\n", data.i);printf("data.f after str: %f\n", data.f);return 0; // 返回 0 表示程序成功结束
}
2基础数据结构
目标:通过思维导图了解数据基础结构核心的知识点,并掌握。
2.1数组
2.1.1数组的基础概念及操作
2.1.2数组实现与解析
2.1.2.1数组实现操作步骤
(1)插入元素函数 insert
:
a.参数:
arr[]
:目标数组。size
:指向数组当前大小的指针。index
:插入位置的索引。value
:要插入的值。
b.功能:
- 检查数组是否已满,如果已满则输出错误信息并返回。
- 检查索引是否有效,如果无效则输出错误信息并返回。
- 将插入位置之后的元素向后移动一位。
- 在指定位置插入新元素。
- 更新数组大小。
(2)删除元素函数 delete
:
a.参数:
arr[]
:目标数组。size
:指向数组当前大小的指针。index
:删除位置的索引。
b.功能:
- 检查数组是否为空,如果为空则输出错误信息并返回。
- 检查索引是否有效,如果无效则输出错误信息并返回。
- 将删除位置之后的元素向前移动一位。
- 更新数组大小。
(3)通过索引查找元素函数 searchByIndex
:
a.参数:
arr[]
:目标数组。size
:数组当前大小。index
:要查找的索引。
b.功能:
- 检查索引是否有效,如果无效则输出错误信息并返回
-1
。 - 返回指定索引的元素。
(4)顺序查找元素函数 searchByValue
:
a.参数:
arr[]
:目标数组。size
:数组当前大小。value
:要查找的值。
b.功能:
- 遍历数组查找指定值。
- 如果找到,返回找到的元素索引。
- 如果未找到,返回
-1
。
(5)主函数 main
:
a.功能:
- 定义一个大小为100的数组
arr
。 - 初始化数组大小
size
为0。 - 插入元素到数组中。
- 查找元素并打印结果。
- 删除元素并打印数组内容。
(6)运行结果解释:
insert(arr, &size, 0, 10); insert(arr, &size, 1, 20); insert(arr, &size, 2, 30);
:依次在索引0
,1
,2
处插入值10
,20
,30
,此时数组内容为[10, 20, 30]
。printf("Element at index 1: %d\n", searchByIndex(arr, size, 1));
:输出索引1
处的元素20
。printf("Index of element 20: %d\n", searchByValue(arr, size, 20));
:输出值20
的索引1
。delete(arr, &size, 1);
:删除索引1
处的元素20
,此时数组内容为[10, 30]
。printf("Array after deletion: ");
:打印数组内容[10, 30]
。
2.1.2.2数组代码实现实例
通过下图代码了解数据结构中的数组实现原理。
#include <stdio.h>// 插入元素到数组指定位置
void insert(int arr[], int *size, int index, int value) {// 检查数组是否已满if (*size >= 100) {printf("Array is full, cannot insert.\n");return;}// 检查索引是否有效if (index < 0 || index > *size) {printf("Invalid index.\n");return;}// 将插入位置之后的元素向后移动一位for (int i = *size; i > index; i--) {arr[i] = arr[i - 1];}// 在指定位置插入新元素arr[index] = value;// 更新数组大小(*size)++;
}// 从数组指定位置删除元素
void delete(int arr[], int *size, int index) {// 检查数组是否为空if (*size == 0) {printf("Array is empty, cannot delete.\n");return;}// 检查索引是否有效if (index < 0 || index >= *size) {printf("Invalid index.\n");return;}// 将删除位置之后的元素向前移动一位for (int i = index; i < *size - 1; i++) {arr[i] = arr[i + 1];}// 更新数组大小(*size)--;
}// 通过索引查找元素
int searchByIndex(int arr[], int size, int index) {// 检查索引是否有效if (index < 0 || index >= size) {printf("Invalid index.\n");return -1;}// 返回指定索引的元素return arr[index];
}// 顺序查找元素
int searchByValue(int arr[], int size, int value) {// 遍历数组查找指定值for (int i = 0; i < size; i++) {if (arr[i] == value) {// 返回找到的元素索引return i;}}// 如果未找到,返回-1return -1;
}int main() {int arr[100]; // 定义一个大小为100的数组int size = 0; // 初始化数组大小为0// 插入元素insert(arr, &size, 0, 10); // 在索引0处插入值10insert(arr, &size, 1, 20); // 在索引1处插入值20insert(arr, &size, 2, 30); // 在索引2处插入值30// 查找元素printf("Element at index 1: %d\n", searchByIndex(arr, size, 1)); // 查找索引1处的元素printf("Index of element 20: %d\n", searchByValue(arr, size, 20)); // 查找值为20的元素索引// 删除元素delete(arr, &size, 1); // 删除索引1处的元素// 打印数组printf("Array after deletion: ");for (int i = 0; i < size; i++) {printf("%d ", arr[i]); // 打印数组中的元素}printf("\n");return 0;
}
2.2链表
2.2.1链表结构特点与操作
2.2.2链表实现与解析
2.2.2.1链表实现操作步骤
(1)结构体定义:
Node
结构体表示单链表中的节点,包含一个整数 data
和一个指向下一个节点的指针 next
。
(2)插入节点函数:
a. insertAtHead
:在单链表头部插入节点。
- 分配内存给新节点,如果内存分配失败则输出错误信息并返回。
- 设置新节点的数据。
- 将新节点的
next
指针指向当前头节点。 - 更新头节点为新节点。
b. insertAtTail
:在单链表尾部插入节点。
- 分配内存给新节点,如果内存分配失败则输出错误信息并返回。
- 设置新节点的数据。
- 将新节点的
next
指针初始化为NULL
。 - 如果链表为空,直接将新节点设置为头节点。
- 否则,从头节点开始遍历,找到最后一个节点并将最后一个节点的
next
指针指向新节点。
(3)删除节点函数:
a. deleteNode
:删除单链表中的节点。
-
如果链表为空,直接返回。
-
如果要删除的节点是头节点,保存头节点,更新头节点为下一个节点,并释放原头节点的内存。
-
否则,从头节点开始遍历,找到要删除节点的前一个节点。
-
如果找到了要删除的节点,保存要删除的节点,将前一个节点的
next
指针指向要删除节点的下一个节点,并释放要删除节点的内存。
(4)查找节点函数:
a. searchNode
:查找单链表中的节点。
从头节点开始遍历。
如果找到目标节点,返回目标节点。
如果未找到目标节点,返回 NULL
。
(5)打印链表函数:
a. printList
:打印单链表。
从头节点开始遍历。
打印当前节点的数据。
打印换行符。
(6)主函数:
- 初始化链表头节点为
NULL
。 - 在头部插入节点
10
,20
,30
。 - 在尾部插入节点
40
,50
。 - 打印链表。
- 查找值为
30
的节点并打印结果。 - 删除值为
20
的节点。 - 打印链表。
(7)运行结果解释:
-
插入节点:
-
insertAtHead(&head, 10); insertAtHead(&head, 20); insertAtHead(&head, 30);
:依次在头部插入值10
,20
,30
,此时链表内容为[30, 20, 10]
。 -
insertAtTail(&head, 40); insertAtTail(&head, 50);
:依次在尾部插入值40
,50
,此时链表内容为[30, 20, 10, 40, 50]
。
-
-
打印链表:
-
printf("Linked list after insertions: "); printList(head);
:输出链表内容[30, 20, 10, 40, 50]
。
-
-
查找节点:
-
Node *foundNode = searchNode(head, 30);
:查找值为30
的节点。 -
if (foundNode != NULL) { printf("Node with value 30 found.\n"); }
:输出Node with value 30 found.
。
-
-
删除节点:
-
deleteNode(&head, 20);
:删除值为20
的节点,此时链表内容为[30, 10, 40, 50]
。
-
-
打印链表:
-
printf("Linked list after deletion: "); printList(head);
:输出链表内容[30, 10, 40, 50]
。
-
2.2.2.2链表代码实现示例
#include <stdio.h>
#include <stdlib.h>// 定义单链表节点结构体
typedef struct Node {int data; // 节点数据域struct Node *next; // 指向下一个节点的指针
} Node;// 在单链表头部插入节点
void insertAtHead(Node **head, int value) {Node *newNode = (Node *)malloc(sizeof(Node)); // 分配新节点的内存newNode->data = value; // 设置新节点的数据newNode->next = *head; // 将新节点的next指针指向当前头节点*head = newNode; // 更新头节点为新节点
}// 在单链表尾部插入节点
void insertAtTail(Node **head, int value) {Node *newNode = (Node *)malloc(sizeof(Node)); // 分配新节点的内存newNode->data = value; // 设置新节点的数据newNode->next = NULL; // 新节点的next指针初始化为NULLif (*head == NULL) { // 如果链表为空*head = newNode; // 直接将新节点设置为头节点return;}Node *current = *head; // 从头节点开始遍历while (current->next != NULL) { // 找到最后一个节点current = current->next;}current->next = newNode; // 将最后一个节点的next指针指向新节点
}// 删除单链表中的节点
void deleteNode(Node **head, int value) {if (*head == NULL) { // 如果链表为空return;}if ((*head)->data == value) { // 如果要删除的节点是头节点Node *temp = *head; // 保存头节点*head = (*head)->next; // 更新头节点为下一个节点free(temp); // 释放原头节点的内存return;}Node *current = *head; // 从头节点开始遍历while (current->next != NULL && current->next->data != value) { // 找到要删除节点的前一个节点current = current->next;}if (current->next != NULL) { // 如果找到了要删除的节点Node *temp = current->next; // 保存要删除的节点current->next = current->next->next; // 将前一个节点的next指针指向要删除节点的下一个节点free(temp); // 释放要删除节点的内存}
}// 查找单链表中的节点
Node *searchNode(Node *head, int value) {Node *current = head; // 从头节点开始遍历while (current != NULL) { // 遍历链表if (current->data == value) { // 如果找到目标节点return current; // 返回目标节点}current = current->next; // 继续遍历下一个节点}return NULL; // 如果未找到目标节点,返回NULL
}// 打印单链表
void printList(Node *head) {Node *current = head; // 从头节点开始遍历while (current != NULL) { // 遍历链表printf("%d ", current->data); // 打印当前节点的数据current = current->next; // 继续遍历下一个节点}printf("\n"); // 打印换行符
}int main() {Node *head = NULL; // 初始化链表头节点为NULL// 在头部插入节点insertAtHead(&head, 10); // 在头部插入值为10的节点insertAtHead(&head, 20); // 在头部插入值为20的节点insertAtHead(&head, 30); // 在头部插入值为30的节点// 在尾部插入节点insertAtTail(&head, 40); // 在尾部插入值为40的节点insertAtTail(&head, 50); // 在尾部插入值为50的节点// 打印链表printf("Linked list after insertions: ");printList(head); // 打印链表// 查找节点Node *foundNode = searchNode(head, 30); // 查找值为30的节点if (foundNode != NULL) { // 如果找到了节点printf("Node with value 30 found.\n"); // 打印提示信息} else { // 如果未找到节点printf("Node with value 30 not found.\n"); // 打印提示信息}// 删除节点deleteNode(&head, 20); // 删除值为20的节点// 打印链表printf("Linked list after deletion: ");printList(head); // 打印链表return 0;
}
2.3栈与队列
2.3.1栈与队列的基本概念
2.3.2栈与队列实现与解析
2.3.2.1顺序栈实现操作步骤
(1)宏定义:MAX_SIZE
定义了栈的最大容量。
(2)结构体定义:Stack
结构体包含一个数组 data
和一个整数 top
,用于存储栈中的元素和指向栈顶的位置。
(3)初始化函数:initStack
将栈顶指针 top
初始化为 -1
,表示栈为空。
(4)判断栈状态:isEmpty
和 isFull
分别用于判断栈是否为空或已满。
(5)栈操作函数:
-
push
:将元素压入栈顶,检查栈是否已满以避免溢出。 -
pop
:弹出栈顶元素,检查栈是否为空以避免下溢。 -
peek
:查看栈顶元素但不弹出,同样需要检查栈是否为空。
(6)主函数:演示了栈的基本操作,包括入栈、查看栈顶元素和出栈。
2.3.2.2顺序栈代码实现示例
#include <stdio.h>
#include <stdlib.h>// 定义栈的最大容量
#define MAX_SIZE 100// 定义栈结构体,包含一个数组和一个指向栈顶的指针
typedef struct {int data[MAX_SIZE]; // 存储栈中元素的数组int top; // 栈顶指针,初始值为-1表示空栈
} Stack;// 初始化栈,将栈顶指针设置为-1
void initStack(Stack *s) {s->top = -1;
}// 判断栈是否为空
int isEmpty(Stack *s) {return s->top == -1;
}// 判断栈是否已满
int isFull(Stack *s) {return s->top == MAX_SIZE - 1;
}// 入栈操作:将元素压入栈顶
void push(Stack *s, int value) {if (isFull(s)) { // 检查栈是否已满printf("Stack Overflow\n"); // 栈满时输出错误信息return;}s->data[++s->top] = value; // 将元素存入栈顶,并更新栈顶指针
}// 出栈操作:弹出栈顶元素
int pop(Stack *s) {if (isEmpty(s)) { // 检查栈是否为空printf("Stack Underflow\n"); // 栈空时输出错误信息return -1;}return s->data[s->top--]; // 返回栈顶元素,并更新栈顶指针
}// 查看栈顶元素但不出栈
int peek(Stack *s) {if (isEmpty(s)) { // 检查栈是否为空printf("Stack is empty\n"); // 栈空时输出错误信息return -1;}return s->data[s->top]; // 返回栈顶元素
}// 主函数,用于测试栈的基本操作
int main() {Stack s;initStack(&s); // 初始化栈// 测试入栈操作push(&s, 1);push(&s, 2);push(&s, 3);// 查看栈顶元素printf("Top element: %d\n", peek(&s));// 弹出栈顶元素printf("Popped element: %d\n", pop(&s));// 再次查看栈顶元素printf("Top element after pop: %d\n", peek(&s));return 0;
}
2.3.2.3链栈实现操作步骤
(1)结构体定义:
Node
结构体表示链表中的节点,每个节点包含一个整数data
和一个指向下一个节点的指针next
。Stack
结构体表示栈,栈由一个指向链表头节点(即栈顶)的指针top
组成。
(2)初始化函数:initStack
将栈顶指针 top
初始化为 NULL
,表示栈为空。
(3)判断栈状态:isEmpty
通过检查栈顶指针是否为 NULL
来判断栈是否为空。
(4)栈操作函数:
push
:创建新节点并将其插入到栈顶。首先分配内存给新节点,如果内存分配失败则输出错误信息并返回。然后设置新节点的数据,并将其next
指针指向当前栈顶,最后更新栈顶指针为新节点。pop
:移除栈顶元素并返回其值。如果栈为空则输出错误信息并返回-1
。否则,保存当前栈顶节点,获取其值,更新栈顶指针为下一个节点,并释放原栈顶节点的内存。peek
:查看栈顶元素但不弹出。如果栈为空则输出错误信息并返回-1
,否则返回栈顶元素的值。
(5)主函数:演示了栈的基本操作,包括入栈、查看栈顶元素和出栈。
(6)运行结果解释:
push(&s, 1); push(&s, 2); push(&s, 3);
:依次将1
,2
,3
压入栈中,此时栈顶元素为3
。printf("Top element: %d\n", peek(&s));
:输出栈顶元素3
。printf("Popped element: %d\n", pop(&s));
:弹出栈顶元素3
并输出。printf("Top element after pop: %d\n", peek(&s));
:再次查看栈顶元素,此时栈顶元素为2
。
2.3.2.4链栈代码实现示例
#include <stdio.h>
#include <stdlib.h>// 定义链表节点结构体,每个节点包含一个整数数据和指向下一个节点的指针
typedef struct Node {int data; // 节点存储的数据struct Node *next; // 指向下一个节点的指针
} Node;// 定义栈结构体,栈由一个指向链表头节点(即栈顶)的指针组成
typedef struct {Node *top; // 指向栈顶元素的指针
} Stack;// 初始化栈,将栈顶指针设置为NULL,表示空栈
void initStack(Stack *s) {s->top = NULL;
}// 判断栈是否为空,如果栈顶指针为NULL,则栈为空
int isEmpty(Stack *s) {return s->top == NULL;
}// 入栈操作:创建新节点并将其插入到栈顶
void push(Stack *s, int value) {// 分配内存给新节点Node *newNode = (Node *)malloc(sizeof(Node));if (newNode == NULL) { // 如果内存分配失败,输出错误信息并返回printf("Memory allocation failed\n");return;}newNode->data = value; // 设置新节点的数据newNode->next = s->top; // 新节点的next指针指向当前栈顶s->top = newNode; // 更新栈顶指针为新节点
}// 出栈操作:移除栈顶元素并返回其值
int pop(Stack *s) {if (isEmpty(s)) { // 如果栈为空,输出错误信息并返回-1printf("Stack Underflow\n");return -1;}Node *temp = s->top; // 保存当前栈顶节点int value = temp->data; // 获取栈顶元素的值s->top = temp->next; // 更新栈顶指针为下一个节点free(temp); // 释放原栈顶节点的内存return value; // 返回栈顶元素的值
}// 查看栈顶元素但不出栈
int peek(Stack *s) {if (isEmpty(s)) { // 如果栈为空,输出错误信息并返回-1printf("Stack is empty\n");return -1;}return s->top->data; // 返回栈顶元素的值
}// 主函数,用于测试栈的基本操作
int main() {Stack s;initStack(&s); // 初始化栈// 测试入栈操作push(&s, 1);push(&s, 2);push(&s, 3);// 查看栈顶元素printf("Top element: %d\n", peek(&s));// 弹出栈顶元素printf("Popped element: %d\n", pop(&s));// 再次查看栈顶元素printf("Top element after pop: %d\n", peek(&s));return 0;
}
2.3.2.5顺序队列实现操作步骤
(1)宏定义:MAX_SIZE
定义了队列的最大容量。
(2)结构体定义:Queue
结构体包含一个数组 data
和两个整数 front
和 rear
,用于存储队列中的元素和指向队头和队尾的位置。
-
初始化函数:
initQueue
将front
和rear
初始化为-1
,表示队列为空。 -
判断队列状态:
isEmpty
通过检查front
是否为-1
来判断队列是否为空。isFull
通过检查(rear + 1) % MAX_SIZE
是否等于front
来判断队列是否已满。
(3)队列操作函数:
enqueue
:将元素添加到队尾。首先检查队列是否已满,如果已满则输出错误信息并返回。如果队列为空,则将front
和rear
都设置为0
,否则更新rear
指针并使用模运算实现循环队列。最后将元素存入队尾。dequeue
:移除队头元素并返回其值。如果队列为空则输出错误信息并返回-1
。否则,获取队头元素的值,如果队列只有一个元素,则重置front
和rear
为-1
,否则更新front
指针并使用模运算实现循环队列。peek
:查看队头元素但不移除。如果队列为空则输出错误信息并返回-1
,否则返回队头元素的值。
(4)主函数:演示了队列的基本操作,包括入队、查看队头元素和出队。
(5)运行结果解释:
enqueue(&q, 1); enqueue(&q, 2); enqueue(&q, 3);
:依次将1
,2
,3
入队,此时队头元素为1
。printf("Front element: %d\n", peek(&q));
:输出队头元素1
。printf("Dequeued element: %d\n", dequeue(&q));
:出队队头元素1
并输出。printf("Front element after dequeue: %d\n", peek(&q));
:再次查看队头元素,此时队头元素为2
。
2.3.2.6顺序队列代码实现示例
#include <stdio.h>
#include <stdlib.h>// 定义队列的最大容量
#define MAX_SIZE 100// 定义队列结构体,包含一个数组和两个指针(front和rear)
typedef struct {int data[MAX_SIZE]; // 存储队列中元素的数组int front, rear; // front指向队头,rear指向队尾
} Queue;// 初始化队列,将front和rear设置为-1,表示队列为空
void initQueue(Queue *q) {q->front = q->rear = -1;
}// 判断队列是否为空,如果front为-1,则队列为空
int isEmpty(Queue *q) {return q->front == -1;
}// 判断队列是否已满,如果(rear + 1) % MAX_SIZE等于front,则队列已满
int isFull(Queue *q) {return (q->rear + 1) % MAX_SIZE == q->front;
}// 入队操作:将元素添加到队尾
void enqueue(Queue *q, int value) {if (isFull(q)) { // 检查队列是否已满printf("Queue Overflow\n"); // 队列满时输出错误信息return;}if (isEmpty(q)) { // 如果队列为空,front和rear都指向0q->front = q->rear = 0;} else {q->rear = (q->rear + 1) % MAX_SIZE; // 更新rear指针,使用模运算实现循环队列}q->data[q->rear] = value; // 将元素存入队尾
}// 出队操作:移除队头元素并返回其值
int dequeue(Queue *q) {if (isEmpty(q)) { // 检查队列是否为空printf("Queue Underflow\n"); // 队列空时输出错误信息return -1;}int value = q->data[q->front]; // 获取队头元素的值if (q->front == q->rear) { // 如果队列只有一个元素,重置front和rear为-1q->front = q->rear = -1;} else {q->front = (q->front + 1) % MAX_SIZE; // 更新front指针,使用模运算实现循环队列}return value; // 返回队头元素的值
}// 查看队头元素但不出队
int peek(Queue *q) {if (isEmpty(q)) { // 检查队列是否为空printf("Queue is empty\n"); // 队列空时输出错误信息return -1;}return q->data[q->front]; // 返回队头元素的值
}// 主函数,用于测试队列的基本操作
int main() {Queue q;initQueue(&q); // 初始化队列// 测试入队操作enqueue(&q, 1);enqueue(&q, 2);enqueue(&q, 3);// 查看队头元素printf("Front element: %d\n", peek(&q));// 出队操作printf("Dequeued element: %d\n", dequeue());// 再次查看队头元素printf("Front element after dequeue: %d\n", peek(&q));return 0;
}
2.3.2.7链式队列实现操作步骤
(1)结构体定义:
Node
结构体表示链表中的节点,每个节点包含一个整数data
和一个指向下一个节点的指针next
。Queue
结构体表示队列,队列由两个指针front
和rear
组成,分别指向队头和队尾。
(2)初始化函数:initQueue
将 front
和 rear
初始化为 NULL
,表示队列为空。
(3)判断队列状态:isEmpty
通过检查 front
是否为 NULL
来判断队列是否为空。
(4)队列操作函数:
enqueue
:将元素添加到队尾。首先分配内存给新节点,如果内存分配失败则输出错误信息并返回。然后设置新节点的数据,并将其next
指针初始化为NULL
。如果队列为空,则将front
和rear
都指向新节点,否则将当前队尾节点的next
指针指向新节点,并更新rear
指针为新节点。dequeue
:移除队头元素并返回其值。如果队列为空则输出错误信息并返回-1
。否则,保存当前队头节点,获取其值,更新front
指针为下一个节点。如果队列为空,则更新rear
指针也为NULL
,并释放原队头节点的内存。peek
:查看队头元素但不移除。如果队列为空则输出错误信息并返回-1
,否则返回队头元素的值。
(5)主函数:演示了队列的基本操作,包括入队、查看队头元素和出队。
(6)运行结果解释:
enqueue(&q, 1); enqueue(&q, 2); enqueue(&q, 3);
:依次将1
,2
,3
入队,此时队头元素为1
。printf("Front element: %d\n", peek(&q));
:输出队头元素1
。printf("Dequeued element: %d\n", dequeue(&q));
:出队队头元素1
并输出。printf("Front element after dequeue: %d\n", peek(&q));
:再次查看队头元素,此时队头元素为2
。
2.3.2.8链式队列代码实现示例
#include <stdio.h>
#include <stdlib.h>// 定义链表节点结构体,每个节点包含一个整数数据和指向下一个节点的指针
typedef struct Node {int data; // 节点存储的数据struct Node *next; // 指向下一个节点的指针
} Node;// 定义队列结构体,队列由两个指针(front和rear)组成
typedef struct {Node *front; // 指向队头元素的指针Node *rear; // 指向队尾元素的指针
} Queue;// 初始化队列,将front和rear设置为NULL,表示队列为空
void initQueue(Queue *q) {q->front = q->rear = NULL;
}// 判断队列是否为空,如果front为NULL,则队列为空
int isEmpty(Queue *q) {return q->front == NULL;
}// 入队操作:将元素添加到队尾
void enqueue(Queue *q, int value) {// 分配内存给新节点Node *newNode = (Node *)malloc(sizeof(Node));if (newNode == NULL) { // 如果内存分配失败,输出错误信息并返回printf("Memory allocation failed\n");return;}newNode->data = value; // 设置新节点的数据newNode->next = NULL; // 新节点的next指针初始化为NULLif (isEmpty(q)) { // 如果队列为空,front和rear都指向新节点q->front = q->rear = newNode;} else {q->rear->next = newNode; // 将当前队尾节点的next指针指向新节点q->rear = newNode; // 更新rear指针为新节点}
}// 出队操作:移除队头元素并返回其值
int dequeue(Queue *q) {if (isEmpty(q)) { // 如果队列为空,输出错误信息并返回-1printf("Queue Underflow\n");return -1;}Node *temp = q->front; // 保存当前队头节点int value = temp->data; // 获取队头元素的值q->front = q->front->next; // 更新front指针为下一个节点if (q->front == NULL) { // 如果队列为空,更新rear指针也为NULLq->rear = NULL;}free(temp); // 释放原队头节点的内存return value; // 返回队头元素的值
}// 查看队头元素但不出队
int peek(Queue *q) {if (isEmpty(q)) { // 如果队列为空,输出错误信息并返回-1printf("Queue is empty\n");return -1;}return q->front->data; // 返回队头元素的值
}// 主函数,用于测试队列的基本操作
int main() {Queue q;initQueue(&q); // 初始化队列// 测试入队操作enqueue(&q, 1);enqueue(&q, 2);enqueue(&q, 3);// 查看队头元素printf("Front element: %d\n", peek(&q));// 出队操作printf("Dequeued element: %d\n", dequeue(&q));// 再次查看队头元素printf("Front element after dequeue: %d\n", peek(&q));return 0;
}
3.高级数据结构
目标:通过思维导图了解高级数据基础结构核心的知识点,并掌握。
3.1树形结构
树形结构基础概念与操作。
3.1.1二叉树的概念
3.1.2二叉树实现与解析
3.1.2.1二叉树节点结构体定义
// 定义了一个二叉树节点的结构体 BiNode //通过 typedef 关键字定义了一个指向 BiNode 结构体的指针类型 BiTree,方便后续使用//typedef existing_type new_type_name; 关键字的原型//existing_type 是已有的数据类型,new_type_name 是你为该数据类型定义的新名称typedef struct BiNode {int data; // 定义一个整型节点数据域 datastruct BiNode *lchild; // 指向左子节点的lchild指针struct BiNode *rchild; // 指向右子节点的rchild指针
} BiNode, *BiTree;/*
3.1.2.2二叉树存储结构定义
(1)数组表示法
// 定义二叉树数组的最大容量#define MAX_SIZE 100 // 定义二叉树数组结构体typedef struct {// 一个整型数组,用于存储二叉树的节点数据,数组的大小为 MAX_SIZE,即100int data[MAX_SIZE]; // size:一个整型变量,用于记录当前二叉树的节点个数int size; //定义了一个名为 BinaryTreeArray 的结构体,用于表示一个二叉树的数组存储结构
//结构体包含两个成员:} BinaryTreeArray;
(2)链表表示法
// 定义二叉树节点名为 BiNode 的结构体,用于表示二叉树的节点,结构体包含三个成员typedef struct BiNode {// 一个整型变量(data),用于存储节点的值int data; // 一个指向 BiNode 结构体的(lchild)指针,用于指向左子节点struct BiNode *lchild;// 一个指向 BiNode 结构体的(rchild)指针,用于指向右子节点struct BiNode *rchild;} BiNode, *BiTree;/*通过这个结构体,可以构建二叉树的数据结构,并进行相关的操作,如插入、删除、遍历等。此外,代码中还使用了 typedef 关键字,为 BiNode 结构体定义了一个别名 BiTree,使得 BiTree 可以作为指向 BiNode 结构体的指针类型使用。这样,在声明变量时可以直接使用 BiTree,而不需要每次都写完整的 struct BiNode **/
3.1.3平衡树的概念
3.1.4平衡树实现与解析
3.1.4.1平衡树节点结构体定义
// 定义平衡树节点结构体
// 定义了一个名为 AVLNode 的结构体,用于表示平衡树的节点,结构体包含四个成员typedef struct AVLNode {// 节点数据域,用于存储节点的值int data; // 指向左子节点的指针,指向 AVLNode 结构体的指针struct AVLNode *left; // 指向右子节点的指针,指向 AVLNode 结构体的指针struct AVLNode *right; // 节点的高度,用于平衡因子的计算,节点的高度是指从该节点到其最远叶子节点的最长路径上的节点数int height; //使用了 typedef 关键字,为 AVLNode 结构体定义了一个别名 AVLTree,使得 AVLTree 可以作为指向 AVLNode 结构体的指针类型使用
//这样,在声明变量时可以直接使用 AVLTree,而不需要每次都写完整的 struct AVLNode *} AVLNode, *AVLTree;
3.1.4.2红黑树树节点结构体定义
// 定义红黑树节点颜色typedef enum {RED,BLACK//枚举(Color )定义了红黑树节点的颜色,包括 RED 和 BLACK} Color;// 定义红黑树节点结构体typedef struct RBNode {int data; // 节点数据struct RBNode *left; // 指向左子节点的指针struct RBNode *right; // 指向右子节点的指针struct RBNode *parent; // 指向父节点的指针Color color; // 节点颜色,可以是 RED 或 BLACK
} RBNode, *RBTree;
3.1.5堆的概念
3.1.6堆实现与解析
#include <stdio.h>
#include <stdlib.h>// 定义最大堆结构体
typedef struct {// 存储堆元素的数组int* array; // 堆的最大容量int capacity; // 当前堆的大小int size;
} MaxHeap;// 创建一个最大堆
MaxHeap* createMaxHeap(int capacity) {// 分配内存给 MaxHeap 结构体MaxHeap* heap = (MaxHeap*)malloc(sizeof(MaxHeap));// 分配内存给存储堆元素的数组heap->array = (int*)malloc(capacity * sizeof(int));// 设置堆的最大容量heap->capacity = capacity;// 初始化堆的大小为 0heap->size = 0;// 返回创建的堆return heap;
}// 交换两个元素的值
void swap(int* a, int* b) {// 临时变量存储 a 的值int temp = *a;// 将 b 的值赋给 a*a = *b;// 将临时变量的值赋给 b*b = temp;
}// 向上调整堆
void heapifyUp(MaxHeap* heap, int index) {// 计算父节点的索引int parent = (index - 1) / 2;// 当索引大于 0 且当前节点的值大于父节点的值时while (index > 0 && heap->array[index] > heap->array[parent]) {// 交换当前节点和父节点的值swap(&heap->array[index], &heap->array[parent]);// 更新当前节点的索引为父节点的索引index = parent;// 重新计算父节点的索引parent = (index - 1) / 2;}
}// 向下调整堆
void heapifyDown(MaxHeap* heap, int index) {// 计算左子节点的索引int leftChild = 2 * index + 1;// 计算右子节点的索引int rightChild = 2 * index + 2;// 初始化最大节点的索引为当前节点的索引int largest = index;// 如果左子节点存在且左子节点的值大于最大节点的值if (leftChild < heap->size && heap->array[leftChild] > heap->array[largest]) {// 更新最大节点的索引为左子节点的索引largest = leftChild;}// 如果右子节点存在且右子节点的值大于最大节点的值if (rightChild < heap->size && heap->array[rightChild] > heap->array[largest]) {// 更新最大节点的索引为右子节点的索引largest = rightChild;}// 如果最大节点的索引不是当前节点的索引if (largest != index) {// 交换当前节点和最大节点的值swap(&heap->array[index], &heap->array[largest]);// 递归调用 heapifyDown 函数,继续向下调整堆heapifyDown(heap, largest);}
}// 插入元素到堆中
void insert(MaxHeap* heap, int value) {// 如果堆已满if (heap->size == heap->capacity) {// 输出提示信息printf("Heap is full. Cannot insert more elements.\n");// 返回return;}// 将元素插入到堆的末尾heap->array[heap->size] = value;// 向上调整堆heapifyUp(heap, heap->size);// 增加堆的大小heap->size++;
}// 删除堆顶元素
int deleteMax(MaxHeap* heap) {// 如果堆为空if (heap->size == 0) {// 输出提示信息printf("Heap is empty. Cannot delete element.\n");// 返回 -1 表示错误return -1;}// 获取堆顶元素的值int max = heap->array[0];// 将堆的最后一个元素移到堆顶heap->array[0] = heap->array[heap->size - 1];// 减少堆的大小heap->size--;// 向下调整堆heapifyDown(heap, 0);// 返回删除的堆顶元素的值return max;
}// 获取堆顶元素
int peek(MaxHeap* heap) {// 如果堆为空if (heap->size == 0) {// 输出提示信息printf("Heap is empty. No element to peek.\n");// 返回 -1 表示错误return -1;}// 返回堆顶元素的值return heap->array[0];
}// 打印堆中的元素
void printHeap(MaxHeap* heap) {// 遍历堆中的元素for (int i = 0; i < heap->size; i++) {// 输出元素的值printf("%d ", heap->array[i]);}// 输出换行符printf("\n");
}// 释放堆的内存
void destroyHeap(MaxHeap* heap) {// 释放存储堆元素的数组的内存free(heap->array);// 释放 MaxHeap 结构体的内存free(heap);
}int main() {// 创建一个最大堆,容量为 10MaxHeap* heap = createMaxHeap(10);// 插入元素到堆中insert(heap, 3);insert(heap, 4);insert(heap, 9);insert(heap, 5);insert(heap, 2);// 打印堆中的元素printf("Max Heap: ");printHeap(heap);// 获取堆顶元素并输出printf("Peek: %d\n", peek(heap));// 删除堆顶元素并输出printf("Deleted: %d\n", deleteMax(heap));// 打印删除堆顶元素后的堆printf("Max Heap after deletion: ");printHeap(heap);// 释放堆的内存destroyHeap(heap);return 0;
}
3.2图结构基础概念
图(Graph)是一种非线性的数据结构,由顶点(Vertex)和边(Edge)组成。顶点表示图中的节点,边表示节点之间的连接关系。图可以用来表示各种实际问题,如社交网络、交通网络、计算机网络等,常见的有邻接矩阵和邻接表。
3.2.1图结构顶点(Vertex)
基本概念如下所示:
3.2.1.1图结构顶点结构体定义
// 定义一个结构体 Vertex,用于表示图中的顶点
typedef struct Vertex {int id; // 顶点的标识符char *name; // 顶点的名字int visited; // 标记顶点是否被访问过,0表示未访问,1表示已访问
} Vertex;
3.2.1.2图结构顶点实现与解析
// 顶点结构体定义
typedef struct Vertex {int id; // 顶点的唯一标识符char *name; // 顶点的名称(可选)int visited; // 标记顶点是否被访问过,0表示未访问,1表示已访问// 可以添加其他属性,如权重、颜色等
} Vertex;// 创建一个新的顶点
Vertex *createVertex(int id, char *name) {// 分配内存以存储新的顶点结构体Vertex *newVertex = (Vertex *)malloc(sizeof(Vertex));// 检查内存分配是否成功if (newVertex == NULL) {printf("内存分配失败\n");// 如果内存分配失败,程序终止exit(1);}// 设置顶点的唯一标识符newVertex->id = id;// 设置顶点的名称newVertex->name = name;// 初始化顶点的访问状态为未访问newVertex->visited = 0;// 返回新创建的顶点指针return newVertex;
}// 打印顶点信息
void printVertex(Vertex *vertex) {// 打印顶点的唯一标识符printf("顶点ID: %d\n", vertex->id);// 如果顶点有名称,打印顶点的名称if (vertex->name != NULL) {printf("顶点名称: %s\n", vertex->name);}// 打印顶点的访问状态printf("是否被访问: %s\n", vertex->visited ? "是" : "否");
}int main() {// 创建顶点Vertex *v1 = createVertex(1, "顶点1");Vertex *v2 = createVertex(2, "顶点2");Vertex *v3 = createVertex(3, "顶点3");// 打印顶点信息printVertex(v1);printVertex(v2);printVertex(v3);// 释放内存free(v1);free(v2);free(v3);return 0;
}
3.2.2图结构边(Edge)
基本概念如下所示:
3.2.2.1图结构边定义
图结构的边可以通过多种方式定义,具体取决于图的表示方法(如邻接矩阵或邻接表)。
//1. 使用结构体定义图边// 定义一个结构体 Edge,用于表示图中的边
typedef struct Edge {int dest; // 边的目标顶点int weight; // 边的权重(如果是有权图)struct Edge* next; // 指向下一个边的指针
} Edge;//2. 使用结构体定义带有源顶点和目标顶点的边typedef struct Edge {int src; // 边的源顶点int dest; // 边的目标顶点int weight; // 边的权重(如果是有权图)struct Edge* next; // 指向下一个边的指针
} Edge;//3. 使用二维数组定义边(适用于邻接矩阵)
#define MAX_VERTICES 100typedef struct {int matrix[MAX_VERTICES][MAX_VERTICES]; // 邻接矩阵int numVertices; // 顶点数量
} GraphMatrix;
3.2.2.2图结构边实现与解析
#include <stdio.h>
#include <stdlib.h>// 定义边结构体
typedef struct Edge {int dest; // 边的目标顶点int weight; // 边的权重(如果是有权图)struct Edge* next; // 指向下一个边的指针
} Edge;//1. 创建一个新的边
Edge* createEdge(int dest, int weight) {// 分配内存以存储新的边结构体Edge* newEdge = (Edge*)malloc(sizeof(Edge));// 检查内存分配是否成功if (newEdge == NULL) {printf("内存分配失败\n");// 如果内存分配失败,程序终止exit(1);}// 设置边的目标顶点newEdge->dest = dest;// 设置边的权重newEdge->weight = weight;// 将下一个边指针初始化为NULLnewEdge->next = NULL;// 返回新创建的边指针return newEdge;
}//2. 插入边到邻接表中
void insertEdge(Edge** adjList, int src, int dest, int weight) {// 创建一个新的边Edge* newEdge = createEdge(dest, weight);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = adjList[src];// 将新边插入到当前顶点的邻接表的头部adjList[src] = newEdge;
}//3. 删除边
void deleteEdge(Edge** adjList, int src, int dest) {// 获取当前顶点的邻接表Edge* current = adjList[src];// 初始化前一个边指针为NULLEdge* prev = NULL;// 遍历邻接表,找到目标边while (current != NULL && current->dest != dest) {prev = current;current = current->next;}// 如果目标边不存在,打印错误信息并返回if (current == NULL) {printf("边不存在\n");return;}// 如果目标边是邻接表的第一个边if (prev == NULL) {// 将邻接表的头指针指向下一个边adjList[src] = current->next;} else {// 将前一个边的下一个边指针指向目标边的下一个边prev->next = current->next;}// 释放目标边的内存free(current);
}//4. 查找边
Edge* findEdge(Edge* adjList, int src, int dest) {// 获取当前顶点的邻接表Edge* current = adjList;// 遍历邻接表,找到目标边while (current != NULL && current->dest != dest) {current = current->next;}// 返回目标边指针return current;
}//5. 遍历边
void traverseEdges(Edge* adjList) {// 获取当前顶点的邻接表Edge* current = adjList;// 遍历邻接表,打印每条边的目标顶点和权重while (current != NULL) {printf("目标顶点: %d, 权重: %d\n", current->dest, current->weight);current = current->next;}
}//6. 更新边的权重
void updateEdgeWeight(Edge* adjList, int src, int dest, int newWeight) {// 查找目标边Edge* edge = findEdge(adjList, src, dest);// 如果目标边存在,更新其权重if (edge != NULL) {edge->weight = newWeight;} else {// 如果目标边不存在,打印错误信息printf("边不存在\n");}
}//7. 判断边是否存在
int edgeExists(Edge* adjList, int src, int dest) {// 查找目标边return findEdge(adjList, src, dest) != NULL;
}//8. 获取边的权重
int getEdgeWeight(Edge* adjList, int src, int dest) {// 查找目标边Edge* edge = findEdge(adjList, src, dest);// 如果目标边存在,返回其权重if (edge != NULL) {return edge->weight;} else {// 如果目标边不存在,打印错误信息并返回-1printf("边不存在\n");return -1;}
}int main() {// 假设图有5个顶点int numVertices = 5;// 定义一个邻接表数组,每个元素指向一个顶点的邻接表Edge* adjList[numVertices];// 初始化邻接表,将每个顶点的邻接表指针初始化为NULLfor (int i = 0; i < numVertices; i++) {adjList[i] = NULL;}// 插入边insertEdge(adjList, 0, 1, 10);insertEdge(adjList, 0, 2, 20);insertEdge(adjList, 1, 3, 30);insertEdge(adjList, 2, 4, 40);// 遍历边printf("遍历边:\n");traverseEdges(adjList[0]);// 更新边的权重updateEdgeWeight(adjList[0], 0, 1, 50);// 查找边Edge* edge = findEdge(adjList[0], 0, 1);if (edge != NULL) {printf("找到边: 目标顶点: %d, 权重: %d\n", edge->dest, edge->weight);}// 判断边是否存在if (edgeExists(adjList[0], 0, 1)) {printf("边存在\n");} else {printf("边不存在\n");}// 获取边的权重int weight = getEdgeWeight(adjList[0], 0, 1);if (weight != -1) {printf("边的权重: %d\n", weight);}// 删除边deleteEdge(adjList, 0, 1);// 再次遍历边printf("再次遍历边:\n");traverseEdges(adjList[0]);return 0;
}
3.2.3图类型解析与实现
基本概念如下所示:
3.2.3.1无向图
#include <stdio.h>
#include <stdlib.h>// 定义最大顶点数
#define MAX_VERTEX_NUM 100// 边的结构体
typedef struct Edge {int dest; // 目标顶点struct Edge* next; // 指向下一个边的指针
} Edge;// 图的结构体
typedef struct Graph {int numVertices; // 顶点数量Edge* adjLists[MAX_VERTEX_NUM]; // 邻接表数组
} Graph;// 创建一个新的边
Edge* createEdge(int dest) {// 分配内存以存储新的边结构体Edge* newEdge = (Edge*)malloc(sizeof(Edge));// 设置边的目标顶点newEdge->dest = dest;// 将下一个边指针初始化为NULLnewEdge->next = NULL;// 返回新创建的边指针return newEdge;
}// 创建图
Graph* createGraph(int vertices) {// 分配内存以存储图结构体Graph* graph = (Graph*)malloc(sizeof(Graph));// 设置图的顶点数量graph->numVertices = vertices;// 初始化邻接表for (int i = 0; i < vertices; i++) {// 将每个顶点的邻接表指针初始化为NULLgraph->adjLists[i] = NULL;}// 返回新创建的图指针return graph;
}// 添加边到图中(无向图,所以添加两次)
void addEdge(Graph* graph, int src, int dest) {// 从src到dest// 创建一个新的边Edge* newEdge = createEdge(dest);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = graph->adjLists[src];// 将新边插入到当前顶点的邻接表的头部graph->adjLists[src] = newEdge;// 从dest到src// 创建一个新的边newEdge = createEdge(src);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = graph->adjLists[dest];// 将新边插入到当前顶点的邻接表的头部graph->adjLists[dest] = newEdge;
}// 打印图的邻接表
void printGraph(Graph* graph) {// 遍历每个顶点for (int i = 0; i < graph->numVertices; i++) {// 获取当前顶点的邻接表Edge* edge = graph->adjLists[i];// 打印当前顶点的编号printf("顶点 %d 的邻接顶点: ", i);// 遍历当前顶点的邻接表while (edge != NULL) {// 打印邻接顶点的编号printf("%d ", edge->dest);// 移动到下一个邻接顶点edge = edge->next;}// 换行printf("\n");}
}int main() {// 创建一个有5个顶点的图Graph* graph = createGraph(5);// 添加边addEdge(graph, 0, 1);addEdge(graph, 0, 4);addEdge(graph, 1, 2);addEdge(graph, 1, 3);addEdge(graph, 1, 4);addEdge(graph, 2, 3);addEdge(graph, 3, 4);// 打印图printGraph(graph);return 0;
}
3.2.3.2有向图
#include <stdio.h>
#include <stdlib.h>// 定义最大顶点数
#define MAX_VERTEX_NUM 100// 边的结构体
typedef struct Edge {int dest; // 目标顶点struct Edge* next; // 指向下一个边的指针
} Edge;// 图的结构体
typedef struct Graph {int numVertices; // 顶点数量Edge* adjLists[MAX_VERTEX_NUM]; // 邻接表数组
} Graph;// 创建一个新的边
Edge* createEdge(int dest) {// 分配内存以存储新的边结构体Edge* newEdge = (Edge*)malloc(sizeof(Edge));// 设置边的目标顶点newEdge->dest = dest;// 将下一个边指针初始化为NULLnewEdge->next = NULL;// 返回新创建的边指针return newEdge;
}// 创建图
Graph* createGraph(int vertices) {// 分配内存以存储图结构体Graph* graph = (Graph*)malloc(sizeof(Graph));// 设置图的顶点数量graph->numVertices = vertices;// 初始化邻接表for (int i = 0; i < vertices; i++) {// 将每个顶点的邻接表指针初始化为NULLgraph->adjLists[i] = NULL;}// 返回新创建的图指针return graph;
}// 添加边到图中(有向图,只添加一次)
void addEdge(Graph* graph, int src, int dest) {// 从src到dest// 创建一个新的边Edge* newEdge = createEdge(dest);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = graph->adjLists[src];// 将新边插入到当前顶点的邻接表的头部graph->adjLists[src] = newEdge;
}// 打印图的邻接表
void printGraph(Graph* graph) {// 遍历每个顶点for (int i = 0; i < graph->numVertices; i++) {// 获取当前顶点的邻接表Edge* edge = graph->adjLists[i];// 打印当前顶点的编号printf("顶点 %d 的邻接顶点: ", i);// 遍历当前顶点的邻接表while (edge != NULL) {// 打印邻接顶点的编号printf("%d ", edge->dest);// 移动到下一个邻接顶点edge = edge->next;}// 换行printf("\n");}
}int main() {// 创建一个有5个顶点的图Graph* graph = createGraph(5);// 添加边addEdge(graph, 0, 1);addEdge(graph, 0, 4);addEdge(graph, 1, 2);addEdge(graph, 1, 3);addEdge(graph, 2, 3);addEdge(graph, 3, 4);// 打印图printGraph(graph);return 0;
}
3.2.3.3加权图
#include <stdio.h>
#include <stdlib.h>// 定义最大顶点数
#define MAX_VERTEX_NUM 100// 边的结构体,包含目标顶点和权重
typedef struct Edge {int dest; // 目标顶点int weight; // 边的权重struct Edge* next; // 指向下一个边的指针
} Edge;// 图的结构体
typedef struct Graph {int numVertices; // 顶点数量Edge* adjLists[MAX_VERTEX_NUM]; // 邻接表数组
} Graph;// 创建一个新的带权重的边
Edge* createEdge(int dest, int weight) {// 分配内存以存储新的边结构体Edge* newEdge = (Edge*)malloc(sizeof(Edge));// 设置边的目标顶点newEdge->dest = dest;// 设置边的权重newEdge->weight = weight;// 将下一个边指针初始化为NULLnewEdge->next = NULL;// 返回新创建的边指针return newEdge;
}// 创建图
Graph* createGraph(int vertices) {// 分配内存以存储图结构体Graph* graph = (Graph*)malloc(sizeof(Graph));// 设置图的顶点数量graph->numVertices = vertices;// 初始化邻接表for (int i = 0; i < vertices; i++) {// 将每个顶点的邻接表指针初始化为NULLgraph->adjLists[i] = NULL;}// 返回新创建的图指针return graph;
}// 添加带权重的边到图中(无向图,所以添加两次)
void addEdge(Graph* graph, int src, int dest, int weight) {// 从src到dest// 创建一个新的边Edge* newEdge = createEdge(dest, weight);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = graph->adjLists[src];// 将新边插入到当前顶点的邻接表的头部graph->adjLists[src] = newEdge;// 从dest到src// 创建一个新的边newEdge = createEdge(src, weight);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = graph->adjLists[dest];// 将新边插入到当前顶点的邻接表的头部graph->adjLists[dest] = newEdge;
}// 打印图的邻接表
void printGraph(Graph* graph) {// 遍历每个顶点for (int i = 0; i < graph->numVertices; i++) {// 获取当前顶点的邻接表Edge* edge = graph->adjLists[i];// 打印当前顶点的编号printf("顶点 %d 的邻接顶点及权重: ", i);// 遍历当前顶点的邻接表while (edge != NULL) {// 打印邻接顶点的编号和权重printf("(%d, %d) ", edge->dest, edge->weight);// 移动到下一个邻接顶点edge = edge->next;}// 换行printf("\n");}
}int main() {// 创建一个有5个顶点的图Graph* graph = createGraph(5);// 添加带权重的边addEdge(graph, 0, 1, 10);addEdge(graph, 0, 4, 20);addEdge(graph, 1, 2, 30);addEdge(graph, 1, 3, 40);addEdge(graph, 1, 4, 50);addEdge(graph, 2, 3, 60);addEdge(graph, 3, 4, 70);// 打印图printGraph(graph);return 0;
}
3.2.3.4无权图
#include <stdio.h>
#include <stdlib.h>// 定义最大顶点数
#define MAX_VERTEX_NUM 100// 边的结构体
typedef struct Edge {int dest; // 目标顶点struct Edge* next; // 指向下一个边的指针
} Edge;// 图的结构体
typedef struct Graph {int numVertices; // 顶点数量Edge* adjLists[MAX_VERTEX_NUM]; // 邻接表数组
} Graph;// 创建一个新的边
Edge* createEdge(int dest) {// 分配内存以存储新的边结构体Edge* newEdge = (Edge*)malloc(sizeof(Edge));// 设置边的目标顶点newEdge->dest = dest;// 将下一个边指针初始化为NULLnewEdge->next = NULL;// 返回新创建的边指针return newEdge;
}// 创建图
Graph* createGraph(int vertices) {// 分配内存以存储图结构体Graph* graph = (Graph*)malloc(sizeof(Graph));// 设置图的顶点数量graph->numVertices = vertices;// 初始化邻接表for (int i = 0; i < vertices; i++) {// 将每个顶点的邻接表指针初始化为NULLgraph->adjLists[i] = NULL;}// 返回新创建的图指针return graph;
}// 添加边到图中(无向图,所以添加两次)
void addEdge(Graph* graph, int src, int dest) {// 从src到dest// 创建一个新的边Edge* newEdge = createEdge(dest);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = graph->adjLists[src];// 将新边插入到当前顶点的邻接表的头部graph->adjLists[src] = newEdge;// 从dest到src// 创建一个新的边newEdge = createEdge(src);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = graph->adjLists[dest];// 将新边插入到当前顶点的邻接表的头部graph->adjLists[dest] = newEdge;
}// 打印图的邻接表
void printGraph(Graph* graph) {// 遍历每个顶点for (int i = 0; i < graph->numVertices; i++) {// 获取当前顶点的邻接表Edge* edge = graph->adjLists[i];// 打印当前顶点的编号printf("顶点 %d 的邻接顶点: ", i);// 遍历当前顶点的邻接表while (edge != NULL) {// 打印邻接顶点的编号printf("%d ", edge->dest);// 移动到下一个邻接顶点edge = edge->next;}// 换行printf("\n");}
}int main() {// 创建一个有5个顶点的图Graph* graph = createGraph(5);// 添加边addEdge(graph, 0, 1);addEdge(graph, 0, 4);addEdge(graph, 1, 2);addEdge(graph, 1, 3);addEdge(graph, 2, 3);addEdge(graph, 3, 4);// 打印图printGraph(graph);return 0;
}
3.2.3.5稠密图
#include <stdio.h>
#include <stdbool.h>// 定义最大顶点数
#define MAX_VERTEX_NUM 100// 图的结构体,使用邻接矩阵表示
typedef struct Graph {int numVertices; // 顶点数量bool adjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵
} Graph;// 创建图
Graph* createGraph(int vertices) {// 分配内存以存储图结构体Graph* graph = (Graph*)malloc(sizeof(Graph));// 设置图的顶点数量graph->numVertices = vertices;// 初始化邻接矩阵为false(表示无连接)for (int i = 0; i < vertices; i++) {for (int j = 0; j < vertices; j++) {graph->adjMatrix[i][j] = false;}}// 返回新创建的图指针return graph;
}// 添加边到图中(无向图)
void addEdge(Graph* graph, int src, int dest) {// 检查源顶点和目标顶点是否在有效范围内if (src >= 0 && src < graph->numVertices && dest >= 0 && dest < graph->numVertices) {// 在邻接矩阵中标记源顶点到目标顶点的边graph->adjMatrix[src][dest] = true;// 在邻接矩阵中标记目标顶点到源顶点的边(因为是无向图)graph->adjMatrix[dest][src] = true;}
}// 打印图的邻接矩阵
void printGraph(Graph* graph) {// 遍历邻接矩阵的每一行for (int i = 0; i < graph->numVertices; i++) {// 遍历邻接矩阵的每一列for (int j = 0; j < graph->numVertices; j++) {// 打印邻接矩阵中的元素(0或1)printf("%d ", graph->adjMatrix[i][j]);}// 换行printf("\n");}
}int main() {// 创建一个有5个顶点的图Graph* graph = createGraph(5);// 添加边addEdge(graph, 0, 1);addEdge(graph, 0, 4);addEdge(graph, 1, 2);addEdge(graph, 1, 3);addEdge(graph, 1, 4);addEdge(graph, 2, 3);addEdge(graph, 3, 4);// 打印图的邻接矩阵printGraph(graph);return 0;
}
3.2.3.6稀疏图
#include <stdio.h>
#include <stdlib.h>// 定义最大顶点数
#define MAX_VERTEX_NUM 100// 边的结构体
typedef struct Edge {int dest; // 目标顶点struct Edge* next; // 指向下一个边的指针
} Edge;// 图的结构体,使用邻接表表示
typedef struct Graph {int numVertices; // 顶点数量Edge* adjLists[MAX_VERTEX_NUM]; // 邻接表数组
} Graph;// 创建一个新的边
Edge* createEdge(int dest) {// 分配内存以存储新的边结构体Edge* newEdge = (Edge*)malloc(sizeof(Edge));// 设置边的目标顶点newEdge->dest = dest;// 将下一个边指针初始化为NULLnewEdge->next = NULL;// 返回新创建的边指针return newEdge;
}// 创建图
Graph* createGraph(int vertices) {// 分配内存以存储图结构体Graph* graph = (Graph*)malloc(sizeof(Graph));// 设置图的顶点数量graph->numVertices = vertices;// 初始化邻接表为空for (int i = 0; i < vertices; i++) {// 将每个顶点的邻接表指针初始化为NULLgraph->adjLists[i] = NULL;}// 返回新创建的图指针return graph;
}// 添加边到图中(无向图)
void addEdge(Graph* graph, int src, int dest) {// 检查源顶点和目标顶点是否在有效范围内if (src >= 0 && src < graph->numVertices && dest >= 0 && dest < graph->numVertices) {// 从src到dest// 创建一个新的边Edge* newEdge = createEdge(dest);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = graph->adjLists[src];// 将新边插入到当前顶点的邻接表的头部graph->adjLists[src] = newEdge;// 如果是无向图,还需要从dest到src// 创建一个新的边newEdge = createEdge(src);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = graph->adjLists[dest];// 将新边插入到当前顶点的邻接表的头部graph->adjLists[dest] = newEdge;}
}// 打印图的邻接表
void printGraph(Graph* graph) {// 遍历每个顶点for (int i = 0; i < graph->numVertices; i++) {// 获取当前顶点的邻接表Edge* edge = graph->adjLists[i];// 打印当前顶点的编号printf("顶点 %d 的邻接顶点: ", i);// 遍历当前顶点的邻接表while (edge != NULL) {// 打印邻接顶点的编号printf("%d ", edge->dest);// 移动到下一个邻接顶点edge = edge->next;}// 换行printf("\n");}
}int main() {// 创建一个有5个顶点的图Graph* graph = createGraph(5);// 添加边addEdge(graph, 0, 1);addEdge(graph, 0, 4);addEdge(graph, 1, 2);addEdge(graph, 1, 3);addEdge(graph, 2, 3);addEdge(graph, 3, 4);// 打印图的邻接表printGraph(graph);return 0;
}
3.2.3.7完全图
#include <stdio.h>
#include <stdbool.h>// 定义最大顶点数
#define MAX_VERTEX_NUM 100// 图的结构体,使用邻接矩阵表示
typedef struct Graph {int numVertices; // 顶点数量bool adjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵
} Graph;// 创建完全图
Graph* createCompleteGraph(int vertices) {// 分配内存以存储图结构体Graph* graph = (Graph*)malloc(sizeof(Graph));// 设置图的顶点数量graph->numVertices = vertices;// 初始化邻接矩阵,完全图中每对顶点间都有边for (int i = 0; i < vertices; i++) {for (int j = 0; j < vertices; j++) {if (i == j) {// 顶点不与自身相连graph->adjMatrix[i][j] = false;} else {// 其他情况都有边相连graph->adjMatrix[i][j] = true;}}}// 返回新创建的图指针return graph;
}// 打印图的邻接矩阵
void printGraph(Graph* graph) {// 遍历邻接矩阵的每一行for (int i = 0; i < graph->numVertices; i++) {// 遍历邻接矩阵的每一列for (int j = 0; j < graph->numVertices; j++) {// 打印邻接矩阵中的元素(0或1)printf("%d ", graph->adjMatrix[i][j]);}// 换行printf("\n");}
}int main() {// 创建一个有5个顶点的完全图Graph* graph = createCompleteGraph(5);// 打印图的邻接矩阵printGraph(graph);return 0;
}
3.2.3.8强连通图
#include <stdio.h>
#include <stdlib.h>// 定义最大顶点数
#define MAX_VERTEX_NUM 100// 边的结构体
typedef struct Edge {int dest; // 目标顶点struct Edge* next; // 指向下一个边的指针
} Edge;// 图的结构体,使用邻接表表示
typedef struct Graph {int numVertices; // 顶点数量Edge* adjLists[MAX_VERTEX_NUM]; // 邻接表数组
} Graph;// 创建一个新的边
Edge* createEdge(int dest) {// 分配内存以存储新的边结构体Edge* newEdge = (Edge*)malloc(sizeof(Edge));// 设置边的目标顶点newEdge->dest = dest;// 将下一个边指针初始化为NULLnewEdge->next = NULL;// 返回新创建的边指针return newEdge;
}// 创建图
Graph* createGraph(int vertices) {// 分配内存以存储图结构体Graph* graph = (Graph*)malloc(sizeof(Graph));// 设置图的顶点数量graph->numVertices = vertices;// 初始化邻接表为空for (int i = 0; i < vertices; i++) {// 将每个顶点的邻接表指针初始化为NULLgraph->adjLists[i] = NULL;}// 返回新创建的图指针return graph;
}// 添加边到图中(有向图)
void addEdge(Graph* graph, int src, int dest) {// 检查源顶点和目标顶点是否在有效范围内if (src >= 0 && src < graph->numVertices && dest >= 0 && dest < graph->numVertices) {// 从src到dest// 创建一个新的边Edge* newEdge = createEdge(dest);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = graph->adjLists[src];// 将新边插入到当前顶点的邻接表的头部graph->adjLists[src] = newEdge;}
}// 打印图的邻接表
void printGraph(Graph* graph) {for (int i = 0; i < graph->numVertices; i++) {Edge* edge = graph->adjLists[i];printf("顶点 %d 的邻接顶点: ", i);while (edge != NULL) {printf("%d ", edge->dest);edge = edge->next;}printf("\n");}
}int main() {// 创建一个有4个顶点的图Graph* graph = createGraph(4);// 添加边以形成强连通图addEdge(graph, 0, 1);addEdge(graph, 1, 2);addEdge(graph, 2, 3);addEdge(graph, 3, 0);addEdge(graph, 1, 3);addEdge(graph, 3, 1);addEdge(graph, 2, 0);addEdge(graph, 0, 2);// 打印图的邻接表printGraph(graph);return 0;
}
3.2.3.9并查集
#include <stdio.h>
#include <stdlib.h>// 定义最大顶点数
#define MAX_VERTEX_NUM 100// 并查集结构体
typedef struct UnionFind {int parent[MAX_VERTEX_NUM]; // 存储每个顶点的父节点int rank[MAX_VERTEX_NUM]; // 存储每个顶点的秩(树的高度)
} UnionFind;// 初始化并查集
void initUnionFind(UnionFind* uf, int vertices) {for (int i = 0; i < vertices; i++) {uf->parent[i] = i; // 初始时,每个顶点的父节点是它自己uf->rank[i] = 0; // 初始时,每个顶点的秩为0}
}// 查找顶点的根节点
int find(UnionFind* uf, int x) {if (uf->parent[x] != x) {uf->parent[x] = find(uf, uf->parent[x]); // 路径压缩}return uf->parent[x];
}// 合并两个集合
void unionSets(UnionFind* uf, int x, int y) {int rootX = find(uf, x);int rootY = find(uf, y);if (rootX != rootY) {if (uf->rank[rootX] < uf->rank[rootY]) {uf->parent[rootX] = rootY; // 将秩小的树合并到秩大的树上} else if (uf->rank[rootX] > uf->rank[rootY]) {uf->parent[rootY] = rootX;} else {uf->parent[rootY] = rootX;uf->rank[rootX]++; // 秩相同,合并后秩加1}}
}// 检查两个顶点是否在同一个集合中
int isConnected(UnionFind* uf, int x, int y) {return find(uf, x) == find(uf, y);
}int main() {// 创建一个有4个顶点的图int vertices = 4;UnionFind uf;initUnionFind(&uf, vertices);// 添加边以形成强连通图unionSets(&uf, 0, 1);unionSets(&uf, 1, 2);unionSets(&uf, 2, 3);unionSets(&uf, 3, 0);unionSets(&uf, 1, 3);unionSets(&uf, 3, 1);unionSets(&uf, 2, 0);unionSets(&uf, 0, 2);// 检查顶点是否连通printf("顶点0和顶点1是否连通: %d\n", isConnected(&uf, 0, 1));printf("顶点0和顶点2是否连通: %d\n", isConnected(&uf, 0, 2));printf("顶点0和顶点3是否连通: %d\n", isConnected(&uf, 0, 3));return 0;
}
3.2.4图的表示方法
图的表示方法概念如下所示:
3.2.4.1领接矩阵
#include <stdio.h>
#include <stdbool.h>// 定义最大顶点数
#define MAX_VERTEX_NUM 100// 图的结构体,使用邻接矩阵表示
typedef struct Graph {int numVertices; // 顶点数量bool adjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵
} Graph;// 创建图
Graph* createGraph(int vertices) {// 分配内存以存储图结构体Graph* graph = (Graph*)malloc(sizeof(Graph));// 设置图的顶点数量graph->numVertices = vertices;// 初始化邻接矩阵,无连接时设为falsefor (int i = 0; i < vertices; i++) {for (int j = 0; j < vertices; j++) {graph->adjMatrix[i][j] = false;}}// 返回新创建的图指针return graph;
}// 添加边到图中(无向图)
void addEdge(Graph* graph, int src, int dest) {// 检查源顶点和目标顶点是否在有效范围内if (src >= 0 && src < graph->numVertices && dest >= 0 && dest < graph->numVertices) {// 设置邻接矩阵中对应位置为true,表示有边连接graph->adjMatrix[src][dest] = true;// 如果是无向图,对称位置也需要设置为truegraph->adjMatrix[dest][src] = true;}
}// 打印图的邻接矩阵
void printGraph(Graph* graph) {for (int i = 0; i < graph->numVertices; i++) {for (int j = 0; j < graph->numVertices; j++) {// 打印邻接矩阵中的元素(0或1)printf("%d ", graph->adjMatrix[i][j]);}// 换行printf("\n");}
}int main() {// 创建一个有5个顶点的图Graph* graph = createGraph(5);// 添加边addEdge(graph, 0, 1);addEdge(graph, 0, 4);addEdge(graph, 1, 2);addEdge(graph, 1, 3);addEdge(graph, 1, 4);addEdge(graph, 2, 3);addEdge(graph, 3, 4);// 打印图的邻接矩阵printGraph(graph);return 0;
}
3.2.4.2邻接表
#include <stdio.h>
#include <stdlib.h>// 定义最大顶点数
#define MAX_VERTEX_NUM 100// 边的结构体
typedef struct Edge {int dest; // 目标顶点struct Edge* next; // 指向下一个边的指针
} Edge;// 图的结构体,使用邻接表表示
typedef struct Graph {int numVertices; // 顶点数量Edge* adjLists[MAX_VERTEX_NUM]; // 邻接表数组
} Graph;// 创建一个新的边
Edge* createEdge(int dest) {// 分配内存以存储新的边结构体Edge* newEdge = (Edge*)malloc(sizeof(Edge));// 设置边的目标顶点newEdge->dest = dest;// 将下一个边指针初始化为NULLnewEdge->next = NULL;// 返回新创建的边指针return newEdge;
}// 创建图
Graph* createGraph(int vertices) {// 分配内存以存储图结构体Graph* graph = (Graph*)malloc(sizeof(Graph));// 设置图的顶点数量graph->numVertices = vertices;// 初始化邻接表为空for (int i = 0; i < vertices; i++) {// 将每个顶点的邻接表指针初始化为NULLgraph->adjLists[i] = NULL;}// 返回新创建的图指针return graph;
}// 添加边到图中(有向图)
void addEdge(Graph* graph, int src, int dest) {// 检查源顶点和目标顶点是否在有效范围内if (src >= 0 && src < graph->numVertices && dest >= 0 && dest < graph->numVertices) {// 从src到dest// 创建一个新的边Edge* newEdge = createEdge(dest);// 将新边的下一个边指针指向当前顶点的邻接表newEdge->next = graph->adjLists[src];// 将新边插入到当前顶点的邻接表的头部graph->adjLists[src] = newEdge;}
}// 打印图的邻接表
void printGraph(Graph* graph) {for (int i = 0; i < graph->numVertices; i++) {Edge* edge = graph->adjLists[i];printf("顶点 %d 的邻接顶点: ", i);while (edge != NULL) {printf("%d ", edge->dest);edge = edge->next;}printf("\n");}
}int main() {// 创建一个有4个顶点的图Graph* graph = createGraph(4);// 添加边以形成特定的图结构addEdge(graph, 0, 1);addEdge(graph, 0, 2);addEdge(graph, 1, 2);addEdge(graph, 2, 0);addEdge(graph, 2, 3);addEdge(graph, 3, 3);// 打印图的邻接表printGraph(graph);return 0;
}
3.2..4.3边缘列表
#include <stdio.h>
#include <stdlib.h>// 定义最大边数
#define MAX_EDGE_NUM 100// 边的结构体
typedef struct Edge {int src; // 边的起始顶点int dest; // 边的目标顶点// 可选:int weight; // 边的权重(如果是有权图)struct Edge* next; // 指向下一个边的指针
} Edge;// 图的结构体,使用边缘列表表示
typedef struct Graph {int numVertices; // 顶点数量int numEdges; // 边的数量Edge* edgeList[MAX_EDGE_NUM]; // 边缘列表数组
} Graph;// 创建一个新的边
Edge* createEdge(int src, int dest) {// 分配内存以存储新的边结构体Edge* newEdge = (Edge*)malloc(sizeof(Edge));// 设置边的起始顶点newEdge->src = src;// 设置边的目标顶点newEdge->dest = dest;// 将下一个边指针初始化为NULLnewEdge->next = NULL;// 返回新创建的边指针return newEdge;
}// 创建图
Graph* createGraph(int vertices, int edges) {// 分配内存以存储图结构体Graph* graph = (Graph*)malloc(sizeof(Graph));// 设置图的顶点数量graph->numVertices = vertices;// 设置图的边数量graph->numEdges = edges;// 初始化边缘列表为空for (int i = 0; i < edges; i++) {// 将每个边缘列表指针初始化为NULLgraph->edgeList[i] = NULL;}// 返回新创建的图指针return graph;
}// 添加边到图中
void addEdge(Graph* graph, int index, int src, int dest) {// 检查索引是否在有效范围内if (index >= 0 && index < graph->numEdges) {// 在指定索引处创建一个新的边graph->edgeList[index] = createEdge(src, dest);}
}// 打印图的边缘列表
void printGraph(Graph* graph) {printf("图的边缘列表:\n");for (int i = 0; i < graph->numEdges; i++) {Edge* edge = graph->edgeList[i];if (edge != NULL) {// 打印每条边的起始顶点和目标顶点printf("边 %d: %d -> %d\n", i, edge->src, edge->dest);}}
}int main() {// 创建一个有4个顶点和5条边的图Graph* graph = createGraph(4, 5);// 添加边以形成特定的图结构addEdge(graph, 0, 0, 1);addEdge(graph, 1, 0, 2);addEdge(graph, 2, 1, 2);addEdge(graph, 3, 2, 0);addEdge(graph, 4, 2, 3);// 打印图的边缘列表printGraph(graph);return 0;
}
3.3散列表(哈希表)
基础概念如下所示:
3.3.1散列表结构定义
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define TABLE_SIZE 10 // 散列表的大小// 键值对结构体
typedef struct {char* key; // 键int value; // 值
} KeyValuePair;// 散列表节点结构体
typedef struct HashNode {KeyValuePair pair; // 存储键值对struct HashNode* next; // 指向下一个节点的指针,用于处理冲突
} HashNode;// 散列表结构体
typedef struct {HashNode* buckets[TABLE_SIZE]; // 哈希表数组,每个元素是一个链表的头节点
} HashTable;
3.3.2哈希函数定义
// 将字符串键转换为整数索引
unsigned int hashFunction(const char* key) {unsigned int hash = 0; // 初始化哈希值为0while (*key) { // 遍历字符串中的每个字符hash = (hash << 5) + *key++; // 将当前哈希值左移5位,并加上当前字符的ASCII值}return hash % TABLE_SIZE; // 返回哈希值对散列表大小取模的结果,确保索引在散列表范围内
}
3.3.3创建和初始化散列表
// 创建哈希表
HashTable* createHashTable() {// 分配内存以存储哈希表结构体HashTable* table = (HashTable*)malloc(sizeof(HashTable));// 初始化哈希表的每个桶为空for (int i = 0; i < TABLE_SIZE; i++) {table->buckets[i] = NULL;}// 返回创建的哈希表指针return table;
}
3.3.4插入或更新键值对
// 插入或更新键值对
void insertOrUpdate(HashTable* table, const char* key, int value) {unsigned int index = hashFunction(key); // 计算键的哈希值,获取对应的桶索引HashNode* node = table->buckets[index]; // 获取桶的头节点// 搜索键是否已存在,若存在则更新值while (node != NULL) {if (strcmp(node->pair.key, key) == 0) { // 比较当前节点的键与传入的键node->pair.value = value; // 更新值return; // 结束函数}node = node->next; // 移动到下一个节点}// 若键不存在,则创建新节点并插入node = (HashNode*)malloc(sizeof(HashNode)); // 分配新节点的内存if (node == NULL) { // 检查内存分配是否成功fprintf(stderr, "内存分配失败\n"); // 输出错误信息exit(1); // 终止程序}node->pair.key = strdup(key); // 复制键字符串到新节点node->pair.value = value; // 设置新节点的值node->next = table->buckets[index]; // 将新节点的下一个指针指向当前桶的头节点table->buckets[index] = node; // 将新节点设置为桶的头节点
}
3.3.5查找键对应的值
// 在哈希表中查找键对应的值
int find(HashTable* table, const char* key) {unsigned int index = hashFunction(key); // 计算键的哈希值,获取对应的桶索引HashNode* node = table->buckets[index]; // 获取桶的头节点while (node != NULL) { // 遍历桶中的链表if (strcmp(node->pair.key, key) == 0) { // 比较当前节点的键与传入的键return node->pair.value; // 如果找到匹配的键,返回对应的值}node = node->next; // 移动到下一个节点}return -1; // 表示未找到
}
3.3.6散列表代码汇总
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define TABLE_SIZE 10// 键值对结构体
typedef struct {char* key; // 键int value; // 值
} KeyValuePair;// 散列表节点结构体(链地址法处理冲突)
typedef struct HashNode {KeyValuePair pair; // 存储键值对struct HashNode* next; // 指向下一个节点的指针,用于处理冲突
} HashNode;// 散列表结构体
typedef struct {HashNode* buckets[TABLE_SIZE]; // 哈希表数组,每个元素是一个链表的头节点
} HashTable;// 哈希函数
unsigned int hashFunction(const char* key) {unsigned int hash = 0;while (*key) {hash = (hash << 5) + *key++; // 将当前哈希值左移5位,并加上当前字符的ASCII值}return hash % TABLE_SIZE; // 返回哈希值对散列表大小取模的结果,确保索引在散列表范围内
}// 创建散列表
HashTable* createHashTable() {HashTable* table = (HashTable*)malloc(sizeof(HashTable)); // 分配内存以存储哈希表结构体if (table == NULL) {fprintf(stderr, "内存分配失败\n"); // 错误处理exit(1);}for (int i = 0; i < TABLE_SIZE; i++) {table->buckets[i] = NULL; // 初始化哈希表的每个桶为空}return table;
}// 插入或更新键值对
void insertOrUpdate(HashTable* table, const char* key, int value) {unsigned int index = hashFunction(key); // 计算键的哈希值,获取对应的桶索引HashNode* node = table->buckets[index]; // 获取桶的头节点while (node!= NULL) { // 遍历桶中的链表if (strcmp(node->pair.key, key) == 0) { // 比较当前节点的键与传入的键node->pair.value = value; // 如果键已存在,更新值return;}node = node->next; // 移动到下一个节点}node = (HashNode*)malloc(sizeof(HashNode)); // 分配新节点的内存if (node == NULL) {fprintf(stderr, "内存分配失败\n");exit(1);}node->pair.key = strdup(key); // 复制键字符串到新节点node->pair.value = value; // 设置新节点的值node->next = table->buckets[index]; // 将新节点的下一个指针指向当前桶的头节点table->buckets[index] = node; // 将新节点设置为桶的头节点
}// 查找键对应的值
int find(HashTable* table, const char* key) {unsigned int index = hashFunction(key); // 计算键的哈希值,获取对应的桶索引HashNode* node = table->buckets[index]; // 获取桶的头节点while (node!= NULL) { // 遍历桶中的链表if (strcmp(node->pair.key, key) == 0) { // 比较当前节点的键与传入的键return node->pair.value; // 如果找到匹配的键,返回对应的值}node = node->next; // 移动到下一个节点}return -1; // 表示未找到
}// 删除键值对
void delete(HashTable* table, const char* key) {unsigned int index = hashFunction(key); // 计算键的哈希值,获取对应的桶索引HashNode* node = table->buckets[index]; // 获取桶的头节点HashNode* prev = NULL; // 用于记录当前节点的前一个节点while (node!= NULL) { // 遍历桶中的链表if (strcmp(node->pair.key, key) == 0) { // 比较当前节点的键与传入的键if (prev == NULL) { // 如果当前节点是桶的头节点table->buckets[index] = node->next; // 将桶的头节点指向下一个节点} else {prev->next = node->next; // 将前一个节点的下一个指针指向当前节点的下一个节点}free(node->pair.key); // 释放键字符串的内存free(node); // 释放当前节点的内存return;}prev = node; // 记录当前节点为前一个节点node = node->next; // 移动到下一个节点}
}// 打印散列表(用于调试)
void printHashTable(HashTable* table) {for (int i = 0; i < TABLE_SIZE; i++) {HashNode* node = table->buckets[i];printf("Bucket %d: ", i);while (node!= NULL) {printf("(%s, %d) -> ", node->pair.key, node->pair.value);node = node->next;}printf("NULL\n");}
}// 释放散列表内存
void freeHashTable(HashTable* table) {for (int i = 0; i < TABLE_SIZE; i++) {HashNode* node = table->buckets[i];while (node!= NULL) {HashNode* next = node->next;free(node->pair.key); // 释放键字符串的内存free(node); // 释放当前节点的内存node = next; // 移动到下一个节点}}free(table); // 释放哈希表结构体的内存
}int main() {HashTable* table = createHashTable(); // 创建哈希表insertOrUpdate(table, "apple", 1); // 插入键值对insertOrUpdate(table, "banana", 2);insertOrUpdate(table, "orange", 3);printf("apple: %d\n", find(table, "apple")); // 查找键对应的值printf("banana: %d\n", find(table, "banana"));printf("orange: %d\n", find(table, "orange"));delete(table, "banana"); // 删除键值对printf("After deleting banana:\n");printHashTable(table); // 打印哈希表freeHashTable(table); // 释放哈希表内存return 0;
}
4.特殊数据结构
基础概念如下所示:
4.1Trie树(又称前缀树)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define ALPHABET_SIZE 26 // 字母表大小,假设只处理小写字母// Trie树节点结构体
typedef struct TrieNode {struct TrieNode* children[ALPHABET_SIZE]; // 子节点指针数组,每个元素对应一个字母int isEndOfWord; // 标记该节点是否为单词的结尾
} TrieNode;// 创建新的Trie树节点
TrieNode* createNode() {TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode)); // 分配内存if (node) {node->isEndOfWord = 0; // 初始化节点,不是单词结尾for (int i = 0; i < ALPHABET_SIZE; i++) {node->children[i] = NULL; // 初始化子节点指针为空}}return node;
}// 插入字符串到Trie树
void insert(TrieNode* root, const char* key) {TrieNode* node = root;for (int i = 0; key[i] != '\0'; i++) {int index = key[i] - 'a'; // 计算字符在字母表中的索引if (!node->children[index]) {node->children[index] = createNode(); // 如果子节点不存在,创建新节点}node = node->children[index]; // 移动到下一个节点}node->isEndOfWord = 1; // 标记当前节点为单词结尾
}// 在Trie树中搜索字符串
int search(TrieNode* root, const char* key) {TrieNode* node = root;for (int i = 0; key[i] != '\0'; i++) {int index = key[i] - 'a'; // 计算字符在字母表中的索引if (!node->children[index]) {return 0; // 如果子节点不存在,说明字符串不存在}node = node->children[index]; // 移动到下一个节点}return (node != NULL && node->isEndOfWord); // 返回是否找到单词
}// 释放Trie树的内存
void freeTrie(TrieNode* root) {if (root == NULL) {return;}for (int i = 0; i < ALPHABET_SIZE; i++) {if (root->children[i]) {freeTrie(root->children[i]); // 递归释放子节点内存}}free(root); // 释放当前节点内存
}int main() {TrieNode* root = createNode(); // 创建Trie树的根节点// 插入一些单词insert(root, "apple");insert(root, "banana");insert(root, "cherry");// 搜索单词printf("Search 'apple': %s\n", search(root, "apple") ? "Found" : "Not Found");printf("Search 'banana': %s\n", search(root, "banana") ? "Found" : "Not Found");printf("Search 'cherry': %s\n", search(root, "cherry") ? "Found" : "Not Found");printf("Search 'grape': %s\n", search(root, "grape") ? "Found" : "Not Found");// 释放Trie树的内存freeTrie(root);return 0;
}
4.2并查集
是一种数据结构,用于处理一些不相交集合的合并及查询问题。它主要支持以下两种操作
(1)查找(Find):确定某个元素属于哪个集合。
(2)合并(Union):将两个集合合并成一个集合。
#include <stdio.h>
#include <stdlib.h>#define MAX_SIZE 100 // 并查集的最大元素数量// 并查集结构体
typedef struct {int parent[MAX_SIZE]; // 父节点数组,用于存储每个元素的父节点int rank[MAX_SIZE]; // 秩数组,用于存储每个集合的秩(树的高度)
} DisjointSet;// 初始化并查集
void makeSet(DisjointSet* ds, int n) {for (int i = 0; i < n; i++) {ds->parent[i] = i; // 初始时每个元素的父节点是自己ds->rank[i] = 0; // 初始时秩为0}
}// 查找根节点,并进行路径压缩
int find(DisjointSet* ds, int x) {if (ds->parent[x] != x) {ds->parent[x] = find(ds, ds->parent[x]); // 路径压缩,将x的父节点直接指向根节点}return ds->parent[x];
}// 合并两个集合,按秩合并
void unionSets(DisjointSet* ds, int x, int y) {int rootX = find(ds, x);int rootY = find(ds, y);if (rootX != rootY) {if (ds->rank[rootX] < ds->rank[rootY]) {ds->parent[rootX] = rootY; // 将秩小的集合合并到秩大的集合} else if (ds->rank[rootX] > ds->rank[rootY]) {ds->parent[rootY] = rootX;} else {ds->parent[rootY] = rootX; // 秩相等时,任选一个作为根节点,并增加其秩ds->rank[rootX]++;}}
}// 示例使用
int main() {DisjointSet ds;int n = 10; // 假设有10个元素makeSet(&ds, n); // 初始化并查集unionSets(&ds, 1, 2); // 合并元素1和元素2所在的集合unionSets(&ds, 3, 4); // 合并元素3和元素4所在的集合unionSets(&ds, 1, 4); // 合并元素1和元素4所在的集合printf("元素1和元素4是否在同一个集合中?%s\n",find(&ds, 1) == find(&ds, 4) ? "是" : "否"); // 查找元素1和元素4是否在同一个集合中printf("元素1和元素5是否在同一个集合中?%s\n",find(&ds, 1) == find(&ds, 5) ? "是" : "否"); // 查找元素1和元素5是否在同一个集合中return 0;
}
4.3跳表
是一种基于链表的数据结构,用于高效地实现有序集合的插入、删除和查找操作。它通过在链表中添加多级索引来提高查找效率,类似于二分查找的思想。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>#define MAX_LEVEL 16 // 跳表的最大层数// 跳表节点结构体
typedef struct SkipListNode {int key; // 键int value; // 值struct SkipListNode* forward[MAX_LEVEL]; // 指向不同层的下一个节点
} SkipListNode;// 跳表结构体
typedef struct {SkipListNode* header; // 跳表的头节点int level; // 跳表的当前层数
} SkipList;// 创建新的跳表节点
SkipListNode* createNode(int key, int value, int level) {SkipListNode* node = (SkipListNode*)malloc(sizeof(SkipListNode)); // 分配内存if (node) {node->key = key; // 设置键node->value = value; // 设置值for (int i = 0; i < level; i++) {node->forward[i] = NULL; // 初始化指向不同层的下一个节点的指针为NULL}}return node;
}// 创建新的跳表
SkipList* createSkipList() {SkipList* list = (SkipList*)malloc(sizeof(SkipList)); // 分配内存if (list) {list->header = createNode(0, 0, MAX_LEVEL); // 创建头节点,键和值都为0,层数为最大层数list->level = 1; // 初始时跳表的层数为1}return list;
}// 生成随机层数
int randomLevel() {int level = 1;while (rand() < RAND_MAX / 2 && level < MAX_LEVEL) {level++; // 以一定概率增加层数,直到达到最大层数}return level;
}// 插入键值对到跳表
void insert(SkipList* list, int key, int value) {SkipListNode* update[MAX_LEVEL]; // 用于记录插入位置的前驱节点SkipListNode* current = list->header;for (int i = list->level - 1; i >= 0; i--) {while (current->forward[i] && current->forward[i]->key < key) {current = current->forward[i]; // 在当前层找到插入位置的前驱节点}update[i] = current;}current = current->forward[0];if (current == NULL || current->key != key) {int newLevel = randomLevel(); // 生成新节点的随机层数if (newLevel > list->level) {for (int i = list->level; i < newLevel; i++) {update[i] = list->header; // 更新新层的前驱节点为头节点}list->level = newLevel; // 更新跳表的层数}SkipListNode* newNode = createNode(key, value, newLevel); // 创建新节点for (int i = 0; i < newLevel; i++) {newNode->forward[i] = update[i]->forward[i]; // 更新新节点在各层的指针update[i]->forward[i] = newNode;}} else {current->value = value; // 如果键已存在,更新值}
}// 在跳表中查找键对应的值
int search(SkipList* list, int key) {SkipListNode* current = list->header;for (int i = list->level - 1; i >= 0; i--) {while (current->forward[i] && current->forward[i]->key < key) {current = current->forward[i]; // 在当前层查找键}}current = current->forward[0];if (current && current->key == key) {return current->value; // 如果找到键,返回对应的值} else {return -1; // 如果未找到键,返回-1}
}// 删除跳表中的键值对
void delete(SkipList* list, int key) {SkipListNode* update[MAX_LEVEL]; // 用于记录删除位置的前驱节点SkipListNode* current = list->header;for (int i = list->level - 1; i >= 0; i--) {while (current->forward[i] && current->forward[i]->key < key) {current = current->forward[i]; // 在当前层找到删除位置的前驱节点}update[i] = current;}current = current->forward[0];if (current && current->key == key) {for (int i = 0; i < list->level; i++) {if (update[i]->forward[i] != current) {break; // 如果在某一层没有找到要删除的节点,跳出循环}update[i]->forward[i] = current->forward[i]; // 更新前驱节点的指针}free(current); // 释放被删除节点的内存while (list->level > 1 && list->header->forward[list->level - 1] == NULL) {list->level--; // 如果某一层没有节点,减少跳表的层数}}
}// 释放跳表的内存
void freeSkipList(SkipList* list) {SkipListNode* current = list->header;SkipListNode* next;while (current) {next = current->forward[0]; // 保存下一个节点的指针free(current); // 释放当前节点的内存current = next; // 移动到下一个节点}free(list); // 释放跳表结构体的内存
}// 打印跳表(用于调试)
void printSkipList(SkipList* list) {for (int i = list->level - 1; i >= 0; i--) {SkipListNode* current = list->header->forward[i];printf("Level %d: ", i + 1);while (current) {printf("(%d, %d) -> ", current->key, current->value); // 打印当前节点的键值对current = current->forward[i]; // 移动到下一个节点}printf("NULL\n"); // 打印当前层的结束标志}
}int main() {srand(time(NULL)); // 初始化随机数生成器SkipList* list = createSkipList(); // 创建跳表// 插入一些键值对insert(list, 1, 10);insert(list, 2, 20);insert(list, 3, 30);insert(list, 4, 40);insert(list, 5, 50);// 打印跳表printSkipList(list);// 查找键对应的值printf("Search key 3: %d\n", search(list, 3));printf("Search key 6: %d\n", search(list, 6));// 删除键值对delete(list, 3);// 打印跳表printSkipList(list);// 释放跳表的内存freeSkipList(list);return 0;
}
5.文件组织和外部存储数据结构
基本概念定义如下所示:
5.1顺序文件
5.1.1 顺序文件的特点
- 简单性:顺序文件的结构简单,易于理解和实现。
- 顺序访问效率高:如果数据的访问模式是顺序的,那么顺序文件的访问效率很高。
- 不适合随机访问:由于数据是按照顺序存储的,因此随机访问(即直接访问文件中的某个特定位置的数据)效率较低。
5.1.2 顺序文件的操作
- 写入数据:数据按照顺序依次写入文件。
- 读取数据:数据按照顺序依次从文件中读取。
5.1.3 顺序文件的实现解析
#include <stdio.h> #define MAX_RECORDS 100 // 定义最大记录数// 定义学生结构体,包含ID、姓名和成绩
typedef struct {int id; // 学生IDchar name[50]; // 学生姓名,最多49个字符加上结束符float score; // 学生成绩
} Student;// 顺序写入文件函数
void writeSequentialFile(const char* filename) {FILE* file = fopen(filename, "wb"); // 以二进制写模式打开文件if (file == NULL) { // 如果文件打开失败perror("无法打开文件"); // 打印错误信息return; // 返回}Student students[MAX_RECORDS]; // 创建学生数组// 循环填充学生数组for (int i = 0; i < MAX_RECORDS; i++) {students[i].id = i + 1; // 设置IDsprintf(students[i].name, "学生%d", i + 1); // 设置姓名students[i].score = (float)(i + 1) * 10; // 设置成绩fwrite(&students[i], sizeof(Student), 1, file); // 写入文件}fclose(file); // 关闭文件
}// 顺序读取文件函数
void readSequentialFile(const char* filename) {FILE* file = fopen(filename, "rb"); // 以二进制读模式打开文件if (file == NULL) { // 如果文件打开失败perror("无法打开文件"); // 打印错误信息return; // 返回}Student student; // 创建学生变量用于存储读取的数据// 循环读取文件直到文件结束while (fread(&student, sizeof(Student), 1, file) == 1) {printf("ID: %d, 姓名: %s, 成绩: %.2f\n", student.id, student.name, student.score); // 打印学生信息}fclose(file); // 关闭文件
}int main() {const char* filename = "students.dat"; // 定义文件名writeSequentialFile(filename); // 写入文件readSequentialFile(filename); // 读取文件return 0; // 程序结束
}
5.2 索引文件
索引文件是一种通过建立索引来提高数据查找速度的文件组织方式。索引指向数据在文件中的位置,使得可以快速定位到特定的数据记录。
5.2.1 索引文件的特点
- 查找效率高:通过索引可以快速定位到数据记录,提高了查找效率。
- 增加了存储空间:需要额外的存储空间来存储索引。
- 维护成本:当数据发生变化时,需要更新索引,增加了维护成本。
5.2.2 索引文件的操作
- 写入数据:数据按照顺序依次写入文件,同时更新索引。
- 读取数据:通过索引快速定位到数据记录,然后读取数据。
5.2.3 索引文件的实现与解析
#include <stdio.h>
#include <stdlib.h>#define MAX_RECORDS 100 // 定义最大记录数// 定义学生结构体,包含ID、姓名和成绩
typedef struct {int id; // 学生IDchar name[50]; // 学生姓名,最多49个字符加上结束符float score; // 学生成绩
} Student;// 定义索引条目结构体,包含ID和文件偏移量
typedef struct {int id; // 记录IDlong offset; // 文件中的偏移量
} IndexEntry;// 写入索引文件函数
void writeIndexedFile(const char* filename) {FILE* dataFile = fopen(filename, "wb"); // 以二进制写模式打开数据文件if (dataFile == NULL) { // 如果数据文件打开失败perror("无法打开数据文件"); // 打印错误信息return; // 返回}FILE* indexFile = fopen("index.dat", "wb"); // 以二进制写模式打开索引文件if (indexFile == NULL) { // 如果索引文件打开失败perror("无法打开索引文件"); // 打印错误信息fclose(dataFile); // 关闭已打开的数据文件return; // 返回}Student students[MAX_RECORDS]; // 创建学生数组IndexEntry index[MAX_RECORDS]; // 创建索引条目数组// 循环填充学生数组和索引数组,并写入文件for (int i = 0; i < MAX_RECORDS; i++) {students[i].id = i + 1; // 设置IDsprintf(students[i].name, "学生%d", i + 1); // 设置姓名students[i].score = (float)(i + 1) * 10; // 设置成绩fwrite(&students[i], sizeof(Student), 1, dataFile); // 写入数据文件index[i].id = students[i].id; // 设置索引IDindex[i].offset = ftell(dataFile) - sizeof(Student); // 计算并设置文件偏移量fwrite(&index[i], sizeof(IndexEntry), 1, indexFile); // 写入索引文件}fclose(dataFile); // 关闭数据文件fclose(indexFile); // 关闭索引文件
}// 读取索引文件函数
void readIndexedFile(const char* filename, int id) {FILE* dataFile = fopen(filename, "rb"); // 以二进制读模式打开数据文件if (dataFile == NULL) { // 如果数据文件打开失败perror("无法打开数据文件"); // 打印错误信息return; // 返回}FILE* indexFile = fopen("index.dat", "rb"); // 以二进制读模式打开索引文件if (indexFile == NULL) { // 如果索引文件打开失败perror("无法打开索引文件"); // 打印错误信息fclose(dataFile); // 关闭已打开的数据文件return; // 返回}IndexEntry index; // 创建索引条目变量用于存储读取的数据// 循环读取索引文件直到找到匹配的ID或文件结束while (fread(&index, sizeof(IndexEntry), 1, indexFile) == 1) {if (index.id == id) { // 如果找到匹配的IDfseek(dataFile, index.offset, SEEK_SET); // 定位到数据文件中的记录位置Student student; // 创建学生变量用于存储读取的数据fread(&student, sizeof(Student), 1, dataFile); // 读取学生信息printf("ID: %d, 姓名: %s, 成绩: %.2f\n", student.id, student.name, student.score); // 打印学生信息break; // 结束循环}}fclose(dataFile); // 关闭数据文件fclose(indexFile); // 关闭索引文件
}int main() {const char* filename = "students.dat"; // 定义数据文件名writeIndexedFile(filename); // 写入索引文件readIndexedFile(filename, 50); // 读取ID为50的学生信息return 0; // 程序结束
}