CS50 第五周 Data Structures

先进先出 (FIFO)

  • 工作机制: 在队列中,第一个进入的元素是第一个被移除的。就像排队一样,先来的人先离开队列。
  • 操作:
    • 入队 (Enqueue): 向队列的末尾添加元素
    • 出队 (Dequeue): 从队列的前端移除元素
Enqueue(1)
Enqueue(2)
Enqueue(3)
Dequeue()  // 移除 1

先进后出 (LIFO)

  • 工作机制: 在堆栈中,最后一个进入的元素是第一个被移除的。就像一摞盘子,放在最上面的盘子是最先被拿走的。
  • 操作:
    • 入栈 (Push): 将元素压入堆栈的顶部
    • 出栈 (Pop): 从堆栈的顶部移除元素
Push(1)
Push(2)
Push(3)
Pop()  // 移除 3

list1.c

#include <stdio.h>
#include <stdlib.h>
// #include <cs50.h>

typedef struct node
{
    int number;
    struct node *next;
} node;

int main(void)
{
    node *list = NULL;
    // 为第一个节点分配内存
    node *n1 = malloc(sizeof(node));
    // 如果不使用动态分配内存,可以通过创建一个node结构体的实例并直接声明一个node指针
    /*
    node myNode;   // 创建一个node结构体实例
    node *list = &myNode;  // 通过指针指向该实例
    */
    if (n1 == NULL)
    {
        return 1; // 如果分配失败,返回错误码
    }

    n1->number = 1;
    n1->next = NULL;
    list = n1;

    // 为第二个节点分配内存
    node *n2 = malloc(sizeof(node));
    if (n2 == NULL)
    {
        free(n1); // 释放第一个节点的内存
        return 1;
    }

    n2->number = 2;
    n2->next = NULL;

    // 将第二个节点连接到链表
    n1->next = n2;

    // 打印链表的数据
    node *current = list; // 使用一个新的指针来遍历链表
    while (current != NULL)
    {
        printf("%d ", current->number);
        current = current->next;
    }
    printf("\n");

    // 释放链表的内存
    current = list; // 使用一个新的指针来遍历链表
    while (current != NULL)
    {
        node *temp = current;
        current = current->next;
        free(temp);
    }

    return 0;
}

list2.c

#include <stdio.h>
#include <stdlib.h>

// 定义节点结构体
typedef struct node
{
    int number;
    struct node *next;
} node;

int main(int argc, char *argv[])
{
    // 初始化链表头指针
    node *list = NULL;

    // 遍历命令行参数,创建节点并添加到链表头
    for (int i = 1; i < argc; i++)
    {
        // 将命令行参数转换为整数
        int number = atoi(argv[i]);

        // 分配内存以存储节点
        node *n = malloc(sizeof(node));
        if (n == NULL)
        {
            return 1; // 内存分配失败,返回错误码
        }
        
        // 初始化节点数据
        n->number = number;
        n->next = NULL;

        // 将新节点插入到链表头
        n->next = list;
        list = n;
    }

    // 遍历链表并打印节点数据
    node *ptr = list;
    while (ptr != NULL)
    {
        printf("%i ", ptr->number);
        ptr = ptr->next;
    }
    printf("\n");

    // 释放链表内存
    ptr = list;
    while (ptr != NULL)
    {
        node *temp = ptr;
        ptr = ptr->next;
        free(temp);
    }

    return 0;
}

list3.c

#include <stdio.h>
#include <stdlib.h>

typedef struct Node
{
    int number;
    struct Node *next;
} Node;

// 在链表末尾添加新节点
void appendNode(Node **head, int data)
{
    Node *newNode = malloc(sizeof(Node));
    if (newNode == NULL)
    {
        fprintf(stderr, "Memory allocation failed.\n");
        exit(EXIT_FAILURE);
    }

    newNode->number = data;
    newNode->next = NULL;

    if (*head == NULL)
    {
        // 如果链表为空,将新节点设置为头节点
        *head = newNode;
    }
    else
    {
        // 否则,找到链表的末尾,并将新节点连接到最后一个节点
        Node *current = *head;
        while (current->next != NULL)
        {
            current = current->next;
        }
        current->next = newNode;
    }
}

