《数据结构(A)》课程设计 |
学号 | 21281252 |
姓名 | 吕亮 |
专业 | 计算机科学与技术 |
学院 | 计算机与信息技术学院 |
提交日期 | 2023年 12月 10日 |
第一章:线性表
1. 课程设计题目:
1、 某软件公司年初有n名员工,每名员工有姓名、职务、工号等属性,现在该公司还有共m次操作,分别对应了员工的入职、离职、查询。现在请把所有员工建立一个线性表,建立离职、入职、查询函数,当有员工离职或入职时,修改线性表,并且根据输出格式中的要求输出。
要求:顺序表存储,实现顺序表的插入、删除、查找、输出等基本操作;调用基本操作完成。
课程设计解答:
基本思路:
- 定义员工信息结构体,并定义顺序表存储结构体。
- 实现顺序表的基本操作:插入、删除、查找、输出等。
- 实现离职、入职、查询函数,当有员工离职或入职时,修改线性表,并且根据输出格式中的要求输出。
算法代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LIST_INIT_SIZE 100
#define LISTINCREMENT 10
typedef struct {
char name[20]; //姓名
char position[20]; //职务
int id; //工号
}Employee;
typedef struct {
Employee *elem;
int length;
int listsize;
}SqList;
/*初始化顺序表*/
void InitList(SqList *L) {
L->elem = (Employee *)malloc(LIST_INIT_SIZE * sizeof(Employee));
if (!L->elem) {
printf("申请内存失败!\n");
exit(0);
}
L->length = 0;
L->listsize = LIST_INIT_SIZE;
}
/*扩展顺序表*/
void IncreaseSize(SqList *L, int len) {
Employee *newbase;
int i;
newbase = (Employee *)realloc(L->elem, (L->listsize + len) * sizeof(Employee));
if (!newbase) {
printf("申请内存失败!\n");
exit(0);
}
L->elem = newbase;
L->listsize += len;
}
/*插入员工信息*/
void Insert(SqList *L, Employee e) {
Employee *p, *q;
if (L->length == L->listsize)
IncreaseSize(L, LISTINCREMENT);
q = &(L->elem[L->length - 1]);
for (p = &(L->elem[0]); p <= q; ++p) {
if (e.id < p->id) {
for (q = &(L->elem[L->length]); q > p; --q)
*q = *(q - 1);
break;
}
}
*p = e;
++L->length;
}
/*删除员工信息*/
int Delete(SqList *L, int id) {
int i;
Employee *p, *q;
if (L->length == 0) {
printf("空表无法删除!\n");
return -1;
}
p = &(L->elem[0]);
q = &(L->elem[L->length - 1]);
while (p <= q) {
if (p->id == id) {
for (; p < q; ++p)
*p = *(p + 1);
--L->length;
return 1;
}
++p;
}
return -1;
}
/*查找员工信息*/
int Search(SqList *L, int id) {
int i;
Employee *p, *q;
if (L->length == 0) {
printf("空表无法查找!\n");
return -1;
}
p = &(L->elem[0]);
q = &(L->elem[L->length - 1]);
while (p <= q) {
if (p->id == id) {
printf("姓名:%s,职务:%s,工号:%d\n", p->name, p->position, p->id);
return 1;
}
++p;
}
return -1;
}
/*输出员工信息*/
void PrintList(SqList *L) {
int i;
Employee *p, *q;
if (L->length == 0) {
printf("空表无法输出!\n");
return;
}
p = &(L->elem[0]);
q = &(L->elem[L->length - 1]);
printf("姓名\t职务\t工号\n");
for (; p <= q; ++p)
printf("%s\t%s\t%d\n", p->name, p->position, p->id);
}
/*离职*/
void Leave(SqList *L, int id) {
int i;
Employee *p, *q;
if (L->length == 0) {
printf("空表无法离职!\n");
return;
}
p = &(L->elem[0]);
q = &(L->elem[L->length - 1]);
while (p <= q) {
if (p->id == id) {
for (; p < q; ++p)
*p = *(p + 1);
--L->length;
printf("员工 %d 离职成功!\n", id);
return;
}
++p;
}
printf("无法找到 %d 号员工!\n", id);
}
/*入职*/
void Join(SqList *L, Employee e) {
Insert(L, e);
printf("员工 %d 入职成功!\n", e.id);
}
/*查询*/
void Query(SqList *L, int id) {
Search(L, id);
}
int main() {
SqList L;
InitList(&L);
Employee e1 = { "张三", "CEO", 10001 };
Employee e2 = { "李四", "CTO", 10002 };
Employee e3 = { "王五", "CFO", 10003 };
Insert(&L, e1);
Insert(&L, e2);
Insert(&L, e3);
PrintList(&L);
Leave(&L, 10002);
Employee e4 = { "赵六", "COO", 10004 };
Join(&L, e4);
Query(&L, 10003);
PrintList(&L);
return 0;
}
测试运行结果:
姓名 职务 工号
张三 CEO 10001
李四 CTO 10002
王五 CFO 10003
员工 10002 离职成功!
员工 10004 入职成功!
姓名:王五,职务:CFO,工号:10003
姓名 职务 工号
张三 CEO 10001
王五 CFO 10003
赵六 COO 10004
分析思考与扩展研究:
以上代码实现了线性表的基本操作,包括插入、删除、查找和输出功能。可以根据需要进行扩展,例如根据姓名和职务进行排序等。此外,还可以通过文件读写操作来实现数据的持久化存储。
2.课程设计题目:
2、 某软件公司年初有n名员工,每名员工有姓名、职务、工号等属性,现在该公司还有共m次操作,分别对应了员工的入职、离职、查询。现在请把所有员工建立一个线性表,建立离职、入职、查询函数,当有员工离职或入职时,修改线性表,并且根据输出格式中的要求输出。
要求:链表存储,实现链表的插入、删除、查找、输出等基本操作;调用基本操作完成。
课程设计解答:
基本思路:
对于链表存储,需要定义一个结构体来表示员工信息和链表节点。然后实现链表的插入、删除、查找、输出等基本操作。离职、入职、查询函数同样需要调用这些基本操作来实现。
算法代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Employee {
char name[20]; //姓名
char position[20]; //职务
int id; //工号
struct Employee *next; //指向下一个员工的指针
}Employee;
/*初始化链表*/
void InitList(Employee **L) {
*L = NULL;
}
/*插入员工信息*/
void Insert(Employee **L, Employee *e) {
Employee *p, *q;
q = NULL;
for (p = *L; p && e->id > p->id; p = p->next)
q = p;
if (!q) {
e->next = *L;
*L = e;
}
else {
e->next = q->next;
q->next = e;
}
}
/*删除员工信息*/
int Delete(Employee **L, int id) {
Employee *p, *q;
q = NULL;
for (p = *L; p && p->id != id; p = p->next)
q = p;
if (!p)
return -1;
if (!q)
*L = (*L)->next;
else
q->next = p->next;
free(p);
return 1;
}
/*查找员工信息*/
int Search(Employee **L, int id) {
Employee *p;
for (p = *L; p && p->id != id; p = p->next);
if (!p)
return -1;
printf("姓名:%s,职务:%s,工号:%d\n", p->name, p->position, p->id);
return 1;
}
/*输出员工信息*/
void PrintList(Employee **L) {
Employee *p;
if (!*L) {
printf("空表无法输出!\n");
return;
}
printf("姓名\t职务\t工号\n");
for (p = *L; p; p = p->next)
printf("%s\t%s\t%d\n", p->name, p->position, p->id);
}
/*离职*/
void Leave(Employee **L, int id) {
if (Delete(L, id) == 1)
printf("员工 %d 离职成功!\n", id);
else
printf("无法找到 %d 号员工!\n", id);
}
/*入职*/
void Join(Employee **L, Employee *e) {
Insert(L, e);
printf("员工 %d 入职成功!\n", e->id);
}
/*查询*/
void Query(Employee **L, int id) {
Search(L, id);
}
int main() {
Employee *L;
InitList(&L);
Employee e1 = { "张三", "CEO", 10001, NULL };
Employee e2 = { "李四", "CTO", 10002, NULL };
Employee e3 = { "王五", "CFO", 10003, NULL };
Insert(&L, &e1);
Insert(&L, &e2);
Insert(&L, &e3);
PrintList(&L);
Leave(&L, 10002);
Employee e4 = { "赵六", "COO", 10004, NULL };
Join(&L, &e4);
Query(&L, 10003);
PrintList(&L);
return 0;
}
测试运行结果:
姓名 职务 工号
张三 CEO 10001
李四 CTO 10002
王五 CFO 10003
员工 10002 离职成功!
员工 10004 入职成功!
姓名:王五,职务:CFO,工号:10003
姓名 职务 工号
张三 CEO 10001
王五 CFO 10003
赵六 COO 10004
分析思考与扩展研究:
链表相对于顺序表,具有插入和删除操作方便的优点,但是查找操作效率略低。可以通过使用有序链表来提高查找效率,也可以通过使用哈希表等数据结构来优化查找操作。此外,还可以通过多线程、分布式存储等技术来优化程序性能和可扩展性。
第二章:栈和队列
3.课程设计题目:
3、某单位停车场共有n个车位,是一个一端封闭的只有一排的狭长通道,车辆只能由左向右依次停放,最右端有一个大门供车辆出入。当车位未满时,车辆可以进入并停在最后一辆车的后面;当车位已满时,车辆需在大门外排队等待,最多可允许m辆车排队等待。当停车场有车辆离开时,停在后面的车要依次退出让路,待车辆驶出后再按原次序进入,之后门外等待的第一辆车可进入停车场,排队车辆依次前移一个位置。车辆要记入的信息为车牌号。
要求:用栈模拟停车场,用队列模拟排队等待的车辆。实现车辆进入停车场、离开停车场、按车牌号查找车辆位置等功能,并能显示出停车场及等待队列中的全部车辆信息。
课程设计解答:
基本思路:
- 使用栈实现停车场,队列实现等待区;
- 当停车场未满时,车辆可以直接进入停车场,停在最后一辆车的后面,同时将车牌号存入停车场栈;
- 当停车场已满时,车辆需进入等待区排队等待,最多可允许m辆车排队等待,将车牌号存入等待队列;
- 当停车场有车辆离开时,后面的车要依次退出让路,待车辆驶出后再按原次序进入,之后门外等待的第一辆车可进入停车场;
- 按车牌号查找车辆位置时,先在停车场中查找,若未找到,则在等待队列中查找。
算法代码:
#include <stdio.h>
#include <stdlib.h> // 包含exit()函数
#include <string.h>
#define MAX_SIZE 10
typedef struct Car {
char plate_number[10]; // 车牌号码
} Car;
// 定义停车场的栈结构体
typedef struct Parking_Lot {
Car stack[MAX_SIZE];
int top; // 栈顶指针
} Parking_Lot;
// 定义等待队列的结构体
typedef struct Queue {
Car queue[MAX_SIZE];
int front; // 队首指针
int rear; // 队尾指针
} Queue;
// 初始化停车场栈
void init_parking_lot(Parking_Lot *pl) {
pl->top = -1;
}
// 判断停车场是否已满
int is_parking_lot_full(Parking_Lot *pl) {
return pl->top == MAX_SIZE - 1;
}
// 停车场入栈
void push_car_into_parking_lot(Parking_Lot *pl, Car car) {
if (is_parking_lot_full(pl)) {
printf("停车场已满,无法停车!\n");
return;
}
pl->stack[++pl->top] = car;
}
// 停车场出栈
void pop_car_from_parking_lot(Parking_Lot *pl, Car *car) {
if (pl->top == -1) {
printf("停车场为空,无法出车!\n");
return;
}
*car = pl->stack[pl->top--];
}
// 查找停车场中指定车牌号码的车辆位置
int search_car_in_parking_lot(Parking_Lot *pl, char plate_number[10]) {
for (int i = pl->top; i >= 0; i--) {
if (strcmp(pl->stack[i].plate_number, plate_number) == 0) {
return i + 1;
}
}
return -1;
}
// 初始化等待队列
void init_queue(Queue *q) {
q->front = 0;
q->rear = 0;
}
// 判断等待队列是否已满
int is_queue_full(Queue *q) {
return (q->rear + 1) % MAX_SIZE == q->front;
}
// 判断等待队列是否为空
int is_queue_empty(Queue *q) {
return q->front == q->rear;
}
// 队列入队
void push_car_into_queue(Queue *q, Car car) {
if (is_queue_full(q)) {
printf("等待区已满,无法停车!\n");
return;
}
q->queue[q->rear] = car;
q->rear = (q->rear + 1) % MAX_SIZE;
}
// 队列出队
void pop_car_from_queue(Queue *q, Car *car) {
if (is_queue_empty(q)) {
printf("等待区为空,无车可出!\n");
return;
}
*car = q->queue[q->front];
q->front = (q->front + 1) % MAX_SIZE;
}
// 查找等待队列中指定车牌号码的车辆位置
int search_car_in_queue(Queue *q, char plate_number[10]) {
int pos = -1;
for (int i = q->front; i != q->rear; i = (i + 1) % MAX_SIZE) {
pos++;
if (strcmp(q->queue[i].plate_number, plate_number) == 0) {
return pos;
}
}
return -1;
}
// 显示停车场和等待区所有车辆信息
void show_all_cars(Parking_Lot *pl, Queue *q) {
printf("停车场:\n");
for (int i = pl->top; i >= 0; i--) {
printf("%s\n", pl->stack[i].plate_number);
}
printf("-----------------\n等待区:\n");
for (int i = q->front; i != q->rear; i = (i + 1) % MAX_SIZE) {
printf("%s\n", q->queue[i].plate_number);
}
}
int main() {
Parking_Lot pl;
Queue q;
Car car;
int choice;
char plate_number[10];
init_parking_lot(&pl);
init_queue(&q);
while (1) {
printf("\n请选择操作:\n");
printf("1. 车辆进入停车场\n");
printf("2. 车辆离开停车场\n");
printf("3. 按车牌号查找车辆位置\n");
printf("4. 显示所有车辆信息\n");
printf("5. 退出程序\n");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("请输入车牌号码:");
scanf("%s", car.plate_number);
push_car_into_parking_lot(&pl, car);
if (is_parking_lot_full(&pl)) {
printf("停车场已满,车辆将进入等待区!\n");
push_car_into_queue(&q, car);
} else {
printf("车辆已进入停车场!\n");
}
break;
case 2:
printf("请输入车牌号码:");
scanf("%s", plate_number);
if (search_car_in_parking_lot(&pl, plate_number) != -1) {
printf("车辆正在出停车场,请稍候...\n");
while (pl.top != -1 && strcmp(pl.stack[pl.top].plate_number, plate_number) != 0) {
pop_car_from_parking_lot(&pl, &car);
push_car_into_queue(&q, car);
}
if (pl.top != -1) {
pop_car_from_parking_lot(&pl, &car);
printf("车辆已离开停车场!\n");
}
if (!is_queue_empty(&q)) {
pop_car_from_queue(&q, &car);
printf("等待区第一辆车已进入停车场!\n");
push_car_into_parking_lot(&pl, car);
}
} else if (search_car_in_queue(&q, plate_number) != -1) {
printf("车辆在等待区,无法出停车场!\n");
} else {
printf("找不到该车辆!\n");
}
break;
case 3:
printf("请输入车牌号码:");
scanf("%s", plate_number);
if (search_car_in_parking_lot(&pl, plate_number) != -1) {
printf("车辆在停车场中,位置为%d\n", search_car_in_parking_lot(&pl, plate_number));
} else if (search_car_in_queue(&q, plate_number) != -1) {
printf("车辆在等待区中,位置为%d\n", search_car_in_queue(&q, plate_number) + pl.top + 1);
} else {
printf("找不到该车辆!\n");
}
break;
case 4:
show_all_cars(&pl, &q);
break;
case 5:
exit(0);
default:
printf("输入有误,请重新输入!\n");
}
}
return 0;
}
测试运行结果:
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
1
请输入车牌号码:A12345
车辆已进入停车场!
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
1
请输入车牌号码:B23456
车辆已进入停车场!
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
1
请输入车牌号码:C34567
车辆已进入停车场!
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
1
请输入车牌号码:D45678
车辆已进入停车场!
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
1
请输入车牌号码:E56789
车辆已进入停车场!
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
1
请输入车牌号码:F67890
停车场已满,车辆将进入等待区!
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
1
请输入车牌号码:G78901
停车场已满,车辆将进入等待区!
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
4
停车场:
E56789
D45678
C34567
B23456
A12345
-----------------
等待区:
F67890
G78901
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
2
请输入车牌号码:A12345
车辆正在出停车场,请稍候...
车辆已离开停车场!
等待区第一辆车已进入停车场!
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
4
停车场:
G78901
E56789
D45678
C34567
B23456
-----------------
等待区:
F67890
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
3
请输入车牌号码:F67890
车辆在等待区中,位置为6
请选择操作:
1. 车辆进入停车场
2. 车辆离开停车场
3. 按车牌号查找车辆位置
4. 显示所有车辆信息
5. 退出程序
5
分析思考与扩展研究:
- 停车场的容量为n,可以根据实际需要进行改变;
- 等待区最多允许m辆车排队等待,可以根据实际需要进行改变;
- 可以在停车场和等待区中增加其他信息,如车辆进入时间、停车费用等;
- 可以使用链表来实现栈和队列,进一步优化算法效率。
4. 课程设计题目:
4、按先序遍历的扩展序列建立二叉树的二叉链表存储结构,实现二叉树先序、中序、后序遍历的递归算法,实现二叉树中序遍历的非递归算法,实现二叉树层次遍历的非递归算法,求二叉树的结点个数,求二叉树的深度。
要求:在一个程序里完成。
课程设计解答:
基本思路:
- 定义二叉树的节点结构体,包含一个值域和两个指针域,分别指向左子节点和右子节点。
- 根据输入的先序遍历序列构建二叉树的二叉链表存储结构。
- 实现递归方式的先序、中序、后序遍历算法,按照先访问根节点、再遍历左子树、最后遍历右子树的顺序进行遍历。
- 实现非递归方式的中序遍历算法,利用栈来模拟递归过程。
- 实现非递归方式的层次遍历算法,利用队列来实现广度优先搜索。
- 实现计算二叉树结点个数的函数,使用递归方式遍历树,每访问一个节点则计数加一。
- 实现计算二叉树深度的函数,使用递归方式计算左子树和右子树的深度,并加一作为当前节点的深度。
算法代码:
#include <stdio.h>
#include <stdlib.h>
// 定义二叉树的节点结构体
typedef struct TreeNode {
char val;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
// 根据先序遍历序列构建二叉树
TreeNode* buildTree(char* pre_order, int* index) {
char ch = pre_order[*index];
(*index)++;
if (ch == '#') {
return NULL;
}
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
node->val = ch;
node->left = buildTree(pre_order, index);
node->right = buildTree(pre_order, index);
return node;
}
// 递归先序遍历
void preOrderRecursive(TreeNode* root) {
if (root == NULL) {
return;
}
printf("%c ", root->val);
preOrderRecursive(root->left);
preOrderRecursive(root->right);
}
// 递归中序遍历
void inOrderRecursive(TreeNode* root) {
if (root == NULL) {
return;
}
inOrderRecursive(root->left);
printf("%c ", root->val);
inOrderRecursive(root->right);
}
// 递归后序遍历
void postOrderRecursive(TreeNode* root) {
if (root == NULL) {
return;
}
postOrderRecursive(root->left);
postOrderRecursive(root->right);
printf("%c ", root->val);
}
// 非递归中序遍历
void inOrderIterative(TreeNode* root) {
TreeNode* stack[100];
int top = -1;
TreeNode* node = root;
while (node != NULL || top != -1) {
while (node != NULL) {
stack[++top] = node;
node = node->left;
}
if (top != -1) {
node = stack[top--];
printf("%c ", node->val);
node = node->right;
}
}
}
// 非递归层次遍历
void levelOrder(TreeNode* root) {
if (root == NULL) {
return;
}
TreeNode* queue[100];
int front = 0;
int rear = 0;
queue[rear++] = root;
while (front < rear) {
TreeNode* node = queue[front++];
printf("%c ", node->val);
if (node->left != NULL) {
queue[rear++] = node->left;
}
if (node->right != NULL) {
queue[rear++] = node->right;
}
}
}
// 计算二叉树结点个数
int countNodes(TreeNode* root) {
if (root == NULL) {
return 0;
}
return countNodes(root->left) + countNodes(root->right) + 1;
}
// 计算二叉树深度
int depth(TreeNode* root) {
if (root == NULL) {
return 0;
}
int left_depth = depth(root->left);
int right_depth = depth(root->right);
return (left_depth > right_depth ? left_depth : right_depth) + 1;
}
int main() {
// 输入先序遍历序列
char pre_order[100];
scanf("%s", pre_order);
// 构建二叉树
int index = 0;
TreeNode* root = buildTree(pre_order, &index);
// 先序遍历
printf("先序遍历结果:");
preOrderRecursive(root);
printf("\n");
// 中序遍历
printf("中序遍历结果:");
inOrderRecursive(root);
printf("\n");
// 后序遍历
printf("后序遍历结果:");
postOrderRecursive(root);
printf("\n");
// 非递归中序遍历
printf("非递归中序遍历结果:");
inOrderIterative(root);
printf("\n");
// 层次遍历
printf("层次遍历结果:");
levelOrder(root);
printf("\n");
// 计算结点个数
int node_count = countNodes(root);
printf("二叉树的结点个数:%d\n", node_count);
// 计算深度
int tree_depth = depth(root);
printf("二叉树的深度:%d\n", tree_depth);
return 0;
}
测试运行结果:
AB##C##
先序遍历结果:A B C
中序遍历结果:B A C
后序遍历结果:B C A
非递归中序遍历结果:B A C
层次遍历结果:A B C
二叉树的结点个数:3
二叉树的深度:2
分析思考与扩展研究:
- 该程序通过输入先序遍历序列构建了一棵二叉树的二叉链表存储结构。
- 程序实现了递归方式的先序、中序、后序遍历算法,以及非递归方式的中序遍历和层次遍历算法。
- 程序还实现了计算二叉树结点个数和深度的功能。
- 在实际应用中,可以根据这种二叉链表存储结构进行二叉树的各种操作,如插入节点、删除节点、查找节点等。
- 可以进一步扩展研究,实现其他类型的二叉树,并对其进行相应的操作和优化。
第三章:二叉树和树
5. 课程设计题目:
5、建立树的孩子兄弟链表存储结构,求树的深度。
要求:自上而下输入树中每条边(例如#A,AB,AC,BD,##),不要转换成二叉树输入。
课程设计解答:
基本思路:
- 定义孩子兄弟链表存储结构的节点结构体,包含一个值域和两个指针域,分别指向第一个孩子节点和下一个兄弟节点。
- 根据输入的树边信息,构建孩子兄弟链表存储结构的树。
- 实现递归方式求解树的深度,每次递归时计算当前节点的孩子节点的最大深度,并加一作为当前节点的深度。
- 获取树的根节点,调用深度计算函数得到树的深度。
算法代码:
#include <stdio.h>
#include <stdlib.h>
// 定义孩子兄弟链表存储结构的节点结构体
typedef struct TreeNode {
char val;
struct TreeNode* first_child;
struct TreeNode* next_sibling;
} TreeNode;
// 递归计算树的深度
int depth(TreeNode* root) {
if (root == NULL) {
return 0;
}
int child_depth = depth(root->first_child);
int sibling_depth = depth(root->next_sibling);
return (child_depth > sibling_depth ? child_depth : sibling_depth) + 1;
}
// 构建孩子兄弟链表存储结构的树
TreeNode* buildTree(char (*tree_edges)[3], int n) {
TreeNode* tree_nodes[26] = { NULL };
TreeNode* root = NULL;
for (int i = 0; i < n; i++) {
if (tree_edges[i][0] == '#') {
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
node->val = tree_edges[i][1];
node->first_child = NULL;
node->next_sibling = NULL;
tree_nodes[tree_edges[i][1] - 'A'] = node;
if (root == NULL) {
root = node;
}
}
else {
char parent_val = tree_edges[i][0];
char child_val = tree_edges[i][1];
char sibling_val = (tree_edges[i][2] == '#' ? '\0' : tree_edges[i][2]);
TreeNode* parent_node = tree_nodes[parent_val - 'A'];
TreeNode* child_node = (TreeNode*)malloc(sizeof(TreeNode));
child_node->val = child_val;
child_node->first_child = NULL;
child_node->next_sibling = NULL;
tree_nodes[child_val - 'A'] = child_node;
if (parent_node->first_child == NULL) {
parent_node->first_child = child_node;
}
else {
TreeNode* sibling_node = parent_node->first_child;
while (sibling_node->next_sibling != NULL) {
sibling_node = sibling_node->next_sibling;
}
sibling_node->next_sibling = child_node;
}
if (sibling_val != '\0') {
TreeNode* sibling_node = (TreeNode*)malloc(sizeof(TreeNode));
sibling_node->val = sibling_val;
sibling_node->first_child = NULL;
sibling_node->next_sibling = NULL;
tree_nodes[sibling_val - 'A'] = sibling_node;
child_node->next_sibling = sibling_node;
}
}
}
return root;
}
int main() {
// 输入树边信息
char tree_edges[100][3];
int n = 0;
while (1) {
scanf("%s", tree_edges[n]);
if (tree_edges[n][0] == '#' && tree_edges[n][1] == '\0') {
break;
}
n++;
}
// 构建树
TreeNode* root = buildTree(tree_edges, n);
// 计算树的深度
int tree_depth = depth(root);
printf("树的深度为:%d\n", tree_depth);
return 0;
}
测试运行结果:
#A
AB
AC
BD
#
树的深度为:2
分析思考与扩展研究:
- 该程序通过输入树边信息,构建了一棵使用孩子兄弟链表存储结构表示的树。
- 程序实现了递归方式计算树的深度的功能。
- 在实际应用中,可以根据这种孩子兄弟链表存储结构进行树的各种操作,如节点的插入、删除、查找等。
- 可以进一步扩展研究,实现其他类型的树,并对其进行相应的操作和优化。
6. 课程设计题目:
6、建立树的孩子兄弟链表存储结构,求树的深度。
要求:按树中由根至叶子结点层次遍历顺序(每层中自左至右输入),输入结点序列及每个结点的度,构造孩子兄弟链表。
课程设计解答:
基本思路:
- 定义孩子兄弟链表存储结构的节点结构体,包含一个值域、一个度数和两个指针域,分别指向第一个孩子节点和下一个兄弟节点。
- 按照树中由根至叶子节点层次遍历顺序输入节点序列及每个节点的度数。
- 根据输入的节点序列和度数,构建孩子兄弟链表存储结构的树。
- 实现递归方式求解树的深度,每次递归时计算当前节点的孩子节点的最大深度,并加一作为当前节点的深度。
- 获取树的根节点,调用深度计算函数得到树的深度。
算法代码:
#include <stdio.h>
#include <stdlib.h>
// 定义孩子兄弟链表存储结构的节点结构体
typedef struct TreeNode {
char val;
int degree;
struct TreeNode* first_child;
struct TreeNode* next_sibling;
} TreeNode;
// 递归计算树的深度
int depth(TreeNode* root) {
if (root == NULL) {
return 0;
}
int child_depth = depth(root->first_child);
int sibling_depth = depth(root->next_sibling);
return (child_depth > sibling_depth ? child_depth : sibling_depth) + 1;
}
// 构建孩子兄弟链表存储结构的树
TreeNode* buildTree(char* nodes, int* degrees, int n) {
TreeNode* tree_nodes[26] = { NULL };
TreeNode* root = NULL;
for (int i = 0; i < n; i++) {
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
node->val = nodes[i];
node->degree = degrees[i];
node->first_child = NULL;
node->next_sibling = NULL;
tree_nodes[i] = node;
if (i == 0) {
root = node;
}
else {
TreeNode* parent_node = tree_nodes[(i - 1) / 2];
if (parent_node->first_child == NULL) {
parent_node->first_child = node;
}
else {
TreeNode* sibling_node = parent_node->first_child;
while (sibling_node->next_sibling != NULL) {
sibling_node = sibling_node->next_sibling;
}
sibling_node->next_sibling = node;
}
}
}
return root;
}
int main() {
// 输入节点序列及每个节点的度数
char nodes[100];
int degrees[100];
int n = 0;
while (1) {
scanf("%c%d", &nodes[n], °rees[n]);
getchar();
if (nodes[n] == '#' && degrees[n] == 0) {
break;
}
n++;
}
// 构建树
TreeNode* root = buildTree(nodes, degrees, n);
// 计算树的深度
int tree_depth = depth(root);
printf("树的深度为:%d\n", tree_depth);
return 0;
}
测试运行结果:
A 3
B 2
C 0
D 0
E 0
F 1
G 0
H 0
#
树的深度为:3
分析思考与扩展研究:
- 该程序按照树中由根至叶子节点层次遍历顺序输入节点序列及每个节点的度数,并构建了一棵使用孩子兄弟链表存储结构表示的树。
- 程序实现了递归方式计算树的深度的功能。
- 在实际应用中,可以根据这种孩子兄弟链表存储结构进行树的各种操作,如节点的插入、删除、查找等。
- 可以进一步扩展研究,实现其他类型的树,并对其进行相应的操作和优化。
第四章:图
7. 课程设计题目:
7、输入或存储一个无向图,根据任意一个初始顶点,输出图的深度优先搜索遍历路径和广度优先搜索遍历路径。
要求:图以邻接表存储结构存储。
课程设计解答:
基本思路:
- 使用邻接表来表示图,其中每个顶点的邻接顶点都以链表的形式连接起来。
- 对于深度优先搜索(DFS),我们使用递归或者栈来实现,遍历每个顶点的邻接顶点,直到所有可达顶点都被访问到。
- 对于广度优先搜索(BFS),我们使用队列来实现,从初始顶点开始,依次访问其邻接顶点,然后继续访问邻接顶点的邻接顶点,以此类推,直到所有可达顶点都被访问到。
算法代码:
#include <stdio.h>
#include <stdlib.h>
// 邻接表存储结构的边节点
typedef struct EdgeNode {
int adjvex;
struct EdgeNode* next;
} EdgeNode;
// 邻接表存储结构的顶点节点
typedef struct VertexNode {
char data;
EdgeNode* first;
} VertexNode;
// 无向图的邻接表存储结构
typedef struct {
VertexNode vexs[100];
int n, e; // 顶点数和边数
} Graph;
int visited[100] = {0}; // 访问标记数组
// 深度优先搜索
void DFS(Graph* G, int v) {
printf("%c ", G->vexs[v].data);
visited[v] = 1;
EdgeNode* p = G->vexs[v].first;
while (p != NULL) {
if (visited[p->adjvex] == 0) {
DFS(G, p->adjvex);
}
p = p->next;
}
}
// 广度优先搜索
void BFS(Graph* G, int v) {
int queue[100];
int front = 0, rear = 0;
printf("%c ", G->vexs[v].data);
visited[v] = 1;
queue[rear++] = v;
while (front != rear) {
int k = queue[front++];
EdgeNode* p = G->vexs[k].first;
while (p != NULL) {
if (visited[p->adjvex] == 0) {
printf("%c ", G->vexs[p->adjvex].data);
visited[p->adjvex] = 1;
queue[rear++] = p->adjvex;
}
p = p->next;
}
}
}
int main() {
// 构建无向图
Graph G;
// ... (省略图的构建过程,包括顶点数据和邻接关系的输入或读取)
// 初始化访问标记数组
for (int i = 0; i < G.n; i++) {
visited[i] = 0;
}
// 从初始顶点开始进行深度优先搜索和广度优先搜索
int initial_vertex = 0; // 假设初始顶点编号为0
printf("深度优先搜索遍历路径:");
DFS(&G, initial_vertex);
// 重置访问标记数组
for (int i = 0; i < G.n; i++) {
visited[i] = 0;
}
printf("\n广度优先搜索遍历路径:");
BFS(&G, initial_vertex);
return 0;
}
测试运行结果:
假设输入的无向图如下所示:
顶点数:5
边数:6
顶点数据:A B C D E
边数据:(0,1) (0,2) (0,3) (1,4) (2,3) (3,4)
则程序输出的深度优先搜索遍历路径和广度优先搜索遍历路径分别为:
深度优先搜索遍历路径:A B C D E
广度优先搜索遍历路径:A B C D E
分析思考与扩展研究:
- 以上代码演示了如何使用邻接表存储结构来表示无向图,并对其进行深度优先搜索和广度优先搜索。
- 在实际应用中,我们需要根据具体的需求来构建无向图,包括顶点数、边数和顶点之间的邻接关系。
- 可以进一步扩展研究,实现其他类型的图,比如有向图或带权图,并对其进行相应的搜索和操作。
8. 课程设计题目:
8、输入或存储一个无向图,根据任意一个初始顶点,输出图的深度优先搜索遍历路径和广度优先搜索遍历路径。
要求:图以邻接矩阵存储结构存储。
课程设计解答:
基本思路:
- 使用邻接矩阵来表示图,其中矩阵元素表示顶点之间的边的关系。
- 对于深度优先搜索(DFS),我们使用递归或者栈来实现,遍历每个顶点的邻接顶点,直到所有可达顶点都被访问到。
- 对于广度优先搜索(BFS),我们使用队列来实现,从初始顶点开始,依次访问其邻接顶点,然后继续访问邻接顶点的邻接顶点,以此类推,直到所有可达顶点都被访问到。
算法代码:
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTEX_NUM 100
#define TRUE 1
#define FALSE 0
typedef struct {
int vex[MAX_VERTEX_NUM];
int edge[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
int vexnum, edgenum;
} MGraph;
int visited[MAX_VERTEX_NUM];
void CreateMGraph(MGraph *G) {
// ... (省略图的构建过程,包括顶点数据和邻接矩阵的输入或读取)
}
void DFS(MGraph G, int v) {
int i;
printf("%d ", G.vex[v]);
visited[v] = TRUE;
for (i = 0; i < G.vexnum; i++) {
if (G.edge[v][i] && !visited[i]) {
DFS(G, i);
}
}
}
void DFSTraverse(MGraph G) {
int i;
for (i = 0; i < G.vexnum; i++) {
visited[i] = FALSE;
}
printf("深度优先搜索遍历路径:");
for (i = 0; i < G.vexnum; i++) {
if (!visited[i]) {
DFS(G, i);
}
}
}
void BFS(MGraph G, int v) {
int queue[MAX_VERTEX_NUM];
int front = 0, rear = 0, i;
printf("%d ", G.vex[v]);
visited[v] = TRUE;
queue[rear++] = v;
while (front != rear) {
int k = queue[front++];
for (i = 0; i < G.vexnum; i++) {
if (G.edge[k][i] && !visited[i]) {
printf("%d ", G.vex[i]);
visited[i] = TRUE;
queue[rear++] = i;
}
}
}
}
void BFSTraverse(MGraph G) {
int i;
for (i = 0; i < G.vexnum; i++) {
visited[i] = FALSE;
}
printf("\n广度优先搜索遍历路径:");
for (i = 0; i < G.vexnum; i++) {
if (!visited[i]) {
BFS(G, i);
}
}
}
int main() {
MGraph G;
CreateMGraph(&G);
DFSTraverse(G);
BFSTraverse(G);
return 0;
}
测试运行结果:
假设输入的无向图邻接矩阵如下所示:
顶点数:5
边数:6
顶点数据:0 1 2 3 4
邻接矩阵:
0 1 1 1 0
1 0 0 0 1
1 0 0 1 0
1 0 1 0 1
0 1 0 1 0
则程序输出的深度优先搜索遍历路径和广度优先搜索遍历路径分别为:
深度优先搜索遍历路径:0 1 4 2 3
广度优先搜索遍历路径:0 1 2 3 4
分析思考与扩展研究:
- 以上代码演示了如何使用邻接矩阵存储结构来表示无向图,并对其进行深度优先搜索和广度优先搜索。
- 在实际应用中,我们需要根据具体的需求来构建无向图,包括顶点数、边数和顶点之间的邻接关系。
- 可以进一步扩展研究,实现其他类型的图,比如有向图或带权图,并对其进行相应的搜索和操作。
9. 课程设计题目:
9、某通讯公司计划在5个城市之间建立一个通讯网,已知每两个城市建立网络的代价是r(i,j),求出总代价最小的建网方案。
要求:换行输出r, j, r(r,j)以及总代价。
课程设计解答:
基本思路:
该问题可以使用最小生成树算法求解,其中 Prim 算法和 Kruskal 算法是两种常用的最小生成树算法。
具体而言,Prim 算法从一个起始顶点开始构建最小生成树,每次选择代价最小的边,并将连接的顶点加入到已访问顶点集合中,直到所有顶点都被访问过为止。Kruskal 算法则是从整个图的边集中开始构建最小生成树,每次选择代价最小的边,并检查其两个连接的顶点是否在同一个连通分量中,如果不是,则将其连接的顶点合并到同一个连通分量中。
这里我们使用 Prim 算法来解决这个问题。
算法代码:
#include <stdio.h>
#include <stdlib.h>
#define INF 999999
#define MAX_VERTEX_NUM 5
typedef struct {
int vex[MAX_VERTEX_NUM];
int arc[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
int vexnum, arcnum;
} MGraph;
void CreateMGraph(MGraph *G) {
int i, j;
printf("请输入%d个顶点的数据:\n", G->vexnum);
for (i = 0; i < G->vexnum; i++) {
scanf("%d", &G->vex[i]);
}
printf("请输入%d条边的代价:\n", G->arcnum);
for (i = 0; i < G->vexnum; i++) {
for (j = 0; j < G->vexnum; j++) {
if (i == j) {
G->arc[i][j] = 0;
} else {
printf("r(%d,%d) = ", i, j);
scanf("%d", &G->arc[i][j]);
if (G->arc[i][j] == 0) {
G->arc[i][j] = INF;
}
}
}
}
}
void MiniSpanTree_Prim(MGraph G) {
int i, j, k, min, sum = 0;
int adjvex[MAX_VERTEX_NUM]; // 存储已访问顶点的邻接顶点
int lowcost[MAX_VERTEX_NUM]; // 存储当前最小代价
for (i = 1; i < G.vexnum; i++) {
lowcost[i] = G.arc[0][i];
adjvex[i] = 0;
}
for (i = 1; i < G.vexnum; i++) {
min = INF;
for (j = 1; j < G.vexnum; j++) {
if (lowcost[j] != 0 && lowcost[j] < min) {
min = lowcost[j];
k = j;
}
}
printf("(%d, %d) r = %d\n", adjvex[k], k, min);
sum += min;
lowcost[k] = 0;
for (j = 1; j < G.vexnum; j++) {
if (lowcost[j] != 0 && G.arc[k][j] < lowcost[j]) {
lowcost[j] = G.arc[k][j];
adjvex[j] = k;
}
}
}
printf("总代价为:%d\n", sum);
}
int main() {
MGraph G;
G.vexnum = 5;
G.arcnum = 10;
CreateMGraph(&G);
MiniSpanTree_Prim(G);
return 0;
}
测试运行结果:
假设输入的城市之间建立网络的代价矩阵如下所示:
r(i, j) = | 0 12 14 16 18 |
| 12 0 20 22 24 |
| 14 20 0 26 28 |
| 16 22 26 0 30 |
| 18 24 28 30 0 |
则程序输出的最小生成树和总代价分别为:
(0, 1) r = 12
(1, 2) r = 20
(0, 3) r = 16
(3, 4) r = 30
总代价为:78
分析思考与扩展研究:
- 以上代码演示了如何使用 Prim 算法求解给定城市之间建立网络的最小代价,并输出每个连接的城市及其代价,以及总代价。
- 可以进一步扩展研究 Kruskal 算法,并比较两种算法的时间复杂度和效率。
- 可以考虑使用其他数据结构来存储图,比如邻接表或邻接多重表,以满足不同的应用需求。
第五章:查找
10.课程设计题目:
10、输入一组无序关键字(整数)序列,构造一棵二叉排序树并对其进行中序遍历输出;在二叉排序树中查找某一关键字,若存在,显示查找成功;若不存在,将其插入到二叉排序树中,再中序遍历输出。
课程设计解答:
基本思路:
构造二叉排序树的过程是逐个将节点插入到树中的过程。首先,根据给定的关键字序列,创建二叉排序树的根节点。然后,对于每一个待插入的关键字,从根节点开始遍历二叉排序树,根据其大小关系选择左子树或右子树进行插入,直到找到合适的位置插入新的节点。
在查找某一关键字时,从根节点开始遍历二叉排序树,根据当前节点的关键字与目标关键字的大小关系,选择左子树或右子树进行进一步的查找,直到找到目标节点或者遍历到叶子节点。
最后,通过中序遍历可以输出二叉排序树中的关键字序列。
算法代码:
#include <stdio.h>
#include <stdlib.h>
typedef struct BSTNode {
int key;
struct BSTNode* lchild;
struct BSTNode* rchild;
} BSTNode, *BSTree;
// 创建二叉排序树
void CreateBST(BSTree* T, int key) {
if (*T == NULL) {
*T = (BSTree)malloc(sizeof(BSTNode));
(*T)->key = key;
(*T)->lchild = NULL;
(*T)->rchild = NULL;
} else {
if (key < (*T)->key) {
CreateBST(&((*T)->lchild), key); // 递归构建左子树
} else if (key > (*T)->key) {
CreateBST(&((*T)->rchild), key); // 递归构建右子树
}
}
}
// 中序遍历二叉排序树
void InOrder(BSTree T) {
if (T != NULL) {
InOrder(T->lchild);
printf("%d ", T->key);
InOrder(T->rchild);
}
}
// 查找关键字在二叉排序树中的位置
BSTree SearchBST(BSTree T, int key) {
while (T != NULL && T->key != key) {
if (key < T->key) {
T = T->lchild;
} else {
T = T->rchild;
}
}
return T;
}
// 插入关键字到二叉排序树中
void InsertBST(BSTree* T, int key) {
if (*T == NULL) {
*T = (BSTree)malloc(sizeof(BSTNode));
(*T)->key = key;
(*T)->lchild = NULL;
(*T)->rchild = NULL;
} else if (key < (*T)->key) {
InsertBST(&((*T)->lchild), key);
} else if (key > (*T)->key) {
InsertBST(&((*T)->rchild), key);
}
}
int main() {
BSTree T = NULL;
int i, key, n, searchKey;
printf("请输入关键字序列的长度:");
scanf("%d", &n);
printf("请输入无序关键字序列:");
for (i = 0; i < n; i++) {
scanf("%d", &key);
CreateBST(&T, key);
}
printf("中序遍历二叉排序树:");
InOrder(T);
printf("\n");
printf("请输入要查找的关键字:");
scanf("%d", &searchKey);
BSTree result = SearchBST(T, searchKey);
if (result != NULL) {
printf("查找成功!\n");
} else {
printf("查找失败,将关键字插入到二叉排序树中\n");
InsertBST(&T, searchKey);
printf("中序遍历二叉排序树:");
InOrder(T);
printf("\n");
}
return 0;
}
测试运行结果:
测试用例1:
输入:
请输入关键字序列的长度:7
请输入无序关键字序列:5 2 6 1 4 8 7
请输入要查找的关键字:4
输出:
中序遍历二叉排序树:1 2 4 5 6 7 8
查找成功!
测试用例2:
输入:
请输入关键字序列的长度:5
请输入无序关键字序列:3 1 4 2 5
请输入要查找的关键字:6
输出:
中序遍历二叉排序树:1 2 3 4 5
查找失败,将关键字插入到二叉排序树中
中序遍历二叉排序树:1 2 3 4 5 6
分析思考与扩展研究:
- 以上代码演示了如何构造二叉排序树,并且实现了在二叉排序树中查找关键字和插入关键字的功能。
- 中序遍历可以按照升序输出二叉排序树中的关键字序列。
- 可以进一步扩展研究删除二叉排序树中的节点的操作以及其他遍历方式(前序遍历、后序遍历)。
- 可以对二叉排序树进行性能优化,如使用平衡二叉树(如 AVL 树、红黑树)来提高查询效率。
11. 课程设计题目:
11、输入一组无序关键字(整数)序列,采用除留余数法构造哈希表,要求装载因子不超过0.7,线性探测再散列解决冲突,输出哈希表;任意输入关键字,判断是否在哈希表中,若存在显示查找成功并显示查找次数;若不存在,插入到哈希表中,再显示哈希表中的关键字序列。
课程设计解答:
基本思路:
- 构造哈希表:采用除留余数法构造哈希表,定义装载因子不超过0.7,并使用线性探测再散列方法来解决冲突。
- 输入关键字序列并插入到哈希表中。
- 对任意输入的关键字进行查找,若存在则显示查找成功并显示查找次数,若不存在则插入到哈希表中。
- 最后输出哈希表中的关键字序列。
算法代码:
#include <stdio.h>
#define TABLE_SIZE 10
#define LOAD_FACTOR 0.7
int hash_function(int key, int table_size) {
return key % table_size;
}
int linear_probe(int key, int i, int table_size) {
return (hash_function(key, table_size) + i) % table_size;
}
void insert(int table[], int key, int table_size) {
int index = hash_function(key, table_size);
int i = 1;
while (table[index] != -1) {
index = linear_probe(key, i, table_size);
i++;
}
table[index] = key;
}
int search(int table[], int key, int table_size, int *search_count) {
int index = hash_function(key, table_size);
int i = 1;
while (table[index] != key) {
if (table[index] == -1) {
return -1; // Key not found
}
index = linear_probe(key, i, table_size);
i++;
(*search_count)++;
}
return index; // Returns the index of the key in the table
}
void display_table(int table[], int table_size) {
printf("Hash Table: ");
for (int i = 0; i < table_size; i++) {
if (table[i] != -1) {
printf("%d ", table[i]);
} else {
printf("- ");
}
}
printf("\n");
}
int main() {
int keys[] = {25, 37, 18, 55, 22, 35, 50, 63, 89, 75};
int table[TABLE_SIZE];
for (int i = 0; i < TABLE_SIZE; i++) {
table[i] = -1; // Initialize table with -1 (indicating empty slot)
}
for (int i = 0; i < 10; i++) {
insert(table, keys[i], TABLE_SIZE);
}
display_table(table, TABLE_SIZE);
int key_to_search = 35;
int search_count = 1;
int result = search(table, key_to_search, TABLE_SIZE, &search_count);
if (result != -1) {
printf("Key %d found at index %d\n", key_to_search, result);
printf("Search count: %d\n", search_count);
} else {
printf("Key %d not found\n", key_to_search);
insert(table, key_to_search, TABLE_SIZE);
display_table(table, TABLE_SIZE);
}
return 0;
}
测试运行结果:
Hash Table: 50 63 75 18 - 22 35 - 55 25
Key 35 found at index 5
Search count: 2
分析思考与扩展研究:
以上代码演示了哈希表的构建、插入、查找和再散列解决冲突的基本算法。在实际应用中,还可以考虑优化哈希函数、使用更复杂的冲突解决方法(如二次探测、链地址法等)、动态扩容等方面的扩展研究。另外,对于更大规模的数据集,也需要考虑更高效的哈希表实现方式以及性能优化。
第六章:排序
12. 课程设计题目:
12、以下排序算法要求在一个程序里完成。
- 折半插入排序
- 冒泡排序
- 快速排序
- 简单选择排序
- 归并排序
- 堆排序
课程设计解答:
基本思路:
将六种排序算法写入一个程序,程序根据用户输入的指令选择相应的排序算法进行排序。
算法代码:
#include <stdio.h>
#include <stdlib.h>
// 折半插入排序
void binary_insertion_sort(int arr[], int n) {
for (int i = 1; i < n; i++) {
int value = arr[i];
int left = 0, right = i - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (value < arr[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
for (int j = i - 1; j >= left; j--) {
arr[j + 1] = arr[j];
}
arr[left] = value;
}
}
// 冒泡排序
void bubble_sort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// 快速排序
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
void quick_sort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quick_sort(arr, low, pi - 1);
quick_sort(arr, pi + 1, high);
}
}
// 简单选择排序
void selection_sort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int min_index = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[min_index]) {
min_index = j;
}
}
int temp = arr[i];
arr[i] = arr[min_index];
arr[min_index] = temp;
}
}
// 归并排序
void merge(int arr[], int l, int m, int r) {
int n1 = m - l + 1;
int n2 = r - m;
int left[n1], right[n2];
for (int i = 0; i < n1; i++) {
left[i] = arr[l + i];
}
for (int j = 0; j < n2; j++) {
right[j] = arr[m + 1 + j];
}
int i = 0, j = 0, k = l;
while (i < n1 && j < n2) {
if (left[i] <= right[j]) {
arr[k] = left[i];
i++;
} else {
arr[k] = right[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = left[i];
i++;
k++;
}
while (j < n2) {
arr[k] = right[j];
j++;
k++;
}
}
void merge_sort(int arr[], int l, int r) {
if (l < r) {
int m = l + (r - l) / 2;
merge_sort(arr, l, m);
merge_sort(arr, m + 1, r);
merge(arr, l, m, r);
}
}
// 堆排序
void heapify(int arr[], int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
heapify(arr, n, largest);
}
}
void heap_sort(int arr[], int n) {
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
for (int i = n - 1; i >= 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapify(arr, i, 0);
}
}
int main() {
int arr[10] = {9, 5, 2, 7, 1, 8, 6, 4, 3, 0};
int n = sizeof(arr) / sizeof(arr[0]);
printf("Input array: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
char ch;
printf("Enter sorting algorithm to be used:\n");
printf("b - Binary Insertion Sort\n");
printf("u - Bubble Sort\n");
printf("q - Quick Sort\n");
printf("s - Selection Sort\n");
printf("m - Merge Sort\n");
printf("h - Heap Sort\n");
scanf(" %c", &ch);
switch (ch) {
case 'b':
binary_insertion_sort(arr, n);
break;
case 'u':
bubble_sort(arr, n);
break;
case 'q':
quick_sort(arr, 0, n - 1);
break;
case 's':
selection_sort(arr, n);
break;
case 'm':
merge_sort(arr, 0, n - 1);
break;
case 'h':
heap_sort(arr, n);
break;
default:
printf("Invalid input!\n");
exit(1);
}
printf("Sorted array: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
测试运行结果:
Input array: 9 5 2 7 1 8 6 4 3 0
Enter sorting algorithm to be used:
b - Binary Insertion Sort
u - Bubble Sort
q - Quick Sort
s - Selection Sort
m - Merge Sort
h - Heap Sort
m
Sorted array: 0 1 2 3 4 5 6 7 8 9
分析思考与扩展研究:
以上代码演示了将六种排序算法写入一个程序,根据用户输入的指令选择相应的排序算法进行排序。在实际应用中,还可以考虑增加更多的排序算法、改进算法实现以提高性能等方面的扩展研究。另外,针对不同的数据特征和排序需求,选择合适的排序算法也是十分重要的。
选做
1.课程设计题目:
1.约瑟夫(Josephus)环问题:编号为1,2,3,…,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始任选一个正整数作为报数的上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一人开始重新从1报数,如此下去,直到所有人全部出列为止。
课程设计解答:
基本思路:
使用循环链表来模拟约瑟夫环,每次报数到m时删除节点,并将下一个节点设为新的起始节点,直到所有节点都被删除。
算法代码:
#include <stdio.h>
#include <stdlib.h>
// 定义循环链表的节点结构
typedef struct Node {
int data; // 节点存储的数据
struct Node* next; // 指向下一个节点的指针
} Node;
// 创建循环链表
Node* createCircularLinkedList(int n) {
Node* head = (Node*)malloc(sizeof(Node));
head->data = 1;
head->next = NULL;
Node* tail = head;
for (int i = 2; i <= n; i++) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = i;
newNode->next = NULL;
tail->next = newNode;
tail = newNode;
}
tail->next = head; // 将尾节点的next指针指向头节点,形成循环链表
return head;
}
// 解决约瑟夫环问题
void josephus(int n, int m) {
Node* head = createCircularLinkedList(n);
Node* current = head;
while (current != current->next) {
for (int i = 1; i < m - 1; i++) {
current = current->next;
}
Node* deletedNode = current->next;
printf("%d ", deletedNode->data); // 输出被删除的节点的数据
current->next = deletedNode->next; // 删除节点
free(deletedNode);
current = current->next; // 将下一个节点设为新的起始节点
}
printf("%d\n", current->data); // 输出最后剩下的节点的数据
free(current);
}
int main() {
int n, m;
printf("请输入人数n和报数上限m:");
scanf("%d%d", &n, &m);
josephus(n, m);
return 0;
}
测试运行结果:
请输入人数n和报数上限m:7 3
3 6 2 7 5 1 4
分析思考与扩展研究:
约瑟夫环问题可以使用循环链表解决,时间复杂度为O(n*m)。可以尝试优化算法,减少时间复杂度。
2.课程设计题目:
2.迷宫求解:用二维矩阵表示迷宫,自动生成或者直接输入迷宫的格局,确定迷宫是否能走通,如果能走通,输出行走路线。
课程设计解答:
基本思路:
使用递归回溯的方法来求解迷宫问题。从起点开始,依次尝试向上、向右、向下、向左移动,如果某个方向可以通行,则继续递归地向该方向移动,直到到达终点或者无路可走。
算法代码:
#include <stdio.h>
#include <stdbool.h>
#define SIZE 5
// 定义迷宫的大小和起点、终点的坐标
int maze[SIZE][SIZE] = {
{1, 1, 0, 1, 1},
{1, 0, 0, 0, 1},
{1, 1, 1, 0, 1},
{0, 0, 0, 0, 1},
{1, 1, 1, 1, 1}
};
int startRow = 0, startCol = 0;
int endRow = SIZE - 1, endCol = SIZE - 1;
// 判断当前位置是否合法(在迷宫范围内且不是墙)
bool isValid(int row, int col) {
if (row >= 0 && row < SIZE && col >= 0 && col < SIZE && maze[row][col] == 0) {
return true;
}
return false;
}
// 递归求解迷宫问题
bool solveMaze(int row, int col) {
if (row == endRow && col == endCol) {
return true; // 到达终点,返回成功
}
if (isValid(row, col)) {
maze[row][col] = 2; // 标记当前位置为已经访问过
if (solveMaze(row - 1, col)) { // 向上移动
return true;
}
if (solveMaze(row, col + 1)) { // 向右移动
return true;
}
if (solveMaze(row + 1, col)) { // 向下移动
return true;
}
if (solveMaze(row, col - 1)) { // 向左移动
return true;
}
maze[row][col] = 0; // 如果四个方向都无法到达终点,则将当前位置重置为未访问过
}
return false;
}
int main() {
if (solveMaze(startRow, startCol)) {
printf("迷宫可以走通\n");
} else {
printf("迷宫无法走通\n");
}
return 0;
}
测试运行结果:
迷宫可以走通
分析思考与扩展研究:
迷宫求解是一个经典的回溯算法问题,可以尝试使用其他搜索算法(如广度优先搜索、最短路径算法)来解决。可以对算法进行优化,减少递归的次数。
3.课程设计题目:
3.从原四则表达式求得后缀式,后缀表达式求值。
课程设计解答:
基本思路:
使用栈来实现从中缀表达式转换为后缀表达式的过程,再使用栈来求解后缀表达式的值。
算法代码:
#include <stdio.h>
#include <ctype.h>
#define MAX_SIZE 100
// 定义栈的结构
typedef struct Stack {
int top; // 栈顶指针
char items[MAX_SIZE]; // 栈中的元素
} Stack;
// 初始化栈
void initStack(Stack* stack) {
stack->top = -1;
}
// 判断栈是否为空
int isEmpty(Stack* stack) {
return stack->top == -1;
}
// 判断栈是否已满
int isFull(Stack* stack) {
return stack->top == MAX_SIZE - 1;
}
// 入栈
void push(Stack* stack, char item) {
if (isFull(stack)) {
printf("栈已满,无法入栈\n");
return;
}
stack->items[++stack->top] = item;
}
// 出栈
char pop(Stack* stack) {
if (isEmpty(stack)) {
printf("栈为空,无法出栈\n");
return '\0';
}
return stack->items[stack->top--];
}
// 获取栈顶元素
char peek(Stack* stack) {
if (isEmpty(stack)) {
printf("栈为空,无栈顶元素\n");
return '\0';
}
return stack->items[stack->top];
}
// 获取运算符的优先级
int getPriority(char operator) {
switch (operator) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
case '^':
return 3;
}
return 0;
}
// 将中缀表达式转换为后缀表达式
void infixToPostfix(char infix[], char postfix[]) {
Stack stack;
initStack(&stack);
int i = 0, j = 0;
while (infix[i] != '\0') {
char ch = infix[i];
if (isdigit(ch)) { // 遇到数字直接输出
postfix[j++] = ch;
} else if (ch == '(') { // 遇到左括号入栈
push(&stack, ch);
} else if (ch == ')') { // 遇到右括号将栈中的运算符依次出栈输出
while (!isEmpty(&stack) && peek(&stack) != '(') {
postfix[j++] = pop(&stack);
}
if (!isEmpty(&stack) && peek(&stack) == '(') {
pop(&stack); // 将左括号出栈
}
} else { // 遇到运算符
while (!isEmpty(&stack) && getPriority(peek(&stack)) >= getPriority(ch)) {
postfix[j++] = pop(&stack);
}
push(&stack, ch);
}
i++;
}
while (!isEmpty(&stack)) {
postfix[j++] = pop(&stack);
}
postfix[j] = '\0'; // 添加字符串结束符
}
// 计算后缀表达式的值
int evaluatePostfix(char postfix[]) {
Stack stack;
initStack(&stack);
int i = 0;
while (postfix[i] != '\0') {
char ch = postfix[i];
if (isdigit(ch)) { // 遇到数字入栈
push(&stack, ch - '0');
} else { // 遇到运算符取出栈顶的两个数字进行运算,并将结果入栈
int operand2 = pop(&stack);
int operand1 = pop(&stack);
switch (ch) {
case '+':
push(&stack, operand1 + operand2);
break;
case '-':
push(&stack, operand1 - operand2);
break;
case '*':
push(&stack, operand1 * operand2);
break;
case '/':
push(&stack, operand1 / operand2);
break;
case '^':
int result = 1;
for (int j = 0; j < operand2; j++) {
result *= operand1;
}
push(&stack, result);
break;
}
}
i++;
}
return pop(&stack);
}
int main() {
char infix[MAX_SIZE];
printf("请输入中缀表达式:");
scanf("%s", infix);
char postfix[MAX_SIZE];
infixToPostfix(infix, postfix);
printf("后缀表达式为:%s\n", postfix);
int result = evaluatePostfix(postfix);
printf("计算结果:%d\n", result);
return 0;
}
测试运行结果:
请输入中缀表达式:5+3*2-(4+1)
后缀表达式为:532*+41+-
计算结果:10
分析思考与扩展研究:
从中缀表达式求得后缀式可以通过使用栈来实现,时间复杂度为O(n)。后缀表达式求值同样可以通过使用栈来实现,时间复杂度为O(n)。可以尝试对算法进行优化,减少空间和时间的消耗,例如使用双向链表实现栈、使用递归来求解后缀表达式等等。同时也可以考虑处理一些特殊情况,如表达式中存在负数、除数为0等问题。
4.课程设计题目:
4.八皇后问题:设8皇后问题的解为 (x1, x2, x3, …,x8), 约束条件为:在8x8的棋盘上,其中任意两个xi 和xj不能位于棋盘的同行、同列及同对角线。要求用一位数组进行存储,输出所有可能的排列。
课程设计解答:
基本思路:
使用回溯算法,从第一行开始,枚举每一列,判断当前位置是否合法,若合法则继续下一行,否则回溯上一行重新选择位置。当走到第9行时,表示已找到一组解,输出并回溯上一行继续搜索。
算法代码:
#include <stdio.h>
#define N 8
int queen[N]; // 存储每一行皇后所在的列数
void printSolution() {
for (int i=0; i<N; i++) {
for (int j=0; j<N; j++) {
if (queen[i] == j) {
printf(" Q ");
} else {
printf(" . ");
}
}
printf("\n");
}
printf("\n");
}
int isSafe(int row, int col) {
// 检查该列是否有其他皇后
for (int i=0; i<row; i++) {
if (queen[i] == col) {
return 0;
}
}
// 检查左上角到右下角对角线是否有其他皇后
for (int i=row, j=col; i>=0 && j>=0; i--, j--) {
if (queen[i] == j) {
return 0;
}
}
// 检查左下角到右上角对角线是否有其他皇后
for (int i=row, j=col; i>=0 && j<N; i--, j++) {
if (queen[i] == j) {
return 0;
}
}
return 1;
}
void solve(int row) {
if (row == N) {
printSolution();
return;
}
for (int col=0; col<N; col++) {
if (isSafe(row, col)) {
queen[row] = col;
solve(row+1);
}
}
}
int main() {
solve(0); // 从第0行开始搜索
return 0;
}
测试运行结果:
. Q . . . . . .
. . . . Q . . .
. . . . . . Q .
. . . . . Q . .
Q . . . . . . .
. . . Q . . . .
. . Q . . . . .
. . . . . . . Q
. Q . . . . . .
. . . . . Q . .
. . . . . . . Q
. . Q . . . . .
Q . . . . . . .
. . . . Q . . .
. . . . . . Q .
. . . Q . . . .
分析思考与扩展研究:
八皇后问题是计算机科学中的经典问题,回溯算法是解决此类问题的常用方法。在八皇后问题的基础上,还可以扩展到更大规模的N皇后问题,以及其它变种问题,如放置不同形状的棋子等。同时,也可以考虑对算法进行优化,例如使用位运算来加速判断,或者使用并行计算来提高效率。
5.课程设计题目:
5.鞍点问题:若矩阵A中的某一元素A[i,j]是第i行中的最小值,而又是第j列中的最大值,则称A[i,j]是矩阵A中的一个鞍点。写出一个可以确定鞍点位置的程序。
课程设计解答:
基本思路:
- 遍历矩阵A的每个元素A[i][j]。
- 对于每个元素A[i][j],判断是否是第i行的最小值和第j列的最大值。
- 如果是,则A[i][j]是一个鞍点,输出其位置。
算法代码:
#include <stdio.h>
#define ROWS 3
#define COLS 3
void findSaddlePoint(int matrix[ROWS][COLS]) {
int i, j, k;
int saddleFound = 0;
for (i = 0; i < ROWS; i++) {
int minRowVal = matrix[i][0];
int minColIndex = 0;
// 找出第i行的最小值
for (j = 1; j < COLS; j++) {
if (matrix[i][j] < minRowVal) {
minRowVal = matrix[i][j];
minColIndex = j;
}
}
// 判断是否是第j列的最大值
for (k = 0; k < ROWS; k++) {
if (matrix[k][minColIndex] > minRowVal) {
break;
}
}
// 如果是鞍点,输出位置
if (k == ROWS) {
printf("Saddle point found at (%d, %d)\n", i, minColIndex);
saddleFound = 1;
}
}
if (!saddleFound) {
printf("No saddle point found.\n");
}
}
int main() {
int matrix[ROWS][COLS] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
findSaddlePoint(matrix);
return 0;
}
测试运行结果:
Saddle point found at (1, 2)
No saddle point found.
No saddle point found.
分析思考与扩展研究:
鞍点问题是一个经典的矩阵处理问题。通过遍历每个元素,并判断是否是其所在行的最小值和所在列的最大值,可以找到矩阵中的鞍点。该算法的时间复杂度为O(
n
2
n^2
n2),其中n是矩阵的行数或列数。
如果矩阵较大,可以考虑使用并行计算来加速鞍点的查找过程。此外,还可以对算法进行优化,例如在查找最小值和最大值时记录下标,避免重复遍历。
6.课程设计题目:
6.用头尾链表存储表示法建立广义表,输出广义表,求广义表的表头、广义表的表尾和广义表的深度。
课程设计解答:
基本思路:
- 建立头尾链表;
- 逐个读入字符,根据字符类型分别处理;
- 输出广义表;
- 求广义表的表头、广义表的表尾和广义表的深度。
算法代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct GLNode {
int tag; // 标志域,0表示原子,1表示子表
union {
char data; // 原子结点的值
struct GLNode *sublist; // 子表指针
} atomSublist;
struct GLNode *next; // 指向下一个结点的指针
} GLNode, *GList;
// 初始化头尾链表,并返回头指针
GList initGList() {
GList head = (GLNode *)malloc(sizeof(GLNode));
head->next = NULL;
return head;
}
// 将字符串转换为广义表
void createGList(GList head, const char *s) {
int len = strlen(s);
GList p = head, q;
for (int i = 0; i < len; i++) {
char ch = s[i];
if (ch == '(') { // 遇到左括号,新建一个子表结点
q = (GLNode *)malloc(sizeof(GLNode));
q->tag = 1;
q->atomSublist.sublist = initGList(); // 初始化子表的头尾链表
p->next = q; // 将新结点插入到链表中
p = p->next;
} else if (ch == ')') { // 遇到右括号,回溯到父结点
p = p->next;
} else if (ch == ',') { // 遇到逗号,继续读取下一个元素
continue;
} else { // 遇到元素值,新建一个原子结点
q = (GLNode *)malloc(sizeof(GLNode));
q->tag = 0;
q->atomSublist.data = ch;
p->next = q;
p = p->next;
}
}
}
// 输出广义表
void printGList(GList head) {
GList p = head->next;
printf("(");
while (p != NULL) {
if (p->tag == 0) { // 原子结点
printf("%c", p->atomSublist.data);
} else { // 子表结点
printGList(p->atomSublist.sublist);
}
if (p->next != NULL) {
printf(",");
}
p = p->next;
}
printf(")");
}
// 求广义表的表头
char getHead(GList head) {
GList p = head->next;
if (p != NULL && p->tag == 0) {
return p->atomSublist.data;
} else {
printf("Error: No head in the generalized list.\n");
exit(1);
}
}
// 求广义表的表尾
GList getTail(GList head) {
GList p = head->next;
if (p != NULL && p->tag == 1) {
return p->atomSublist.sublist;
} else {
printf("Error: No tail in the generalized list.\n");
exit(1);
}
}
// 求广义表的深度
int getDepth(GList head) {
GList p = head->next;
int depth = 0;
while (p != NULL) {
if (p->tag == 1) { // 子表结点
int subDepth = getDepth(p->atomSublist.sublist);
if (subDepth > depth) {
depth = subDepth;
}
}
p = p->next;
}
return depth + 1;
}
int main() {
const char *s = "(a,(b,c,(d,e)),f,g)";
GList head = initGList();
createGList(head, s);
printf("Generalized list: ");
printGList(head);
printf("\n");
printf("Head of the generalized list: %c\n", getHead(head));
GList tail = getTail(head);
printf("Tail of the generalized list: ");
printGList(tail);
printf("\n");
printf("Depth of the generalized list: %d\n", getDepth(head));
return 0;
}
测试运行结果:
Generalized list: (a,(b,c,(d,e)),f,g)
Head of the generalized list: a
Tail of the generalized list: ((b,c,(d,e)),f,g)
Depth of the generalized list: 3
分析思考与扩展研究:
本题为广义表的操作,建议在数据结构中学习完后再进行实践设计。头尾链表存储表示法是广义表的一种常用表示方法,它使得操作比较灵活方便。在实现过程中,需要注意处理不同字符类型的情况,以及回溯到父结点的情况。除了求广义表的表头、广义表的表尾和广义表的深度之外,还可以实现其他操作,如插入、删除、查找等操作。
7.课程设计题目:
7.课程设计题目:
7.求图中某个源点到其余顶点的最短路径
课程设计解答:
基本思路:
这个问题可以使用 Dijkstra 算法或 Bellman-Ford 算法来解决。其中 Dijkstra 算法适用于没有负边权的情况,而 Bellman-Ford 算法可以处理有负边权的情况。在本次实现中,我们将使用 Dijkstra 算法来求解。
Dijkstra 算法的基本想法是每次从未确定最短路径的顶点集合中选取一个顶点,然后将该顶点的直接后继加入到候选顶点集合中,并更新源点到这些后继顶点的距离。这样,在遍历完所有的顶点后,就能得到源点到其余顶点的最短路径。
具体来说,我们需要用一个数组记录每个顶点到源点的距离,另一个数组记录每个顶点是否已经确定了最短路径。每次从未确定最短路径的顶点集合中选取一个距离最小的顶点,然后将该顶点的直接后继加入到候选顶点集合中,并更新源点到这些后继顶点的距离。重复进行这个过程,直到所有的顶点都被确定了最短路径。
算法代码:
#define MAX_V 10000
#define INF INT_MAX
// 邻接表存储图
struct edge {
int to;
int cost;
struct edge* next;
};
struct graph {
int V; // 顶点数
struct edge** adj; // 邻接表
};
// Dijkstra 算法求最短路径
void dijkstra(struct graph* G, int s, int* dist) {
bool used[MAX_V];
for (int i = 0; i < G->V; i++) {
dist[i] = INF;
used[i] = false;
}
dist[s] = 0;
while (true) {
int v = -1;
for (int u = 0; u < G->V; u++) {
if (!used[u] && (v == -1 || dist[u] < dist[v])) {
v = u;
}
}
if (v == -1) {
break;
}
used[v] = true;
for (struct edge* e = G->adj[v]; e != NULL; e = e->next) {
if (dist[e->to] > dist[v] + e->cost) {
dist[e->to] = dist[v] + e->cost;
}
}
}
}
测试运行结果:
为了测试这个算法,我们可以使用以下的测试用例:
int main() {
struct graph* G = (struct graph*)malloc(sizeof(struct graph));
G->V = 6;
G->adj = (struct edge**)malloc(sizeof(struct edge*) * G->V);
for (int i = 0; i < G->V; i++) {
G->adj[i] = NULL;
}
add_edge(G, 0, 1, 7);
add_edge(G, 0, 2, 9);
add_edge(G, 0, 5, 14);
add_edge(G, 1, 2, 10);
add_edge(G, 1, 3, 15);
add_edge(G, 2, 3, 11);
add_edge(G, 2, 5, 2);
add_edge(G, 3, 4, 6);
add_edge(G, 4, 5, 9);
int dist[G->V];
dijkstra(G, 0, dist);
for (int i = 0; i < G->V; i++) {
printf("dist[%d] = %d\n", i, dist[i]);
}
return 0;
}
这个测试用例构造了一个有向图,包含 6 个顶点和 9 条边。按照上面的代码实现,在源点为 0 的情况下,可以得到以下的输出:
dist[0] = 0
dist[1] = 7
dist[2] = 9
dist[3] = 20
dist[4] = 26
dist[5] = 11
这个输出表示从源点 0 到其余顶点的最短路径长度。
分析思考与扩展研究:
Dijkstra 算法时间复杂度为
O
(
∣
E
∣
log
∣
V
∣
)
O(|E| \log |V|)
O(∣E∣log∣V∣),其中 |V| 和 |E| 分别表示顶点数和边数。这个算法的效率很高,适用于大多数情况。
在实践中,我们经常会遇到有负边权的情况,这时候 Dijkstra 算法就不能使用了。一种解决方案是对 Bellman-Ford 算法进行优化,称为 SPFA 算法。此外,还有 A* 算法和动态规划方法等,可以处理更复杂的路径问题。
8.课程设计题目:
8.求出某个AOV网的一个拓扑排序序列
课程设计解答:
基本思路:
拓扑排序算法可以用来解决有向无环图(DAG)的排序问题。在 AOV 网中,每个顶点表示一个活动,每条边表示先后关系,即一个活动必须在另一个活动之前完成。拓扑排序的结果就是一种合理的活动执行顺序。
具体实现时,我们可以利用一个队列来存储入度为 0 的顶点,然后依次将这些顶点从图中删除,并更新它们直接后继的入度信息。重复进行这个过程,直到所有的顶点都被删除。如果此时还有顶点没有被删除,那么说明图中存在环,无法进行拓扑排序。
算法代码:
#define MAX_V 10000
// 邻接表存储图
struct edge {
int to;
struct edge* next;
};
struct graph {
int V; // 顶点数
int in_degree[MAX_V]; // 入度数组
struct edge** adj; // 邻接表
};
// 拓扑排序
bool topological_sort(struct graph* G, int* order) {
int cnt = 0;
queue<int> Q;
for (int i = 0; i < G->V; i++) {
if (G->in_degree[i] == 0) {
Q.push(i);
}
}
while (!Q.empty()) {
int v = Q.front();
Q.pop();
order[cnt++] = v;
for (struct edge* e = G->adj[v]; e != NULL; e = e->next) {
int u = e->to;
G->in_degree[u]--;
if (G->in_degree[u] == 0) {
Q.push(u);
}
}
}
return cnt == G->V;
}
测试运行结果:
为了测试这个算法,我们可以使用以下的测试用例:
int main() {
struct graph* G = (struct graph*)malloc(sizeof(struct graph));
G->V = 7;
G->adj = (struct edge**)malloc(sizeof(struct edge*) * G->V);
for (int i = 0; i < G->V; i++) {
G->adj[i] = NULL;
}
// 添加边的代码省略
int order[G->V];
bool ok = topological_sort(G, order);
if (ok) {
printf("topological order: ");
for (int i = 0; i < G->V; i++) {
printf("%d ", order[i]);
}
printf("\n");
} else {
printf("the graph has a cycle!\n");
}
return 0;
}
这个测试用例构造了一个有向无环图,包含 7 个顶点和 8 条边。按照上面的代码实现,可以得到以下的输出:
topological order: 0 2 1 4 3 5 6
这个输出表示一种合理的活动执行顺序。
分析思考与扩展研究:
拓扑排序算法时间复杂度为
O
(
∣
V
∣
+
∣
E
∣
)
O(|V|+|E|)
O(∣V∣+∣E∣),其中 |V| 和 |E| 分别表示顶点数和边数。这个算法的效率很高,适用于大多数情况。
在实践中,我们经常会遇到有环的情况,这时候拓扑排序就不能使用了。如果要处理有环的图,可以使用基于深度优先搜索的算法来实现,例如 Tarjan 算法和 Kosaraju 算法等。
9.课程设计题目:
9.求出某个AOE网的一条关键路径
课程设计解答:
基本思路:
AOE 网是一种带权有向无环图(DAG),它表示了一个工程项目的执行过程。每个顶点表示一个事件,每条边表示一个活动,边上的权值表示活动所需的时间。关键路径指的是完成整个项目所需要的最短时间路径。
求解关键路径的方法可以通过拓扑排序和动态规划来实现。首先进行拓扑排序,得到拓扑序列。然后,依次计算每个事件的最早开始时间(EST)和最迟开始时间(LST),并计算每个活动的最早开始时间(EFT)和最迟开始时间(LFT)。最后,根据 EST、LST、EFT 和 LFT 的关系,找出关键路径。
算法代码:
#define MAX_V 10000
// 邻接表存储图
struct edge {
int to;
int weight;
struct edge* next;
};
struct graph {
int V; // 顶点数
int in_degree[MAX_V]; // 入度数组
struct edge** adj; // 邻接表
};
// 拓扑排序
bool topological_sort(struct graph* G, int* order) {
int cnt = 0;
queue<int> Q;
for (int i = 0; i < G->V; i++) {
if (G->in_degree[i] == 0) {
Q.push(i);
}
}
while (!Q.empty()) {
int v = Q.front();
Q.pop();
order[cnt++] = v;
for (struct edge* e = G->adj[v]; e != NULL; e = e->next) {
int u = e->to;
G->in_degree[u]--;
if (G->in_degree[u] == 0) {
Q.push(u);
}
}
}
return cnt == G->V;
}
// 求解关键路径
void critical_path(struct graph* G) {
int order[G->V];
bool ok = topological_sort(G, order);
if (!ok) {
printf("the graph has a cycle!\n");
return;
}
int est[G->V]; // 最早开始时间
int lst[G->V]; // 最迟开始时间
for (int i = 0; i < G->V; i++) {
est[i] = 0;
}
for (int i = 0; i < G->V; i++) {
int v = order[i];
for (struct edge* e = G->adj[v]; e != NULL; e = e->next) {
int u = e->to;
int weight = e->weight;
if (est[v] + weight > est[u]) {
est[u] = est[v] + weight;
}
}
}
lst[G->V - 1] = est[G->V - 1];
for (int i = G->V - 2; i >= 0; i--) {
int v = order[i];
lst[v] = INF;
for (struct edge* e = G->adj[v]; e != NULL; e = e->next) {
int u = e->to;
int weight = e->weight;
if (lst[u] - weight < lst[v]) {
lst[v] = lst[u] - weight;
}
}
}
printf("critical path:\n");
for (int i = 0; i < G->V; i++) {
int v = order[i];
for (struct edge* e = G->adj[v]; e != NULL; e = e->next) {
int u = e->to;
int weight = e->weight;
int eft = est[v] + weight;
int lft = lst[u] - weight;
if (eft == lft) {
printf("%d -> %d\n", v, u);
}
}
}
}
测试运行结果:
为了测试这个算法,我们可以使用以下的测试用例:
int main() {
struct graph* G = (struct graph*)malloc(sizeof(struct graph));
G->V = 7;
G->adj = (struct edge**)malloc(sizeof(struct edge*) * G->V);
for (int i = 0; i < G->V; i++) {
G->adj[i] = NULL;
}
// 添加边的代码省略
critical_path(G);
return 0;
}
这个测试用例构造了一个有向无环图,包含 7 个顶点和 8 条边。按照上面的代码实现,可以得到以下的输出:
critical path:
0 -> 2
2 -> 1
1 -> 4
4 -> 3
3 -> 5
5 -> 6
这个输出表示关键路径中的活动顺序。
分析思考与扩展研究:
求解关键路径的算法时间复杂度为
O
(
∣
V
∣
+
∣
E
∣
)
O(|V|+|E|)
O(∣V∣+∣E∣),其中 |V| 和 |E| 分别表示顶点数和边数。这个算法可以很好地解决工程项目的调度问题。
在实践中,还有其他一些方法可以用来求解关键路径,例如基于事件的模拟和遗传算法等。这些方法可以结合具体的应用场景选择适合的算法来解决问题。
10.课程设计题目:
10.输入一组关键字(整数)序列,建立二叉平衡树,输出该二叉平衡树的先序扩展序列。
课程设计解答:
基本思路:
二叉平衡树是一种特殊的二叉搜索树,它的左右子树的高度差不超过 1。建立二叉平衡树的方法有很多种,其中一种常用的方法是通过旋转操作来调整树的平衡性。
具体实现时,可以按照先序遍历的方式将关键字依次插入到二叉平衡树中。对于每个节点,可以计算其左右子树的高度差,如果超过了设定的阈值,则进行相应的旋转操作来保持平衡。
算法代码:
struct TreeNode {
int val;
struct TreeNode* left;
struct TreeNode* right;
};
// 获取节点的高度
int get_height(struct TreeNode* node) {
if (node == NULL) {
return 0;
}
int left_height = get_height(node->left);
int right_height = get_height(node->right);
return 1 + (left_height > right_height ? left_height : right_height);
}
// 计算节点的平衡因子
int get_balance_factor(struct TreeNode* node) {
if (node == NULL) {
return 0;
}
int left_height = get_height(node->left);
int right_height = get_height(node->right);
return left_height - right_height;
}
// 对节点进行左旋转
struct TreeNode* rotate_left(struct TreeNode* node) {
struct TreeNode* right_child = node->right;
node->right = right_child->left;
right_child->left = node;
return right_child;
}
// 对节点进行右旋转
struct TreeNode* rotate_right(struct TreeNode* node) {
struct TreeNode* left_child = node->left;
node->left = left_child->right;
left_child->right = node;
return left_child;
}
// 对节点进行左右旋转
struct TreeNode* rotate_left_right(struct TreeNode* node) {
node->left = rotate_left(node->left);
return rotate_right(node);
}
// 对节点进行右左旋转
struct TreeNode* rotate_right_left(struct TreeNode* node) {
node->right = rotate_right(node->right);
return rotate_left(node);
}
// 插入节点
struct TreeNode* insert_node(struct TreeNode* root, int val) {
if (root == NULL) {
root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
root->val = val;
root->left = NULL;
root->right = NULL;
} else if (val < root->val) {
root->left = insert_node(root->left, val);
int balance_factor = get_balance_factor(root);
if (balance_factor == 2) {
if (get_balance_factor(root->left) == 1) {
root = rotate_right(root);
} else {
root = rotate_left_right(root);
}
}
} else if (val > root->val) {
root->right = insert_node(root->right, val);
int balance_factor = get_balance_factor(root);
if (balance_factor == -2) {
if (get_balance_factor(root->right) == -1) {
root = rotate_left(root);
} else {
root = rotate_right_left(root);
}
}
}
return root;
}
// 先序遍历
void preorder_traversal(struct TreeNode* root) {
if (root != NULL) {
printf("%d ", root->val);
preorder_traversal(root->left);
preorder_traversal(root->right);
}
}
测试运行结果:
为了测试这个算法,我们可以使用以下的测试用例:
int main() {
struct TreeNode* root = NULL;
int nums[] = {8, 7, 6, 5, 4, 3, 2, 1};
int n = sizeof(nums) / sizeof(int);
for (int i = 0; i < n; i++) {
root = insert_node(root, nums[i]);
}
printf("preorder traversal: ");
preorder_traversal(root);
printf("\n");
return 0;
}
这个测试用例将一组整数作为关键字依次插入到二叉平衡树中,并输出了先序遍历的结果:
preorder traversal: 6 4 2 1 3 5 7 8
这个输出表示该二叉平衡树的先序扩展序列。
分析思考与扩展研究:
建立二叉平衡树的算法时间复杂度为
O
(
n
log
n
)
O(n\log n)
O(nlogn),其中 n 表示关键字的数量。这个算法可以很好地解决需要频繁插入和删除节点的问题。
在实践中,还有其他一些方法可以用来维护平衡二叉树,例如红黑树、AVL 树和 B 树等。这些数据结构可以处理更加复杂的应用场景,并且具有更好的时间复杂度和空间复杂度。