老师给的去年真题
选择题
| 得 分 | 一、单项选择题(本题 30 分,每题 2 分) | |
|---|---|---|
| 评卷人 |
1.一个算法应该是( B )。
A.程序
B.问题求解步骤的一种描述
C.要满足五个基本特征
D.A和C
2、数据结构在计算机中的映像称为( A )。
A.数据的存储结构 B.数据的基本操作
C.程序的算法 D.数据的逻辑结构
4、在线性表的顺序存储结构上执行( D )操作效率较高。
A.插入元素 B.删除元素 C.添加元素 D.访问元素
线性表的顺序存储(比如数组)是用一段连续的内存空间存储数据元素,并且支持随机访问。只要知道元素的下标,就可以通过公式 地址 = 基地址 + 下标 × 元素大小 直接定位到目标元素,时间复杂度为 O(1) ,因此访问操作的效率极高。
5、对空栈S进行Push和Pop操作,入栈序列为a,b,c,d,经过Push、Push、Pop、Push、Pop、Push、Pop操作后得到的出栈序列是( C )。
A.b d c B.a c d C.b c d D.b d a
-
初始状态:栈 S 为空
-
Push(a) → 栈内元素:
[a] -
Push(b) → 栈内元素:
[a, b] -
Pop() → 弹出栈顶的
b,出栈序列:[b],栈内剩余:[a] -
Push(c) → 栈内元素:
[a, c] -
Pop() → 弹出栈顶的
c,出栈序列:[b, c],栈内剩余:[a] -
Push(d) → 栈内元素:
[a, d] -
Pop() → 弹出栈顶的
d,出栈序列:[b, c, d]
6、在进行入栈运算时,应先判断栈是否( B )。
A.空 B.满 C.上溢 D.下溢
- 如果栈已满,此时再执行入栈会触发 上溢(栈溢出)的错误,无法完成操作。
- 选项 C 的上溢是入栈失败的结果,而非判断条件;
- 选项 A 的空是执行出栈(Pop)运算前的判断条件;
- 选项 D 的下溢是栈空时执行出栈的错误结果。
7、若一棵二叉树有2个度为2的结点,3个度为1的结点,则叶子结点个数为( B )。
A.1 B.3 C.2 D.不确定
对于任意一棵二叉树,存在以下两个关键关系:
-
结点总数
n = n₀ + n₁ + n₂其中,n₀= 叶子结点数(度为 0),n₁= 度为 1 的结点数,n₂= 度为 2 的结点数。 -
分支总数
B = n - 1 = n₁ + 2×n₂二叉树中,度为 1 的结点贡献 1 个分支,度为 2 的结点贡献 2 个分支,叶子结点无分支;且所有结点的分支总数等于结点总数减 1(根结点没有父分支)。
已知条件:n₂ = 2,n₁ = 3
- 根据分支公式计算结点总数减 1 的值:
B = 3 + 2×2 = 7因此结点总数n = 7 + 1 = 8 - 代入结点总数公式求叶子结点数
n₀:n₀ = n - n₁ - n₂ = 8 - 3 - 2 = 3
8、下述编码中哪一个是前缀码?( D )
A.(00,001,010,011) B.(10,11,100,111)
C.(10,100,1110,1111) D.(000,001,010,101)
前缀码要求:集合中的任意一个编码,都不能是另一个编码的前缀。前缀码常用于哈夫曼编码等场景,目的是保证解码时不会产生歧义。
9、连通具有 n 个顶点(n>1)的有向图至少需要( B )条边。
A.n-1 B.n C.n+1 D.n(n-1)/2
- 选项 A(n-1) :这是无向连通图的最少边数(生成树的边数),但有向图中,n-1 条边只能形成一条有向链,无法保证任意两点的可达性,不满足连通要求。
- 选项 C(n+1) :边数超过了最小值,不符合 “至少” 的要求。
- 选项 D(n (n-1)/2) :这是 n 个顶点无向完全图的边数,数量远大于最小值。
10、在一个有向图的邻接表中,若表结点的个数为m,则图中弧的个数为( A )。
A.m B.m/2 C.2m D.不确定
比如这张图 5 = 5
11、下图所示的有向图进行拓扑排序,得到的拓扑序列可能是( C )。
A.1,3,5,2,4,6 B.1,3,4,2,5,6
C.1,3,2,4,5,6 D.1,3,5,4,2,6
12、二叉排序树上的删除操作,通常发生在( A )的情况。
A.查找成功 B.查找失败 C.插入成功 D.插入失败
二叉排序树的删除操作,目的是移除指定的目标结点。要完成这个操作,必须先找到这个目标结点
13、对线性表(23,34,48,20,63,79,71,56)进行散列存储时,若选用H(K)=K % 7 作为散列函数,则散列地址为1的元素有( A )个。
A.1 B.2 C.3 D.4
小学生题目
14、关键字初始序列为{1,9,4,5,7,6},以1为枢轴,一趟快排后为(C)。
A.{1,4,5,6,7,9} B.{6,1,7,5,4,9}
C.{1,9,4,5,7,6} D.{6,5,1,4,7,9}
推导过程基于快速排序的一趟划分规则。
以指定元素为枢轴,将序列划分为两部分:左边元素 ≤ 枢轴,右边元素 ≥ 枢轴,最终枢轴落到其最终位置。我们以 1 作为枢轴,分析初始序列 {1,9,4,5,7,6} 的一趟划分过程:
-
初始化双指针:左指针
low指向第一个元素(值为 1,即枢轴),右指针high指向最后一个元素(值为 6)。 -
快速排序的划分逻辑是先移动右指针找小于枢轴的元素,再移动左指针找大于枢轴的元素,然后交换两者。
-
因为枢轴值为
1,是序列中的最小值:- 右指针
high从右往左遍历,所有元素(9、4、5、7、6)都大于1,会一直移动到low的位置(即枢轴自身)。 - 此时左右指针重合,无需交换任何元素,直接将枢轴放在当前位置即可。
- 右指针
-
一趟划分后,序列没有发生任何变化,仍然是
{1,9,4,5,7,6}。
15、将数据序列(7,8,9,3,1,2,6)建成大顶堆时,正确的序列变化过程是( C )。
A.7,8,9,1,3,2,6 → 7,8,9,1,2,3,6 → 7,8,9,1,2,3,6 → 9,8,7,3,1,2,6
B.7,8,9,3,1,6,2 → 7,8,9,3,1,6,2 → 9,8,7,3,1,2,6 → 9,8,7,3,1,2,6
C.7,8,9,3,1,2,6 → 7,8,9,3,1,2,6 → 9,8,7,3,1,2,6 → 9,8,7,3,1,2,6
D.7,8,9,1,3,2,6 → 7,8,9,1,3,2,6 → 7,8,9,3,1,6,2 → 9,8,7,3,1,2,6
大顶堆的构建规则(筛选法) 。
大顶堆的定义是:每个结点的值都大于或等于其左右孩子结点的值,且堆是一棵完全二叉树。构建大顶堆的顺序是从最后一个非叶子结点开始,向前依次进行堆调整。
步骤 1:确定初始序列的完全二叉树结构
初始序列 (7,8,9,3,1,2,6),结点下标(从 0 开始)与对应关系:
- 下标 0(根):7,左孩子 1(8)、右孩子 2(9)
- 下标 1:8,左孩子 3(3)、右孩子 4(1)
- 下标 2:9,左孩子 5(2)、右孩子 6(6)
- 下标 3~6:叶子结点
最后一个非叶子结点的下标 = ⌊(n−1)/2⌋ = ⌊(7−1)/2⌋=2,因此调整顺序是 下标 2 → 下标 1 → 下标 0。
步骤 2:依次调整每个非叶子结点
- 调整下标 2 的结点(值为 9左右孩子是 2(值 2)和 6(值 6),9≥2 且 9≥6,满足大顶堆要求,无需调整,序列保持
(7,8,9,3,1,2,6)。 - 调整下标 1 的结点(值为 8左右孩子是 3(值 3)和 1(值 1),8≥3 且 8≥1,满足大顶堆要求,无需调整,序列仍为
(7,8,9,3,1,2,6)。 - 调整下标 0 的结点(值为 7左右孩子是 8(值 8)和 9(值 9),其中最大的孩子是右孩子 9。因为 7<9,需要交换根结点和右孩子的值,交换后序列变为
(9,8,7,3,1,2,6)。交换后检查被交换的子树(原右孩子位置,现在值为 7),其左右孩子是 2 和 6,7≥2 且 7≥6,满足要求,调整结束。
步骤 3:匹配选项的序列变化过程
整个构建的序列变化为:7,8,9,3,1,2,6 → 7,8,9,3,1,2,6 → 9,8,7,3,1,2,6 → 9,8,7,3,1,2,6与选项 C 完全一致。
简答题
| 得 分 | 二、简答题(本题 共 8 分,每题 4 分) | |
|---|---|---|
| 评卷人 |
1、已知数组定义A[5][6],每个元素占2个字节,数组基址为1000,分别求A[3][2]的按行序和列序的存储地址?(各维下标从0开始)(满分4分)
提示:
解:
行序:LOC(3, 2) = 1000 + (6*3+2)*2 = 1040
列序:LOC(3, 2) = 1000 + (5*2+3)*2 = 1026
A [5][6]:数组
-
含义:表示一个二维数组的类型声明或大小说明,其中:
- 第一个维度
5表示数组有5 行(行下标范围通常为0~4); - 第二个维度
6表示数组有6 列(列下标范围通常为0~5);
- 第一个维度
-
示例:在 C 语言中,
int A[5][6];占用5×6×sizeof(int)字节的连续内存空间。
A [3][2]:数组中的具体元素
- 含义:表示二维数组
A中第 4 行第 3 列的元素. - 示例:若数组
A按行序存储,基址为 1000,每个元素占 2 字节,则A[3][2]的地址为:1000+(3×6+2)×2=1040该地址对应的值即为A[3][2]的内容
2、已知矩阵如下,请给出其三元组表示。(满分4分)
解:
假设 i 表示 行 , j 表示列 , e 表示 element .
| i | j | e |
|---|---|---|
| 1 | 6 | 1 |
| 2 | 3 | 3 |
| 3 | 2 | 1 |
| 4 | 3 | 4 |
| 4 | 5 | 7 |
综合题
| 得 分 | 三、综合题(本题 共 47 分) | |
|---|---|---|
| 评卷人 |
1、已知二叉树如图,请给出其先序访问序列。并在图中加上线索,使之构成先序线索树。(满分6分)
解:
先序访问序列 : ABCDEF
先序线索树的规则是:
- 若结点的左孩子为空,则左指针改为指向其先序前驱;
- 若结点的右孩子为空,则右指针改为指向其先序后继;
- 需添加标志位区分指针是 “孩子指针” 还是 “线索指针”(通常用
ltag/rtag:0 表示孩子,1 表示线索)。
对该二叉树的线索化处理(结合先序序列 A,B,C,D,E,F):
| 结点 | 左孩子情况 | 左线索(先序前驱) | 右孩子情况 | 右线索(先序后继) |
|---|---|---|---|---|
A | 有左孩子 B | - | 有右孩子 E | - |
B | 有左孩子 C | - | 有右孩子 D | - |
C | 无左孩子 | 先序前驱 B | 无右孩子 | 先序后继 D |
D | 无左孩子 | 先序前驱 C | 无右孩子 | 先序后继 E |
E | 无左孩子 | 先序前驱 D | 有右孩子 F | - |
F | 无左孩子 | 先序前驱 E | 无右孩子 | 无(序列末尾) |
线索树的连线方式:
2、画出和下列已知访问序列对应的二叉树T。并画出该二叉树对应的森林。(满分6分)
先序序列为:abced
中序序列为:acebd
解:
构建步骤:
-
确定根节点:先序序列的第一个元素为根节点,即
a。 -
划分左右子树:在中序序列中,根节点
a左侧无元素,右侧为c e b d,因此a无左子树,右子树的先序序列为b c e d,中序序列为c e b d。 -
递归构建右子树:
- 右子树的根节点为
b(先序序列第二个元素)。 - 在中序序列
c e b d中,b左侧为c e(左子树),右侧为d(右子树)。 - 左子树的先序序列为
c e,中序序列为c e:根节点为c,e为其右子树。 - 右子树的先序序列为
d,中序序列为d:根节点为d,无左右子树。
- 右子树的根节点为
二叉树结构:
a
\
b
/ \
c d
\
e
将二叉树转换为森林
转换规则:
- 二叉树的根节点是森林中第一棵树的根节点。
- 根节点的右子树依次是森林中后续树的根节点。
- 每个节点的左子树为该节点在对应树中的子树。
3、假设用于通信的电文仅由7个字母组成,字母在电文中出现的频率分别为2、22、30、21、14、1、10(单位:%),请为这7个字母设计哈夫曼编码。(满分7分)
要求:先构造哈夫曼树,然后再依据该树构造哈夫曼编码。
解:
哈夫曼树的构造规则是每次选取权值最小的两个结点合并,新结点权值为两者之和,重复此过程直到合并为一棵树。
哈夫曼编码的规则是从根结点到叶子结点的路径,左分支记为 0,右分支记为 1,最终每个叶子结点对应的路径序列即为其编码。
4、请画出右图的邻接矩阵。(满分4分)
解:
最简单的一道题
5、对于右图所给的无向网,请画图分步骤给出用普里姆算法构造其最小生成树的过程(从a点出发)。(满分6分)
解:
普里姆算法的规则是:从起始顶点出发,每次选择 “已选顶点集合” 到 “未选顶点集合” 的权值最小的边,将对应未选顶点加入已选集合,重复至所有顶点加入。
6、请在下图所示的AOE-网中标识出每个顶点的 ve, vl , vl-ve。并找出其中关键路径上的点。(满分7分)(e : early ; l : last)
提示:可以使用如下方法标识:
看题
解答:
vl(F)=ve(F)=12(终点最迟时间等于最早时间)
ve 的计算规则:从起点(A)开始,按拓扑序累加边权,取最大值。
vl 的计算规则:从终点(F)开始,按逆拓扑序递减边权,取最小值。
vl-ve 值为 0 的是关键路径上的点。
7、设查找的关键字序列为 { 3,2,1,6,7} ,请给出从空树出发,构造二叉排序树的过程。(满分6分)
解:
二叉排序树的构造规则是:新结点小于当前结点则插入左子树,大于则插入右子树
8、已知关键字初始序列{9,7,8,5,2,4},请给出使用直接插入排序算法排序的过程。(满分5分)
解:
| 初始状态:{(9),7,8,5,2,4} |
|---|
| 第一趟后:{(7,9),8,5,2,4} |
| 第二趟后:{(7,8,9),5,2,4} |
| 第三趟后:{(5,7,8,9),2,4} |
| 第四趟后:{(2,5,7,8,9),4} |
| 第五趟后:{(2,4,5,7,8,9)} |
上面这个哈哈哈送分题
代码题
| 得 分 | 四、算法设计题(本题 共 15 分) | |
|---|---|---|
| 评卷人 |
统计结点总数(同8题)
1、请编写递归算法,计算二叉树 T 中结点总数。(满分8分)
提示:已知二叉树的存储结构为:
typedef struct BiTNode { // 结点结构
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;
解:
int NodeCount(BiTree T){
//统计二叉树T中结点的个数
if(T == NULL ){
return 0; //如果是空树,则结点个数为0,递归结束
} else {
return NodeCount(T -> lchild) + NodeCount(T -> rchild) + 1;
//否则结点个数为左子树结点个数+右子树结点个数+1
}
}
递归查找算法(同15题)
2、请写出 二叉排序树 中的 递归查找算法。(满分7分)
解:
BSTree SearchBST(BSTree T, KeyType key){
//在根指针T所指的二叉排序树中递归地查找
//某关键字等于key的数据元素。
//若查找成功,则返回指向该数据元素结点的指针,
//否则返回空指针。
if((!T) || key == T -> data.key){
return T;//查找结束
} else if (key < T -> data.key){
return SearchBST(T -> lchild, key);//在左子树中继续查找
} else {
return SearchBST(T -> rchild, key); //在右子树中继续查找
}
} // SearchBST
代码题(15分)题库
二叉树的 先中后 序遍历
1.先序遍历
1、请编写递归算法,对二叉树进行先序遍历。
提示:已知二叉树的存储结构为:
typedef struct BiTNode { // 结点结构
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;
解:
void PreOrder (BiTree T)){
// 先序遍历二叉树
if (T) {
printf(T -> data); // 访问结点(根)
PreOrder(T -> lchild); // 遍历左子树
PreOrder(T -> rchild); // 遍历右子树
}
}
2.中序遍历
2、请编写递归算法,对二叉树进行中序遍历。
提示:已知二叉树的存储结构为:
typedef struct BiTNode { // 结点结构
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;
解:
void InOrder (BiTree T){
// 中序遍历二叉树
if (T) {
InOrder(T->lchild); // 遍历左子树
printf(T->data); // 访问结点
InOrder(T->rchild); // 遍历右子树
}
}
3.后序遍历(考)
3、请编写递归算法,对二叉树进行后序遍历。
提示:已知二叉树的存储结构为:
typedef struct BiTNode { // 结点结构
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;
解:
void PostOrder (BiTree T) {
// 后序遍历二叉树
if (T) {
PostOrder(T->lchild); // 遍历左子树
PostOrder(T->rchild); // 遍历右子树
printf(T->data); // 访问结点
}
}
二叉树的基本操作
4.统计叶子结点个数
4、请编写递归算法,统计二叉树中叶子结点的个数。
提示:已知二叉树的存储结构为:
typedef struct BiTNode { // 结点结构
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;
解:
void CountLeaf (BiTree T, int &count){
// 统计树中叶子结点的个数
if ( T ) { // 1. 结点非空判断:递归的终止条件之一(避免访问空指针)
// 2. 叶子结点判断条件:当前结点无左孩子且无右孩子
if ((!T -> lchild) && (!T -> rchild)){
count++; // 3. 叶子结点计数:满足条件则计数器自增
}
// 4. 递归遍历左子树:继续统计左子树中的叶子结点
CountLeaf( T -> lchild, count);
// 5. 递归遍历右子树:继续统计右子树中的叶子结点
CountLeaf( T -> rchild, count);
}
}
5.复制二叉树
5、请编写递归算法,复制一棵和二叉树 T 完全相同的二叉树 NewT。
提示:已知二叉树的存储结构为:
typedef struct BiTNode { // 结点结构
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;
解:
void Copy(BiTree T, BiTree &NewT){
//复制一棵和T完全相同的二叉树
if (T==NULL){//如果是空树,递归结束
NewT=NULL; return;
}
else{
NewT = new BiTNode;
NewT->data = T->data; //复制根结点
Copy(T->lchild, NewT->lchild);//递归复制左子树
Copy(T->rchild, NewT->rchild);//递归复制右子树
}
}
6.左右子树交换
6、请编写递归算法,将二叉树中所有节点的左、右子树互相交换。
提示:已知二叉树的存储结构为:
typedef struct BiTNode { // 结点结构
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;
一、Status 的来源与定义
在很多教材(尤其是严蔚敏版《数据结构(C 语言版)》)和配套代码中,为了统一管理函数的返回状态,会预先用 typedef 定义一个 Status 类型,例如:
typedef enum {
OK,
ERROR,
TRUE,
FALSE
} Status;
这里的 Status 本质上就是一个整数类型(或者枚举类型),用于:
- 表示函数执行是否成功(
OK表示成功,ERROR表示失败)。 - 表示某些条件是否满足(
TRUE表示真,FALSE表示假)。
解:
// Status 是自定义状态类型(通常对应 int,OK 是自定义常量,通常为 1 表示操作成功)
Status Exchange_BT(BiTree *T) {
BiTree p; // 临时指针,用于交换左右子树时的暂存
if (*T) { // 1. 判断当前结点(*T 是指向二叉树根结点的实际内容,非空才执行交换逻辑)
// 2. 核心交换操作:互换当前结点的左、右子树
p = (*T)->lchild; // 用临时指针 p 暂存左孩子
(*T)->lchild = (*T)->rchild; // 右孩子赋值给左孩子
(*T)->rchild = p; // 临时指针中暂存的原左孩子赋值给右孩子
// 3. 递归交换左子树的所有结点左右子树
if ((*T)->lchild)
Exchange_BT(&(*T)->lchild);
// 4. 递归交换右子树的所有结点左右子树
if ((*T)->rchild)
Exchange_BT(&(*T)->rchild);
return OK; // 非空结点处理完成,返回成功状态
} else {
return OK; // 空结点无需处理,直接返回成功状态
}
}
7.求二叉树的深度
7、请编写递归算法,求二叉树的深度。
提示:已知二叉树的存储结构为:
typedef struct BiTNode { // 结点结构
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;
解:
int Depth(BiTree T){//计算二叉树T的深度
if(T==NULL){
return 0; //如果是空树,深度为0,递归结束
}else{
m=Depth (T->lchild); //递归计算左子树的深度记为m
n=Depth (T->rchild); //递归计算右子树的深度记为n
if(m>n){
return(m+1); //二叉树的深度为m与n的较大者加1
}else{
return (n+1);
}
}
}
8.统计结点总数
8、请编写递归算法,计算二叉树 T 中结点总数。
提示:已知二叉树的存储结构为:
typedef struct BiTNode { // 结点结构
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;
解:
int NodeCount(BiTree T){
//统计二叉树T中结点的个数
if(T == NULL ){
return 0; //如果是空树,则结点个数为0,递归结束
}else{
return NodeCount(T -> lchild) + NodeCount(T -> rchild) + 1;
//否则结点个数为左子树结点个数+右子树结点个数+1
}
}
查找和排序算法
9.顺序查找
9、请写出顺序查找算法。
typedef struct {
ElemType R[100]; // 存储顺序表的数组(R[0]为哨兵位,R[1]~R[99]为有效元素)
int length; // 顺序表的实际长度(有效元素的个数) ;
}SSTable;
解:
int Search_Seq(SSTable ST, KeyType key){
//在顺序表 ST 中顺序查找其关键字等于key的数据元素。
//若找到,则函数值为该元素在表中的位置,否则为0
//**前置操作**:把要查找的`key`放到
//顺序表的**第 0 个位置**(`ST.R[0].key = key`),这个位置的元素就是 “哨兵”。
//**查找过程**:从表的**最后一个元素**(`i = ST.length`)
//开始往前遍历,直到找到`ST.R[i].key == key`为止。
//- **无需越界判断**:因为第 0 个位置已经存了`key`,
//所以循环一定会在`i=0`时终止(此时`ST.R[0].key == key`)
//不需要额外判断`i >= 1`,简化了循环条件。
int i; // 声明循环变量
//在顺序表 ST
ST.R[0].key = key; //“哨兵”
for(i = ST.length; ST.R[i].key != key; --i); //从后往前找
return i;
}
10.冒泡排序(考)
10、请写出冒泡排序算法。
假设结构定义如下(参考严蔚敏版《数据结构》):
#define MAXSIZE 100
typedef int KeyType;
typedef struct {
KeyType key;
// 其他数据域...
} ElemType;
typedef struct {
ElemType r[MAXSIZE + 1]; // r[0] 闲置或用作哨兵
int length;
} SqList;
解:
// 最基础的冒泡排序(升序排列,无任何优化)
void bubble_sort_basic(int a[], int n) {
// 外层循环:控制冒泡的趟数,总共需要 n-1 趟
// 每一趟都会将当前未排序部分的最大元素"浮"到末尾
for (int i = 0; i < n - 1; ++i) {
// 内层循环:遍历当前未排序部分,进行相邻元素比较交换
// 每趟结束后,末尾 i 个元素已排序,无需再比较,故 j < n-1 - i
for (int j = 0; j < n - 1 - i; ++j) {
// 升序判断:前一个元素大于后一个,交换两者位置
if (a[j] > a[j + 1]) {
int temp = a[j]; // 临时变量暂存前一个元素
a[j] = a[j + 1]; // 后一个元素前移
a[j + 1] = temp; // 前一个元素后移(完成交换)
}
}
}
}
### 无标志位的传统冒泡排序
- **逻辑**:无论序列是否已经有序,都会完整执行`n-1`轮(`n`是序列长度)
的 “相邻元素比较 + 交换”。
- **问题**:如果序列在某一轮后已经完全有序,后续轮次的比较是无效的,会浪费时间。
### 2. 加标志位的优化版冒泡排序
- **逻辑**:每轮排序前先将标志位设为`false`;若本轮发生了元素交换
说明序列还未完全有序,将标志位设为`true`;若本轮未发生
交换(标志位保持`false`),说明序列已经有序,直接终止排序。
- **优势**:能提前终止不必要的轮次,减少比较次数
//## 优化版冒泡排序
void bubble_sort(int a[], int n) {
// 将数组 a 按升序排列
bool change; // 标记本趟冒泡是否发生元素交换,用于提前终止排序
// 外层循环:控制冒泡趟数,i 表示每趟未排序部分的末尾下标
for (int i = n - 1; i >= 1 && change; --i) {
change = false; //初始假设本趟没有交换,先置为false
// 内层循环:遍历当前未排序部分,进行相邻元素比较交换
for (int j = 0; j < i; ++j) {
if (a[j] > a[j + 1]) {
// 交换 a[j] 和 a[j+1]
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
change = true;// 标记本趟发生了交换,说明数组尚未完全有序
}
}
}
}
//## 顺序表冒泡排序
void BubbleSort(SqList *L) {
int m = L->length - 1;
int flag = 1; // 1 表示有交换,0 表示无交换
while (m > 0 && flag == 1) {
flag = 0; // 初始假设无交换
for (int j = 1; j <= m; j++) {
if (L->r[j].key > L->r[j + 1].key) {
flag = 1; // 有交换
ElemType t = L->r[j];
L->r[j] = L->r[j + 1];
L->r[j + 1] = t;
}
}
--m;
}
}
11.折半查找
11、请写出折半查找算法。
假设顺序表结构定义如下(参考严蔚敏版《数据结构》):
#define MAXSIZE 100 // 顺序表最大长度
typedef int KeyType; // 关键字类型
typedef struct {
KeyType key; // 关键字域
// 其他数据域...
} ElemType;
typedef struct {
ElemType R[MAXSIZE + 1]; // 存储数据元素,R[0] 闲置
int length; // 顺序表长度
} SSTable;
解:
int Search_Bin(SSTable ST, KeyType key) {
// 在有序表 ST 中折半查找关键字等于 key 的元素
// 若找到,返回其位置(从 1 开始);否则返回 0
int low = 1; // 查找区间左边界
int high = ST.length; // 查找区间右边界
int mid; // 中间位置
while (low <= high) {
mid = (low + high) / 2; // 取中间位置
if (key == ST.R[mid].key) {
return mid; // 找到元素,返回位置
} else if (key < ST.R[mid].key) {
high = mid - 1; // 在前半部分继续查找
} else {
low = mid + 1; // 在后半部分继续查找
}
}
return 0; // 未找到元素
}
12.选择排序(考)
12、请写出简单选择排序的算法。
假设顺序表结构定义如下(参考严蔚敏版《数据结构》):
#define MAXSIZE 100 // 顺序表最大长度
typedef int KeyType; // 关键字类型
typedef struct {
KeyType key; // 关键字域
// 其他数据域...
} ElemType;
typedef struct {
ElemType r[MAXSIZE + 1]; // r[0] 闲置或用作哨兵
int length; // 顺序表长度
} SqList;
简单选择排序算法:
void SelectSort(SqList *L) {
// 对顺序表 L 作简单选择排序
int i, j, k;
ElemType temp; // 临时变量,用于交换
for (i = 1; i < L->length; ++i) {
// 在 L->r[i..L->length] 中选择关键字最小的记录
k = i; // 假设第 i 个记录是最小的
for (j = i + 1; j <= L->length; ++j) {
if (L->r[j].key < L->r[k].key) {
k = j; // 更新最小记录的位置
}
}
// 如果最小记录不在第 i 个位置,则交换
if (k != i) {
temp = L->r[i];
L->r[i] = L->r[k];
L->r[k] = temp;
}
}
}
13.深度优先搜索
13、请写出从图 G 中顶点 v 出发,深度优先搜索 遍历连通图 G 的算法。
解:
// 深度优先搜索
void DFS(Graph G, int v) {
cout << v; // 访问当前顶点, adjlist是邻接表
visited[v] = true; // 标记为已访问
int w;
for (w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, v, w)) {
if (!visited[w]) { // 如果邻接点未访问
DFS(G, w); // 递归访问
}
}
}
14.插入排序
14、请写出对顺序表 L 作直接插入排序的算法。
解:
// 直接插入排序(无哨兵版本)
void InsertSort(vector<int>& arr) {
int n = arr.size();
// 从第 2 个元素开始(下标从 1 开始)
for (int i = 1; i < n; ++i) {
// 如果当前元素比前一个小
if (arr[i] < arr[i - 1]) {
int temp = arr[i]; // 保存当前元素
int j;
// 向前查找插入位置
for (j = i - 1; j >= 0 && arr[j] > temp; --j) {
arr[j + 1] = arr[j]; // 元素后移
}
arr[j + 1] = temp; // 插入到正确位置
}
}
}
15.二叉排序树递归查找
15、请写出 二叉排序树 中的 递归查找算法。
解:
BSTree SearchBST(BSTree T, KeyType key){
//在根指针T所指的二叉排序树中递归地查找
//某关键字等于key的数据元素。
//若查找成功,则返回指向该数据元素结点的指针,
//否则返回空指针。
if((!T) || key==T->data.key) return T;//查找结束
else if (key < T -> data.key)
return SearchBST(T -> lchild, key);//在左子树中继续查找
else
return SearchBST(T -> rchild, key); //在右子树中继续查找
}
16.顺序表的初始化
16、请写出对顺序表 L 进行初始化的算法。
解:
Status InitList_Sq(SqList &L){ //构造一个空的顺序表L
L.elem = new ElemType[MAXSIZE]; //为顺序表分配空间
if(!L.elem) exit(OVERFLOW); //存储分配失败
L.length = 0; //空表长度为0
return OK;
}