文章目录
- C 环境准备
- 官方文档
- 环境准备
- 在线运行
- VSCode
 
- 环境报错解决
 
- 绪论
- 线性表
- 顺序表
- 链表
- 错题
 
- 栈、队列和数组
- 栈
- 队列
- 栈的应用之中缀转后缀
- 特殊矩阵用数组压缩存储
- 错题
 
- 串
- 模式匹配之暴力和KMP
 
- 树与二叉树
- 二叉树
- 树和森林
- 哈夫曼树和哈夫曼编码
- 并查集
- 错题
 
- 图
- 图的基本概念
- 图的存储及基本操作
- 图的遍历
- 图的应用
- 错题
 
- 查找
- 顺序查找
- 二分查找
- 分块查找
- 树型查找
- B树和B+树
- 散列表
- 错题
 
- 排序
- 错题
 
- C++相关零碎知识点
 
- 参考资料
C 环境准备
官方文档
-  ISO/IEC 
  
-  The GNU C Reference Manual 
-  cppreference.com 
环境准备
在线运行
菜鸟教程在线运行
VSCode
安装两个插件,C/C++(必须安装) 和 Code Runner,编写C示例程序,运行即可

也可这么编译、运行(初次运行,选择 mac 自带的 clang 编译器)

查看编译器支持的 C/C++ 版本(VSCode 可用 command + 逗号,输入 C_Cpp.default.cp,C_Cpp.default.cp)
printf("%ld\n",__STDC_VERSION__);
printf("%ld\n",__cplusplus);
环境报错解决
报错:Undefined symbols for architecture arm64
 解决:删除非代码文件的其他文件/目录,选用 C/C++: clang++ build active file 重新编译
绪论
线性表
顺序表
#include<cstdio>
#include<cstdlib>
// ----- 2.2.1 顺序表的定义 ----- 
// 为方便写代码,不爆红,不妨如此定义一下
typedef int ElementType;
// 静态分配
#define MaxSize 50
typedef struct {
    ElementType data[MaxSize];
    int length;
} SqList;
// 动态分配
#define InitSize 100
typedef struct {
    ElementType *data;
    int length;
} SqList;
// 动态分配实现
// L.data = (ElementType *)malloc(sizeof(ElementType) * InitSize);
// ----- 2.2.2 顺序表基本操作 ----- 
// 插入,第 i 位置(线性表位序从 1 开始)插入 e
bool ListInsert(SqList &L, int i, ElementType e) {
    if(L.length >= MaxSize) {
        return false;
    }
    // 插入位置合法范围是 [1, L.length + 1]
    if(i < 1 || i > L.length + 1) {
        return false;
    }
    for(int j = L.length; j >= i; j--) {
        L.data[j] = L.data[j - 1];
    }
    L.data[i - 1] = e;
    L.length++;
    return true;
}
// 删除,删除第 i 个元素
bool ListDelete(SqList &L, int i, ElementType &e) {
    if(i < 1 || i > L.length) {
        return false;
    }
    e = L.data[i - 1];
    for(int j = i; j < L.length; j++) {
        L.data[j - 1] = L.data[j];
    }
    L.length--;
    return true;
}
 