// 打印链表的数据
void printList(Node *head)
{
    Node *current = head;
    while (current != NULL)
    {
        printf("%d ", current->number);
        current = current->next;
    }
    printf("\n");
}

int main(void)
{
    Node *list = NULL; // 初始化链表为空

    // 在链表末尾添加两个节点
    appendNode(&list, 1);
    appendNode(&list, 2);
    appendNode(&list, 3);

    // 打印链表的数据
    printList(list);

    return 0;
}

trees.c

#include <stdio.h>
#include <stdlib.h>

// 定义树节点结构
typedef struct TreeNode
{
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
} TreeNode;

// 创建新节点
TreeNode *createNode(int data)
{
    TreeNode *newNode = (TreeNode *)malloc(sizeof(TreeNode));
    if (newNode != NULL)
    {
        newNode->data = data;
        newNode->left = NULL;
        newNode->right = NULL;
    }
    return newNode;
}

// 插入节点到二叉搜索树
TreeNode *insert(TreeNode *root, int data)
{
    if (root == NULL)
    {
        return createNode(data);
    }

    if (data < root->data)
    {
        root->left = insert(root->left, data);
    }
    else if (data > root->data)
    {
        root->right = insert(root->right, data);
    }

    return root;
}

// 中序遍历二叉树(升序排列)
void inorderTraversal(TreeNode *root)
{
    if (root != NULL)
    {
        inorderTraversal(root->left);
        printf("%d ", root->data);
        inorderTraversal(root->right);
    }
}

// 释放二叉树内存
void freeTree(TreeNode *root)
{
    if (root != NULL)
    {
        freeTree(root->left);
        freeTree(root->right);
        free(root);
    }
}

int main()
{
    // 创建根节点
    TreeNode *root = NULL;

    // 插入一些数据到二叉搜索树
    root = insert(root, 5);
    root = insert(root, 3);
    root = insert(root, 7);
    root = insert(root, 2);
    root = insert(root, 4);

    // 中序遍历并打印节点数据
    printf("Inorder Traversal: ");
    inorderTraversal(root);
    printf("\n");

    // 释放二叉树内存
    freeTree(root);

    return 0;
}

trie.c

wget https://cdn.cs50.net/2022/fall/labs/5/trie.zip

$ ls
Makefile  dog_names.txt  trie.c

./trie dog_names.txt
// 在 trie 中保存常见狗名
// https://www.dailypaws.com/dogs-puppies/dog-names/common-dog-names

#include <cs50.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE_OF_ALPHABET 26
#define MAXCHAR 20

// 这是一个用于实现 Trie(字典树)数据结构的结构体定义
/*
bool is_word;
这个成员表示当前节点是否代表一个完整的单词。
如果 is_word 为 true,则说明从根节点到当前节点的路径构成了一个单词
struct node *children[SIZE_OF_ALPHABET];
这个成员是一个数组,包含了所有可能的子节点
在英文字母的情况下,SIZE_OF_ALPHABET 的大小通常是 26,代表了英文字母的数量
每个数组元素都是一个指向 struct node 类型的指针,表示当前节点的子节点
这样,每个节点都能够连接到多个可能的下一个字符
这个结构体的设计用于构建 Trie 树,该树将存储一组字符串,每个字符串代表一个单词
在 Trie 树中,每个节点表示一个字符,而路径表示字符串
每个节点的 children 数组存储了当前字符后可能的下一个字符的节点.通过在树中移动,可以构建和检索存储的单词
在 is_word 标志的帮助下,我们可以知道某个节点是否代表一个完整的单词
*/

typedef struct node
{
    bool is_word;
    struct node *children[SIZE_OF_ALPHABET];
} node;


// 函数原型
bool check(char *word);
bool unload(void);
void unloader(node *current);

