考研

878 分析

2023年浙江大学研究生入学考试

——《计算机专业基础》(878) 考试大纲

Ⅰ考查目标

《计算机专业基础》(878) 综合考试涵盖程序设计、数据结构两门专业基础课程.要考生比较系统地掌握上述专业基础课程的基本概念、基本原理和基本方法, 能够综合运所学的基本原理和基本方法分析、判断和解决有关理论问题和实际问题.

Ⅱ考试形式和试卷结构

一、试卷满分及考试时间

本试卷满分为150分, 考试时间为180分钟

二、答题方式

答题方式为闭卷、笔试

三、试卷内容结构

程序设计基础(C) 60分 数据结构90分

四、试卷题型结构

单项选择题70分(35小题, 每小题2分)

综合应用题80分

Ⅲ考查范围

程序设计基础(C)

【考查目标】

  1. 理解C程序设计语言结构, 掌握数据表示和输入输出的基本方法, 掌握流程控制、函数设计与调用方法;
  2. 理解模块化程序设计方法, 掌握基本的C语言程序设计过程和技巧;
  3. 掌握初步的算法设计及数据组织方法, 具备基本的问题分析和利用C语言进行求解问题的能力.

第一级

运算符含义使用结合方向
[]数组下标数组名字[常量表达式]左到右
()圆括号表达式 或 函数名(形参)左到右
.取成员对象 . 成员名左到右
->成员选择(指针)对象指针 -> 成员名左到右

第二级

运算符含义使用结合方向
-负号运算符- 表达式右到左
~按位取反运算符~ 表达式右到左
++自增运算符++ 表达式 ++右到左
--自减运算符– 表达式 –右到左
*取值运算符* 指针变量右到左
&取地址运算符& 变量名右到左
!逻辑非运算符! 表达式右到左
(类型)强制类型转换(数据类型) 表达式右到左
sizeof长度运算符sizeof(表达式)右到左

第三级

运算符含义使用结合方向
/表达式 / 表达式左到右
*表达式 * 表达式左到右
%按位取反运算符% 表达式左到右

第四级

运算符含义使用结合方向
+表达式 + 表达式左到右
-表达式 - 表达式左到右

第五级

运算符含义使用结合方向
<<左移变量 « 表达式左到右
>>右移变量 » 表达式左到右

第六级

运算符含义使用结合方向
>大于表达式 > 表达式左到右
>=大于等于表达式 >= 表达式左到右
<小于表达式 < 表达式左到右
<=小于等于表达式 <= 表达式左到右

第七级

运算符含义使用结合方向
==等于表达式 == 表达式左到右
!=不等于表达式 != 表达式左到右

第八 ~ 十二级

运算符含义使用结合方向
&按位与表达式 & 表达式左到右
^按位异或表达式 ^ 表达式左到右
|按位或表达式 | 表达式左到右
&&逻辑与表达式 && 表达式左到右
||逻辑或表达式 || 表达式左到右

第十三级

运算符含义使用结合方向
?:条件运算符表达式 1 ?表达式 2 : 表达式 3右到左

第十四级

运算符含义使用结合方向
=赋值运算符变量 = 表达式右到左
/=除后赋值变量 /= 表达式右到左
*=乘后赋值变量 ** 表达式右到左
%=取模赋值变量 %% 表达式右到左
+=加后赋值变量 ++ 表达式右到左
-=减后赋值变量 – 表达式右到左
<<=左移后赋值变量 «= 表达式右到左
>>=右移后赋值变量 »= 表达式右到左
&=按位与后赋值变量 &= 表达式右到左
^=按位异或后赋值变量 ^= 表达式右到左
|=按位或后赋值变量 |= 表达式右到左

第十五级

运算符含义使用结合方向
,逗号运算符变量 , 表达式左到右

同级依照结合方向顺序.

一、数据表达与组织

(一) 常量, 变量, 运算与表达式 (二) 一维和二维数组, 字符数组和字符串 (三) 指针与数组, 结构与数组 (四) 指针与结构, 单向链表

二、语句及流程控制

(一) 复合语句 (二) 分支控制(if、switch) (三) 循环控制(for、while、do—while)

三、程序结构和函数

(一) C程序结构 (二) 函数的定义、参数传递和调用 (三) 函数的递归调用 (四) 变量的存储类别、作用域, 全局变量和局部变量

四、输入/输出和文件

(一) 标准输入和输出 (二) 文本文件与二进制文件 (三) 文件打开、关闭、读写和定位

五、编译预处理和命令行参数

(一) 宏定义和宏函数 (二) 命令行参数和使用

六、基本算法设计与程序实现

(一) 简单排序算法(插入、选择、冒泡) 、二分查找 (二) 链表、文件中查找 (三) 级数求和、进制转换

查找 平均查找长度 : 设找到集合中第 i 个记录的概论是 $p_i(\sum_{i=0}^{N-1} p_i = 1)$ 且需要 n_i 次才很找到, 则平均查找长度为 $\sum_{i=0}^{N-1} p_i n_i = 1$

二分查找

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#define NotFound 0

Position BinarySearch(List Tb1, ElementType K) {
    Position left, right, mid;
    left = 1;
    right = Tb1 -> Last;
    while(left <= right) {
        mid = (left + right) / 2;
        if(K < Tb1 -> Data[mid]) right = mid - 1;
        else if(K > Tb1 -> Data[mid]) left = mid + 1;
        else return mid;
    }
    return NotFound;
}

数据结构

【考查目标】

  1. 掌握数据结构的基本概念、基本原理和基本方法;
  2. 掌握数据的逻辑结构、存储结构及基本操作的实现, 能够对算法进行基本的时间复杂度与空间复杂度的分析;
  3. 能应用数据结构基本原理和方法进行问题的分析与求解, 具备采用C或C++语言设计与实现算法的能力.

一、栈、队列和数组

类型: 线性表 概念: 表头, 表尾, 前驱, 后继