// 查找
int LocateElem(SqList &L, ElementType e) {
    for(int i = 0; i < L.length; i++) {
        if(L.data[i] == e) {
            return i + 1;
        }
    }
    return 0;
}
// ----- 历年真题 ----- 
/*
    2010,一维数组循环左移
    
    例子,abcdefgh 循环左移 3 位,得到 defghabc
    算法,1) abc 逆置得到 cbadefgh;2) defgh 再逆置得到 cbahgfed;3) 整体逆置得到 defghabc
    tip,统考题目写出可行解法就可得到大部分分数,不必耗费心思写出最优解!
*/
void Swap(int a[], int i, int j) {
    int t = a[i];
    a[i] = a[j];
    a[j] = t;
}
void Reverse(int R[], int from, int to) {
    int len = to - from + 1;
    for(int i = 0; i < len / 2; i++) {
        Swap(R, i + from, to - i);    // len - 1 - i + from = to - i
    }
}
void Converse(int R[], int n, int p) {
    Reverse(R, 0, p - 1);
    Reverse(R, p, n - 1);
    Reverse(R, 0, n - 1);
}
链表
#include<cstdio>
#include<cstdlib>
// 为方便写代码,不爆红,不妨如此定义一下
typedef int ElementType;
// ----- 2.3.1 单链表的定义 -----
typedef struct LNode{
    ElementType data;
    struct LNode *next;
} LNode, *LinkList;
// (可选)头结点:第一个结点之前附加一个结点。
// ----- 2.3.2 单链表基本操作 -----
// 创建单链表,头插法
LinkList ListCreateHeadInsert(LinkList L) {
    int x;
    scanf("%d", &x);
    while(x != 9999) {  // 9999 表示停止输入
        LNode *node = (LNode *)malloc(sizeof(LNode));
        node->data = x;
        node->next = L->next;
        L->next = node;
        scanf("%d", &x);
    }
    return L;
}
// 创建单链表,尾插法
LinkList ListCreateTailInsert(LinkList L) {
    int x;
    LNode * tail = L;
    scanf("%d", &x);
    while(x != 9999) {  // 9999 表示停止输入
        LNode *node = (LNode *)malloc(sizeof(LNode));
        node->data = x;
        node->next = NULL;
        tail->next = node;
        tail = node;
        scanf("%d", &x);
    }
    return L;
}
// 按序号查找结点
LNode *ListGetElem(LinkList L, int i) {
    if(i < 0) {
        return NULL;
    }
    if(i == 0) {
        return L;
    }
    LNode *p = L->next;
    int j = 1;
    while(j < i && p) { // 若 i 大于表长,最终会返回 NULL
        p = p->next;
        j++;
    }
    return p;
}
// 按值查找结点
LNode *ListGetElem(LinkList L, ElementType e) {
    LNode *p = L->next;
    while(p && p->data != e) {  // 找不到 e 就返回 NULL
        p = p->next;
    }
    return p;
}
// 插入,在第 i 个位置
bool ListInsert(LinkList L, int i, LNode *node) {
    LNode *pre = ListGetElem(L, i - 1);
    if(pre == NULL) {
        return false;
    }
    node->next = pre->next;
    pre->next = node;
    return true;
}
// 插入,在指定结点后插。如果前插,需要先找前驱结点,而比较 tricky 的办法是后插并交换结点值!
void ListInsert(LinkList L, LNode *node, LNode *specify) {
    node->next = specify->next;
    specify->next = node;
}
// 删除,第 i 个位置。如果删除指定结点,需要先找前驱结点,而比较 tricky 的办法是把后继结点的值赋予待删结点并删除后继结点,然而后继结点若是 NULL,此办法也不可行!
bool ListDelete(LinkList L, int i) {
    LNode *pre = ListGetElem(L, i - 1);
    if(pre == NULL) {
        return false;
    }
    LNode *deleteNode = pre->next;
    pre->next = deleteNode->next;
    free(deleteNode);
    return true;
}
// 表长
int ListLength(LinkList L) {
    int length = 0;
    LNode *p = L;
    while(p->next != NULL) {
        length++;
        p = p->next;
    }
    return length;
}
// ----- 2.3.3 双链表 -----
// 双链表结点类型描述
typedef struct DNode {
    ElementType data;
    struct DNode *pre, *next;
} DNode, *DLinkList;
// 双链表插入
void DListInsert(DNode *node, DNode *specify) {
    node->next = specify->next;
    specify->next->pre = node;
    node->pre = specify;
    specify->next = node;
}
// 双链表删除
void DListDelete(DNode *node) {
    node->pre->next = node->next;
    node->next->pre = node->pre;
    free(node);
}
// ----- 2.3.4 循环链表 -----
// 循环单链表,尾结点的 next 指向 L,注意删除、插入、判空相较于单链表的特殊处理
// 循环双链表,尾结点的 next 指向 L,头结点的 pre 指向 尾结点
// ----- 2.3.5 静态链表 -----
// 静态链表利用数组实现链式存储结构
#define MaxSize 100
typedef struct {
    ElementType data;
    int next;   // 指向数组索引,以 -1 作为终止标志
} SLinkList[MaxSize];
// ----- 2.3.6 顺序表和链表比较 -----
// 顺序表删除平均移动半个表长数据,而单链表虽说也要查找前驱,但只是查找(比较操作),不需移动操作,效率更优
错题

答案:A

答案:D(注意是第 i 个元素,双向链表也得从头开始走到第 i 个节点)

答案:C

