为了对付大二上期末考——数据结构与算法

107 阅读25分钟

老师给的去年真题

选择题

得  分 一、单项选择题(本题 30 分,每题 2 分)
评卷人 

  1.一个算法应该是(  B  )。

A.程序    

B.问题求解步骤的一种描述

C.要满足五个基本特征

D.A和C

2、数据结构在计算机中的映像称为(   A    )。

A.数据的存储结构     B.数据的基本操作

C.程序的算法   D.数据的逻辑结构

image.png

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.不确定

对于任意一棵二叉树,存在以下两个关键关系:

  1. 结点总数 n = n₀ + n₁ + n₂其中,n₀ = 叶子结点数(度为 0),n₁ = 度为 1 的结点数,n₂ = 度为 2 的结点数。

  2. 分支总数 B = n - 1 = n₁ + 2×n₂二叉树中,度为 1 的结点贡献 1 个分支,度为 2 的结点贡献 2 个分支,叶子结点无分支;且所有结点的分支总数等于结点总数减 1(根结点没有父分支)。

已知条件:n₂ = 2n₁ = 3

  1. 根据分支公式计算结点总数减 1 的值:B = 3 + 2×2 = 7因此结点总数 n = 7 + 1 = 8
  2. 代入结点总数公式求叶子结点数 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

image.png

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

  image.png

 

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} 的一趟划分过程:

  1. 初始化双指针:左指针 low 指向第一个元素(值为 1,即枢轴),右指针 high 指向最后一个元素(值为 6)。

  2. 快速排序的划分逻辑是先移动右指针找小于枢轴的元素,再移动左指针找大于枢轴的元素,然后交换两者

  3. 因为枢轴值为 1,是序列中的最小值:

    • 右指针 high 从右往左遍历,所有元素(9、4、5、7、6)都大于 1,会一直移动到 low 的位置(即枢轴自身)。
    • 此时左右指针重合,无需交换任何元素,直接将枢轴放在当前位置即可。
  4. 一趟划分后,序列没有发生任何变化,仍然是 {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:依次调整每个非叶子结点

  1. 调整下标 2 的结点(值为 9左右孩子是 2(值 2)和 6(值 6),9≥2 且 9≥6,满足大顶堆要求,无需调整,序列保持 (7,8,9,3,1,2,6)
  2. 调整下标 1 的结点(值为 8左右孩子是 3(值 3)和 1(值 1),8≥3 且 8≥1,满足大顶堆要求,无需调整,序列仍为 (7,8,9,3,1,2,6)
  3. 调整下标 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,67,8,9,3,1,2,69,8,7,3,1,2,69,8,7,3,1,2,6与选项 C 完全一致。

简答题

得  分 二、简答题(本题 共 8 分,每题 4 分)
评卷人 

1、已知数组定义A[5][6],每个元素占2个字节,数组基址为1000,分别求A[3][2]的按行序和列序的存储地址?(各维下标从0开始)(满分4分)

提示:

image.png

解:

行序: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分)

image.png

解:

假设 i 表示 行 , j 表示列 , e 表示 element .

ije
161
233
321
434
457

综合题

得  分 三、综合题(本题 共 47 分)
评卷人 

1、已知二叉树如图,请给出其先序访问序列。并在图中加上线索,使之构成先序线索树。(满分6分)

image.png

解:

先序访问序列 : ABCDEF

image.png  

 先序线索树的规则是:

  • 若结点的左孩子为空,则左指针改为指向其先序前驱
  • 若结点的右孩子为空,则右指针改为指向其先序后继
  • 需添加标志位区分指针是 “孩子指针” 还是 “线索指针”(通常用 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

解:

image.png

构建步骤

  1. 确定根节点:先序序列的第一个元素为根节点,即 a

  2. 划分左右子树:在中序序列中,根节点 a 左侧无元素,右侧为 c e b d,因此 a 无左子树,右子树的先序序列为 b c e d,中序序列为 c e b d

  3. 递归构建右子树

    • 右子树的根节点为 b(先序序列第二个元素)。
    • 在中序序列 c e b d 中,b 左侧为 c e(左子树),右侧为 d(右子树)。
    • 左子树的先序序列为 c e,中序序列为 c e:根节点为 ce 为其右子树。
    • 右子树的先序序列为 d,中序序列为 d:根节点为 d,无左右子树。

二叉树结构:

    a
     \
      b
     / \
    c   d
     \
      e

将二叉树转换为森林

转换规则

  1. 二叉树的根节点是森林中第一棵树的根节点。
  2. 根节点的右子树依次是森林中后续树的根节点。
  3. 每个节点的左子树为该节点在对应树中的子树。

 

3、假设用于通信的电文仅由7个字母组成,字母在电文中出现的频率分别为2、22、30、21、14、1、10(单位:%),请为这7个字母设计哈夫曼编码。(满分7分)

要求:先构造哈夫曼树,然后再依据该树构造哈夫曼编码。

解:

image.png

哈夫曼的构造规则是每次选取权值最小的两个结点合并,新结点权值为两者之和,重复此过程直到合并为一棵树。

哈夫曼编码的规则是从根结点到叶子结点的路径,左分支记为 0,右分支记为 1,最终每个叶子结点对应的路径序列即为其编码。  

4、请画出右图的邻接矩阵。(满分4分)

解:

image.png

最简单的一道题

5、对于右图所给的无向网,请画图分步骤给出用普里姆算法构造其最小生成树的过程(从a点出发)。(满分6分)

解:

image.png

普里姆算法的规则是:从起始顶点出发,每次选择 “已选顶点集合” 到 “未选顶点集合” 的权值最小的边,将对应未选顶点加入已选集合,重复至所有顶点加入。

6、请在下图所示的AOE-网中标识出每个顶点的 ve, vl , vl-ve。并找出其中关键路径上的点。(满分7分)(e : early ; l : last)

提示:可以使用如下方法标识:

image.png

看题

image.png  

 解答:

image.png

vl​(F)=ve​(F)=12(终点最迟时间等于最早时间)

ve 的计算规则:从起点(A)开始,按拓扑序累加边权,取最大值。

vl 的计算规则:从终点(F)开始,按逆拓扑序递减边权,取最小值。

vl-ve 值为 0 的是关键路径上的点。

7、设查找的关键字序列为 { 3,2,1,6,7} ,请给出从空树出发,构造二叉排序树的过程。(满分6分)

解:

image.png

二叉排序树的构造规则是:新结点小于当前结点则插入左子树,大于则插入右子树

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;

image.png

解:

// 最基础的冒泡排序(升序排列,无任何优化)
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; // 插入到正确位置
        }
    }
}

想看图解在这里wowowoow!!!🫲🫲🫲


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;

}