顺序存储实现 从下表 0 开始存, Last指向最后一个元素所在位置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
typedef int ElementType;
typedef int Position; /* 数组下标 */
typedef struct LNode* PtrToLNode;

struct LNode {
    ElementType Data[MAXSIZE];
    Position Last; /* 记录线性表的最后一个元素所在位置(初始 -1)  */
};

typedef PtrToLNode List;

List MakeEmpty(){
    List L;
    L = (List)malloc(sizeof(struct LNode));
    L -> Last = -1;
    return L;
}

Position Find(List L, ElementType X) {
    Position i = 0;
    while(i <= L -> Last && L -> Data[i] != X) 
        i ++;
    if(i > L -> Last) return ERROR;
    return i;
}

/* 在为序 i 前插入一个新元素 */
bool Insert(List L, ElementType X, int i) {
    Position j;

    if(L -> Last == MAXSIZE - 1) {
        printf("表满");
        return false;
    }

    if(i < 1 || i > L -> Last + 2) {
        printf("位序不合法");
        return false;
    }

    for(j = L -> Last; j >= i - 1; j --) 
        L -> Data[j + 1] = L -> Data[j];
    L -> Data[i - 1] = X;
    L -> Last ++;
    return true;
}

/* 删除为序为 i 的元素 */
bool Delete(List L, int i) {
    Position j;

    if(i < 1 || i > L -> Last + 1) {
        printf("位序不合法");
        return false;
    }

    for(j = i; j <= L -> Last; j ++) 
        L -> Data[j - 1] = L -> Data[j];
    L -> Last --;
    return true;
}

链式存储实现 没有头节点!header指针直接指向第一个元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
typedef int ElementType;
typedef struct LNode * PtrToLNode;

struct LNode{
    ElementType Data;
    PtrToLNode Next;
};

typedef PtrToLNode Position;
typedef PtrToLNode List;

int Length(List L) {
    Position p;
    int cnt = 0;
    p = L;
    while(p) {
        p = p -> Next;
        cnt ++;
    }
    return cnt;
}

/* 查找第 k 个元素 */
ElementType FindKth(List L, int K) {
    Position p;
    int cnt = 1;
    p = L;
    while(p && cnt < K) {
        p = p -> Next;
        cnt ++;
    }
    if((cnt == K) && p)
        return p -> Data;
    else
        return ERROR;
}

/* 查找第一个值为 X 的元素的地址 */
Position Find(List L, ElementType X) {
    Position p = L;
    while(p && p -> Data != X) 
        p = p -> Next;
    if(p) return p;
    else return NULL;
}

/* 在指定为序 i 前插入元素 X, 如果该链表有空的头节点, 则可省略 i==1 的特判 */
List Insert(List L, ElementType X, int i) {
    Position tmp, pre;

    tmp = (Position)malloc(sizeof(struct LNode));
    tmp -> Data = X;
    if(i == 1) {
        tmp -> Next = L;
        return tmp;
    } else {
        int cnt = 1;
        pre = L;
        while(pre && cnt < i - 1) {
            pre = pre -> Next;
            cnt ++;
        }
        if(pre == NULL || cnt != i - 1) {
            printf("插入位置参数错误!");
            free(tmp);
            return NULL;
        } else {
            tmp -> Next = pre -> Next;
            pre -> Next = tmp;
            return L;
        }
    }
}

/* 删除为序为 i 的元素 */
bool Delete(List L, int i) {
    Position tmp, pre = L;
    int cnt = 0;
    while(pre && cnt < i - 1) {
        pre = pre -> Next;
        cnt ++;
    }
    if(pre == NULL || cnt != i - 1 || pre -> Next == NULL) {
        printf("插入位置参数错误!");
        return false;
    } else {
        tmp = pre -> Next;
        pre -> Next = tmp -> Next;
        free(tmp);
        return true;
    }
}

(一) 栈和队列的基本概念

Stack(后入先出 First In Last OUT)

应用:

全排列 n! .

后缀表达式的计算.

将中缀表达式转化为后缀表达式(注意: 1.运算数直接输出 2.左括号入栈 3.遇到右括号一直弹出栈内运算符直到左括号(左右括号弹出但不用输出) 4. 运算符小于等于栈顶运算符则一直输出栈顶直到该运算符大于栈顶为止 5.处理完毕, 栈内全部输出) .

迷宫问题(模拟 DFS) ,函数调用和递归实现

队列 Queue(先进先出 First In First OUT)

简单法: Front 放数组下标小的, Rear 放大的, 容易出现‘假溢出’.

循环队列: Front = Rear = 0, 元素个数 n + 1 种情况, 但是 FrontRear 之间差距最多 n 种情况.

法一: 增设变量 Size , 或最后一次操作是什么入队还是出队的 Flag .

法二: 少用一个元素空间, 堆满条件: (Rear + 1) % MaxSize == Front; 队空: Rear == Front. 应用: 多项式加法运算(用链表也可以, 返回结果多项式, 不改变原有多项式) BFS

(二) 栈和队列的顺序存储结构 (三) 栈和队列的链式存储结构 (四) 栈和队列的应用

顺序栈, Top 指针从 -1 开始, MaxSize - 1 为满

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
typedef int ElementType;
typedef int Position;
typedef struct SNode *PtrToSNode;

struct SNode{
    ElementType *Data;
    Position Top;           /*栈顶指针, -1 为空, MaxSize - 1 为满 */
    int MaxSize;
};
typedef PtrToSNode Stack;

Stack CreatStack(int MaxSize) {
    Stack S = (Stack)malloc(sizeof (struct SNode));
    S -> Data = (ElementType *)malloc(MaxSize * sizeof(ElementType));
    S -> Top = -1;
    S -> MaxSize = MaxSize;
    return S;
}

bool IsFull(Stack S) {
    return (S -> Top == S -> MaxSize -1);
}

bool Push(Stack S, ElementType X) {
    if(IsFull(S)) {
        printf("堆栈满");
        return false;
    }
    S -> Data[++(S -> Top)] = X;
    return true;
}

