前言:
本章由三部分组成,请按需快速选择阅读
基础扫盲:栈(stack)、队列(queue)的图文理解。(可根据自身情况略过该部分)
栈实现:顺序栈+可扩容循序栈+链式栈(完整代码)
队列实现:循环可扩容队列+链式队列(完整代码)
目录
一、基础扫盲:
1.栈(stack)
1.1栈的性质:
栈也是一种数据呈线性排列的数据结构,不过在这种结构中,我们只能访问最新添加的数据。栈就像是一摞书,拿到新书时我们会把它放在书堆的最上面,取书时也只能从最上面的新书开始取。
像栈这种最后添加的数据最先被取出,即“后进先出”的结构,我们称为 Last In First Out,简称 LIFO。 与链表和数组一样,栈的数据也是线性排列,但在栈中,添加和删除数据的操作只能在一端进行,访问数据也只能访问到顶端的数据。想要访问中间的数据时,就必须通过出栈操作将目标数据移到栈顶才行
1.2图文演示:
1)这就是栈的概念图。现在存储在栈中的只有数据Blue。
2)然后,栈中添加了数据Green。
3)接下来,数据Red入栈。
4)从栈中取出数据时,是从最上面,也就是最新 的数据开始取出的。这里取出的是Red。
5)如果再进行一次出栈操作,取出的就是Green了。
2.队列(queue)
2.1队列性质:
队列中的数据也呈线性排列。虽然与栈有些相似,但队列中添加和删除数据的操作分别是在两端进行的。就和“队列”这个名字一样,把它想象成排成一 队的人更容易理解。在队列中,处理总是从第一名开始往后进行,而新来的人只能排在队尾。
像队列这种最先进去的数据最先被取来,即“先进先出”的结构,我们称为 First In First Out,简称 FIFO。 与栈类似,队列中可以操作数据的位置也有一定的限制。在栈中,数据的添加和删除都在同一端进行,而在队列中则分别是在两端进行的。队列也不能直接访问位于中间的数据,必须通过出队操作将目标数据变成首位后才能访问。
2.2图文演示:
1)这就是队列的概念图。现在队列中只有数据Blue。
2)然后,队列中添加了数据Green。
3)紧接着,数据Red也入队了。
4)从队列中取出(删除)数据时,是从最下面,也就是最早入队的数据开始的。这里取出的是Blue。
5)如果再进行一次出队操作,取出的就是Green了。
二、栈实现
栈是特殊的线性表。允许在其中的一端进行数据的运算(数据的插入和删除的操作),允许操作的一端称为栈顶;而另外一端是固定端称为栈底。栈中如果没有元素称为空栈。
栈的元素特性:先进后出。
栈的存储方式有两种方式:分别为顺序存储和链式存储。
栈的分类:
在元素操作的时候,按照地址的加和减可以分为递增栈和递减栈。
在元素操作的时候,按照栈顶元素的操作和栈顶标签修改的先后顺序可以分为空栈和满栈
1.栈的顺序实现原理
既然栈是线性表的特例,那么栈的顺序存储其实也是线性表顺序存储的简化,我们简称为顺序栈。线性表是用数组来实现的,对于栈这种只能一头插入删除的线性表来说,用数组哪一端来作为栈顶和栈底比较好?
栈的顺序存储也称为顺序栈,是顺序表示特殊实例。以下为顺序栈的三种状态(一般栈,空栈和满栈)
2.顺序栈实现
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#define MAX_SIZE 100 // 宏定义,表示顺序栈的最大容量
typedef int data_t; // 元素类型定义为 int
/* 顺序栈的结构体 */
typedef struct {
data_t data[MAX_SIZE]; // 存放元素的数组
int top; // 栈顶指针,-1 表示栈为空
} SeqStack;
/* 初始化栈 */
void InitStack(SeqStack* s) {
s->top = -1; // 栈顶指针初始化为-1,表示栈为空
}
/* 判断栈是否为空 */
bool IsEmpty(SeqStack* s) {
return s->top == -1; // 当栈顶指针为-1时表示栈为空
}
/* 判断栈是否已满 */
bool IsFull(SeqStack* s) {
return s->top == MAX_SIZE - 1; // 栈顶指针为MAX_SIZE-1时表示栈已满
}
/* 入栈操作 */
bool Push(SeqStack* s, data_t x) {
/* 先判断栈是否已满,如果已满则不能继续入栈,返回false表示操作失败 */
if (IsFull(s)) {
return false;
}
s->top++; // 栈顶指针加1
s->data[s->top] = x; // 将新元素插入到栈顶,并更新栈顶指针
return true; // 入栈操作成功,返回true
}
/* 出栈操作 */
bool Pop(SeqStack* s, data_t* x) {
/* 先判断栈是否为空,如果为空则不能继续出栈,返回false表示操作失败 */
if (IsEmpty(s)) {
return false;
}
/* 获取栈顶元素的值,并将其出栈 */
*x = s->data[s->top];
s->top--; // 栈顶指针减1
return true; // 出栈操作成功,返回true
}
/* 取栈顶元素的值 */
bool GetTop(SeqStack* s, data_t* x) {
/* 先判断栈是否为空,如果为空则不能获取栈顶元素,返回false表示操作失败 */
if (IsEmpty(s)) {
return false;
}
*x = s->data[s->top]; // 获取栈顶元素的值,不改变栈顶指针
return true;
}
/* 获取栈长度(元素个数) */
int GetLength(SeqStack* s) {
return s->top + 1; // 栈顶指针+1就是元素个数
}
/* 清空栈 */
void ClearStack(SeqStack* s) {
s->top = -1; // 直接将栈顶指针初始化为-1,表示栈为空
}
/* 销毁栈 */
void DestroyStack(SeqStack* s) {
free(s); // 释放申请的内存,即销毁栈
}
/* 测试代码 */
int main() {
SeqStack* s = (SeqStack*)malloc(sizeof(SeqStack)); // 动态申请内存来创建顺序栈
InitStack(s); // 初始化栈
/* 入栈操作 */
int i;
for (i = 0; i < 10; i++) {
Push(s, i); // 将整数i入栈
}
/* 获取栈长度并输出 */
int len = GetLength(s);
printf("length: %d\n", len);
/* 取栈顶元素 */
data_t top;
if (GetTop(s, &top)) {
printf("top: %d\n", top);
}
else {
printf("stack is empty\n");
}
/* 出栈操作,并输出栈中的元素 */
data_t x;
while (Pop(s, &x)) {
printf("pop: %d\n", x); // 输出出栈的元素
}
/* 再次获取栈顶元素,但是此时栈为空 */
if (GetTop(s, &top)) {
printf("top: %d\n", top);
}
else {
printf("stack is empty\n");
}
/* 获取栈长度并输出,此时栈已经为空 */
len = GetLength(s);
printf("length: %d\n", len);
ClearStack(s); // 清空栈
/* 再次获取栈顶元素,但是此时栈为空 */
if (GetTop(s, &top)) {
printf("top: %d\n", top);
}
else {
printf("stack is empty\n");
}
/* 销毁栈,释放动态申请的内存 */
DestroyStack(s);
return 0;
}
2.可扩容顺序栈
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
// 定义初始空间大小和每次扩容的大小
#define INIT_SIZE 10
#define INCREMENT_SIZE 5
typedef int data_t;
typedef struct {
data_t* data; // 存放栈的元素
int top; // 栈顶指针
int stack_size; // 当前栈空间大小
} SeqStack;
/**
* 初始化顺序栈
* @param s 带初始化的栈指针
*/
void InitStack(SeqStack* s) {
// 动态分配初始空间
s->data = (data_t*)malloc(INIT_SIZE * sizeof(data_t));
s->stack_size = INIT_SIZE; // 初始化栈的空间大小
s->top = -1; // 初始化为空栈
}
/**
* 判断栈是否为空
* @param s 被判断的栈
*/
bool IsEmpty(SeqStack* s) {
return s->top == -1; // 栈顶下标为-1表示空栈
}
/**
* 判断栈是否已满
* @param s 被判断的栈
*/
bool IsFull(SeqStack* s) {
return s->top == s->stack_size - 1; // 栈顶指针等于容量-1表示栈已满
}
/**
* 入栈操作
* @param s 待操作的栈指针
* @param x 要入栈的元素
* @return 操作成功返回true,否则返回false
*/
bool Push(SeqStack* s, int x) {
if (IsFull(s)) { // 若栈满,需要扩容
// realloc函数动态扩容
int* new_data = (data_t*)realloc(s->data, (s->stack_size + INCREMENT_SIZE) * sizeof(data_t));
if (!new_data) { // 扩容失败,返回false
return false;
}
s->data = new_data; // 将原数据内容复制到新内存区
s->stack_size += INCREMENT_SIZE; // 更新栈的空间大小
}
s->data[++s->top] = x; // 元素x入栈
return true;
}
/**
* 出栈操作
* @param s 待操作的栈指针
* @param x 出栈元素的指针
* @return 操作成功返回true,否则返回false
*/
bool Pop(SeqStack* s, data_t* x) {
if (IsEmpty(s)) {
return false; // 空栈不能出栈
}
*x = s->data[s->top--]; // 弹出栈顶元素
return true;
}
/**
* 获取栈顶元素
* @param s 待操作的栈指针
* @param x 栈顶元素的指针
* @return 操作成功返回true,否则返回false
*/
bool GetTop(SeqStack* s, data_t* x) {
if (IsEmpty(s)) {
return false; // 空栈没有栈顶元素
}
*x = s->data[s->top]; // 取出栈顶元素
return true;
}
/**
* 获取栈长度(元素个数)
* @param s 被计算的栈
* @return 栈的元素个数
*/
int GetLength(SeqStack* s) {
return s->top + 1; // 栈顶指针+1就是元素个数
}
/**
* 清空栈
* @param s 待操作的栈指针
*/
void ClearStack(SeqStack* s) {
s->top = -1;
}
/**
* 销毁栈
* @param s 待操作的栈指针
*/
void DestroyStack(SeqStack* s) {
// 释放动态分配的内存
free(s->data);
free(s); // 释放栈的空间
}
int main() {
SeqStack* s = (SeqStack*)malloc(sizeof(SeqStack)); //申请栈空间
// 动态申请内存来创建顺序栈
InitStack(s); // 初始化栈
// 将15个元素入栈并动态扩容
int i, x;
for (i = 1; i <= 15; ++i) {
if (!Push(s, i)) {
printf("Push %d failed.\n", i);
}
}
printf("The length of stack is %d.\n", GetLength(s));
// 获取栈顶元素并输出
if (GetTop(s, &x)) {
printf("The top of stack is %d.\n", x);
}
else {
printf("GetTop failed, the stack is empty.\n");
}
// 将元素出栈并输出
while (!IsEmpty(s)) {
if (Pop(s, &x)) {
printf("%d ", x);
}
else {
printf("Pop failed.\n");
}
}
printf("\n");
printf("The length of stack is %d.\n", GetLength(s));
// 获取栈顶元素并输出
if (!GetTop(s, &x)) {
printf("GetTop failed, the stack is empty.\n");
}
// 清空栈并输出栈长度
ClearStack(s);
printf("After clear, the length of stack is %d.\n", GetLength(s));
// 销毁栈并释放动态内存
DestroyStack(s);
return 0;
}
2.链式栈实现
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
// 定义栈元素类型
typedef int data_t;
typedef struct node {
data_t data;
struct node* next;
} StackNode;
typedef struct {
StackNode* top; // 栈顶指针
int length; // 栈长度(元素个数)
} LinkedStack;
/**
* 初始化链式栈
* @param s 待初始化的链式栈指针
*/
void InitStack(LinkedStack* s) {
s->top = NULL;
s->length = 0;
}
/**
* 判断链式栈是否为空
* @param s 被判断的链式栈
* @return 如果栈为空返回true,否则返回false
*/
bool IsEmpty(LinkedStack* s) {
return s->top == NULL;
}
/**
* 元素入栈
* @param s 待操作的链式栈指针
* @param x 入栈的元素
* @return 如果入栈成功返回true,否则返回false
*/
bool Push(LinkedStack* s, data_t x) {
StackNode* node = (StackNode*)malloc(sizeof(StackNode)); // 创建新节点
if (!node) { // 创建节点失败
return false;
}
node->data = x; // 节点存储元素x
node->next = s->top; // 新节点的next指针指向原来的栈顶
s->top = node; // 新节点成为新的栈顶
s->length++; // 栈长度+1
return true;
}
/**
* 元素出栈
* @param s 待操作的链式栈指针
* @param x 出栈元素的指针
* @return 如果出栈成功返回true,否则返回false
*/
bool Pop(LinkedStack* s, data_t* x) {
if (IsEmpty(s)) { // 空栈不能出栈
return false;
}
StackNode* node = s->top; // 把栈顶元素取出来
*x = node->data; // 取出节点存储的值
s->top = node->next; // 栈顶指针指向下一个元素
free(node); // 释放节点内存
s->length--; // 栈长度-1
return true;
}
/**
* 获取栈顶元素
* @param s 待操作的链式栈指针
* @param x 栈顶元素的指针
* @return 如果获取成功返回true,否则返回false
*/
bool GetTop(LinkedStack* s, data_t* x) {
if (IsEmpty(s)) {
return false;
}
*x = s->top->data; // 取出栈顶元素
return true;
}
/**
* 获取栈的长度(元素个数)
* @param s 被计算长度的链式栈
* @return 栈的元素个数
*/
int GetLength(LinkedStack* s) {
return s->length;
}
/**
* 清空链式栈
* @param s 待操作的链式栈指针
*/
void ClearStack(LinkedStack* s) {
data_t tmp;
while (!IsEmpty(s)) { // 不断进行出栈操作,直到栈为空
if (!Pop(s, &tmp)) { // 弹出失败时退出循环
break;
}
}
}
/**
* 销毁链式栈
* @param s 待操作的链式栈指针的地址
*/
void DestroyStack(LinkedStack** s) {
ClearStack(*s); // 先清空栈
free(*s); // 释放链式栈节点内存
*s = NULL; // 置链式栈指针为空指针
}
int main() {
LinkedStack* s = (LinkedStack*)malloc(sizeof(LinkedStack));
InitStack(s); // 初始化链式栈指针
int i, x;
for (i = 1; i <= 10; i++) {
Push(s, i); // 元素入栈
}
printf("The length of stack is %d.\n", GetLength(s)); // 获取栈长度
GetTop(s, &x);
printf("The top of stack is %d.\n", x); // 获取栈顶元素
for (i = 1; i <= 10; i++) {
if (Pop(s, &x)) { // 元素出栈
printf("%d ", x); // 输出出栈元素
}
}
printf("\n");
printf("The length of stack is %d.\n", GetLength(s)); // 获取栈长度
DestroyStack(&s); // 销毁链式栈
return 0;
}
三、队列实现
所谓的队列是一个特殊的线性表,不可以在线性表的中间位置进行操作,在线性表的两端分别实现数据的插入操作和删除操作。
- 队头(front):线性表的表头,数据的删除端,也称为数据元素的出队;
- 队尾(rear):线性表的表尾,数据的插入端,也称为数据元素的入队。
数据操作的特点:先进先出
数据的存储方式:顺序存储和和链式存储。
1.循环队列原理
这里要向各位介绍一下如何用数组实现一个存放整型数据的队列。用数组实现队列主要需要以变量和函数。(下面文字描述较多,可先看图理解)
► 用来存放数据的一维整型数组:Q
enqueue 来的数据存放在数组 0的各元素中。需要根据问题内容保证足够的内存空间。
► 用作队头指针的整型变量:head
指示队列开头位置的变 dequeue 会取出所指的元素。请注意,队头元素的下标不总是 0。
► 用作队尾指针的整型变量:tail
指示队列末尾 + 1 是哪个位置( 最后一个元素的后一个位置 )的变量。tail所指的位置就是要添加新元素的位置。head与tail夹着的部分( 不包含tail所指的元素 )是队列的内容。
► 向队列添加新元素 x 的函数:enqueue ( x)
将x代queue[tail]然后tail加 1。
► 从队列开头取出元素的函数:dequeue ( )
返问queue[head] 的值,然后head加1
下面我们来看看实际操作顺序队列时的样子。下图演示了队列中值的添加和取出。
当head和tail—致时队列为空,但此时这两个变量不一定为0。每执行一次 enqueue (x) ,新元素就会加人到 tail 的位置,然后 tail 增加 1。执行 dequeue ( ) 则会返冋 head 所指的元素,然后 head 加 1。
用数组实现队列会随着数据不断进,head 与 tail 之间的队列主体部分会逐渐向数组末尾( 图的右侧 ) 移动。这样一来,tail 和 head 很快会超出数组的容量上限。如果 tail 超出数组下标上限即判定为向上溢出,那么整个数组中会有相当大的一部分空间被白白浪费掉。然而,若想防止这一情况需要让 head 时常保持在 0, 即每次执行完 dequeue() 之后让数据整体向数组开头( 左侧 )移动,但每次移动都会增加 0(n) 的复杂度。
为应对这个问题,我们可以把构成队列的数组视为环形缓冲K来管理数据。
环形缓冲区由一维数组构成,指示队列范围的 head 和 tail 指针在超出数组范围时重新从数组开头开始循环。也就是说,如果指针加 1 之后超出了数组范围,就重新置为 0。
上图模拟了在一个已存有若干数据的队列中存取数据时的情况。执行 enqueue(1)向队列中添加 1 时, tail 的值 7 + 1 = 8 超出了数组范围,因此将 tail 重置为 0。如果按照顺时针方向观察环形缓冲区,那么队列中的现存数据按照 head-->tail 的顺序排列。另外,为区分队列的空与满,我们规定 tail --> head 之间至少要有一个空位。
2.链式队列原理
链式队列是一种同时带有队头指针和队尾指针的单链表,头指针指向队头结点,尾指针指向队尾结点。
如下图,Node区域为队列链表结点区(带头结点链表),queue为队列头尾指针区(头指针 head 指向队头结点,尾指针 tail 指向队尾结点)。
3.循环顺序队列
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int data_t;
typedef struct {
data_t* data; // 数组指针,用于动态分配存储空间
int max_size; // 队列的最大长度
int front; // 队头指针,指向队列中第一个元素的下标
int rear; // 队尾指针,指向队列中最后一个元素的下一个位置的下标
} queue;
/**
* 初始化队列
* @param q 队列指针
* @param size 队列的初始大小
*/
void InitQueue(queue* q, int size) {
q->data = (data_t*)malloc(size * sizeof(data_t)); // 分配max_size个data_t类型的空间
q->max_size = size;
q->front = q->rear = 0;
}
/**
* 获取队列的当前长度(元素个数)
* @param q 队列指针
* @return 队列的当前长度
*/
int GetLength(queue* q) {
return (q->rear - q->front + q->max_size) % q->max_size;
}
/**
* 扩展队列的容量
* @param q 队列指针
*/
void ExpandQueue(queue* q) {
int new_size = q->max_size * 2; // 扩容后的队列最大长度是原来的两倍
data_t* new_data = (data_t*)malloc(new_size * sizeof(data_t));
int i, j;
// 将原队列中的元素复制到新队列中
j = q->front;
for (i = 0; i < GetLength(q); i++) {
new_data[i] = q->data[j];
j = (j + 1) % q->max_size;
}
free(q->data); // 释放原有空间
q->data = new_data; // 替换为新的空间
q->front = 0;
q->rear = i; // 注意这里的队尾指针
q->max_size = new_size;
}
/**
* 判断队列是否已满
* @param q 队列指针
* @return 如果队列已满返回true,否则返回false
*/
bool IsFull(queue* q) {
return (q->rear + 1) % q->max_size == q->front;
}
/**
* 入队操作
* @param q 队列指针
* @param x 入队的元素
*/
void InQueue(queue* q, data_t x) {
if (IsFull(q)) {
ExpandQueue(q); // 如果队列满了,先进行扩容
}
q->data[q->rear] = x;
q->rear = (q->rear + 1) % q->max_size;
}
/**
* 判断队列是否为空
* @param q 队列指针
* @return 如果队列为空返回true,否则返回false
*/
bool IsEmpty(queue* q) {
return q->front == q->rear;
}
/**
* 出队操作
* @param q 队列指针
* @param x 出队元素的指针
* @return 如果出队成功返回true,否则返回false
*/
bool OutQueue(queue* q, data_t* x) {
if (IsEmpty(q)) {
printf("queue is empty!\n");
return false;
}
else {
*x = q->data[q->front];
q->front = (q->front + 1) % q->max_size;
return true;
}
}
/**
* 获取队头元素
* @param q 队列指针
* @param x 队头元素的指针
* @return 如果获取成功返回true,否则返回false
*/
bool GetHead(queue* q, data_t* x) {
if (!IsEmpty(q)) {
*x = q->data[q->front];
return true;
}
else {
return false;
}
}
/**
* 清空队列
* @param q 队列指针
*/
void ClearQueue(queue* q) {
q->front = q->rear = 0;
}
/**
* 销毁队列
* @param q 队列指针
*/
void DestroyQueue(queue* q) {
ClearQueue(q); // 清空队列
free(q->data); // 释放动态分配的内存
q->data = NULL; // 防止野指针
}
int main() {
queue q; // 普通结构体不需要动态分配内存
int i, x;
InitQueue(&q, 5);
for (i = 0; i < 7; i++) { // 入队7个元素,会自动扩容
InQueue(&q, i);
}
printf("Head element: %d\n", GetHead(&q, &x));
while (!IsEmpty(&q)) {
OutQueue(&q, &x);
printf("%d\n", x);
}
DestroyQueue(&q);
return 0;
}
4.链式队列
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
/* 定义数据类型 */
typedef int data_t;
/* 定义节点结构体 */
typedef struct qnode {
data_t data;
struct qnode* next;
} qNode;
/* 定义队列结构体,进行封装 */
typedef struct {
qNode* front; // 队头指针
qNode* rear; // 队尾指针
int length; // 队列长度
} queue;
/* 初始化队列 */
void initQueue(queue* q) {
q->front = q->rear = NULL;
q->length = 0;
}
/* 判断队列是否为空 */
bool isEmpty(queue* q) {
return q->front == NULL;
}
/* 入队,返回值表示是否入队成功 */
bool inQueue(queue* q, data_t x) {
qNode* p = (qNode*)malloc(sizeof(qNode));
if (!p) { // 分配内存失败
return false;
}
/* 创建新的节点 */
p->data = x;
p->next = NULL;
if (isEmpty(q)) { // 队列为空
q->front = q->rear = p;
}
else { // 队列非空,将新节点插入到队尾
q->rear->next = p;
q->rear = p;
}
q->length++; // 更新队列长度
return true;
}
/* 出队,返回值表示是否出队成功 */
bool outQueue(queue* q, data_t* x) {
if (isEmpty(q)) { // 队列为空,无法出队
return false;
}
/* 取得队头节点及数据 */
qNode* p = q->front;
*x = p->data;
q->front = p->next;
/* 若队列只存在一个节点,则需要更新队尾指针 */
if (q->rear == p) {
q->rear = NULL;
}
free(p); // 释放节点内存
q->length--; // 更新队列长度
return true;
}
/* 获取队头元素,返回值表示是否获取成功 */
bool getHead(queue* q, data_t* x) {
if (isEmpty(q)) { // 队列为空,无法获取
return false;
}
/* 取得队头数据 */
*x = q->front->data;
return true;
}
/* 获取队列长度 */
int getLength(queue* q) {
return q->length;
}
/* 清空队列 */
void clearQueue(queue* q) {
data_t x;
while (outQueue(q, &x)) {
continue;
}
}
/* 销毁队列 */
void destroyQueue(queue* q) {
clearQueue(q); // 先清空队列元素
free(q); // 释放队列内存
q = NULL;
}
/* 主函数 */
int main() {
queue* q = (queue*)malloc(sizeof(queue)); // 创建队列
initQueue(q); // 初始化队列
int len = getLength(q); // 获取队列长度
printf("len = %d\n", len);
/* 插入10个元素 */
for (int i = 0; i < 10; i++) {
inQueue(q, i);
}
len = getLength(q); // 获取队列长度
printf("len = %d\n", len);
/* 依次出队两个元素,然后获取队列长度并输出 */
data_t x;
outQueue(q, &x);
printf("outqueue %d\n", x);
outQueue(q, &x);
printf("outqueue %d\n", x);
len = getLength(q);
printf("len = %d\n", len);
clearQueue(q); // 清空队列元素
len = getLength(q); // 获取队列长度
printf("len = %d\n", len);
destroyQueue(q); // 销毁队列
return 0;
}
参考:
- 石田保辉:《我的第一本算法书》
- 渡部有隆:《挑战程序设计竞赛2》
- 程杰:《大话数据结构》