欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 名人名企 > C语言-----数据结构从门到精通

C语言-----数据结构从门到精通

2025/2/6 4:23:42 来源:https://blog.csdn.net/weixin_45136594/article/details/145392752  浏览:    关键词:C语言-----数据结构从门到精通

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);:依次在索引 012 处插入值 102030,此时数组内容为 [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
  • 在头部插入节点 102030
  • 在尾部插入节点 4050
  • 打印链表。
  • 查找值为 30 的节点并打印结果。
  • 删除值为 20 的节点。
  • 打印链表。

(7)运行结果解释:

  • 插入节点

    • insertAtHead(&head, 10); insertAtHead(&head, 20); insertAtHead(&head, 30);:依次在头部插入值 102030,此时链表内容为 [30, 20, 10]

    • insertAtTail(&head, 40); insertAtTail(&head, 50);:依次在尾部插入值 4050,此时链表内容为 [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);:依次将 123 压入栈中,此时栈顶元素为 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,用于存储队列中的元素和指向队头和队尾的位置。

    1. 初始化函数initQueue 将 front 和 rear 初始化为 -1,表示队列为空。

    2. 判断队列状态

    • 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);:依次将 123 入队,此时队头元素为 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);:依次将 123 入队,此时队头元素为 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;  // 程序结束
    }

    版权声明:

    本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

    我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com