bool IsEmpty(Stack S) {
    return (S -> Top == -1);
}

ElementType Pop(Stack S) {
    if(IsEmpty(S)) {
        printf("堆栈空");
        return ERROR;
    }
    return S -> Data[(S -> Top)--];
}

还有双栈, 最大化利用数组空间, 一个 Top-1 开始, 另一个从 MaxSize 开始.

链栈, 头节点的 Next 就指向链表的头节点.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
typedef struct SNode * PtrToSNode;
struct SNode{
    ElementType Data;
    PtrToSNode Next;
};

typedef PtrToSNode Stack;

Stack CreatStack(int MaxSize) {
    Stack S;
    S = (Stack)malloc(sizeof (struct SNode));
    S -> Next = NULL;
    return S;
}

bool Push(Stack S, ElementType X) {
    PtrToSNode TmpCell;
    TmpCell = (PtrToSNode)malloc(sizeof(struct SNode));
    TmpCell -> Data = X;
    TmpCell -> Next = S -> Next;
    S -> Next = TmpCell;
    return true;
}

bool IsEmpty(Stack S) { return (S -> Next == NULL); }

ElementType Pop(Stack S) {
    if(IsEmpty(S)) {
        printf("堆栈空");
        return ERROR;
    }
    
    PtrToSNode FirstCell;
    ElementType TopElem;

    FirstCell = S -> Next;
    TopElem = FirstCell -> Data;
    S -> Next = FirstCell -> Next;
    free(FirstCell);
    return TopElem;
}

队列 数组实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
typedef int Position;
typedef struct QNode * PtrToQNode;

struct QNode {
    ElementType * Data;
    Position Front, Rear;
    int MaxSize;
};

typedef PtrToQNode Queue;

Queue CreateQueue(int MaxSize) {
    Queue Q = (Queue)malloc(sizeof(struct QNode));
    Q -> Data = (ElementType *)malloc(MaxSize * sizeof(ElementType));
    Q -> Front = Q -> Rear = 0;
    Q -> MaxSize = MaxSize;
    return Q;
}

bool IsFull(Queue Q) { return ((Q -> Rear + 1) % Q -> MaxSize == Q -> Front);}

bool Add(Queue Q, ElementType X) {
    if(IsFull(Q)) {
        printf("队列满");
        return false;
    }
    Q -> Rear = (Q -> Rear + 1) % Q -> MaxSize;
    Q -> Data[Q -> Rear] = X;
    return true;
}

bool IsEmpty(Queue Q) { return (Q -> Front == Q -> Rear);}

ElementType Delete(Queue Q) {
    if(IsEmpty(Q)) {
        printf("队列空");
        return ERROR;
    }
    Q -> Front = (Q -> Front + 1) % Q -> MaxSize;
    return Q -> Data[Q -> Front];
}

链表实现, 不带头节点的链表实现队列

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
typedef struct Node * PtrToNode;
struct Node{
    ElementType Data;
    PtrToNode Next;
};
typedef PtrToNode Position;

typedef struct QNode * PtrToQNode;
struct QNode {
    Position Front, Rear;
};
typedef PtrToQNode Queue;

Queue CreateQueue(int MaxSize) {
    Queue Q = (Queue)malloc(sizeof(struct QNode));
    Q -> Front = Q -> Rear = NULL;
    return Q;
}

bool Add(Queue Q, ElementType X) {
    Position Tmp = (Position)malloc(sizeof(struct Node));
    Tmp -> Data = X;
    Tmp -> Next = NULL;
    if(Q -> Rear == NULL){
        Q -> Rear = Q -> Front = Tmp;
        return true;
    } 
    Q -> Rear -> Next = Tmp;
    Q -> Rear = Tmp;
    return 0;
}

bool IsEmpty(Queue Q) { return(Q -> Front == NULL);}

ElementType Delete(Queue Q) {
    Position FrontCell; 
    ElementType FrontElem;

    if(IsEmpty(Q)) {
        printf("队列空");
        return ERROR;
    }

    FrontCell = Q -> Front;
    if(Q -> Front == Q -> Rear) 
        Q -> Front = Q -> Rear = NULL;
    else 
        Q -> Front = Q -> Front -> Next;
    
    FrontElem = FrontCell -> Data;
    free(FrontCell);
    return FrontElem;
}

二、树与二叉树

(一) 树的基本概念

Tree

树根, 子树, 父节点, N-1 条边, 森林 — 任何一棵树都是一个二元组 Tree = (Root, Forest)

结点的度: 子树的个数

树的度: 树所有结点中最大的度

叶节点: 度为 0 的结点

父节点: 具有子树, 则其为其子树根节点的父节点 ; 子节点: 与父节点相反

兄弟结点: 有同一父节点的彼此

祖先结点: 沿树根到某节点路径上的所有结点都是

子孙结点: 某结点的子树中的所有结点

结点的层次: 根为 1 , 其余为其父节点层数 +1

树的深度: 所有结点中最大层次

树的高度: 其所有子节点的最大高度层数 +1, 叶节点为 1

分支: 两个相邻结点的分边

路径: 结点序列; 路径长度: 路径包含的边数 = 结点数 - 1

(二) 二叉树 1. 二叉树的定义及其主要特性 2. 二叉树的顺序存储结构和链式存储结构 3. 二叉树的遍历

Binary Tree

每个结点至多两个子树, 有左右序之分

斜二叉树(退化二叉树): 深度 N, 退化为线性表

完美二叉树(满二叉树): 所有分支结点都有左右子树, 且叶节点在同一层 $2^N - 1$

完全二叉树: 叶节点只会出现在最下层或次下层,且最下层叶节点都在左部

对于任何非空二叉树: $n_0$ 为叶节点数, $n_2$ 为度为 2 的节点数, 则 $n_0= n_2+ 1$

具有 n 个结点的完全二叉树深度 $\left \lfloor \log_2 n \right \rfloor +1$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
typedef struct TNode * BTPosition;
typedef BTPosition BinTree;
struct TNode {
    ElementType Data;
    BinTree Left;
    BinTree Right;
};