答案:D
栈、队列和数组
栈
// 为方便写代码,不爆红,不妨如此定义一下
typedef int ElementType;
// ----- 3.1.2 栈的顺序存储结构 -----
#define MaxSize 100
typedef struct {
    ElementType data[MaxSize];
    int top;
} SqStack;
// 初始化
void Init(SqStack &s) {
    s.top = -1;
}
// 判栈空
bool Empty(SqStack &s) {
    return s.top == -1;
} 
// 判栈满
bool Full(SqStack &s) {
    return s.top + 1 == MaxSize;
}
// 栈长度
int Length(SqStack &s) {
    return s.top + 1;
}
// 进栈
bool Push(SqStack &s, ElementType e) {
    if(Full(s)) {
        return false;
    }
    s.data[++s.top] = e;
    return true;
}
// 出栈
bool Pop(SqStack &s, ElementType &e) {
    if(Empty(s)) {
        return false;
    }
    e = s.data[s.top--];
    return true;
}
// 读取栈顶元素
bool Top(SqStack &s, ElementType &e) {
    if(Empty(s)) {
        return false;
    }
    e = s.data[s.top];
    return true;
}
// 共享栈,两个顺序栈共用一个一维数组,两栈底分别在数组两端,两栈顶相邻时,共享栈满
// ----- 3.1.3 栈的链式存储结构 -----
typedef struct LinkNode {
    ElementType data;
    struct LinkNode *next;
} *LinkStack;
队列
#include<cstdlib>
// 为方便写代码,不爆红,不妨如此定义一下
typedef int ElementType;
// ----- 3.2.2 队列的顺序存储结构 -----
#define MaxSize 100
// 队空:front = tail = 0;入队:data[tail++];出队:data[front++];
typedef struct {
    ElementType data[MaxSize];
    int front, tail;
} SqQueue;
// 循环队列
void InitCircularQ(SqQueue &Q) {
    Q.front = Q.tail = 0;
}
// 循环队列判空
bool CircularQEmpty(SqQueue &Q) {
    return Q.front == Q.tail;
}
// 循环队列判满,牺牲一个单元判队满
bool CircularQFull(SqQueue &Q) {
    return (Q.tail + 1) % MaxSize == Q.front;
}
// 循环队列入队
bool CircularQIn(SqQueue &Q, ElementType e) {
    if(CircularQFull(Q)) {
        return false;
    }
    Q.data[Q.tail] = e;
    Q.tail = (Q.tail + 1) % MaxSize;
    return true;
}
// 循环队列出队
bool CircularQOut(SqQueue &Q, ElementType &e) {
    if(CircularQEmpty(Q)) {
        return false;
    }
    e = Q.data[Q.front];
    Q.front = (Q.front + 1) % MaxSize;
    return true;
}
// ----- 3.2.3 队列的链式存储结构 -----
typedef struct LinkNode{
    ElementType data;
    struct LinkNode *next;
} LinkNode;
typedef struct {
    LinkNode *front, *tail;
} LinkQueue;
// 初始化,使用含头结点的链表方式实现
void Init(LinkQueue &Q) {
    Q.front = Q.tail = (LinkNode *)malloc(sizeof(LinkNode));
    Q.front = NULL;
}
// 判空
bool Empty(LinkQueue &Q) {
    return Q.front == Q.tail;
}
// 入队
void Push(LinkQueue &Q, ElementType e) {
    LinkNode *node = (LinkNode *)malloc(sizeof(LinkNode));
    node->next = NULL;
    node->data = e;
    Q.tail->next = node;
    Q.tail = node;
}
// 出队
bool Pop(LinkQueue &Q, ElementType &e) {
    if(Empty(Q)) {
        return false;
    }
    LinkNode *p = Q.front->next;
    e = p->data;
    Q.front->next = p->next;
    if(Q.tail == p) {
        Q.tail = Q.front;
    }
    free(p);
    return true;
}
栈的应用之中缀转后缀


特殊矩阵用数组压缩存储



答案:A(注意了解对称矩阵、三角矩阵和三对角矩阵的概念,以及按列或行优先存储成压缩数组的含义)
错题

答案:B(都是线性结构)

答案:C(头插)

答案:D

答案:C(后面的 3 都没出来呢,你这个 4 咋可能先出来呢!)

答案:D(数组长度是 n + 1)

答案:D

答案:C(注意双端队列进出顺序,比如全左进 1 2 3 4,全左出 4 3 2 1)