// trie 的根节点
node *root;

// 用于读取狗名的缓冲区
char name[MAXCHAR];

int main(int argc, char *argv[])
{
    // 检查命令行参数
    if (argc != 2)
    {
        printf("Usage: ./trie infile\n");
        return 1;
    }

    // 包含狗名的文件
    FILE *infile = fopen(argv[1], "r");
    if (!infile)
    {
        printf("Error opening file!\n");
        return 1;
    }

    // 分配 trie 的根节点
    root = malloc(sizeof(node));

    if (root == NULL)
    {
        return 1;
    }
    // 初始化根节点的成员
    //is_word 被设置为 false,因为当前还没有插入任何单词
    //children 数组被初始化为 NULL,表示根节点还没有与任何子节点相连接
    root->is_word = false;
    for (int i = 0; i < SIZE_OF_ALPHABET; i++)
    {
        root->children[i] = NULL;
    }

    // 将单词添加到 trie 中
    //通过 fscanf 逐行读取文件中的单词,然后将每个单词插入到 Trie 中
    /*
    对于每个单词,程序从根节点开始,逐个字符地遍历单词
    对于每个字符,程序计算出对应的索引 index(表示字母 'a' 到当前字符的相对位置)
    然后检查当前节点的 children 数组中是否存在对应索引的子节点
    如果不存在,就创建一个新的节点,然后移动到这个新节点
    这个过程一直持续到单词的末尾,最后将末尾节点的 is_word 设置为 true,表示这是一个完整的单词
    这样,通过这个过程,所有的单词都被成功地插入到了 Trie 树中
    */
    while (fscanf(infile, "%s", name) == 1)
    {
        node *cursor = root;

        for (int i = 0, n = strlen(name); i < n; i++)
        {
            int index = tolower(name[i]) - 'a';
            if (cursor->children[index] == NULL)
            {

                // 创建新的节点
                node *new = malloc(sizeof(node));
                new->is_word = false;
                for (int j = 0; j < SIZE_OF_ALPHABET; j++)
                {
                    new->children[j] = NULL;
                }
                cursor->children[index] = new;
            }

            // 移动到刚刚创建的节点
            cursor = cursor->children[index];
        }

        // 如果到达单词的末尾,将其标记为一个单词
        cursor->is_word = true;
    }

    if (check(get_string("检查单词:")))
    {
        printf("找到了!\n");
    }
    else
    {
        printf("未找到。\n");
    }

    if (!unload())
    {
        printf("释放内存时出现问题!\n");
        return 1;
    }

    fclose(infile);
}

// TODO: 完成 check 函数,如果找到返回 true,未找到返回 false
bool check(char *word)
{
    // 从 trie 的根节点开始
    node *cursor = root;

    // 遍历输入单词的每个字符
    for (int i = 0, n = strlen(word); i < n; i++)
    {
        // 计算字符对应的索引
        int index = tolower(word[i]) - 'a';

        // 如果当前节点的子节点为 NULL,说明单词不存在
        if (cursor->children[index] == NULL)
        {
            return false;
        }

        // 移动到下一个节点
        cursor = cursor->children[index];
    }

    // 到达单词的末尾,检查是否标记为一个单词
    return cursor->is_word;
}

// 释放 trie 占用的内存
bool unload(void)
{
    // 递归函数处理所有的释放
    unloader(root);

    return true;
}

void unloader(node *current)
{
    // 遍历所有的子节点,查看它们是否指向了某个地方,如果是则移动到那里
    for (int i = 0; i < SIZE_OF_ALPHABET; i++)
    {
        if (current->children[i] != NULL)
        {
            unloader(current->children[i]);
        }
    }

    // 在检查所有的子节点都指向空之后,可以释放节点,并返回到这个函数的前一次迭代。
    free(current);
}

inheritance.c