void OrderTraversal(BinTree BT) {
    if(BT) {
        /* 此处加入判断叶节点, 则可实现遍历叶节点 */
        /* 先序遍历 (根左右)*/
        OrderTraversal(BT -> Left);
        /* 中序遍历 (左根右)*/
        OrderTraversal(BT -> Right);
        /* 后序遍历 (左右根)*/
    }
}

void InorderTraversal(BinTree BT) {
    BinTree T = BT;
    Stack S = CreatStack();

    while(T || !IsEmpty(S)) {
        while(T) {
            Push(S, T);
            T = T -> Left;
        }
        T = (BinTree)Pop(S);
        /* T -> Data 访问结点 */
        T = T -> Right;
    }
}

/*层序遍历*/
void LevellorderTraversal(BinTree BT) {
    Queue Q;
    BinTree T;

    if(!BT) return;

    Q = CreatQueue();
    Add(Q, BT);
    while(!IsEmpty(Q)) {
        T = (BinTree)Delete(Q);
        /* 取出结点 */
        if(T -> Left) Add(Q, T -> Left);
        if(T -> Right) Add(Q, T -> Right);
    }
}

int GetHight(BinTree BT) {
    int HL, HR, MaxH;

    if(BT) {
        HL = GetHight(BT -> Left);
        HR = GetHight(BT -> Right);
        MaxH = HL > HR ? HL : HR;
        return (MaxH + 1);
    }
    return 0;
}

/*层序生成二叉树*/
BinTree CreatBinTree(){
    int Data;
    BinTree BT, T;
    Queue Q = CreatQueue();

    // 输入根节点
    scanf("%d", &Data);
    if(Data != 0) {
        BT = (BinTree)malloc(sizeof(struct TNode));
        BT -> Data = Data;
        BT -> Left = BT -> Right = NULL;
        Add(Q, BT);
    } else return NULL;

    while(!IsEmpty(Q)) {
        T = (BinTree)Delete(Q);
        scanf("%d", &Data);
        if(Data != 0) {
            T -> Left = (BinTree)malloc(sizeof(struct TNode));
            T -> Left -> Data = Data;
            T -> Left -> Left = T -> Left -> Right = NULL;
            Add(Q, T -> Left);
        } else T -> Left = NULL;
        scanf("%d", &Data);
        if(Data != 0) {
            T -> Right = (BinTree)malloc(sizeof(struct TNode));
            T -> Right -> Data = Data;
            T -> Right -> Left = T -> Left -> Right = NULL;
            Add(Q, T -> Right);
        } else T -> Right = NULL;
    }
}

(三) 树、森林 1. 树的存储结构 – 数组(根为 1),链表. 2. 森林与二叉树的转换 3. 树和森林的遍历

唯一确定一棵二叉树的方法

先序 / 后序 + 中序

善于利用根的位置关系, 与子树大小

树与二叉树的转换

  1. 兄弟结点之间连边
  2. 只保留第一个儿子的边
  3. 旋转调整为二叉树

森林与二叉树的转换

  1. 对每棵树各自转换为二叉树
  2. 每棵树的根节点看作兄弟结点, 连边
  3. 调整为二叉树

二叉树转森林或树 若根节点有右儿子, 则转化为森林

  1. 把根的右儿子断开, 根的右儿子的右儿子 … 断开
  2. 把各各部分转化为树
  3. 二叉树转树为上面的逆

森林的遍历

理论上应当把森林转化为二叉树后遍历, 实际效果等同于一次对各棵树作各种遍历, 没有后序遍历

(四) 树与二叉树的应用 1. 二叉排序树 2. 堆结构 3. 哈夫曼(Huffman) 树和哈夫曼编码

二叉排序 / 搜索树 各键值彼此不同, 不会有重复元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
typedef struct TNode * BSTPosition;
typedef BSTPosition BinTree;
struct TNode {
    ElementType Data;
    BinTree Left;
    BinTree Right;
};

void OrderTraversal(BinTree BT) {
    if(BT) {
        OrderTraversal(BT -> Left);
        OrderTraversal(BT -> Right);
    }
}

BSTPosition Find(BinTree BST, ElementType X) {
    if(!BST) return NULL;

    if(X > BST -> Data) {
        Find(BST -> Right, X);
    } else if(X < BST -> Data) {
        Find(BST -> Left, X);
    } else return BST;
}
BSTPosition FindMax(BinTree BST) {
    if(!BST) return NULL;
    else if(!BST -> Left) return BST;
    else return FindMax(BST -> Left);
}
BSTPosition FindMin(BinTree BST) {
    if(BST) 
        while (BST -> Right) {
            BST = BST -> Right;
        }
    return BST;
}

BSTPosition Insert(BinTree BST, ElementType X) {
    if(!BST) {
        BST = (BSTPosition) malloc (sizeof(struct TNode));
        BST -> Data = X;
        BST -> Left = BST -> Right = NULL;
    } else {
        if(X < BST -> Data)
            BST -> Left = Insert(BST -> Left, X);
        else if(X > BST -> Data)
            BST -> Right = Insert(BST -> Right, X);
        /*X 已存在则什么都不做*/
    }
    return BST;
}

BinTree Delete(BinTree BST, ElementType X) {
    BSTPosition Tmp;
    if(!BST)
        printf("要删除的元素未找到");
    else {
        if(X < BST -> Data)
            BST -> Left = Delete(BST -> Left, X);
        else if(X >BST -> Data)
            BST -> Right = Delete(BST -> Right, X);
        else { // 应当删除的结点
            if(BST -> Left && BST -> Right) {
                Tmp = FindMin(BST -> Right); // 找右子树的最小结点
                BST -> Data = Tmp -> Data;
                BST -> Right = Delete(BST -> Right, BST -> Data);
            } else { /* 只有一个儿子或没有儿子 */
                Tmp = BST;
                if(!BST -> Left) // 只有右儿子或者没有子结点(NULL), 
                    BST = BST -> Right;
                else
                    BST = BST -> Left;
                free(Tmp);
            }
        }
    }
    return BST;
}