答案:B(题意是插入 A[0] 后,front = rear = 0)
串
模式匹配之暴力和KMP
#include<cstdio>
#include<string>
#include<iostream>
using namespace std;
int indexOf(string s1, string s2);
int kmp(string s1, string s2);
int main() {
    string s1 = "abccabc";
    string s2 = "cab";
    
    int loc = s1.find(s2);
    printf("%d\n", loc);
    loc = indexOf(s1, s2);
    printf("%d\n", loc);
    loc = kmp(s1, s2);
    printf("%d\n", loc);
   
}
int indexOf(string s1, string s2) {
    for(int i = 0; i < s1.length(); i++) {
        int j = 0, k = i;
        while (j < s2.length() && k < s1.length() && s1[k] == s2[j]) {
            j++;
            k++;
        }
        if(j == s2.length()) {
            return i;
        }
        
    }
    return -1;
}
/**
 * next 数组求解
 * 
 * 例子,s = "ABABC"
 * next[0] = 0,前 1 个字符 "A" 最长相同前后缀长度是 0
 * next[1] = 0,前 2 个字符 "AB" 最长相同前后缀长度是 0
 * next[2] = 1,前 3 个字符 "ABA" 最长相同前后缀是 "A",长度是 1
 * next[3] = 2,前 4 个字符 "ABAB" 最长相同前后缀是 "AB",长度是 2
 * next[4] = 0,前 5 个字符 "ABABC" 最长形态前后缀长度是 0
 * 
*/
int * buildNext(string s) {
    int *next = (int *)malloc(sizeof(int) * s.length());
    next[0] = 0;
    int commonPrefixSuffixLen = 0;
    int i = 1;
    while(i < s.length()) {
        if(s[commonPrefixSuffixLen] == s[i]) {
            commonPrefixSuffixLen++;
            next[i] = commonPrefixSuffixLen;
            i++;
        } else {
            if(commonPrefixSuffixLen == 0) { // 当前字符跟首字符不同,next[i] 必定是 0
                next[i] = 0;
                i++;
            } else {    // 迭代
                commonPrefixSuffixLen = next[commonPrefixSuffixLen - 1];
            }
        }
    }
    return next;
}
/**
 * kmp 算法,主串 s1,子串 s2
 * 
 * 例子,
 * 主串:ABABABCAA
 * 子串:ABABC
 * 
 * 首次不匹配的是主串的 A 和子串的 C,此时后移变为:
 * 主串:ABABABCAA
 * 子串:  ABABC
 * 
*/
int kmp(string s1, string s2) {
    int *next = buildNext(s2);
    int i = 0, j = 0;
    while(i < s1.length()) {
        if(s1[i] == s2[j]) {
            i++;
            j++;
        } else if(j > 0) {
            j = next[j - 1];
        } else {    // 子串第一个字符就失配
            i++;
        }
        if(j == s2.length()) {
            return i - j;
        }
    }
    return -1;
}
树与二叉树
二叉树
#include<cstdio>
#include<stack>
#include<queue>
using namespace std;
// 为方便写代码,不爆红,不妨如此定义一下
typedef int ElementType;
// ----- 5.2.1 二叉树的定义及其主要特性 -----
/**
 * 二叉树的性质
 * 1. 非空二叉树的叶结点数等于度为 2 的结点数加 1。
 *    推导依据,树的分叉数加 1 等于结点数,n = n0 + n1 + n2 = n0 * 0 + n1 * 1 + n2 * 2 + 1,得到 n0 = n2 + 1。
 * 
 * 2. 非空二叉树有 n + 1 个空指针。
 *    推导依据,每个度为 0 和 1 的结点分别有 2 个和 1 个空指针,则空指针总数是 n0 * 2 + n1 * 1,又 n0 = n2 + 1,可求得空指针总数是 n + 1。
*/
// ----- 5.2.2 二叉树的存储结构 -----
// 顺序存储,适合满二叉树和完全二叉树,否则要用 0 填充很多不存在的空结点
// 链式存储。重要结论:含有 n 个结点的二叉链表中,有 n + 1 个空链域
typedef struct BiTNode {
    ElementType data;
    struct BiTNode *lChild, *rChild;
} BiTNode, *BiTree;
// ----- 5.3.1 二叉树的遍历 -----
void Visit(BiTNode *node) {}
// 先序遍历
void PreOrder(BiTree T) {
    if(T != NULL) {
        Visit(T);
        PreOrder(T->lChild);
        PreOrder(T->rChild);
    }
}
// 中序遍历
void InOrder(BiTree T) {
    if(T != NULL) {
        InOrder(T->lChild);
        Visit(T);
        InOrder(T->rChild);
    }
}
// 后序遍历
void PostOrder(BiTree T) {
    if(T != NULL) {
        PostOrder(T->lChild);
        PostOrder(T->rChild);
        Visit(T);
    }
}
// 先序遍历,非递归
void PreOrderStack(BiTree T) {
    BiTNode *p = T;
    stack<BiTree> s;
    while(p || !s.empty()) {
        if(p) {
            Visit(p);
            s.push(p);
            p = p->lChild;
        } else {
            p = s.top();
            s.pop();
            p = p->rChild;
        }
    }
}
// 中序遍历,非递归
void InOrderStack(BiTree T) {
    BiTNode *p = T;
    stack<BiTree> s;
    while(p || !s.empty()) {
        if(p) {
            s.push(p);
            p = p->lChild;
        } else {
            p = s.top();
            s.pop();
            Visit(p);
            p = p->rChild;
        }
    }
}
// 后序遍历,非递归,按根右左顺序入栈,最后总出栈时就是左右根的顺序,即后序遍历
void PostOrderStack(BiTree T) {
    stack<BiTree> s1;
    stack<BiTree> s2;
    BiTNode *p = T;
    s1.push(p);
    while(!s1.empty()) {
        p = s1.top();
        s1.pop();
        s2.push(p);
        if(p->lChild != NULL) {
            s1.push(p->lChild);
        }
        if(p->rChild != NULL) {
            s1.push(p->rChild);
        }
    }
    while (!s2.empty()) {
        p = s2.top();
        s2.pop();
        Visit(p);
    }
}
// 层次遍历
void LevelOrder(BiTree T) {
    queue<BiTree> q;
    BiTNode *p = T;
    q.push(p);
    while (!q.empty()) {
        p = q.front();
        q.pop();
        Visit(p);
        if(p->lChild != NULL) {
            q.push(p->lChild);
        }
        if(p->rChild != NULL) {
            q.push(p->rChild);
        }
    }
}
// ----- 5.3.2 线索二叉树 -----
// 线索二叉树方便得到二叉树结点的前驱和后继
typedef struct ClueBiTNode {
    ElementType data;
    struct ClueBiTNode *lChild, *rChild;
    // lTag = 0,lChild 指向左孩子;lChild = 1,lChild 指向前驱
    // rTag = 0,rChild 指向右孩子;rChild = 1,rChild 指向后继
    int lTag, rTag;
} ClueBiTNode, *ClueBiTree;
// 中序遍历对二叉树线索化
void ClueBiTreeIn(ClueBiTree p, ClueBiTNode *pre) {
    if(p != NULL) {
        ClueBiTreeIn(p->lChild, pre);
        if(p->lChild == NULL) {
            p->lChild = pre;
            p->lTag = 1;
        }
        if(pre != NULL && pre->rChild == NULL) {
            pre->rChild = p;
            pre->rTag = 1;
        }
        pre = p;
        ClueBiTreeIn(p->rChild, pre);
    }    
}
// 中序遍历建立线索二叉树
void CreateClueBiTreeIn(ClueBiTree T) {
    ClueBiTNode *pre = NULL;
    if(T != NULL) {
        ClueBiTreeIn(T, pre);
        pre->rChild = NULL; // 最后一个结点的后继置为 NULL
        pre->rTag = 1;
    }
}
// 中序线索二叉树中序序列以 T 为树根的第一个结点
ClueBiTNode * ClueBiTFirstNodeIn(ClueBiTree T) {
    ClueBiTNode *p = T;
    while(p->lTag == 0) {
        p = p->lChild;
    }
    return p;
}
// 中序线索二叉树中序序列结点 p 的后继
ClueBiTNode * ClueBiTNextNodeIn(ClueBiTNode *p) {
    if(p->rTag == 1) {
        return p->rChild;
    }
    return ClueBiTFirstNodeIn(p->rChild);
}
// 中序线索二叉树的中序遍历
void ClueBiIn(ClueBiTree T) {
    for(ClueBiTNode *p = ClueBiTFirstNodeIn(T); p != NULL; p = ClueBiTNextNodeIn(p)) {
        // 操作当前遍历到的结点 p
    }
}
线索二叉树
 