一个人的血型由两个等位基因(即基因的不同形式)决定。三种可能的等位基因是 A、B 和 O,每个人都有两个(可能相同,也可能不同)。每个孩子的父母会随机地将自己的两个血型等位基因之一传递给他们的孩子。因此,可能的血型组合有:OO、OA、OB、AO、AA、AB、BO、BA 和 BB。

例如,如果一个父亲的血型是AO,另一个父亲的血型是BB,那么孩子的可能血型将是AB和OB,具体取决于从每个父亲那里接收了哪个等位基因。同样,如果一个父亲的血型是AO,另一个父亲的血型是OB,那么孩子的可能血型将是AO、OB、AB 和 OO。

wget https://cdn.cs50.net/2022/fall/labs/5/inheritance.zip

$ ls
Makefile  inheritance.c

$ ./inheritance
// 模拟血型的遗传传承

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// 每个人有两位父母和两个等位基因
typedef struct person
{
    struct person *parents[2];
    char alleles[2];
} person;

const int GENERATIONS = 3;
const int INDENT_LENGTH = 4;

// 创建一个新的家庭,包括 `generations` 代人
person *create_family(int generations);
void print_family(person *p, int generation);
void free_family(person *p);
char random_allele();

int main(void)
{
    // 种子随机数生成器
    srand(time(0));

    // 创建一个有三代人的新家庭
    person *p = create_family(GENERATIONS);

    // 打印血型家谱
    print_family(p, 0);

    // 释放内存
    free_family(p);
}

// 创建一个具有 `generations` 代的新个体
person *create_family(int generations)
{
    // TODO: 为新个体分配内存
    person *new_person = malloc(sizeof(person));
    // 如果仍有待创建的代数
    if (generations > 1)
    {
        // 通过递归调用 create_family 创建当前个体的两位新父母
        person *parent0 = create_family(generations - 1);
        person *parent1 = create_family(generations - 1);

        // TODO: 为当前个体设置父母指针
        new_person->parents[0] = parent0;
        new_person->parents[1] = parent1;
        // TODO: 根据父母的等位基因,随机分配当前个体的等位基因
        new_person->alleles[0] = random_allele();
        new_person->alleles[1] = random_allele();
    }

    // 如果没有代数需要创建
    else
    {
        // TODO: 设置父母指针为 NULL
        new_person->parents[0] = NULL;
        new_person->parents[1] = NULL;

        // TODO: 随机分配等位基因
        new_person->alleles[0] = random_allele();
        new_person->alleles[1] = random_allele();
    }

    // TODO: 返回新创建的个体
    return new_person;
}

// 释放 `p` 及其所有祖先的内存。
void free_family(person *p)
{
    // 处理基本情况
    if (p == NULL)
    {
        return;
    }

    // 递归释放父母
    free_family(p->parents[0]);
    free_family(p->parents[1]);

    // 释放子代
    free(p);
}

// 打印每个家庭成员及其等位基因。
void print_family(person *p, int generation)
{
    // 处理基本情况
    if (p == NULL)
    {
        return;
    }

    // 打印缩进
    for (int i = 0; i < generation * INDENT_LENGTH; i++)
    {
        printf(" ");
    }

    // 打印个体
    if (generation == 0)
    {
        printf("子代(第 %i 代):血型 %c%c\n", generation, p->alleles[0], p->alleles[1]);
    }
    else if (generation == 1)
    {
        printf("父母(第 %i 代):血型 %c%c\n", generation, p->alleles[0], p->alleles[1]);
    }
    else
    {
        for (int i = 0; i < generation - 2; i++)
        {
            printf("曾");
        }
        printf("祖父母(第 %i 代):血型 %c%c\n", generation, p->alleles[0], p->alleles[1]);
    }

    // 打印当前代的父母
    print_family(p->parents[0], generation + 1);
    print_family(p->parents[1], generation + 1);
}

// 随机选择一个血型等位基因。
char random_allele()
{
    int r = rand() % 3;
    if (r == 0)
    {
        return 'A';
    }
    else if (r == 1)
    {
        return 'B';
    }
    else
    {
        return 'O';
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anxonz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值