堆结构 Heap Priority Queue

起始单元为 1, 父节点 $\lfloor i / 2 \rfloor $, 子结点 $2i , 2i+1$

兄弟之间不存在约束关系

0 可以放一个 MAXDATA 在插入时可以有用, 可以自动在 0处跳出循环

删除是用数组的最后一个元素放到根节点上再调整

建立: 从第 $\lfloor n / 2 \rfloor $个结点开始(最后一个有儿子的结点) O(N)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
typedef struct HNode *Heap;
struct HNode {
    ElementType *Data;
    int Size;
    int Capacity;
};
typedef Heap MaxHeap;

#define MaxData 998244353 /* 比所有元素大或小的元素作为哨兵 */

MaxHeap CreatHeap(int MaxSize) {
    MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode));
    H -> Data = (ElementType*)malloc((MaxSize + 1) * sizeof(ElementType));
    H -> Size = 0;
    H -> Capacity = MaxSize;
    H -> Data[0] = MaxData; /* 设置哨兵 */
    return H;
}

bool IsFull(MaxHeap H) { return H -> Size == H -> Capacity; }

bool Insert(MaxHeap H, ElementType X) {
    if(IsFull(H)) {
        printf("最大堆已满");
        return false;
    }

    int idx = ++ H -> Size; /* 指向应当插入的位置 */
    /* 上滤 X */
    for( ; H -> Data[idx / 2] < X; idx /= 2)
        H -> Data[idx] = H -> Data[idx / 2];

    H -> Data[idx] = X;
    return true;
}

bool IsEmpty(MaxHeap H) { return (H -> Size == 0); }

ElementType DeleteMax(MaxHeap H) {
    int Parent, Child;
    ElementType MaxItem, X;
    if(IsEmpty(H)) {
        printf("最大堆为空");
        return false;
    }
    MaxItem = H -> Data[1];
    X = H -> Data[H -> Size --]; /* 规模减小并且取出最后元素 */
    for(Parent = 1; Parent * 2 <= H -> Size; Parent = Child) {
        Child = Parent * 2;
        /* Child指向左右儿子中大的 */
        if((Child != H -> Size) && (H -> Data[Child] < H -> Data[Child + 1])) 
            Child ++;
        /* 找到位置 */
        if(X >= H -> Data[Child]) break;
        /* 下滤 X */
        else
            H -> Data[Parent] = H -> Data[Child];
    }
    H -> Data[Parent] = X;

    return MaxItem;
}

/* 将以 p 为根的子堆调整为最大堆 */
void PercDown(MaxHeap H, int p) {
    int Parent, Child;
    ElementType X;

    X = H -> Data[p];
    for(Parent = p; Parent * 2 <= H -> Size; Parent = Child) {
        Child = Parent * 2;
        if((Child != H -> Size) && (H -> Data[Child] < H -> Data[Child + 1])) 
            Child ++;
        if(X >= H -> Data[Child]) break;
        else
            H -> Data[Parent] = H -> Data[Child];
    }
    H -> Data[Parent] = X;
}

void BuildHeap(MaxHeap H) {
    for(int i = H -> Size / 2; i > 1; i --) 
        PercDown(H, i);
}
  1. 哈夫曼(Huffman) 树和哈夫曼编码

n 结点的树, 叶节点带权值 $W_k$, 从根到叶的长度 $l_k$, 则树的带权路径长度 WPL = $\sum_{k=1}^{n} W_k L_k$

树形可能不同, WPL 最小

复杂度 O(NlogN)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct HTNode *HuffmanTree;
struct HTNode {
    int Weight;
    HuffmanTree Left;
    HuffmanTree Right;
};
HuffmanTree Huffman(MinHeap H) {
    int i, N;
    HuffmanTree T;

    BuildHeap(H); /* 调整 O(N)*/
    N = H -> Size;
    /* 作 Size - 1 次合并 */
    for(int i = 1; i < N; i ++) {
        T = (HuffmanTree)malloc(sizeof(struct HTNode));
        T -> Left = DeleteMin(H);
        T -> Right = DeleteMin(H);
        T -> Weight = T -> Left -> Weight + T -> Right -> Weight;
        Insert(H, T);
    }
    return DeleteMin(H);
}

三、图

(一) 图的基本概念

Graph

非空有限集合 V, 边集合 E, G = (V, E), 数据对象是顶点

无向图: (v, w)

有向图: <v, w>

简单图: 无重边, 无回路, 我们考虑的都是简单图

邻接点: 边的端点互为邻接点, <v, w> v 邻接到 w, w 邻接自 v

路径: 顶点序列, 相邻顶点间在图中有边

简单路径: 除了首位顶点外, 其余顶点都不同

回(环)路: 起点 == 终点; 简单回路: 简单路径的回路 (自环是自回路); 无向图的环路 >= 3 ; 有向图不存在回路 -> 无环图

无向完全图: 任意两点都有边相连 $n(n-1)/2$; 有向完全图: 任意两点都有互为相反的两条弧相连 $n(n-1)$

顶点的度: 该点边数; 入度 + 出度 = 度; 图的总入度 = 总出度 = 一半的度

稠密图: 边数接近完全图; 稀疏图: 边数很少的图

设图 G(V, E), 边数 |E|, 定点数 |V|;

稠密图: 平均顶点度与顶点数目成正比的图 $|E| = k|V|^2( 0 < k < 1/2)$ 或者 $|E| > |V| \log_2|V|$

权: 边上的权值; 网图: 边上带权的图