树和森林
树转成二叉树
 
 5.16 树的先根遍历:ABEFCDG,后根遍历(对应二叉树的中序遍历):EFBCGDA
森林和二叉树互转

 5.17 森林的先序遍历:ABCDEFGHI,中序遍历:BCDAFEHIG。要是不明白的话,就把他转成二叉树再遍历。
哈夫曼树和哈夫曼编码
哈夫曼树
 
 
 
哈夫曼编码

 
并查集

 
错题

 

 

 

 
图
图的基本概念




 
 
 
 

图的存储及基本操作
邻接矩阵
 
 
邻接表

十字链表

 
邻接多重表

图的遍历
#include<cstdlib>
#include<queue>
using namespace std;
#define MaxVertexNum 100
typedef char VertexType;
typedef int EdgeType;
typedef struct Graph {
    VertexType vertex[MaxVertexNum];
    EdgeType edge[MaxVertexNum][MaxVertexNum];
    int vertexNum, edgeNum;
} Graph;
void bfs(Graph G, int v, bool vis[]) {
    queue<int>que;
    vis[v] = true;
    que.push(v);
    while (!que.empty()) {
        int vertexIdx = que.front();
        // process code
        que.pop();
        for (int i = 0; i < G.vertexNum; i++) {
            if (!vis[i] && G.edge[vertexIdx][i] == 1) {
                    vis[i] = 1;
                    que.push(i);
            }
        }
    }
}
// 广度优先遍历。空间复杂度 O(|V|);时间复杂度,邻接表方式是 O(|V| + |E|),邻接矩阵方式是 O(|V|^2)
void bfs(Graph G) {
    bool *vis = (bool *)malloc(sizeof(int) * MaxVertexNum);
    for(int i = 0; i < MaxVertexNum; i++) {
        vis[i] = false;
    }
    for(int i = 0; i < G.vertexNum; i++) {
        if(!vis[i]) {
            bfs(G, i, vis);  // 每一个连通分量调用一次
        }
    }
}
void dfs(Graph G, int v, bool vis[]) {
    vis[v] = true;
    // process code
    for(int i = 0; i < G.vertexNum; i++) {
        if(!vis[i] && G.edge[v][i] == 1) {
            dfs(G, i, vis);
        }
    }
}
// 深度优先遍历。空间复杂度 O(|V|);时间复杂度,邻接表方式是 O(|V| + |E|),邻接矩阵方式是 O(|V|^2)
void dfs(Graph G) {
    bool *vis = (bool *)malloc(sizeof(int) * MaxVertexNum);
    for(int i = 0; i < MaxVertexNum; i++) {
        vis[i] = false;
    }
    for(int i = 0; i < G.vertexNum; i++) {
        if(!vis[i]) {
            dfs(G, i, vis);  // 每一个连通分量调用一次
        }
    }
}
图的应用
最小生成树
参考文章
所谓最小生成树,就是在一个具有N个顶点的带权连通图G中,如果存在某个子图G’,其包含了图G中的所有顶点和一部分边,且不形成回路,并且子图G’的各边权值之和最小,则称G’为图G的最小生成树。
由定义我们可得知最小生成树的三个性质:
•最小生成树不能有回路
•最小生成树可能是一个,也可能是多个(权值相同的边)
•最小生成树边的个数等于顶点的个数减一
Prim 算法,从任一结点出发,每次找到当前已选结点们最近那个结点,加入到已选结点中,直到全部结点都已选出。
 
