一、选择题
1.下列程序段的时间复杂度是()
void func(int n){
int i = 0,m = 0;
while(m < n * n){
i++:
m=m+i;
}
}
A. O(n)B. O(Iogn)C. O(nIogn)D. O(n^2)
2.设p指向一个非空双向链表中的某个结点,将一个q所指新结点插入到该双向链表中,使其成为p所指结点的前驱结点,能正确完成此要求的语句段是 ( )。
A. q->next=p; q->prior=p->prior; p->prior=q; p->prior->next=q;
B. p->prior=q; q->next=p; p->prior->next=q; q->prior=p->prior;
C. q->prior=p->prior; q->next=p; p->prior->next=q;p->prior=q;
D. q->prior=p->next; q->next=p; p->prior->next=q; p->prior=q;
3.一个栈的入栈序列为1,2,3,···,n,其出栈序列是pi,p2,p3,···,Pn。若p1=4,则p3可能取值的个数是多少?()
A. n-3
B. n-2
C. n-1
D. 无法确定
4.二维数组SA中,每个元素的长度为3个字节,行下标 i 从0到7,列下标 j 从0到9,从首地址SA开始连续存放在存储器内,且采用行优先顺序存储,元素A[4][5]的起始地址为 ( )。
A. SA+141
B. SA+111
C. SA+135
D. SA+165
5.一棵度为4的树 T 中,若有10个度为 4 的结点,20个度为 3 的结点,1个度为2的结点,12个度为1的结点,则树 T 的叶子结点个数是 ( )。
A. 63
B. 81
C. 105
D. 72
6.设森林 F 中有 4 棵树 T1,T2,T3,T4,其结点个数分别为 10、15、12、19,将森林 F 转换成一棵二叉树BT,BT 的根结点 R 为 T1 上的结点,则 R 的左子树上的结点个数是 ( )。
A. 9
B. 10
C. 19
D. 27
7.设哈夫曼编码的长度不超过4,若已对两个字符编码为 1 和 01,则最多还可对( )个字符编码。
A. 2
B. 3
C. 4
D. 7
8.下列四个序列中,哪一个是堆 ( )。
A. 65,55,40,10,30,25,20,15
B. 65,55,30,15,25,40,20,10
C. 65,40,55,10,25,30,20,15
D. 65,55,40,30,15,25,20,10
9.在含有33个结点的二叉排序树上,查找关键字为34的结点,以下 ( ) 是可能的关键字比较序列?
A. 25,37,16,45,34
B. 45,37,16,25,34
C. 45,25,16,37,34
D. 16,37,25,45,34
10.序列(5,3,12,9,4,2,10.6,8)是某排序方法第一趟后的结果,该排序算法可能是()。
A.冒泡排序
B.堆排序
C.归并排序
D.简单选择排序
选择题答案
1.答案是 A. O(n)。这段程序的时间复杂度是线性的,因为在每次循环中,i和m都在增加,而m的增长速度比n的平方快,所以循环的次数是线性关系1。【m的取值变化是0->1->3->6->10->15->21->28->36->45->55->66->78->91这样的。】
2.答案是 C. q->prior=p->prior; q->next=p; p->prior->next=q;p->prior=q;。这个语句段首先将q的前驱设置为p的前驱,然后将q的后继设置为p,接着将p的前驱的后继设置为q,最后将p的前驱设置为q,这样就完成了将q插入到p之前的操作2。
3.答案是 B (这里我觉得应该是n-2 因为入栈和出栈是可以间插着来进行的。 我可以把4出栈之后再把3,2出栈,再继续入栈5,6,.... 或者把4出栈之后再把3出栈,再继续入栈5,6,.... 或者把4出栈之后再继续入栈5,再把3,2出栈,6,.... 诸如此类。所以我觉得除了1、4不能在p3位置被出栈,其余都可以)
4.答案是 C. SA+135。二维数组元素A[4][5]的起始地址应该是SA+135。因为每个元素的长度为3个字节,行下标i从0到7,列下标j从0到9,从首地址SA开始连续存放在存储器内,且采用行优先顺序存储。所以,元素A[4][5]的起始地址应该是(4*10+5)*3+SA=SA+135。
5.答案是 D. 72。在一棵度为4的树中,叶子结点的个数可以通过公式:叶子结点数 = 度为4的结点数 * 4 + 度为3的结点数 * 3 + 度为2的结点数 * 2 + 度为1的结点数 * 1,所以叶子结点数 = 10 * 4 + 20 * 3 + 1 * 2 + 12 * 1 = 725。
6.答案是 A. 9。将森林F转换成一棵二叉树BT,BT的根结点R为T1上的结点,R的左子树上的结点个数就是T1的结点个数减1,因为R本身不在左子树上,所以答案是10-1=96。
7.C. 4。在哈夫曼编码中,一个编码不能是任何其他编码的前缀。3位编码可能是001,对应的4位编码只能是0000和0001。3位编码也可能是000,对应的4位编码只能是0010和0011.若全采用4位编码,则可以位0000,0001,0010和0011.题中问的是最多,所以选C。
8.答案是 D
9.答案是 B. 45,37,16,25,34。在二叉排序树中查找关键字,是从根结点开始,如果关键字小于根结点,就在左子树中查找;如果关键字大于根结点,就在右子树中查找。这个序列满足这个查找规则9。
10.答案是C 从大到小的归并排序
二、填空题(每空2分,共20分)
线性结构中元素之间存在一对一关系,树型结构中元素之间存 ( ) 关系,图型结构中元素之间存在 ( ) 关系。
高度为3的满二叉树B(设根结点为第一层),将其还原为森林T,其中包含根结点的那棵树中有 ( ) 个结点。
采用邻接矩阵的方法存储图时,查找图的某一条边的时间复杂度是 ( ) 。
已知序列(12,18,4,3,6,13,2,9,19,8),采用快速排序对该序列作升序排序的第一趟结果是 ( ) 。
设G为具有37条边的无向连通图,则G至少有 ( ) 个顶点,至多有 ( ) 个顶点。
含有n个顶点e条边的无向连通图,利用Prim算法生成最小生成树的时间复杂度为 ( ) 。
一棵二叉树的后序序列为CEFAKHGBD,中序序列为CAEFDKBGH,则先序序列为 ( ) 。
若使用二叉链表作为树的存储结构,在有n个结点的二叉链表中空链域的个数为 ( ) 。
填空题答案
线性结构中元素之间存在一对一关系,树型结构中元素之间存一对多关系,图型结构中元素之间存在多对多关系。
高度为3的满二叉树B(设根结点为第一层),将其还原为森林T,其中包含根结点的那棵树中有4个结点。
采用邻接矩阵的方法存储图时,查找图的某一条边的时间复杂度是。
已知序列(12,18,4,3,6,13,2,9,19,8),采用快速排序对该序列作升序排序的第一趟结果是8,9,4,3,6,2,12,13,19,18
设G为具有37条边的无向连通图,则G至少有10个顶点,至多有37个顶点。
含有n个顶点e条边的无向连通图,利用Prim算法生成最小生成树的时间复杂度为O(n^2)。
一棵二叉树的后序序列为CEFAKHGBD,中序序列为CAEFDKBGH,则先序序列为DACFEBKGH
若使用二叉链表作为树的存储结构,在有n个结点的二叉链表中空链域的个数为n+1
三、 判断题(每题2分,共20分,正确的选T,错误的选F)
数据的逻辑结构包括顺序结构和链式结构两种类型。 ( )
队列的链式存储结构和顺序存储结构相比,其中一个优点是节省了存储空间。 ( )
一棵二叉树的每个结点有且仅有一个前驱结点。 ( )
采用邻接表的方式存储一个图,结果可能是不唯一的。 ( )
在一个AOE网中,一个关键活动提前完成,另一个关键活动延期完成,那么整个工程有可能会按时完成。 ( )
Dijkstra算法除了用来求一个带权有向图中两个顶点之间的最短路径之外,还可以用来判定图中是否存在回路。 ( )
在一个拓扑有序序列中,任意两个相继的结点之间在对应的图中都存在一条路径。 ( )
堆排序算法中,当待排记录初始序列已经为按关键字顺序有序时,其时间复杂度将从O(nlogn)蜕化到O(㎡),其中n为序列中元素的个数。 ( )
线索二叉树中一个结点的左线索指向的是该结点的双亲结点。 ( )
对大顶堆进行层次遍历可以得到一个有序序列。 ( )
判断题答案
数据的逻辑结构包括顺序结构和链式结构两种类型。 (F)
队列的链式存储结构和顺序存储结构相比,其中一个优点是节省了存储空间。 (T)
一棵二叉树的每个结点有且仅有一个前驱结点。 (F)
采用邻接表的方式存储一个图,结果可能是不唯一的。 (T)
在一个AOE网中,一个关键活动提前完成,另一个关键活动延期完成,那么整个工程有可能会按时完成。 (T)
Dijkstra算法除了用来求一个带权有向图中两个顶点之间的最短路径之外,还可以用来判定图中是否存在回路。 (F)
在一个拓扑有序序列中,任意两个相继的结点之间在对应的图中都存在一条路径。 (F)
堆排序算法中,当待排记录初始序列已经为按关键字顺序有序时,其时间复杂度将从O(nlogn)蜕化到O(㎡),其中n为序列中元素的个数。 (F)
线索二叉树中一个结点的左线索指向的是该结点的双亲结点。 (F)
对大顶堆进行层次遍历可以得到一个有序序列。 (F)
四、简答题(共40分)
1.简述创建线索二叉树的目的,以及建立线索二叉树的思路。(4分)
2.给定关键字序列T=(11,13,12,14,5,6,8,7,9,10),采用快速排序算法,以第一个元素为枢轴,对该序列由小到大排序,并写出具体排序过程,要求给出每趟排序后的中间结果。(3分)
3.对于给定11个数据元素的有序表T={2,3,10,15,20,25,28,29,30,35,40}采用折半查找,请回答以下问题。(本题共三小题,前两小题各2分,第三小题4分,共计8分)
(1)若查找给定值为20的元素,将依次与表中哪些元素比较?
(2)若查找给定值为26的元素,将依次与哪些元素比较?
(3)假设查找表中每个元素的概率相同,求查找成功时的平均查找长度和查找不成功时的平均查找长度
4.设有一段正文由字符集{A. B,C,D. E. F)组成,正文长度为100个字符,其中每个字符在正文中出现的次数分别为17,12,5,28,35,3。若采用Huffman编码对这段正文进行压缩存储,请完成如下问题。(本题共四小题,每小题各2分,共计8分)
(1)根据上述背景,构造出Huffman树(规定权值较小的结点为左子树)。
(2)给出每个字符的Huffiman编码。
(3)若有某一段正文的二进制编码序列为01101010110011,请将它翻译成所对应的正文。
(4)计算按Huffman 编码压缩存储这段正文共需要多少个字节。
5.已知一个有向网G的带权邻接矩阵如下所示,回答以下问题。(本题共两小题,第一小题2分,第二小题5分,共计7分)
(1) 画出该带权有向图(假设顶点编号为VO,VI,V2,V3,V4,V5).
(2) 使用Dijkstra(迪杰斯特拉)算法求出从顶点VO到其余各顶点的最短路径,并写出过程。
6.已知一个无向图如下图2所示,分别用Prim和Kruskal 算法生成最小生成树,要求画出构造过程(设Prim算法以顶点V1为起点)。(10分)
简答题答案
1. 创建线索二叉树的主要目的是为了提高对二叉树的遍历效率。在普通的二叉树中,为了实现中序遍历,需要在递归过程中反复进入左子树和右子树,这样会增加递归的次数,消耗额外的内存空间。而线索二叉树通过在二叉树的空指针域中加入线索(指向前驱和后继结点的指针),可以在遍历过程中直接找到前驱和后继结点,从而提高了中序遍历的效率。
建立线索二叉树的思路是通过中序遍历的方式,将二叉树中的空指针域连接起来,形成前驱和后继的线索。具体步骤包括:
- 对二叉树进行中序遍历。
- 在遍历过程中,对每个结点,如果其左子树为空,则将其左子树指针指向其前驱结点;如果其右子树为空,则将其右子树指针指向其后继结点。
- 在遍历过程中,记录前一个访问的结点,将其右子树指针指向当前结点,将当前结点的左子树指针指向前一个结点。
通过这样的操作,就可以将二叉树中的空指针域连接成线索,从而构建出线索二叉树。
2.
下列代码为二分查找的算法核心
int binarySearch(int arr[], int low, int high, int key) {
while (low <= high) {
int mid = low + (high - low) / 2;
if (arr[mid] == key) {
return mid; // 找到了,返回元素的索引
}
if (arr[mid] < key) {
low = mid + 1; // 继续在右半部分查找
} else {
high = mid - 1; // 继续在左半部分查找
}
}
return -1; // 没找到,返回-1
}
3.这道题我写错了【是向下取整的,其余思路没错,大伙自己纠正吧】
第一问:
mid=(0+10)/2=5 => T[5]=25;
20<mid => high=mid-1=4; low = 0; mid=(0+4)/2=2 => T[2]=10;
20>mid => low = mid+1=3; high=4; mid=(3+4)/2=4[向上取整] => T[4]=20;
20=mid; game over 【需要与25,10,20比较】
第二问:
mid=(0+10)/2=5 => T[5]=25;
26>mid => low = mid+1=6, high=10; mid=(6+10)/2=8 => T[8]=30;
26<mid => high=mid-1=7;low=6;mid=(6+7)/2=7 => T[7]=29
26<mid => high=mid-1=6;low=6;mid=(6+6)/2=6 => T[6]=28
找不到,Game over!【需要与25,30,29,28比较】
4.(1)首先,我们将所有的字符按照它们的频率进行排序:F(3), C(5), B(12), A(17), D(28), E(35)。然后,我们每次选择两个频率最小的节点进行合并:
- 合并F和C,得到一个新的节点FC,频率为8。
- 合并FC和B,得到一个新的节点FCB,频率为20。
- 合并FCB和A,得到一个新的节点FCBA,频率为37。
- 合并D和E,得到一个新的节点DE,频率为63。
- 最后,合并FCBA和DE,得到最终的Huffman树,频率为100。
(2)
/ 10 \
/ \
/37\ /63\
/ \ / \
A(17) /20\ D(28) E(35)
/ \
/8\ B(12)
/ \
F(3) C(5)
(3)左0右1原则:
- A: 00
- B: 011
- C: 0101
- D: 10
- E: 11
- F: 0100
(4) BCBAE
5.6懒得写了,去看往年的答案。
五、算法填空题 + 答案
1. 下面是先序遍历二叉树的非递归算法,请在空白处填上适当内容,使其成为一个完整算法。
// 代码填空题(1)
// 先序的非递归算法
void PreOrderTraverse(BiTree T, Status (*Visit)(TElemType))
{
BTNode *p;
SqStack *st;
InitStack(st);
if (T != NULL)
{
Push(st, T);
while (!StackEmpty(st))
{
Pop(st, &p);
Visit(p->data);
if (p->rchild != NULL)
Push(st, p->rchild);
if (p->lchild != NULL)
Push(st, p->lchild);
}
}
}
2. 给定一个带头结点的双向链表L,至少包含一个数据结点。以下代码为实现将L中所有结点按照数据值递增有序排列,排序算法为直接插入排序法,请将代码中空白处填写完整。
void sort(DiLNode *&L)
{
DiLNode *p, *pre, *q;
p = L->next->next;
L->next->next = NULL;
while (p != NULL)
{
q = p->next;
pre = L;
while (pre->next != NULL && pre->next->data < p->data)
pre = pre->next;
p->next = pre->next;
if (pre->next != NULL)
pre->next->prior = p;
pre->next = p;
p->prior = pre;
p = q;
}
}
六、编写算法 + 答案
1. 设有一个长度为n的线性表L采用顺序存储结构存储。设计一个算法,以第一个元素为分界线(基准),将所有小于或等于它的元素移到该元素的前面,将所有大于它的元素移到该元素的后面。要求算法的时间复杂度为O(n),且算法最多只能借助1个辅助变量。
// 定义最大数组大小
#define Maxsize 100
// 定义键类型
typedef int keytype;
// 定义记录类型
typedef struct {
keytype key; // 键
Infotype otherinfo; // 其他信息
} RedType;
// 定义顺序表类型
typedef struct {
RedType r[Maxsize+1]; // 记录数组,r[0]用作哨兵或临时存储
int length; // 表的长度
} SqList;
// 快速排序函数
void Qsort(SqList &L, int low, int high) {
if (low < high) {
pivotloc = Partition(L, low, high); // 对表进行划分,pivotloc是基准的位置
Qsort(L, low, pivotloc - 1); // 对低子表递归排序
Qsort(L, pivotloc + 1, high); // 对高子表递归排序
}
}
// 划分函数
int Partition(SqList &L, int low, int high) {
L.r[0] = L.r[low]; // 用子表的第一个记录作为基准
pivotkey = L.r[low].key; // 基准记录的关键字
while (low < high) { // 从表的两端交替向中间扫描
while (low < high && L.r[high].key >= pivotkey) high--; // 找到比基准小的记录
L.r[low] = L.r[high]; // 将比基准小的记录移到低端
while (low < high && L.r[low].key <= pivotkey) low++; // 找到比基准大的记录
L.r[high] = L.r[low]; // 将比基准大的记录移到高端
}
L.r[low] = L.r[0]; // 将基准记录移到正确的位置
return low; // 返回基准的位置
}
// 2020年考察的形式变了(那个pivot从前面换成后面了)
假设一维数组 A 保存有 N 个整数,以下快速排序算法代码能够对数组 A 进行排序。请在处填上适当内容,使其成为一个完整的算法。
// 定义分区函数
int partition(int* A, int N, int p, int r){
// 选择最后一个元素作为主元
int x = A[r];
// i 是小于主元的元素的索引
int i= p-1;
// 遍历数组,将小于主元的元素放在左边,大于主元的元素放在右边
for (int j = p; j<=r-1; j++){
if(A[j]>=x){
i=i+1;
// 交换 A[i] 和 A[j]
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
// 将主元放到正确的位置
int temp = A[i+1];
A[i+1]=A[r];
A[r] = temp;
// 返回主元的位置
return i+1;
}
// 定义快速排序函数
void QuickSort(int* A, int N, int p, int r){
int q;
// 如果 p < r,说明数组中至少有两个元素
if(p<r){
// 对数组进行分区,并获取主元的位置
q = partition(A, N, p, r);
// 对主元左边的子数组进行快速排序
QuickSort(A,N,p,q-1);
// 对主元右边的子数组进行快速排序
QuickSort(A,N,q+1,r);
}
return;
}
void main(){
QuickSort(A,N,0,N-1);
return 0;
}
2. 设图G有n个顶点,按照如下提示设计一个算法,将G的邻接矩阵转换为对应的邻接表。
// G的邻接表存储类型定义如下:
typedef struct Anode
{
int adjvex;
struct ANode *nextarc;
InfoType weight;
} ArcNode;
typedef struct Vnode
{
Vertex data;
ArcNode *firstac;
} VNode;
typedef struct
{
VNode adjlist[MAXV];
int n, e;
} AdjGraph;
// G的邻接矩阵存储类型定义如下:
typedef struct
{
int no;
InfoType info;
} VertexType;
typedef struct
{
int edges[MAXV][MAXV];
int n, e;
VertexType vexs[MAXV];
} MatGraph:
void MatToList(MatGraph mg, AdjGraph *ag)
{
int i, j;
ArcNode *p;
for (i = 0; i < mg.n; i++)
{
ag->adjlist[i].data = mg.vexs[i].no;
ag->adjlist[i].firstac = NULL;
for (j = mg.n - 1; j >= 0; j--)
{
if (mg.edges[i][j] != 0)
{
p = (ArcNode *)malloc(sizeof(ArcNode));
p->adjvex = j;
p->weight = mg.edges[i][j];
p->nextarc = ag->adjlist[i].firstac;
ag->adjlist[i].firstac = p;
}
}
}
ag->n = mg.n;
ag->e = mg.e;
}
这个算法首先遍历邻接矩阵中的每一行(即每一个顶点),然后对于每一行,它从右到左遍历每一列(即每一个顶点的邻接顶点)。如果在邻接矩阵中找到一个非零元素,那么就在邻接表中为这两个顶点添加一个边。这个边的信息存储在一个ArcNode
结构体中,包括邻接顶点的索引、边的权重以及指向下一个边的指针。这个ArcNode
结构体被插入到邻接表的相应位置。最后,这个算法将邻接矩阵的顶点数和边数复制到邻接表中。
3. 一个公司在某地区有n个产品销售点,现打算在这个地区的某个销售点上建一个中心仓库,负责向该地区的各个销售点提供销售产品。由于运输线路和公交条件不同,向每个销售点运输一次产品的费用也不相同。若公司每天都会向每个运输点运输一次产品,试设计一个算法,以帮助公司解决应将中心仓库建立在哪个销售点上才能使运输费用达到最低的问题。
思路:
-
初始化一个长度为n的数组cost,其中n是销售点的数量。数组的每个元素对应一个销售点,初始值都设为0。
-
对于每个销售点i(从0到n-1),计算它到所有其他销售点的运输费用总和,然后将这个总和存储在cost[i]中。
-
找出cost数组中的最小值,对应的索引就是最佳的中心仓库位置。
这个程序首先定义了一个二维数组cost来存储每对销售点之间的运输费用,以及一个整数n来存储销售点的数量。然后,它定义了一个函数find_min_cost来找出运输费用总和最小的销售点。这个函数首先初始化一个最小费用min_cost和对应的索引min_index,然后遍历每个销售点,计算其到所有其他销售点的运输费用总和,并更新min_cost和min_index。最后,这个函数返回min_index,即运输费用总和最小的销售点的索引。
#include <stdio.h>
#define MAXN 1000
int cost[MAXN][MAXN]; // 运输费用矩阵
int n; // 销售点的数量
int find_min_cost() {
int min_cost = 0;
int min_index = 0;
for (int i = 0; i < n; i++) {
int total_cost = 0;
for (int j = 0; j < n; j++) {
total_cost += cost[i][j];
}
if (i == 0 || total_cost < min_cost) {
min_cost = total_cost;
min_index = i;
}
}
return min_index;
}
int main() {
// 假设cost矩阵和n已经被初始化
int min_index = find_min_cost();
printf("最佳的中心仓库位置是:%d\n", min_index);
return 0;
}