子图: G’(V’, E')

连通图: 无向图中, 如果任意两点是连通的; 连通分量: 无向图的极大连通分量

  1. 子图;
  2. 连通;
  3. 极大顶点数: 再加点会不连通;
  4. 极大边数: 包含依附于这些顶点的所有边

连通的无向图只有一个连通分量(本图)

强连通图: 有向图中, 如果任意两点是连通的(双向路径);

强连通分量: 有向图的极大连通分量

连通的有向图只有一个连通分量(本图)

生成树: 连通图, 包含全部顶点的极小连通子图

有向图生成树: 恰有一个顶点的入度为 0(根), 其余点入度为 1

生成森林: 一个生成树对应一个连通分量

对无向图而言, 连通分量数 == 生成森林中树数

对有向图而言, 非强连通图也可能只需要一棵生成树与之对应

(二) 图的存储及基本操作

  1. 邻接矩阵法
  2. 邻接表法
  3. 邻接多重表、十字链表

邻接矩阵法

网图中 $\infty$ 代表不存在边

无向图所需存储元素个数 $|V| \times (|V| - 1) / 2$

优点: 很容易确定两点之间是否有边, 数邻接点只要遍历行或列

局限性: 数边数, 需要 $O(N^2)$

邻接表法

顶点表数组(顶点域 + 表头指针) + 链表(邻接点域 + 指针域)

采用的是头插法

优点: 节约空间, 无向图中, 顶点的度恰为链表的结点数, 有向图中为出度

缺点: 求有向图的入度很麻烦, 需要遍历全图(可以建逆邻接表优化) ; 判断 $v_i$ 与 $v_j$ 间是否有边不如邻接矩阵

若输入的定点信息为顶点的编号, 则建表复杂度 O(|v| + |E|), 否在 O(|V| + |E|)

邻接多重表、十字链表

十字链表 P70

邻接多重表: 邻接表 , 链表信息增加

mark:标志域,用于标记此节点是否被操作过,例如在对图中顶点做遍历操作时,为了防止多次操作同一节点,mark 域为 0 表示还未被遍历;mark 为 1 表示该节点已被遍历;

ivexjvex:数据域,分别存储图中各边两端的顶点所在数组中的位置下标;

ilink:指针域,指向下一个存储与 ivex 有直接关联顶点的节点;

jlink:指针域,指向下一个存储与 jvex 有直接关联顶点的节点;

info:指针域,用于存储与该顶点有关的其他信息,比如无向网中各边的权;

(三) 图的遍历

  1. 深度优先搜索
  2. 广度优先搜索

DFS

邻接矩阵 O($|V|^2$); 邻接表 O(|V| + |E|)

BFS

邻接矩阵 O($|V|^2$); 邻接表 O(|V| + |E|)

(四) 图的基本应用

  1. 最小(代价) 生成树
  2. 最短路径
  3. 拓扑排序

MST Prim

不断更新dist, 每次添加最小的 dist 的点, O($|V|^2$), 稠密图效果好 用堆优化查找最小 dist的过程, 可以得到和 Kruskal 一样的复杂度 O($|E|\log|V|$)

Kruskal

适合稀疏图

高效排序下, 复杂度 O($|E|\log|V|$)

最短路 不能处理负权

Dijkstra

O($|V|^2$), 稠密图效果好, 堆优化 O($|E|\log|V|$)

Floyd

O($|V|^3$) 多源最短路

TopOrder

邻接表 + 队列优化 复杂度 O(|E| + |V|)

四、动态查找

(一) 平衡二叉树(AVL树) (二) 散列(Hash) 表 (三) 查找算法的分析及应用

AVL 平衡因子 $BF = h_L - h_R$, 不能超过 1, 所有在 -1 0 1之间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
typedef struct AVLNode * AVLPosition;
typedef AVLPosition AVLTree;
struct AVLNode {
    ElementType Data;
    AVLTree Left;
    AVLTree Right;
    int Height;
};

int Max(int a, int b) { return a > b ? a : b; }
int GetHeight(AVLTree T) { return T == NULL ? 0 : T -> Height; }
 
/* 左单旋 */
AVLTree SingleLeftRotataion(AVLTree A) {
    AVLTree B = A -> Left;
    A -> Left = B -> Right;
    B -> Right = A;
    A -> Height = Max(GetHeight(A -> Left), GetHeight(A -> Right)) + 1;
    B -> Height = Max(GetHeight(B -> Left), A -> Height) + 1;
    return B;
}
/* 右单旋 */
AVLTree SingleRightRotataion(AVLTree A) {
    AVLTree B = A -> Right;
    A -> Right = B -> Left;
    B -> Left = A;
    A -> Height = Max(GetHeight(A -> Left), GetHeight(A -> Right)) + 1;
    B -> Height = Max(GetHeight(B -> Left), A -> Height) + 1;
    return B;
}
/* 左-右 双旋 */
AVLTree DoubleLeftRightRotation(AVLTree A) {
    /* 先对左子树作右旋,再对整棵树作左旋 */
    A -> Left = SingleRightRotataion(A -> Left);
    return SingleLeftRotataion(A);
}
/* 右-左 双旋 */
AVLTree DoubleRightLeftRotation(AVLTree A) {
    A -> Right = SingleLeftRotataion(A -> Right);
    return SingleRightRotataion(A);
}

AVLTree Insert(AVLTree T, ElementType X) {
    /* 若插入空树,则新建结点 */
    if(!T) {
        T = (AVLTree)malloc(sizeof (struct AVLNode));
        T -> Data = X;
        T -> Height = 1;
        T -> Left = T -> Right = NULL;
    } else if(X < T -> Data) { 
        /* 插入到左子树 */
        T -> Left = Insert(T -> Left, X);
        /* 判断树是否平衡 */
        if(GetHeight(T -> Left) - GetHeight(T -> Right) == 2) {
            /* 通过 X 与左子树的值的大小判断旋转方式*/
            if(X < T -> Left -> Data)
                T = SingleLeftRotataion(T);
            else
                T = DoubleLeftRightRotation(T);
        }
    } else if(X > T -> Data) {
        T -> Right = Insert(T -> Right, X);
        if(GetHeight(T -> Left) - GetHeight(T -> Right) == -2) {
            if(X > T -> Right -> Data) 
                T = SingleRightRotataion(T);
            else
                T = DoubleRightLeftRotation(T);
        }
    }
    /* X == T -> Data 无需插入 */
    T -> Height = Max(GetHeight(T -> Left), GetHeight(T -> Right)) + 1;

    return T;
}

散列(Hash) 表

类型: 符号表, 散列表, hash表

散列函数(hash 函数): h(key)

装填因子 $\alpha$: 填入表的元素 / 空间大小 ; 分链法 -> 每个链表的平均长度 $\alpha = 0.5$ ~ $0.8$ 为宜

好的散列表: 1. 转换速度快 2. 冲突小

数字关键字的散列表

  1. 直接定址法

$h(key) = a \times key + b$

不会有冲突, 但要求地址和关键字集合大小一致, 大集合不适合用

  1. 除留余数法

TableSize = 集合大小 $n$ / 允许最大装填因子 $\alpha$

简单情况: h(key) = key % p(小于等于 TableSize 的素数, 素数均匀分布可能性大)

  1. 数字分析法

特殊情况特殊处理的函数

字符串关键字的散列表

  1. ASCII 码加和法

简单, 均匀性差

  1. 前三个字符移位法

$h(key) = (ksy[0] + key[1] \times 27 + key[2] \times 27^2) mod TableSize$

26 字符 + 空格 = 27

冲突, 空间浪费严重 $\alpha$ 不到 30%

  1. 移位法

$h(key) = (\sum_{i=0}^{n-1} key[n-i-1] \times 32^i) \mod TableSize$

每个字符占 5 位, 所以为 32, 用 H << 5 实现

n 过大, 前面若干位可能移出界, 可以选有代表性的字符, 如大于 12 时选奇数位

冲突处理

开放寻址法

发生第 i 次冲突, 试探的下一个地址加 $d_i$

$h_i (key) = (h(key) + d_i) \mod TableSize$

删除会引起冲突, 所以要懒标记删除

平均查找长度 ASL = $\sum 每个关键字的比较次数 / 关键字个数$

  1. 线性探测法

$d_i \in 1, 2, \dots , TableSize - 1$

会出现需要多次冲突才能找到空位, 这种现象叫一次聚集

  1. 平方探测法

$d_i = \pm i^2, 1^2, -1^2, 2^2, -2^2, \dots q^2, -q^2, q \leqslant \lfloor TableSize / 2 \rfloor$

如果 TableSize 是某个 $4k + 3$ 形式的素数的话, 平方探测法就可以探测到整个散列空间

散列到同一地址的会探测相同备选单元叫 二次集聚

  1. 双散列探测法

$d_i = i \times h_2(key)$

一般 $h_2(key) = p - (key \mod p)$

增加了计算

  1. 再散列法

装填因子过大时, 新建一个越来两倍的散列表, 旧数据从新分到新表, 可能出现停顿

分离链接法

散列表地址作为指针, 指向一个链表

查找算法的分析及应用

顺序查找 O(N)

二分查找 O(log N)

二叉搜索树 最坏 O(N), 最好 O(log N)

AVL O(log N)

五、排序

(一) 希尔排序(Shell Sort)

(二) 快速排序

(三) 堆排序

(四) 二路归并排序(Merge Sort)

(五) 基数排序

(六) 各种内部排序算法的比较

(七) 排序算法的应用

简单选择排序 时间 O(N^2), 空间 O(1)

1
2
3
4
5
6
7
8
9
void SimpleSelect(ElementType A[], int N) {
    int i, j, min;
    for(i = 0; i < N - 1; i ++) {
        min = i;
        for(j = i + 1; j < N; j ++) 
            if(A[j] < A[min]) min = j;
        Swap(&A[i], &A[min]);
    }
}

堆排序

时间 O(NlogN), 空间 从 O(N) 优化到 O(1)(最后一个元素与堆顶交换) 与堆不同的是, 这里的下标是 0 开始的, 所以左儿子 2i + 1, 右儿子 2i + 2, 父节点 $\lfloor (i-1)/2\rfloor$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void PercDown(ElementType A[], int p, int N) {
    /* 将 A[p] 为根的子堆调整 */
    int Parent, Child;
    ElementType X;

    X = A[p];
    for(Parent = p; (Parent * 2 + 1) < N; Parent = Child) {
        Child = Parent * 2 + 1;
        if((Child != N - 1) && (A[Child] < A[Child + 1]))
            Child ++;
        if(X >= A[Child]) break;
        else A[Parent] = A[Child];
    }
    A[Parent] = X;
}

void HeapSort(ElementType A[], int N) {
    int i;
    for(i = N / 2 - 1; i >= 0; i --) /* 建立最大堆 */
        PercDown(A, i, N);

    for(int i = N - 1; i > 0; i --) {
        Swap(&A[0], &A[i]);
        PercDown(A, 0, i);
    }
}

插入排序

时间 O(N^2), 空间 O(1), 稳定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void InsertionSort(ElementType A[], int N) {
    int i, P;
    ElementType Tmp;
    for(P = 1; P < N; P ++) {
        Tmp = A[P];
        for(i = P; i > 0 && A[i-1] > Tmp; i --) 
            A[i] = A[i - 1];
        A[i] = Tmp;
    }
}

希尔排序

时间最差 O(N^2), 理想 {1, 3, 7, $2^k-1$} $O(N^{5\over 4})$, 最差 $O(N^{3\over 2})$ 空间 O(1) 最后一个增量为 1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void ShellSOrt(ElementType A[], int N) {
    int Si, D, P, i;
    ElementType Tmp;

    int Sedgewick[] = {929, 505, 209, 109, 41, 19, 5, 1, 0};

    for(Si = 0; Sedgewick[Si] >= N; Si ++) ;

    for(D = Sedgewick[Si]; D > 0; D = Sedgewick[++Si]) {
        /* 插入排序 */
        for( P = D; P < N; P ++) {
            Tmp = A[P];
            for(i = P; i >= D && A[i-D] > Tmp; i -= D)
                A[i] = A[i - D];
            A[i] =Tmp;
        }
    }
}

冒泡排序

时间 O(N^2), 空间 O(1), 稳定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void BubbleSort(ElementType A[], int N) {
    int P, i;
    int flag;

    for(P = N - 1; P >= 0; P--) {
        flag = 0;
        for(i = 0; i < P; i ++) {
            if(A[i] > A[i + 1]) {
                Swap(&A[i], &A[i + 1]);
                flag = 1;
            }
        }
        if(!flag) break;
    }
}

快速排序

时间 O($N\log N$) 最坏 O($N^2$), 空间最坏 O(N) 栈空间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* 选中值 */
ElementType Median3(ElementType A[], int Left, int Right) {
    int Center = (Left + Right) / 2;
    if(A[Left] > A[Center]) Swap(&A[Left], &A[Center]);

    if(A[Left] > A[Right]) Swap(&A[Left], &A[Right]);

    if(A[Center] > A[Right]) Swap(&A[Center], &A[Right]);
    /* A[Left] <= A[Center] <= A[Right] 所以中值 A[Center]作为基准 */
    Swap(&A[Center], &A[Right - 1]); /*将基准藏到右边, 只需考虑, left + 1 ... right - 2*/
    return A[Right - 1];
}

void Qsort(ElementType A[], int Left, int Right) {
    int Pivot, Cutoff, Low, High;
    /* 如果元素够多进入快排 */
    if(Cutoff <= Right - Left) {
        Pivot = Median3(A, Left, Right);
        Low = Left; High = High - 1;
        while(1) {
            while (A[++Low] < Pivot);
            while (A[--High] > Pivot);
            if(Low < High) Swap(&A[Low], &A[High]);
            else break;
        }
        Swap(&A[Low], &A[Right - 1]); /* 把基准换到正确的位置 */
        Qsort(A, Left, Low - 1);
        Qsort(A, Low + 1, Right);
    }
    else InsertionSort(A, Right - Left + 1);
}

归并排序

时间 O($N\log N$) , 空间 O(N) ,如果在递归中建数组,则空间消耗为 O($N\log N$);

稳定, 常用于外部排序, 而非内部

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void Merge(ElementType A[], ElementType TmpA[], int L, int R, int RightEnd) {
    int LeftEnd, NumElements, Tmp;
    int i;
    /*L ~ R - 1 与 R ~ RightEnd 合并为有序*/
    LeftEnd = R - 1; /*左边终点*/
    Tmp = L;         /*序列起点*/
    NumElements = RightEnd - L + 1;
    /*复制数组*/
    while(L <= LeftEnd && R <= RightEnd) {
        if(A[L] <= A[R])
            TmpA[Tmp ++] = A[L++];
        else
            TmpA[Tmp ++] = A[R++];;
    }    
    while(L <= LeftEnd) TmpA[Tmp++] = A[L++];
    while(R <= RightEnd) TmpA[Tmp++] = A[R++];

    for(i = 0; i < NumElements; i ++, RightEnd --) 
        A[RightEnd] = TmpA[RightEnd];
}

void Msort(ElementType A[], ElementType TmpA[], int L, int RightEnd) {
    int Center;
    if(L < RightEnd) {
        Center = (L + RightEnd) / 2;
        Msort(A, TmpA, L, Center); 
        Msort(A, TmpA, Center + 1, RightEnd); 
        Merge(A, TmpA, L, Center + 1, RightEnd); 
    }
}

void MergeSort(ElementType A[], int N) {
    ElementType *TmpA;
    TmpA = (ElementType*)malloc(N*sizeof(ElementType));

    if(TmpA != NULL) {
        Msort(A, TmpA, 0, N - 1);
        free(TmpA);
    }
}

桶排序

N 个数据, M 个桶, 时间复杂度 O(N + M)

基数排序 对于有 k 个关键字的情况有

  1. 主位优先法 MSD
  2. 次位优先法 LSD

一般情况下次位优先效率高

分配收集趟数: D, 关键字 N, 桶: R

时间 O(D(N + R))

由于链表操作, O(N) 常数项可能超过 Log N, 且需要额外 O(N) 空间, 稳定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#define MaxDigit 4
#define Radix 10

/*桶元素结点*/
typedef struct Node * PtrToNode;
struct Node{
    int key;
    PtrToNode next;
};
/*桶头结点*/
struct HeadNode{
    PtrToNode head, tail;
};
typedef struct HeadNode Bucket[Radix];

/*返回某数的第 D 位数字*/
int GetDigit(int X, int D) {
    int d, i;
    for(i = 1; i <= D; i++) {
        d = X % Radix;
        X /= Radix;
    }
    return d;
}

void LSDRadixSort(ElementType A[], int N) {
    int D, Di, i;
    Bucket B;
    PtrToNode tmp, p, List = NULL;

    for(i = 0; i < Radix; i ++) 
        B[i].head = B[i].tail = NULL;

    for(i = 0; i < N; i ++) {
        tmp = (PtrToNode)malloc(sizeof(struct Node));
        tmp -> key = A[i];
        tmp -> next = List;
        List = tmp;
    }
    
    for(D = 1; D <= MaxDigit; D ++) {
        p = List;
        /*分配*/
        while(p) {
            Di = GetDigit(p -> key, D);
            tmp = p; p = p->next;
            tmp->next = NULL;
            if(B[Di].head == NULL)
                B[Di].head = B[Di].tail = tmp;
            else {
                B[Di].tail->next = tmp;
                B[Di].tail = tmp;
            }
        }
        /*收集*/
        List = NULL;
        for(Di = Radix - 1; Di >= 0; Di--) {
            if(B[Di].head) {
                B[Di].tail -> next = List;
                List = B[Di].head;
                B[Di].head = B[Di].tail = NULL;
            }
        }
    }
    for(i = 0; i < N; i ++) {
        tmp = List;
        List = List -> next;
        A[i] = tmp -> key;
        free(tmp);
    }
}
Licensed under CC BY-NC-SA 4.0