Kruscal 算法,每次选最小权的边及其结点(该边的两个结点不能都已选中),直到选出所有结点。
 
最短路径
参考文章
-  Dijkstra 算法,带权有向图单源最短路径,时间复杂度 O(|V|^2) 
 每轮选最小距离的结点继续扩展。
  
-  Floyd 算法,带权有向图任意一对结点最短路径,时间复杂度 O(|V|^3) 
  
 每轮分别把 V0、V1 和 V2 作为中间结点,更新所有对结点之间的最小路径值!
    
有向无环图描述表达式

拓扑排序

关键路径
 
 
 

 
 
 
 

 

错题

 解答:先用 6 个顶点做一个完全图,需要 6 * (6 - 1) / 2 = 15 条边,再用 1 条边把第 7 个顶点连上,至少需要 16 条边,保证任何情况下 G 是连通的。

 解答:画图,要遍历完所有结点(所有连通分量),共 5 种可能情况。

 
 解答:计算 ve 的过程其实就能得到关键活动了,关键路径是 bdcg、bdeh 和 bfh,要想缩短工期,答案选项中得覆盖到所有关键路径,选 C。

 解答:特殊例子,二叉树后序遍历,就是执行输出语句后立刻退出递归的,正好就是逆拓扑排序。
查找
顺序查找
二分查找
分块查找


树型查找
二叉排序树

平衡二叉树(AVL)


 
 
 
 
 

 
 
红黑树

 
 
 
 
 
B树和B+树

 
 
 
 
散列表

 
 

