843 / 学习讲义

本地示范 / 先理解,再读代码

把步骤翻译成 C 与 SQL

正文先用伪代码说明过程。这里的完整C程序与固定数据SQL可下载,编译与执行核验记录见源码QA。无需在线运行服务或先安装开发环境即可阅读。

algorithms.c

下载源码

/* C11. 本地教学程序:前提、过程与边界一起展示。
 * a 指向至少 n 个有效 int;n==0 时允许 a==NULL。
 * 线性扫描求和、半开区间二分、稳定插入排序、链表指针操作。
 */
#include <assert.h>
#include <stddef.h>
#include <stdio.h>

/* 返回第一个不小于 key 的位置;输入必须非降序。 */
static size_t lower_bound(const int *a, size_t n, int key) {
    size_t left = 0, right = n; /* 候选区间 [left,right) */
    while (left < right) {
        size_t mid = left + (right - left) / 2;
        if (a[mid] < key) left = mid + 1;
        else right = mid;
    }
    return left; /* 可等于 n,调用者不能直接访问 a[n] */
}

static void insertion_sort(int *a, size_t n) {
    for (size_t i = 1; i < n; ++i) {
        int value = a[i];
        size_t j = i;
        while (j > 0 && a[j - 1] > value) {
            a[j] = a[j - 1];
            --j;
        }
        a[j] = value;
    }
}

struct Node { int value; struct Node *next; };
/* p 与 s 必须指向不同且存活的节点,s 尚未在链中。 */
static void insert_after(struct Node *p, struct Node *s) {
    s->next = p->next; /* 先保住原来的后继 */
    p->next = s;
}
/* 移除并返回后继,不释放由调用者拥有的节点。 */
static struct Node *remove_after(struct Node *p) {
    struct Node *q = p->next;
    if (q != NULL) { p->next = q->next; q->next = NULL; }
    return q;
}

int main(void) {
    int a[] = {3, 1, 2, 2};
    insertion_sort(a, 4);
    assert(a[0] == 1 && a[1] == 2 && a[2] == 2 && a[3] == 3);
    assert(lower_bound(a, 4, 2) == 1);
    assert(lower_bound(a, 4, 4) == 4);
    assert(lower_bound(NULL, 0, 2) == 0);
    insertion_sort(NULL, 0);
    int single[] = {7}; insertion_sort(single, 1);
    assert(lower_bound(single, 1, 6) == 0);
    struct Node b = {7, NULL}, head = {4, &b}, s = {5, NULL};
    insert_after(&head, &s);
    assert(head.next == &s && s.next == &b);
    assert(remove_after(&head) == &s && head.next == &b);
    assert(remove_after(&b) == NULL);
    int values[] = {2, 0, 5}; int sum = 0;
    for (size_t i = 0; i < 3; ++i) sum += values[i];
    assert(sum == 7);
    printf("sorted: %d %d %d %d; sum: %d\n", a[0], a[1], a[2], a[3], sum);
    return 0;
}

library.sql

下载源码

-- 固定教学数据;在空SQLite数据库中执行。
PRAGMA foreign_keys = ON;
CREATE TABLE Reader(id INTEGER PRIMARY KEY, name TEXT NOT NULL);
CREATE TABLE Loan(id INTEGER PRIMARY KEY,
  reader_id INTEGER NOT NULL REFERENCES Reader(id),
  returned INTEGER NOT NULL CHECK(returned IN (0,1)));
INSERT INTO Reader VALUES(1,'小林'),(2,'小周'),(3,'小陈');
INSERT INTO Loan VALUES(10,1,0),(11,1,0),(12,2,1),(13,2,0);
-- 结果 (1,2):先筛未还行,再分组筛选。
SELECT reader_id, COUNT(*) AS n FROM Loan WHERE returned=0
GROUP BY reader_id HAVING COUNT(*)>=2;
-- 结果 (1,2),(2,1),(3,0):条件放ON才能保留无借阅者。
SELECT r.id, COUNT(l.id) AS n FROM Reader r
LEFT JOIN Loan l ON l.reader_id=r.id AND l.returned=0
GROUP BY r.id ORDER BY r.id;
-- 结果 (1,2),(2,2),(3,0):全部借阅次数,COUNT不计补出的NULL。
SELECT r.id, COUNT(l.id) AS n FROM Reader r
LEFT JOIN Loan l ON l.reader_id=r.id GROUP BY r.id ORDER BY r.id;

C语言提示:#include引入声明;int是整数;size_t是长度/下标类型;*表示指针;&取得地址;->访问指针所指结构体字段;NULL表示空指针;assert检查预期条件。函数输入边界在注释中声明,测试空输入、重复值、失败与正常路径。

一点一点,积累下来。

今日学习

00:00:00

累计学习

00:00:00

学习天数

0 天

点击“开始计时”后累计时间。同一科目内切换章节或刷新会接续;切换科目不会自动启动另一科。仅当前获得焦点的可见页面累计,离开、关闭或休眠期间不补计。两科的时间与成就分别保存。

记录保存在当前浏览器,关闭后仍保留;清除网站数据会删除记录。每天累计满 1 分钟记为一个学习日,不要求连续打卡。

学习成就

已达成 0 / 10

成就记录投入与阅读进展,不代表掌握程度。阅读类成就随已读标记更新,撤销标记后会重新计算。