错题

 

 

 
 解答:折半查找判定树是搜索二叉树,不妨先把各结点填上值,再判断计算 mid 时是否都统一向上取整或向下取整了,如果不统一就不对!以选项 C 为例,4 号结点是小于 5 大于 2 的情况下算出来的,即 (2 + 5) / 2 = 4,向上取整,而 6 号 结点是大于 5 小于 8 的情况下算出来的,即 (5 + 8) / 2= 6,向下取整,这说明 mid 计算取整方向没有统一,错误。同样可以验证得到 B 选项和 D 选项都是错误的。
 

 答案:A

 



 

 
排序

 
#include<cmath>
// 为方便写代码,不爆红,不妨如此定义一下
typedef int ElementType;
// ----- 8.2.1 直接插入排序 -----
void swap(ElementType a[], int x, int y) {
    int t = a[x];
    a[x] = a[y];
    a[y] = t;
}
// 稳定,适用顺序表和链表
void insertSort(ElementType a[], int n) {
    if(n < 2) {
        return;
    }
    for(int i = 1; i < n; i++) {
        for(int j = i - 1; j >= 0 && a[j + 1] < a[j]; j--) {
            swap(a, j, j + 1);
        }
    }
}
// ----- 8.2.2 折半插入排序 -----
// 寻找小于等于 v 的最右元素的索引,若无,返回 -1
int binarySearch(ElementType a[], ElementType v, int st, int en) {
    int l = st, r = en;
    int idx = -1;
    while(l <= r) {
        int mid = l + (r - l) / 2;
        if(a[mid] <= v) {
            idx = mid;
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    return idx;
}
// 折半插入排序,待插入位置通过二分查找得到,再插入。稳定,适用顺序表
void insertSortBinary(ElementType a[], int n) {
    if(n < 2) {
        return;
    }
    for(int i = 1; i < n; i++) {
        int idx = binarySearch(a, a[i], 0, i - 1); // 待插入位置是 idx + 1
        ElementType data = a[i];
        for(int j = i - 1; j > idx; j--) {
            swap(a, j + 1, j);
        }
        a[idx + 1] = data;
    }
}
// ----- 8.2.3 希尔排序 -----
// 外层有个步长遍历,内层是直接插入排序。不稳定,适用顺序表。
void shellSortBinary(ElementType a[], int n) {
    if(n < 2) {
        return;
    }
    for(int step = n / 2; step >= 1; step /= 2) {
        for(int i = step; i < n; i += step) {
            for(int j = i - step; j >= 0 && a[j + step] < a[j]; j -= step) {
                swap(a, j, j + step);
            }
        }
    }
}
// ----- 8.3.1 冒泡排序 -----
void bubbleSort(ElementType a[], int n) {
    if(n < 2) {
        return;
    }
    bool flag = false;
    for(int i = n - 1; i > 0; i--) {
        flag = false;
        for(int j = 0; j < i; j++) {
            if(a[j] < a[j + 1]) {
                swap(a, j, j + 1);
                flag = true;
            }
        }
        if(!flag) {
            break;
        }
    }
}
// ----- 8.3.2 快速排序 -----
void quickSort(ElementType a[], int n) {
    if(n < 2) {
        return;
    }
    quickSort(a, 0, n - 1);
}
void quickSort(ElementType a[], int l, int r) {
    if(l < r) {
        int *pos = partition(a, l, r);
        quickSort(a, l, pos[0] - 1);
        quickSort(a, pos[1] + 1, r);
    }
}
int * partition(ElementType a[], int l, int r) {
    int small = l - 1, big = r + 1;
    int i = l;
    int chooseIdx = rand()*(r - l + 1) + l;
    ElementType pivot = a[chooseIdx];
    while(i < big) {
        if(a[i] > pivot) {
            swap(a, i, --big);
        } else if(a[i] < pivot) {
            swap(a, i++, ++small);
        } else {
            i++;
        }
    }
    int *pos = new ElementType[2];
    pos[0] = small + 1;
    pos[1] = big - 1;
    return pos;
}
// ----- 8.4.1 选择排序 -----
void selectSort(ElementType a[], int n) {
    if(n < 2) {
        return;
    }
    for(int i = 0; i < n; i++) {
        int min = i;
        for(int j = i + 1; j < n; j++) {
            if(a[j] < a[min]) {
                min = j;
            }
        }
        swap(a, i, min);
    }
}
// ----- 8.4.2 堆排序 -----
void heapInsert(ElementType a[], int i) {
    while(a[i] < a[(i - 1) / 2]) {
        swap(a, i, (i - 1) / 2);
        i = (i - 1) / 2;
    }
}
void heapify(ElementType a[], int len) {
    int p = 0;
    int l = 2 * p + 1;
    int r = l + 1;
    int maxIdx = l;
    while(l < len) {
        if(r < len && a[maxIdx] < a[r]) {
            maxIdx = r;
        }
        if(a[maxIdx] >= a[p]) {
            return;
        }
        swap(a, maxIdx, p);
        p = maxIdx;
        l = 2 * p + 1;
        maxIdx = l;
        r = l + 1;
    }
}
void heapSort(ElementType a[], int n) {
    if(n < 2) return;
    for(int i = 0; i < n; i++) {
        heapInsert(a, i);
    }
    for(int i = n - 1; i >= 0; i--) {
        swap(a, 0, i);
        heapify(a, i);
    }
}
// ----- 8.5.1 归并排序 -----
void merge(ElementType a[], int mid, int st, int en) {
    int n = en - st + 1;
    ElementType *help = (ElementType *)malloc(n * sizeof(ElementType));
    int l = st, r = mid + 1;
    int cnt = 0;
    while(l <= mid && r <= en) {
        if(a[l] <= a[mid]) {
            help[cnt++] = a[l++];
        } else {
            help[cnt++] = a[r++];
        }
    }
    while(l <= mid) {
        help[cnt++] = a[l++];
    }
    while(r <= en) {
        help[cnt++] = a[r++];
    }
    for(int k = 0; k < cnt; k++) {
        a[k + l] = help[k];
    }
}
void mergeSort(ElementType a[], int st, int en) {
    if(st >= en) return;
    int mid = st + (en - st) / 2;
    mergeSort(a, st, mid);
    mergeSort(a, mid + 1, en);
    merge(a, mid, st, en);
}
void mergeSort(ElementType a[], int n) {
    if(n < 2) return;
    mergeSort(a, 0, n - 1);
}
基数排序
 
 
各种内部排序对比
 
 外部排序
 
 
 
 
 
 

 
错题

 

 

 

 

 解答:显然10TB的数据无法一次存在内存中进行内部排序,只能放在外存中, 排序时将部分数据送入内存进行,显然要用外部排序,而选项中只有归并排序是外部排序。

 
C++相关零碎知识点
#include<cstdio>
#include<map>
#include<stack>
#include<queue>
#include<set>
#include<cmath>
#include<iostream>
using namespace std;
void arrayExample();
void mapExample();
void setExample();
void stackExample();
void queueExample();
int main() {
    printf("Hello, World!\n");
    // printf("%ld\n",__cplusplus);
    // arrayExample();
    // mapExample();
    // setExample();
    // stackExample();
    // queueExample();
}
// ----- array ----- 
void arrayExample() {
    int *a = (int *)malloc(sizeof(int) * 10);
    for(int i = 0; i < 10; i++) {
        *(a + i) = i;
    }
    for(int i = 0; i < 10; i++) {
        printf("%d ", *(a + i));
    }
}
// ----- map ----- 
void mapExample() {
    map<int, int> m;
    m[1] = 11;
    m[2] = 22;
    m[3] = 33;
    
    map<int, int> :: iterator it;
    for(it = m.begin(); it != m.end(); it++) {
        int k = (*it).first;
        int v = (*it).second;
        printf("k = %d, v = %d\n", k, v);
    }
    if(m.find(1) != m.end()) {
        printf("1 exists in m.\n");
    }
}
// ----- set ----- 
void setExample() {
    set<int> s;
    for(int i = 0; i < 5; i++) {
        s.insert(i);
    }
    set<int> :: iterator it;
    for(it = s.begin(); it != s.end(); it++) {
        printf("%d ", *it);
    }
    printf("\n");
    
    if(s.find(3) != s.end()) {
        printf("3 exists in s.\n");
    }
}
// ----- stack ----- 
void stackExample() {
    stack<int> s;
    for(int i = 0; i < 5; i++) {
        s.push(i);
        printf("%d ", i);
    }
    printf("\n");
    while(!s.empty()) {
        int top = s.top();
        s.pop();
        printf("%d ", top);
    }
    printf("\n");
}
// ----- queue ----- 
void queueExample() {
    queue<int> q;
    for(int i = 0; i < 5; i++) {
        q.push(i);
        printf("%d ", i);
    }
    printf("\n");
    while(!q.empty()) {
        int front = q.front();
        q.pop();
        printf("%d ", front);
    }
    printf("\n");
}
参考资料
[1] 《2023年数据结构考研复习指导/王道考研系列》
 [2] 哔哩哔哩王道官方视频
 [3] 菜鸟教程C语言



















