整理日期:2022-12-08
目录
一、线性表
二.栈和队列
三、数组和广义表
四、树和二叉树
五、图
六、查找算法
七、排序算法
(1)将两个递增的有序链表合并为一个递增的有序链表。要求结果链表仍使用原来两个链表的存储空间,不另外占用其它的存储空间。表中不允许有重复的数据。
[题目分析]
合并后的新表使用头指针Lc指向,pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较,当两个链表La和Lb均为到达表尾结点时,依次摘取其中较小者重新链接在Lc表的最后。如果两个表中的元素相等,只摘取La表中的元素,删除Lb表中的元素,这样确保合并后表中无重复的元素。当一个表到达表尾结点,为空时,将非空表的剩余元素直接链接在Lc表的最后。
[算法描述]
void MergeList(LinkList &La,LinkList &Lb,LinkList &Lc)
{//合并链表La和Lb,合并后的新表使用头指针Lc指向
pa=La->next; pb=Lb->next;
//pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点
Lc=pc=La; //用La的头结点作为Lc的头结点
while(pa && pb)
{
if(pa->data
{pc->next=pa;pc=pa;pa=pa->next;}
//取较小者La中的元素,将pa链接在pc的后面,pa指针后移
else if(pa->data>pb->data)
{pc->next=pb; pc=pb; pb=pb->next;}
//取较小者Lb中的元素,将pb链接在pc的后面,pb指针后移
else //相等时取La中的元素,删除Lb中的元素
{
pc->next=pa;pc=pa;pa=pa->next;
q=pb->next;delete pb ;pb =q;
}
}
pc->next=pa?pa:pb; //插入剩余段
delete Lb; //释放Lb的头结点
}
(2)将两个非递减的有序链表合并为一个非递增的有序链表。要求结果链表仍使用原来两个链表的存储空间,
不另外占用其它的存储空间。表中允许有重复的数据。
[题目分析]
合并后的新表使用头指针Lc指向,pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较,当两个链表La和Lb均为到达表尾结点时,依次摘取其中较小者重新链接在Lc表的表头结点之后,如果两个表中的元素相等,只摘取La表中的元素,保留Lb表中的元素。当一个表到达表尾结点,为空时,将非空表的剩余元素依次摘取,链接在Lc表的表头结点之后。
[算法描述]
void MergeList(LinkList& La, LinkList& Lb, LinkList& Lc, )
{//合并链表La和Lb,合并后的新表使用头指针Lc指向
pa=La->next; pb=Lb->next;
//pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点
Lc=pc=La; //用La的头结点作为Lc的头结点
Lc->next=NULL;
while(pa||pb )
{//只要存在一个非空表,用q指向待摘取的元素
if(!pa) {q=pb; pb=pb->next;}
//La表为空,用q指向pb,pb指针后移
else if(!pb) {q=pa; pa=pa->next;}
//Lb表为空,用q指向pa,pa指针后移
else if(pa->data<=pb->data) {q=pa; pa=pa->next;}
//取较小者(包括相等)La中的元素,用q指向pa,pa指针后移
else {q=pb; pb=pb->next;}
//取较小者Lb中的元素,用q指向pb,pb指针后移
q->next = Lc->next; Lc->next = q;
//将q指向的结点插在Lc 表的表头结点之后
}
delete Lb; //释放Lb的头结点
}
(3)已知两个链表A和B分别表示两个集合,其元素递增排列。请设计算法求出A与B的交集,并存放于A链表中。
[题目分析]
只有同时出现在两集合中的元素才出现在结果表中,合并后的新表使用头指针Lc指向。pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较,当两个链表La和Lb均为到达表尾结点时,如果两个表中相等的元素时,摘取La表中的元素,删除Lb表中的元素;如果其中一个表中的元素较小时,删除此表中较小的元素,此表的工作指针后移。当链表La和Lb有一个到达表尾结点,为空时,依次删除另一个非空表中的所有元素。
[算法描述]
void Mix(LinkList& La, LinkList& Lb, LinkList& Lc)
{ pa=La->next;pb=Lb->next;
pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点
Lc=pc=La; //用La的头结点作为Lc的头结点
while(pa&&pb)
{ if(pa->data==pb->data)∥交集并入结果表中。
{ pc->next=pa;pc=pa;pa=pa->next;
u=pb;pb=pb->next; delete u;}
else if(pa->data
else {u=pb; pb=pb->next; delete u;}
}
while(pa) {u=pa; pa=pa->next; delete u;}∥ 释放结点空间
while(pb) {u=pb; pb=pb->next; delete u;}∥释放结点空间
pc->next=null;∥置链表尾标记。
delete Lb; //释放Lb的头结点
}
(4)已知两个链表A和B分别表示两个集合,其元素递增排列。请设计算法求出两个集合A和B
的差集(即仅由在A中出现而不在B中出现的元素所构成的集合),并以同样的形式存储,同时返回该集合的元素个数。
[题目分析]
求两个集合A和B的差集是指在A中删除A和B中共有的元素,即删除链表中的相应结点,所以要保存待删除结点的前驱,使用指针pre指向前驱结点。pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较,当两个链表La和Lb均为到达表尾结点时,如果La表中的元素小于Lb表中的元素,pre置为La表的工作指针pa删除Lb表中的元素;如果其中一个表中的元素较小时,删除此表中较小的元素,此表的工作指针后移。当链表La和Lb有一个为空时,依次删除另一个非空表中的所有元素。
[算法描述]
void Difference(LinkList& La, LinkList& Lb,int *n)
{∥差集的结果存储于单链表La中,*n是结果集合中元素个数,调用时为0
pa=La->next; pb=Lb->next;
∥pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点
pre=La; ∥pre为La中pa所指结点的前驱结点的指针
while(pa&&pb)
{if(pa->data
∥ A链表中当前结点指针后移
else if(pa->data>q->data)q=q->next; ∥B链表中当前结点指针后移
else {pre->next=pa->next; ∥处理A,B中元素值相同的结点,应删除
u=pa; pa=pa->next; delete u;} ∥删除结点
}
}
(5)设计算法将一个带头结点的单链表A分解为两个具有相同结构的链表B、C,其中B表的结点为A表中值小于零的结点,而C表的结点为A表中值大于零的结点(链表A中的元素为非零整数,要求B、C表利用A表的结点)。
[题目分析]
B表的头结点使用原来A表的头结点,为C表新申请一个头结点。从A表的第一个结点开始,依次取其每个结点p,判断结点p的值是否小于0,利用前插法,将小于0的结点插入B表,大于等于0的结点插入C表。
[算法描述]
void DisCompose(LinkedList A)
{ B=A;
B->next= NULL; ∥B表初始化
C=new LNode;∥为C申请结点空间
C->next=NULL; ∥C初始化为空表
p=A->next; ∥p为工作指针
while(p!= NULL)
{ r=p->next; ∥暂存p的后继
if(p->data<0)
{p->next=B->next; B->next=p; }∥将小于0的结点链入B表,前插法
else {p->next=C->next; C->next=p; }∥将大于等于0的结点链入C表,前插法
p=r;∥p指向新的待处理结点。
}
}
(6)设计一个算法,通过一趟遍历在单链表中确定值最大的结点。
[题目分析]
假定第一个结点中数据具有最大值,依次与下一个元素比较,若其小于下一个元素,则设其下一个元素为最大值,反复进行比较,直到遍历完该链表。
[算法描述]
ElemType Max (LinkList L ){
if(L->next==NULL) return NULL;
pmax=L->next; //假定第一个结点中数据具有最大值
p=L->next->next;
while(p != NULL ){//如果下一个结点存在
if(p->data > pmax->data) pmax=p;//如果p的值大于pmax的值,则重新赋值
p=p->next;//遍历链表
}
return pmax->data;
(7)设计一个算法,通过遍历一趟,将链表中所有结点的链接方向逆转,仍利用原表的存储空间。
[题目分析]
从首元结点开始,逐个地把链表L的当前结点p插入新的链表头部。
[算法描述]
void inverse(LinkList &L)
{// 逆置带头结点的单链表 L
p=L->next; L->next=NULL;
while ( p) {
q=p->next; // q指向*p的后继
p->next=L->next;
L->next=p; // *p插入在头结点之后
p = q;
}
}
(8)设计一个算法,删除递增有序链表中值大于mink且小于maxk的所有元素(mink和maxk是给定的两个参数,其值可以和表中的元素相同,也可以不同
)。
[题目分析]
分别查找第一个值>mink的结点和第一个值 ≥maxk的结点,再修改指针,删除值大于mink且小于maxk的所有元素。
[算法描述]
void delete(LinkList &L, int mink, int maxk) {
p=L->next; //首元结点
while (p && p->data<=mink)
{ pre=p; p=p->next; } //查找第一个值>mink的结点
if (p)
{while (p && p->data
// 查找第一个值 ≥maxk的结点
q=pre->next; pre->next=p; // 修改指针
while (q!=p)
{ s=q->next; delete q; q=s; } // 释放结点空间
}//if
}
(9)已知p指向双向循环链表中的一个结点,其结点结构为data、prior、next三个域,写出算法change§,交换p所指向的结点和它的前缀结点的顺序。
[题目分析]
知道双向循环链表中的一个结点,与前驱交换涉及到四个结点(p结点,前驱结点,前驱的前驱结点,后继结点)六条链。
[算法描述]
void Exchange(LinkedList p)
∥p是双向循环链表中的一个结点,本算法将p所指结点与其前驱结点交换。
{q=p->llink;
q->llink->rlink=p; ∥p的前驱的前驱之后继为p
p->llink=q->llink; ∥p的前驱指向其前驱的前驱。
q->rlink=p->rlink; ∥p的前驱的后继为p的后继。
q->llink=p; ∥p与其前驱交换
p->rlink->llink=q; ∥p的后继的前驱指向原p的前驱
p->rlink=q; ∥p的后继指向其原来的前驱
}∥算法exchange结束。
(10)已知长度为n的线性表A采用顺序存储结构,请写一时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除线性表中所有值为item的数据元素。 (2)回文是指正读反读均相同的字符序列,如“abba”和“abdba”均是回文,但“good”不是回文。试写一个算法判定给定的字符向量是否为回文。(提示:将一半字符入栈) int IsHuiwen( char *t) (3)设从键盘输入一整数的序列:a1, a2, a3,…,an,试编写算法实现:用栈结构存储输入的整数,当ai≠-1时,将ai进栈;当ai=-1时,输出栈顶整数并出栈。算法应对异常情况(入栈满等)给出相应的信息。 (4)从键盘上输入一个后缀表达式,试编写算法计算表达式的值。规定:逆波兰表达式的长度不超过一行,以 符 作 为 输 入 结 束 , 操 作 数 之 间 用 空 格 分 隔 , 操 作 符 只 可 能 有 + 、 − 、 ∗ 、 / 四 种 运 算 。 例 如 : 23434 + 2 ∗ 符作为输入结束,操作数之间用空格分隔,操作符只可能有+、-、*、/四种运算。例如:234 34+2* 符作为输入结束,操作数之间用空格分隔,操作符只可能有+、−、∗、/四种运算。例如:23434+2∗。 [算法讨论]假设输入的后缀表达式是正确的,未作错误检查。算法中拼数部分是核心。若遇到大于等于‘0’且小于等于‘9’的字符,认为是数。这种字符的序号减去字符‘0’的序号得出数。对于整数,每读入一个数字字符,前面得到的部分数要乘上10再加新读入的数得到新的部分数。当读到小数点,认为数的整数部分已完,要接着处理小数部分。小数部分的数要除以10(或10的幂数)变成十分位,百分位,千分位数等等,与前面部分数相加。在拼数过程中,若遇非数字字符,表示数已拼完,将数压入栈中,并且将变量num恢复为0,准备下一个数。这时对新读入的字符进入‘+’、‘-’、‘*’、‘/’及空格的判断,因此在结束处理数字字符的case后,不能加入break语句。 (5)假设以I和O分别表示入栈和出栈操作。栈的初态和终态均为空,入栈和出栈的操作序列可表示为仅由I和O组成的序列,称可以操作的序列为合法序列,否则称为非法序列。 (6)假设以带头结点的循环链表表示队列,并且只设一个指针指向队尾元素站点(注意不设头指针) ,试编写相应的置空队、判队空 、入队和出队等算法。 (1) 置空队 (2) 判队空 (3) 入队 (4) 出队 (8)如果允许在循环队列的两端都可以进行插入和删除操作。要求: void enqueue (cycqueue Q, elemtp x) (9)已知Ackermann函数定义如下: ① 写出计算Ack(m,n)的递归算法,并根据此算法给出出Ack(2,1)的计算过程。 (10)已知f为单链表的表头指针, 链表中存储的都是整型数据,试写出实现下列运算的递归算法: (2)设目标为t=“abcaabbabcabaacbacba”,模式为p=“abcabaa” (3)数组A中,每个元素A[i,j]的长度均为32个二进位,行下标从-1到9,列下标从1到11,从首地址S开始连续存放主存储器中,主存储器字长为16位。求: (4)请将香蕉banana用工具 H( )—Head( ),T( )—Tail( )从L中取出。 (二)算法设计题 (3)编写算法,实现下面函数的功能。函数void insert(chars,chart,int pos)将字符串t插入到字符串s中,插入位置为pos。假设分配给字符串s的空间足够让字符串t插入。(说明:不得使用任何库函数) (4)已知字符串S1中存放一段英文,写出算法format(s1,s2,s3,n),将其按给定的长度n格式化成两端对齐的字符串S2, 其多余的字符送S3。 (5)设二维数组a[1…m, 1…n] 含有mn 个整数。 (6)设任意n个整数存放于数组A(1:n)中,试编写算法,将所有正数排在所有负数前面(要求算法复杂度为0(n))。 (3)交换二叉树每个结点的左孩子和右孩子。 (6)用按层次顺序遍历二叉树的方法,统计树中具有度为1的结点数目。 (7)求任意二叉树中第一条最长的路径长度,并输出此路径上各结点的值。 (8)输出二叉树中从每个叶子结点到根结点的路径。 ① ③ ② a → b 4 → c 3 答案:按拓扑有序的顺序计算各个顶点的最早可能开始时间Ve和最迟允许开始时间Vl。然后再计算各个活动的最早可能开始时间e和最迟允许开始时间l,根据l - e = 0? 来确定关键活动,从而确定关键路径。 1 2 3 • 4 5 6 <1, 2> <1, 3> <3, 2> <2, 4> <2, 5> <3, 5> <4, 6> <5, 6> (二)算法设计题 ② 删除顶点v及其相关的边, ③ 增加一条边 ④ 删除一条边 以邻接表作为存储结构,本题只给出Insert_Arc算法.其余算法类似。 (2)一个连通图采用邻接表作为存储结构,设计一个算法,实现从顶点v出发的深度优先遍历的非递归过程。 (3)设计一个算法,求图G中距离顶点v的最短路径长度最大的一个顶点,设v可达其余各个顶点。 (2)在一棵空的二叉排序树中依次插入关键字序列为12,7,17,11,16,2,13,9,21,4,请画出所得到的二叉排序树。 (3)已知如下所示长度为12的表:(Jan, Feb, Mar, Apr, May, June, July, Aug, Sep, Oct, Nov, Dec) (4)对图7.31所示的3阶B-树,依次执行下列操作,画出各步操作的结果。 (5)设哈希表的地址范围为0~17,哈希函数为:H(key)=key%16。用线性探测法处理冲突,输入关键字序列:(10,24,32,17,31,30,46,47,40,63,49),构造哈希表,试回答下列问题: (6)设有一组关键字(9,01,23,14,55,20,84,27),采用哈希函数:H(key)=key %7 ,表长为10,用开放地址法的二次探测法处理冲突。要求:对该关键字序列构造哈希表,并计算查找成功的平均查找长度。 (7)设哈希函数H(K)=3 K mod 11,哈希地址空间为0~10,对关键字序列(32,13,49,24,38,21,4,12),按下述两种解决冲突的方法构造哈希表,并分别求出等概率下查找成功时和查找失败时的平均查找长度ASLsucc和ASLunsucc。 ② ASLsucc =(15+23)/8=11/8 (3)已知二叉排序树采用二叉链表存储结构,根结点的指针为T,链结点的结构为(lchild,data,rchild),其中lchild,rchild分别指向该结点左、右孩子的指针,data域存放结点的数据信息。请写出递归算法,从小到大输出二叉排序树中所有数据值>=x的结点的数据。要求先找到第一个满足条件的结点后,再依次输出其他满足条件的结点。 (4)已知二叉树T的结点形式为(lling,data,count,rlink),在树中查找值为X的结点,若找到,则记数(count)加1,否则,作为一个新结点插入树中,插入后仍为二叉排序树,写出其非递归算法。 if(!T){ (6)分别写出在散列表中插入和删除关键字为K的一个记录的算法,设散列函数为H,解决冲突的方法为链地址法。 bool deletes(){ ② 折半插入排序 排序过程同① ③ 希尔排序(增量选取5,3,1) ④ 冒泡排序 ⑤ 快速排序 ⑥ 简单选择排序 ⑧ 二路归并排序 (2)给出如下关键字序列{321,156,57,46,28,7,331,33,34,63},试按链式基数排序方法,列出每一趟分配和收集的过程。 初始归并段:R1:3,19,31,51,61,71,100,101 (二)算法设计题 (2)有n个记录存储在带头结点的双向链表中,现用双向冒泡排序法对其按上升序进行排序,请写出这种排序的算法。(注:双向冒泡排序即相邻两趟排序向相反方向冒泡)。 (3)设有顺序放置的n个桶,每个桶中装有一粒砾石,每粒砾石的颜色是红,白,蓝之一。要求重新安排这些砾石,使得所有红色砾石在前,所有白色砾石居中,所有蓝色砾石居后,重新安排时对每粒砾石的颜色只能看一次,并且只允许交换操作来调整砾石的位置。 (4)编写算法,对n个关键字取整数值的记录序列进行整理,以使所有关键字为负值的记录排在关键字为非负值的记录之前,要求: (5)借助于快速排序的算法思想,在一组无序的记录中查找给定关键字值等于key的记录。设此组记录存放于数组r[l…n]中。若查找成功,则输出该记录在r数组中的位置及其值,否则显示“not find”信息。请简要说明算法思想并编写算法。 (6)有一种简单的排序算法,叫做计数排序。这种排序算法对一个待排序的表进行排序,并将排序结果存放到另一个新的表中。必须注意的是,表中所有待排序的关键字互不相同,计数排序算法针对表中的每个记录,扫描待排序的表一趟,统计表中有多少个记录的关键字比该记录的关键字小。假设针对某一个记录,统计出的计数值为c,那么,这个记录在新的有序表中的合适的存放位置即为c。 分享结束,整理不易,感谢阅读!
[题目分析]
在顺序存储的线性表上删除元素,通常要涉及到一系列元素的移动(删第i个元素,第i+1至第n个元素要依次前移)。本题要求删除线性表中所有值为item的数据元素,并未要求元素间的相对位置不变。因此可以考虑设头尾两个指针(i=1,j=n),从两端向中间移动,凡遇到值item的数据元素时,直接将右端元素左移至值为item的数据元素位置。
[算法描述]
void Delete(ElemType A[ ],int n)
∥A是有n个元素的一维数组,本算法删除A中所有值为item的元素。
{i=1;j=n;∥设置数组低、高端指针(下标)。
while(i
二.栈和队列
(1)将编号为0和1的两个栈存放于一个数组空间V[m]中,栈底分别处于数组的两端。当第0号栈的栈顶指针top[0]等于-1时该栈为空,当第1号栈的栈顶指针top[1]等于m时该栈为空。两个栈均从两端向中间增长。试编写双栈初始化,判断栈空、栈满、进栈和出栈等算法的函数。双栈数据结构的定义如下:
Typedef struct
{int top[2],bot[2]; //栈顶和栈底指针
SElemType *V; //栈数组
int m; //栈最大可容纳元素个数
}DblStack
[题目分析]
两栈共享向量空间,将两栈栈底设在向量两端,初始时,左栈顶指针为-1,右栈顶为m。两栈顶指针相邻时为栈满。两栈顶相向、迎面增长,栈顶指针指向栈顶元素。
[算法描述]
(1) 栈初始化
int Init()
{S.top[0]=-1;
S.top[1]=m;
return 1; //初始化成功
}
(2) 入栈操作:
int push(stk S ,int i,int x)
∥i为栈号,i=0表示左栈,i=1为右栈,x是入栈元素。入栈成功返回1,失败返回0
{if(i<0||i>1){ cout<<“栈号输入不对”<
{case 0: S.V[++S.top[0]]=x; return(1); break;
case 1: S.V[–S.top[1]]=x; return(1);
}
}∥push
(3) 退栈操作
ElemType pop(stk S,int i)
∥退栈。i代表栈号,i=0时为左栈,i=1时为右栈。退栈成功时返回退栈元素
∥否则返回-1
{if(i<0 || i>1){cout<<“栈号输入错误”<
{case 0: if(S.top[0]-1) {cout<<“栈空”<
case 1: if(S.top[1]m { cout<<“栈空”<
}∥switch
}∥算法结束
(4) 判断栈空
int Empty();
{return (S.top[0]-1 && S.top[1]==m);
}
[算法讨论]
请注意算法中两栈入栈和退栈时的栈顶指针的计算。左栈是通常意义下的栈,而右栈入栈操作时,其栈顶指针左移(减1),退栈时,栈顶指针右移(加1)。
[题目分析]
将字符串前一半入栈,然后,栈中元素和字符串后一半进行比较。即将第一个出栈元素和后一半串中第一个字符比较,若相等,则再出栈一个元素与后一个字符比较,……,直至栈空,结论为字符序列是回文。在出栈元素与串中字符比较不等时,结论字符序列不是回文。
[算法描述]
#define StackSize 100 //假定预分配的栈空间最多为100个元素
typedef char DataType;//假定栈元素的数据类型为字符
typedef struct
{DataType data[StackSize];
int top;
}SeqStack;
{//判断t字符向量是否为回文,若是,返回1,否则返回0
SeqStack s;
int i , len;
char temp;
InitStack( &s);
len=strlen(t); //求向量长度
for ( i=0; i
while( !EmptyStack( &s))
{// 每弹出一个字符与相应字符比较
temp=Pop (&s);
if( temp!=S[i]) return 0 ;// 不等则返回0
else i++;
}
return 1 ; // 比较完毕均相等则返回 1
}
[算法描述]
#define maxsize 栈空间容量
void InOutS(int s[maxsize])
//s是元素为整数的栈,本算法进行入栈和退栈操作。
{int top=0; //top为栈顶指针,定义top=0时为栈空。
for(i=1; i<=n; i++) //n个整数序列作处理。
{cin>>x); //从键盘读入整数序列。
if(x!=-1) // 读入的整数不等于-1时入栈。
{if(topmaxsize-1){cout<<“栈满”<
}
else //读入的整数等于-1时退栈。
{if(top0){ cout<<“栈空”<
}//算法结束。
[题目分析]
逆波兰表达式(即后缀表达式)求值规则如下:设立运算数栈OPND,对表达式从左到右扫描(读入),当表达式中扫描到数时,压入OPND栈。当扫描到运算符时,从OPND退出两个数,进行相应运算,结果再压入OPND栈。这个过程一直进行到读出表达式结束符 , 这 时 O P N D 栈 中 只 有 一 个 数 , 就 是 结 果 。 [ 算 法 描 述 ] f l o a t e x p r ( ) / / 从 键 盘 输 入 逆 波 兰 表 达 式 , 以 ‘ ,这时OPND栈中只有一个数,就是结果。 [算法描述] float expr( ) //从键盘输入逆波兰表达式,以‘ ,这时OPND栈中只有一个数,就是结果。[算法描述]floatexpr()//从键盘输入逆波兰表达式,以‘’表示输入结束,本算法求逆波兰式表达式的值。
{float OPND[30]; // OPND是操作数栈。
init(OPND); //两栈初始化。
float num=0.0; //数字初始化。
cin>>x;//x是字符型变量。
while(x!=’KaTeX parse error: Expected '}', got '&' at position 51: …: while((x>=’0’&̲&x<=’9’)||x==’.…’)
cout<<“后缀表达式的值为”<
①下面所示的序列中哪些是合法的?
A. IOIIOIOO B. IOOIOIIO C. IIIOIOIO D. IIIOOIOO
②通过对①的分析,写出一个算法,判定所给的操作序列是否合法。若合法,返回true,否则返回false(假定被判定的操作序列已存入一维数组中)。
答案:
①A和D是合法序列,B和C 是非法序列。
②设被判定的操作序列已存入一维数组A中。
int Judge(char A[])
//判断字符数组A中的输入输出序列是否是合法序列。如是,返回true,否则返回false。
{i=0; //i为下标。
j=k=0; //j和k分别为I和字母O的的个数。
while(A[i]!=‘\0’) //当未到字符数组尾就作。
{switch(A[i])
{case‘I’: j++; break; //入栈次数增1。
case‘O’: k++; if(k>j){cout<<“序列非法”<
i++; //不论A[i]是‘I’或‘O’,指针i均后移。}
if(j!=k) {cout<<“序列非法”<
[算法讨论]在入栈出栈序列(即由‘I’和‘O’组成的字符串)的任一位置,入栈次数(‘I’的个数)都必须大于等于出栈次数(即‘O’的个数),否则视作非法序列,立即给出信息,退出算法。整个序列(即读到字符数组中字符串的结束标记‘\0’),入栈次数必须等于出栈次数(题目中要求栈的初态和终态都为空),否则视为非法序列。
[题目分析]
置空队就是建立一个头节点,并把头尾指针都指向头节点,头节点是不存放数据的;判队空就是当头指针等于尾指针时,队空;入队时,将新的节点插入到链队列的尾部,同时将尾指针指向这个节点;出队时,删除的是队头节点,要注意队列的长度大于1还是等于1的情况,这个时候要注意尾指针的修改,如果等于1,则要删除尾指针指向的节点。
[算法描述]
//先定义链队结构:
typedef struct queuenode
{Datatype data;
struct queuenode *next;
}QueueNode; //以上是结点类型的定义
typedef struct
{queuenode *rear;
}LinkQueue; //只设一个指向队尾元素的指针
void InitQueue( LinkQueue *Q)
{ //置空队:就是使头结点成为队尾元素
QueueNode *s;
Q->rear = Q->rear->next;//将队尾指针指向头结点
while (Q->rear!=Q->rear->next)//当队列非空,将队中元素逐个出队
{s=Q->rear->next;
Q->rear->next=s->next;
delete s;
}//回收结点空间
}
int EmptyQueue( LinkQueue *Q)
{ //判队空。当头结点的next指针指向自己时为空队
return Q->rear->next->next==Q->rear->next;
}
void EnQueue( LinkQueue *Q, Datatype x)
{ //入队。也就是在尾结点处插入元素
QueueNode *p=new QueueNode;//申请新结点
p->data=x; p->next=Q->rear->next;//初始化新结点并链入
Q-rear->next=p;
Q->rear=p;//将尾指针移至新结点
}
Datatype DeQueue( LinkQueue *Q)
{//出队,把头结点之后的元素摘下
Datatype t;
QueueNode *p;
if(EmptyQueue( Q ))
Error(“Queue underflow”);
p=Q->rear->next->next; //p指向将要摘下的结点
x=p->data; //保存结点中数据
if (p==Q->rear)
{//当队列中只有一个结点时,p结点出队后,要将队尾指针指向头结点
Q->rear = Q->rear->next;
Q->rear->next=p->next;
}
else
Q->rear->next->next=p->next;//摘下结点p
delete p;//释放被删结点
return x;
}
(7)假设以数组Q[m]存放循环队列中的元素, 同时设置一个标志tag,以tag == 0和tag == 1来区别在队头指针(front)和队尾指针(rear)相等时,队列状态为“空”还是“满”。试编写与此结构相应的插入(enqueue)和删除(dlqueue)算法。
[算法描述]
(1)初始化
SeQueue QueueInit(SeQueue Q)
{//初始化队列
Q.front=Q.rear=0; Q.tag=0;
return Q;
}
(2)入队
SeQueue QueueIn(SeQueue Q,int e)
{//入队列
if((Q.tag1) && (Q.rearQ.front)) cout<<“队列已满”<
{Q.rear=(Q.rear+1) % m;
Q.data[Q.rear]=e;
if(Q.tag0) Q.tag=1; //队列已不空
}
return Q;
}
(3)出队
ElemType QueueOut(SeQueue Q)
{//出队列
if(Q.tag0) { cout<<“队列为空”<
{Q.front=(Q.front+1) % m;
e=Q.data[Q.front];
if(Q.front==Q.rear) Q.tag=0; //空队列
}
return(e);
}
① 写出循环队列的类型定义;
② 写出“从队尾删除”和“从队头插入”的算法。
[题目分析] 用一维数组 v[0…M-1]实现循环队列,其中M是队列长度。设队头指针 front和队尾指针rear,约定front指向队头元素的前一位置,rear指向队尾元素。定义front=rear时为队空,(rear+1)%m=front 为队满。约定队头端入队向下标小的方向发展,队尾端入队向下标大的方向发展。
[算法描述]
①
#define M 队列可能达到的最大长度
typedef struct
{elemtp data[M];
int front,rear;
}cycqueue;
②
elemtp delqueue ( cycqueue Q)
//Q是如上定义的循环队列,本算法实现从队尾删除,若删除成功,返回被删除元素,否则给出出错信息。
{if (Q.front==Q.rear) { cout<<“队列空”<
return(Q.data[(Q.rear+1+M)%M]); //返回出队元素。
}//从队尾删除算法结束
// Q是顺序存储的循环队列,本算法实现“从队头插入”元素x。
{if (Q.rear==(Q.front-1+M)%M) { cout<<“队满”<
Q.front=(Q.front-1+M)%M; //修改队头指针。
}// 结束从队头插入算法。
② 写出计算Ack(m,n)的非递归算法。
[算法描述]
int Ack(int m,n)
{if (m0) return(n+1);
else if(m!=0&&n0) return(Ack(m-1,1));
else return(Ack(m-1,Ack(m,m-1));
}//算法结束
① Ack(2,1)的计算过程
Ack(2,1)= Ack(1,Ack(2,0)) //因m<>0,n<>0而得
= Ack(1,Ack(1,1)) //因m<>0,n=0而得
= Ack(1,Ack(0,Ack(1,0))) // 因m<>0,n<>0而得
= Ack(1,Ack(0,Ack(0,1))) // 因m<>0,n=0而得
= Ack(1,Ack(0,2)) // 因m=0而得
= Ack(1,3) // 因m=0而得
= Ack(0,Ack(1,2)) //因m<>0,n<>0而得
= Ack(0,Ack(0,Ack(1,1))) //因m<>0,n<>0而得
= Ack(0,Ack(0,Ack(0,Ack(1,0)))) //因m<>0,n<>0而得
= Ack(0,Ack(0,Ack(0,Ack(0,1)))) //因m<>0,n=0而得
= Ack(0,Ack(0,Ack(0,2))) //因m=0而得
= Ack(0,Ack(0,3)) //因m=0而得
= Ack(0,4) //因n=0而得
=5 //因n=0而得
②
int Ackerman(int m, int n)
{int akm[M][N];int i,j;
for(j=0;j
for(j=1;j
}
return(akm[m][n]);
}//算法结束
① 求链表中的最大整数;
② 求链表的结点个数;
③ 求所有整数的平均值。
[算法描述]
①
int GetMax(LinkList p)
{
if(!p->next)
return p->data;
else
{
int max=GetMax(p->next);
return p->data>=max ? p->data:max;
}
}
②
int GetLength(LinkList p)
{
if(!p->next)
return 1;
else
{
return GetLength(p->next)+1;
}
}
③
double GetAverage(LinkList p , int n)
{
if(!p->next)
return p->data;
else
{
double ave=GetAverage(p->next,n-1);
return (ave*(n-1)+p->data)/n;
}
}三、数组和广义表
(一)应用题
(1)已知模式串t=‘abcaabbabcab’写出用KMP法求得的每个字符对应的next和nextval函数值。
答案:
模式串t的next和nextval值如下:
j 1 2 3 4 5 6 7 8 9 10 11 12
t串 a b c a a b b a b c a b
next[j] 0 1 1 1 2 2 3 1 2 3 4 5
nextval[j] 0 1 1 0 2 1 3 0 1 1 0 5
① 计算模式p的naxtval函数值;
② 不写出算法,只画出利用KMP算法进行模式匹配时每一趟的匹配过程。
答案:
① p的nextval函数值为0110132。(p的next函数值为0111232)。
② 利用KMP(改进的nextval)算法,每趟匹配过程如下:
第一趟匹配: abcaabbabcabaacbacba
abcab(i=5,j=5)
第二趟匹配: abcaabbabcabaacbacba
abc(i=7,j=3)
第三趟匹配: abcaabbabcabaacbacba
a(i=7,j=1)
第四趟匹配: abcaabbabcabaac bacba
(成功) abcabaa(i=15,j=8)
① 存放该数组所需多少单元?
② 存放数组第4列所有元素至少需多少单元?
③ 数组按行存放时,元素A[7,4]的起始地址是多少?
④ 数组按列存放时,元素A[4,7]的起始地址是多少?
答案:
每个元素32个二进制位,主存字长16位,故每个元素占2个字长,行下标可平移至1到11。
(1)242 (2)22 (3)s+182 (4)s+142
L=(apple,(orange,(strawberry,(banana)),peach),pear)
答案:H(H(T(H(T(H(T(L)))))))
(1)写一个算法统计在输入字符串中各个不同字符出现的频度并将结果存入文件(字符串中的合法字符为A-Z这26个字母和0-9这10个数字)。
[题目分析] 由于字母共26个,加上数字符号10个共36个,所以设一长36的整型数组,前10个分量存放数字字符出现的次数,余下存放字母出现的次数。从字符串中读出数字字符时,字符的ASCII代码值减去数字字符 ‘0’的ASCII代码值,得出其数值(0…9),字母的ASCII代码值减去字符‘A’的ASCII代码值加上10,存入其数组的对应下标分量中。遇其它符号不作处理,直至输入字符串结束。
[算法描述]
void Count()
//统计输入字符串中数字字符和字母字符的个数。
{int i,num[36];
char ch;
for(i=0;i<36;i++)num[i]=0;// 初始化
while((ch=getchar())!=‘#’) //‘#’表示输入字符串结束。
if(‘0’<=ch<=‘9’){i=ch-48;num[i]++;} // 数字字符
else if(‘A’<=ch<=‘Z’){i=ch-65+10;num[i]++;}// 字母字符
for(i=0;i<10;i++) // 输出数字字符的个数
cout<<“数字”< for(i=10;i<36;i++)// 求出字母字符的个数
cout<<“字母字符”< }
(2)写一个递归算法来实现字符串逆序存储,要求不另设串存储空间。
[题目分析]实现字符串的逆置并不难,但本题“要求不另设串存储空间”来实现字符串逆序存储,即第一个输入的字符最后存储,最后输入的字符先存储,使用递归可容易做到。
[算法描述]
void InvertStore(char A[])
//字符串逆序存储的递归算法。
{char ch;
static int i = 0;//需要使用静态变量
cin>>ch;
if (ch!= ‘.’) //规定’.'是字符串输入结束标志
{InvertStore(A);
A[i++] = ch;//字符串逆序存储
}
A[i] = ‘\0’; //字符串结尾标记
}
[题目分析]本题是字符串的插入问题,要求在字符串s的pos位置,插入字符串t。首先应查找字符串s的pos位置,将第pos个字符到字符串s尾的子串向后移动字符串t的长度,然后将字符串t复制到字符串s的第pos位置后。
对插入位置pos要验证其合法性,小于1或大于串s的长度均为非法,因题目假设给字符串s的空间足够大,故对插入不必判溢出。
[算法描述]
void insert(char *s,char *t,int pos)
//将字符串t插入字符串s的第pos个位置。
{int i=1,x=0; char *p=s,*q=t; //p,q分别为字符串s和t的工作指针
if(pos<1) {cout<<“pos参数位置非法”<
if(*p == ‘/0’) { cout<
while(*p!= ‘/0’) {p++; i++;} //查到尾时,i为字符‘\0’的下标,p也指向‘\0’。
while(q!= ‘\0’) {q++; x++; } //查找字符串t的长度x,循环结束时q指向’\0’。
for(j=i;j>=pos ;j–){(p+x)=*p; p–;}//串s的pos后的子串右移,空出串t的位置。
q–; //指针q回退到串t的最后一个字符
for(j=1;j<=x;j++) *p–=*q–; //将t串插入到s的pos位置上
[算法讨论] 串s的结束标记(’\0’)也后移了,而串t的结尾标记不应插入到s中。
[题目分析]本题要求字符串s1拆分成字符串s2和字符串s3,要求字符串s2“按给定长度n格式化成两端对齐的字符串”,即长度为n且首尾字符不得为空格字符。算法从左到右扫描字符串s1,找到第一个非空格字符,计数到n,第n个拷入字符串s2的字符不得为空格,然后将余下字符复制到字符串s3中。
[算法描述]
void format (char *s1,*s2,*s3)
//将字符串s1拆分成字符串s2和字符串s3,要求字符串s2是长n且两端对齐
{char *p=s1, *q=s2;
int i=0;
while(*p!= ‘\0’ && *p== ’ ‘) p++;//滤掉s1左端空格
if(*p== ‘\0’) {cout<<“字符串s1为空串或空格串”<
if(p ==’\0’){cout<<“字符串s1没有”<
{p-- ; //p指针也后退
while(*p’ ‘&&*p!=’\0’) p++;//往后查找一个非空格字符作串s2的尾字符
if(*p==’\0’)
{cout<<“s1串没有”<
*(++q)=’\0’; //置s2字符串结束标记
}
*q=s3;p++; //将s1串其余部分送字符串s3。
while (*p!= ‘\0’) {*q=*p; q++; p++;}
*q=’\0’; //置串s3结束标记
}
① 写一个算法判断a中所有元素是否互不相同?输出相关信息(yes/no);
② 试分析算法的时间复杂度。
①
[题目分析]判断二维数组中元素是否互不相同,只有逐个比较,找到一对相等的元素,就可结论为不是互不相同。如何达到每个元素同其它元素比较一次且只一次?在当前行,每个元素要同本行后面的元素比较一次(下面第一个循环控制变量p的for循环),然后同第i+1行及以后各行元素比较一次,这就是循环控制变量k和p的二层for循环。
[算法描述]
int JudgEqual(ing a[m][n],int m,n)
//判断二维数组中所有元素是否互不相同,如是,返回1;否则,返回0。
{for(i=0;i
//只要有一个相同的,就结论不是互不相同
for(k=i+1;k
}// for(j=0;j
}//算法JudgEqual结束
②二维数组中的每一个元素同其它元素都比较一次,数组中共mn个元素,第1个元素同其它mn-1个元素比较,第2个元素同其它mn-2 个元素比较,……,第mn-1个元素同最后一个元素(mn)比较一次,所以在元素互不相等时总的比较次数为 (mn-1)+(mn-2)+…+2+1=(mn)(mn-1)/2。在有相同元素时,可能第一次比较就相同,也可能最后一次比较时相同,设在(mn-1)个位置上均可能相同,这时的平均比较次数约为(mn)(m*n-1)/4,总的时间复杂度是O(n4)。
[题目分析]本题属于排序问题,只是排出正负,不排出大小。可在数组首尾设两个指针i和j,i自小至大搜索到负数停止,j自大至小搜索到正数停止。然后i和j所指数据交换,继续以上过程,直到 i=j为止。
[算法描述]
void Arrange(int A[],int n)
//n个整数存于数组A中,本算法将数组中所有正数排在所有负数的前面
{int i=0,j=n-1,x; //用类C编写,数组下标从0开始
while(i
while(i
[算法讨论]对数组中元素各比较一次,比较次数为n。最佳情况(已排好,正数在前,负数在后)不发生交换,最差情况(负数均在正数前面)发生n/2次交换。用类c编写,数组界偶是0…n-1。空间复杂度为O(1).四、树和二叉树
(一)应用题
(1)试找出满足下列条件的二叉树
① 先序序列与后序序列相同 ②中序序列与后序序列相同
③ 先序序列与中序序列相同 ④中序序列与层次遍历序列相同
答案:先序遍历二叉树的顺序是“根—左子树—右子树”,中序遍历“左子树—根—右子树”,后序遍历顺序是:“左子树—右子树―根",根据以上原则有
① 或为空树,或为只有根结点的二叉树
② 或为空树,或为任一结点至多只有左子树的二叉树.
③ 或为空树,或为任一结点至多只有右子树的二叉树.
④ 或为空树,或为任一结点至多只有右子树的二叉树
(2) 假设用于通信的电文仅由8个字母组成,字母在电文中出现的频率分别为0.07,0.19,0.02,0.06,0.32,0.03,0.21,0.10。
① 试为这8个字母设计赫夫曼编码。
② 试设计另一种由二进制表示的等长编码方案。
③ 对于上述实例,比较两种方案的优缺点。
答案:方案1;哈夫曼编码
先将概率放大100倍,以方便构造哈夫曼树。
w={7,19,2,6,32,3,21,10},按哈夫曼规则:【[(2,3),6], (7,10)】, ¬……19, 21, 32
(100)
(40) (60)
19 21 32 (28)
(17) (11)
7 10 6 (5)
2 3
方案比较:
方案1的WPL=2(0.19+0.32+0.21)+4(0.07+0.06+0.10)+5(0.02+0.03)=1.44+0.92+0.25=2.61
方案2的WPL=3(0.19+0.32+0.21+0.07+0.06+0.10+0.02+0.03)=3
结论:哈夫曼编码优于等长二进制编码
(4)已知下列字符A、B、C、D、E、F、G的权值分别为3、12、7、4、2、8,11,试填写出其对应哈夫曼树HT的存储结构的初态和终态。
答案:
(二)算法设计题
以二叉链表作为二叉树的存储结构,编写以下算法:
(1)统计二叉树的叶结点个数。
[题目分析]如果二叉树为空,返回0,如果二叉树不为空且左右子树为空,返回1,如果二叉树不为空,且左右子树不同时为空,返回左子树中叶子节点个数加上右子树中叶子节点个数。
[算法描述]
int LeafNodeCount(BiTree T)
{
if(TNULL)
return 0; //如果是空树,则叶子结点个数为0
else if(T->lchildNULL&&T->rchildNULL)
return 1; //判断结点是否是叶子结点(左孩子右孩子都为空),若是则返回1
else
return LeafNodeCount(T->lchild)+LeafNodeCount(T->rchild);
}
(2)判别两棵树是否相等。
[题目分析]先判断当前节点是否相等(需要处理为空、是否都为空、是否相等),如果当前节点不相等,直接返回两棵树不相等;如果当前节点相等,那么就递归的判断他们的左右孩子是否相等。
[算法描述]
int compareTree(TreeNode* tree1, TreeNode* tree2)
//用分治的方法做,比较当前根,然后比较左子树和右子树
{bool tree1IsNull = (tree1NULL);
bool tree2IsNull = (tree2==NULL);
if(tree1IsNull != tree2IsNull)
{
return 1;
}
if(tree1IsNull && tree2IsNull)
{//如果两个都是NULL,则相等
return 0;
}//如果根节点不相等,直接返回不相等,否则的话,看看他们孩子相等不相等
if(tree1->c != tree2->c)
{
return 1;
}
return (compareTree(tree1->left,tree2->left)&compareTree(tree1->right,tree2->right))
(compareTree(tree1->left,tree2->right)&compareTree(tree1->right,tree2->left));
}//算法结束
[题目分析]如果某结点左右子树为空,返回,否则交换该结点左右孩子,然后递归交换左右子树。
[算法描述]
void ChangeLR(BiTree &T)
{
BiTree temp;
if(T->lchildNULL&&T->rchildNULL)
return;
else
{
temp = T->lchild;
T->lchild = T->rchild;
T->rchild = temp;
}//交换左右孩子
ChangeLR(T->lchild); //递归交换左子树
ChangeLR(T->rchild); //递归交换右子树
}
(4)设计二叉树的双序遍历算法(双序遍历是指对于二叉树的每一个结点来说,先访问这个结点,再按双序遍历它的左子树,然后再一次访问这个结点,接下来按双序遍历它的右子树)。
[题目分析]若树为空,返回;若某结点为叶子结点,则仅输出该结点;否则先输出该结点,递归遍历其左子树,再输出该结点,递归遍历其右子树。
[算法描述]
void DoubleTraverse(BiTree T)
{
if(T == NULL)
return;
else if(T->lchildNULL&&T->rchildNULL)
cout else
{
cout DoubleTraverse(T->lchild); //递归遍历左子树
cout DoubleTraverse(T->rchild); //递归遍历右子树
}
}
(5)计算二叉树最大的宽度(二叉树的最大宽度是指二叉树所有层中结点个数的最大值)。
[题目分析] 求二叉树高度的算法见上题。求最大宽度可采用层次遍历的方法,记下各层结点数,每层遍历完毕,若结点数大于原先最大宽度,则修改最大宽度。
[算法描述]
int Width(BiTree bt)//求二叉树bt的最大宽度
{if (bt==null) return (0); //空二叉树宽度为0
else
{BiTree Q[];//Q是队列,元素为二叉树结点指针,容量足够大
front=1;rear=1;last=1;
//front队头指针,rear队尾指针,last同层最右结点在队列中的位置
temp=0; maxw=0; //temp记局部宽度, maxw记最大宽度
Q[rear]=bt; //根结点入队列
while(front<=last)
{p=Q[front++]; temp++; //同层元素数加1
if (p->lchild!=null) Q[++rear]=p->lchild; //左子女入队
if (p->rchild!=null) Q[++rear]=p->rchild; //右子女入队
if (front>last) //一层结束,
{last=rear;
if(temp>maxw) maxw=temp;
//last指向下层最右元素, 更新当前最大宽度
temp=0;
}//if
}//while
return (maxw);
}//结束width
[题目分析]
若某个结点左子树空右子树非空或者右子树空左子树非空,则该结点为度为1的结点
[算法描述]
int Level(BiTree bt) //层次遍历二叉树,并统计度为1的结点的个数
{int num=0; //num统计度为1的结点的个数
if(bt){QueueInit(Q); QueueIn(Q,bt);//Q是以二叉树结点指针为元素的队列
while(!QueueEmpty(Q))
{p=QueueOut(Q); cout if(p->lchild && !p->rchild ||!p->lchild && p->rchild)num++;
//度为1的结点
if(p->lchild) QueueIn(Q,p->lchild); //非空左子女入队
if(p->rchild) QueueIn(Q,p->rchild); //非空右子女入队
} // while(!QueueEmpty(Q))
}//if(bt)
return(num);
}//返回度为1的结点的个数
[题目分析]因为后序遍历栈中保留当前结点的祖先的信息,用一变量保存栈的最高栈顶指针,每当退栈时,栈顶指针高于保存最高栈顶指针的值时,则将该栈倒入辅助栈中,辅助栈始终保存最长路径长度上的结点,直至后序遍历完毕,则辅助栈中内容即为所求。
[算法描述]
void LongestPath(BiTree bt)//求二叉树中的第一条最长路径长度
{BiTree p=bt,l[],s[];
//l, s是栈,元素是二叉树结点指针,l中保留当前最长路径中的结点
int i,top=0,tag[],longest=0;
while(p || top>0)
{while§ {s[++top]=p;tag[top]=0; p=p->Lc;} //沿左分枝向下
if(tag[top]==1) //当前结点的右分枝已遍历
{if(!s[top]->Lc && !s[top]->Rc) //只有到叶子结点时,才查看路径长度
if(top>longest)
{for(i=1;i<=top;i++) l[i]=s[i]; longest=top; top–;}
//保留当前最长路径到l栈,记住最高栈顶指针,退栈
}
else if(top>0) {tag[top]=1; p=s[top].Rc;} //沿右子分枝向下
}//while(p!=null||top>0)
}//结束LongestPath
[题目分析]采用先序遍历的递归方法,当找到叶子结点b时,由于b叶子结点尚未添加到path中,因此在输出路径时还需输出b->data值。
[算法描述]
void AllPath(BTNode *b,ElemType path[],int pathlen)
{int i;
if (b!=NULL)
{if (b->lchildNULL && b->rchildNULL) //*b为叶子结点
{cout << " " << b->data << “到根结点路径:” << b->data;
for (i=pathlen-1;i>=0;i–)
cout << endl;
}
else
{path[pathlen]=b->data; //将当前结点放入路径中
pathlen++; //路径长度增1
AllPath(b->lchild,path,pathlen); //递归扫描左子树
AllPath(b->rchild,path,pathlen); //递归扫描右子树
pathlen–; //恢复环境
}
}// if (b!=NULL)
}//算法结束五、图
(一)应用题
(1)已知如图6.33所示的无向网,请给出:
① 邻接矩阵;
② 邻接表;
③ 最小生成树
答案:
b → a 4 → c 5 → d 5 → e 9
c → a 3 → b 5 → d 5 → h 5
d → b 5 → c 5 → e 7 → f 6 → g 5 → h 4
e → b 9 → d 7 → f 3
f → d 6 → e 3 → g 2
g → d 5 → f 2 → h 6
(2)试对图6.36所示的AOE-网:
① 求这个工程最早可能在什么时间结束;
② 求每个活动的最早开始时间和最迟开始时间;
③ 确定哪些活动是关键活动
1
Ve 0 19 15 29 38 43
Vl 0 19 15 37 38 43
1
e 0 0 15 19 19 15 29 38
l 17 0 15 27 19 27 37 38
-e 17 0 0 8 0 12 8 0
此工程最早完成时间为43。关键路径为<1, 3><3, 2><2, 5><5, 6>
1)分别以邻接矩阵和邻接表作为存储结构,实现以下图的基本操作:
① 增加一个新顶点v,InsertVex(G, v);
② 删除顶点v及其相关的边,DeleteVex(G, v);
③ 增加一条边
④ 删除一条边
[算法描述]
假设图G为有向无权图,以邻接矩阵作为存储结构四个算法分别如下:
① 增加一个新顶点v
Status Insert_Vex(MGraph &G, char v)//在邻接矩阵表示的图G上插入顶点v
{
if(G.vexnum+1)>MAX_VERTEX_NUM return INFEASIBLE;
G.vexs[++G.vexnum]=v;
return OK;
}//Insert_Vex
Status Delete_Vex(MGraph &G,char v)//在邻接矩阵表示的图G上删除顶点v
{
n=G.vexnum;
if((m=LocateVex(G,v))<0) return ERROR;
G.vexs[m]<->G.vexs[n]; //将待删除顶点交换到最后一个顶点
for(i=0;i
G.arcs[m]=G.arcs[n];
G.arcs[m]=G.arcs[n]; //将边的关系随之交换
}
G.arcs[m][m].adj=0;
G.vexnum–;
return OK;
}//Delete_Vex
分析:如果不把待删除顶点交换到最后一个顶点的话,算法将会比较复杂,而伴随着大量元素的移动,时间复杂度也会大大增加。
Status Insert_Arc(MGraph &G,char v,char w)//在邻接矩阵表示的图G上插入边(v,w)
{
if((i=LocateVex(G,v))<0) return ERROR;
if((j=LocateVex(G,w))<0) return ERROR;
if(i==j) return ERROR;
if(!G.arcs[j].adj)
{
G.arcs[j].adj=1;
G.arcnum++;
}
return OK;
}//Insert_Arc
Status Delete_Arc(MGraph &G,char v,char w)//在邻接矩阵表示的图G上删除边(v,w)
{
if((i=LocateVex(G,v))<0) return ERROR;
if((j=LocateVex(G,w))<0) return ERROR;
if(G.arcs[j].adj)
{
G.arcs[j].adj=0;
G.arcnum–;
}
return OK;
}//Delete_Arc
Status Insert_Arc(ALGraph &G,char v,char w)//在邻接表表示的图G上插入边(v,w)
{
if((i=LocateVex(G,v))<0) return ERROR;
if((j=LocateVex(G,w))<0) return ERROR;
p=new ArcNode;
p->adjvex=j;p->nextarc=NULL;
if(!G.vertices.firstarc) G.vertices.firstarc=p;
else
{
for(q=G.vertices.firstarc;q->q->nextarc;q=q->nextarc)
if(q->adjvex==j) return ERROR; //边已经存在
q->nextarc=p;
}
G.arcnum++;
return OK;
}//Insert_Arc
[算法描述]
Void DFSn(Graph G,int v)
{ //从第v个顶点出发非递归实现深度优先遍历图G
Stack s;
SetEmpty(s);
Push(s,v);
While(!StackEmpty(s))
{ //栈空时第v个顶点所在的连通分量已遍历完
Pop(s,k);
If(!visited[k])
{ visited[k]=TRUE;
VisitFunc(k); //访问第k个顶点
//将第k个顶点的所有邻接点进栈
for(w=FirstAdjVex(G,k);w;w=NextAdjVex(G,k,w))
{
if(!visited[w]&&w!=GetTop(s)) Push(s,w); //图中有环时w==GetTop(s)
}
}
}
[题目分析]
利用Dijkstra算法求v0到其它所有顶点的最短路径,分别保存在数组D[i]中,然后求出D[i]中值最大的数组下标m即可。
[算法描述]
int ShortestPath_MAX(AMGraph G, int v0){
//用Dijkstra算法求距离顶点v0的最短路径长度最大的一个顶点m
n=G.vexnum; //n为G中顶点的个数
for(v = 0; v
D[v] = G.arcs[v0][v]; //将v0到各个终点的最短路径长度初始化
if(D[v]< MaxInt) Path [v]=v0; //如果v0和v之间有弧,则将v的前驱置为v0
else Path [v]=-1; //如果v0和v之间无弧,则将v的前驱置为-1
}//for
S[v0]=true; //将v0加入S
D[v0]=0; //源点到源点的距离为0
/开始主循环,每次求得v0到某个顶点v的最短路径,将v加到S集/
for(i=1;i
for(w=0;w
S[v]=true; //将v加入S
for(w=0;w
Path [w]=v; //更改w的前驱为v
}//if
}//for
/*最短路径求解完毕,设距离顶点v0的最短路径长度最大的一个顶点为m */
Max=D[0];
m=0;
for(i=1;i
}
(4)试基于图的深度优先搜索策略写一算法,判别以邻接表方式存储的有向图中是否存在由顶点vi到顶点vj的路径(i≠j)。
[题目分析]
引入一变量level来控制递归进行的层数
[算法描述]
int visited[MAXSIZE]; //指示顶点是否在当前路径上
int level=1;//递归进行的层数
int exist_path_DFS(ALGraph G,int i,int j)//深度优先判断有向图G中顶点i到顶点j
是否有路径,是则返回1,否则返回0
{
if(ij) return 1; //i就是j
else
{
visited[i]=1;
for(p=G.vertices[i].firstarc;p;p=p->nextarc,level–)
{ level++;
k=p->adjvex;
if(!visited[k]&&exist_path(k,j)) return 1;//i下游的顶点到j有路径
}//for
}//else
if (level1) return 0;
}//exist_path_DFS
(5)采用邻接表存储结构,编写一个算法,判别无向图中任意给定的两个顶点之间是否存在一条长度为为k的简单路径。
[算法描述]
int visited[MAXSIZE];
int exist_path_len(ALGraph G,int i,int j,int k)
//判断邻接表方式存储的有向图G的顶点i到j是否存在长度为k的简单路径
{if(ij&&k0) return 1; //找到了一条路径,且长度符合要求
else if(k>0)
{visited[i]=1;
for(p=G.vertices[i].firstarc;p;p=p->nextarc)
{l=p->adjvex;
if(!visited[l])
if(exist_path_len(G,l,j,k-1)) return 1; //剩余路径长度减一
}//for
visited[i]=0; //本题允许曾经被访问过的结点出现在另一条路径中
}//else
return 0; //没找到
}//exist_path_len六、查找算法
(一)应用题
(1)假定对有序表:(3,4,5,7,24,30,42,54,63,72,87,95)进行折半查找,试回答下列问题:
① 画出描述折半查找过程的判定树;
② 若查找元素54,需依次与哪些元素比较?
③ 若查找元素90,需依次与哪些元素比较?
④ 假定每个元素的查找概率相等,求查找成功时的平均查找长度。
答案:
①先画出判定树如下(注:mid=(1+12)/2=6):
30
5 63
3 7 42 87
4 24 54 72 95
②查找元素54,需依次与30, 63, 42, 54 元素比较;
③查找元素90,需依次与30, 63,87, 95元素比较;
④求ASL之前,需要统计每个元素的查找次数。判定树的前3层共查找1+2×2+4×3=17次;
但最后一层未满,不能用8×4,只能用5×4=20次,
所以ASL=1/12(17+20)=37/12≈3.08
答案:
12
7 17
2 11 16 21
4 9 13
验算方法: 用中序遍历应得到排序结果:2,4,7,9,11,12,13,16,17,21
① 试按表中元素的顺序依次插入一棵初始为空的二叉排序树,画出插入完成之后的二叉排序树,并求其在等概率的情况下查找成功的平均查找长度。
② 若对表中元素先进行排序构成有序表,求在等概率的情况下对此有序表进行折半查找时查找成功的平均查找长度。
③ 按表中元素顺序构造一棵平衡二叉排序树,并求其在等概率的情况下查找成功的平均查找长度。
答案:
① 插入90 ② 插入25 ③ 插入45 ④ 删除60
① 画出哈希表的示意图;
② 若查找关键字63,需要依次与哪些关键字进行比较?
③ 若查找关键字60,需要依次与哪些关键字比较?
④ 假定每个关键字的查找概率相等,求查找成功时的平均查找长度。
答案:
①画表如下:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
32 17 63 49 24 40 10 30 31 46 47
②查找63,首先要与H(63)=63%16=15号单元内容比较,即63与31比较 ,不匹配;
然后顺移,与46,47,32,17,63相比,一共比较了6次!
③查找60,首先要与H(60)=60%16=12号单元内容比较,但因为12号单元为空(应当有空标记),所以应当只比较这一次即可。
④对于黑色数据元素,各比较1次;共6次;
对红色元素则各不相同,要统计移位的位数。“63”需要6次,“49”需要3次,“40”需要2次,“46”需要3次,“47”需要3次,
所以ASL=1/11(6+2+3×3+6)=23/11
答案:
散列地址 0 1 2 3 4 5 6 7 8 9
关键字 14 1 9 23 84 27 55 20
比较次数 1 1 1 2 3 4 1 2
平均查找长度:ASLsucc=(1+1+1+2+3+4+1+2)/8=15/8
以关键字27为例:H(27)=27%7=6(冲突) H1=(6+1)%10=7(冲突)
H2=(6+22)%10=0(冲突) H3=(6+33)%10=5 所以比较了4次。
① 线性探测法;
② 链地址法。
答案:
①
散列地址 0 1 2 3 4 5 6 7 8 9 10
关键字 4 12 49 38 13 24 32 21
比较次数 1 1 1 2 1 2 1 2
ASLsucc =(1+1+1+2+1+2+1+2)/8=11/8
ASLunsucc=(1+2+1+8+7+6+5+4+3+2+1)/11=40/11
ASLunsucc=(1+2+1+2+3+1+3+1+3+1+1)/11=19/11
(二)算法设计题
(1)试写出折半查找的递归算法。
[算法描述]
int BinSrch(rectype r[ ],int k,low,high)
//在长为n的有序表中查找关键字k,若查找成功,返回k所在位置,查找失败返回0。
{if(low≤high) //low和high分别是有序表的下界和上界
{mid=(low+high)/2;
if(r[mid].keyk)return (mid);
else if(r[mid].key>k)return (BinSrch(r,k,mid+1,high));
else return (BinSrch(r,k,low,mid-1));
}
else return (0);//查找失败。
}//算法结束
(2)试写一个判别给定二叉树是否为二叉排序树的算法。
[题目分析] 根据二叉排序树中序遍历所得结点值为增序的性质,在遍历中将当前遍历结点与其前驱结点值比较,即可得出结论,为此设全局指针变量pre(初值为null)和全局变量flag,初值为true。若非二叉排序树,则置flag为false。
[算法描述]
#define true 1
#define false 0
typedef struct node
{datatype data; struct node *lchild,*rchild;} *BTree;
void JudgeBST(BTree T,int flag)
// 判断二叉树是否是二叉排序树,本算法结束后,在调用程序中由flag得出结论。
{ if(T!=null && flag)
{ Judgebst(T->lchild,flag);// 中序遍历左子树
if(prenull)pre=T;// 中序遍历的第一个结点不必判断
else if(pre->datadata)pre=T;//前驱指针指向当前结点
else{flag=flase;} //不是完全二叉树
Judgebst (T->rchild,flag);// 中序遍历右子树
}//JudgeBST算法结束
[题目分析]本题算法之一是如上题一样,中序遍历二叉树,在“访问根结点”处判断结点值是否≥x,如是则输出,并记住第一个≥x值结点的指针。这里给出另一个算法,利用二叉排序树的性质,如果根结点的值>=x,则除左分枝中可能有
// 中序输出以t为根的二叉排序树的结点
{if(t){Print(t->lchild);
Cout<
}
}
void PrintAllx(BSTree bst,datatype x)
//在二叉排序树bst中,查找值≥x的结点并输出
{p=bst;
if(p)
{while(p && p->data
bst=p; //bst所指结点是值≥x的结点的树的根
if(p)
{f=p; p=p->lchild ;//找第一个值
if§ f->lchild=null; //双亲与找到的第一个值
}//while
}//内层if(p)
}//第一层if(p)
}//PrintAllx
[算法描述]
void SearchBST(BiTree &T,int target){
BiTree s,q,f; //以数据值target,新建结点s
s=new BiTNode;
s->data.x=target;
s->data.count=0;
s->lchild=s->rchild=NULL;
T=s;
return ;
} //如果该树为空则跳出该函数
f=NULL;
q=T;
while (q){
if (q->data.x==target){
q->data.count++;
return ;
} //如果找到该值则计数加一
f=q;
if (q->data.x>target) //如果查找值比目标值大,则为该树左孩子
q=q->lchild;
else //否则为右孩子
q=q->rchild;
} //将新结点插入树中
if(f->data.x>target)
f->lchild=s;
else
f->rchild=s;
}
(5)假设一棵平衡二叉树的每个结点都表明了平衡因子b,试设计一个算法,求平衡二叉树的高度。
[题目分析] 因为二叉树各结点已标明了平衡因子b,故从根结点开始记树的层次。根结点的层次为1,每下一层,层次加1,直到层数最大的叶子结点,这就是平衡二叉树的高度。当结点的平衡因子b为0时,任选左右一分枝向下查找,若b不为0,则沿左(当b=1时)或右(当b=-1时)向下查找。
[算法描述]
int Height(BSTree t)
// 求平衡二叉树t的高度
{level=0;p=t;
while(p)
{level++; // 树的高度增1
if(p->bf<0)p=p->rchild;//bf=-1 沿右分枝向下
//bf是平衡因子,是二叉树t结点的一个域,因篇幅所限,没有写出其存储定义
else p=p->lchild; //bf>=0 沿左分枝向下
}//while
return (level);//平衡二叉树的高度
} //算法结束
[算法描述]
bool insert(){
int data;
cin>>data;
int ant=hash(data);
LinkList p=HT[ant]; //初始化散列表
while (p->next){
if(p->next->data==data)
return false;
p=p->next;
} //找到插入位置
LinkList s;
s=new LNode;
s->data=data;
s->next=p->next;
p->next=s; //插入该结点
return true;
}
int data;
cin>>data;
int ant=hash(data);
LinkList p=HT[ant]; //初始化散列表
while (p->next){
if(p->next->data==data){
LinkList s=p->next;
p->next=s->next;
delete s; //删除该结点
return true;
} //找到删除位置
p=p->next; //遍历下一个结点
}
return false;
}七、排序算法
(一)应用题
(1)设待排序的关键字序列为{12,2,16,30,28,10,16*,20,6,18},试分别写出使用以下排序方法,每趟排序结束后关键字序列的状态。
① 直接插入排序
② 折半插入排序
③ 希尔排序(增量选取5,3,1)
④ 冒泡排序
⑤ 快速排序
⑥ 简单选择排序
⑦ 堆排序
⑧ 二路归并排序
答案:
①直接插入排序
[2 12] 16 30 28 10 16* 20 6 18
[2 12 16] 30 28 10 16* 20 6 18
[2 12 16 30] 28 10 16* 20 6 18
[2 12 16 28 30] 10 16* 20 6 18
[2 10 12 16 28 30] 16* 20 6 18
[2 10 12 16 16* 28 30] 20 6 18
[2 10 12 16 16* 20 28 30] 6 18
[2 6 10 12 16 16* 20 28 30] 18
[2 6 10 12 16 16* 18 20 28 30]
10 2 16 6 18 12 16* 20 30 28 (增量选取5)
6 2 12 10 18 16 16* 20 30 28 (增量选取3)
2 6 10 12 16 16* 18 20 28 30 (增量选取1)
2 12 16 28 10 16* 20 6 18 [30]
2 12 16 10 16* 20 6 18 [28 30]
2 12 10 16 16* 6 18 [20 28 30]
2 10 12 16 6 16* [18 20 28 30]
2 10 12 6 16 [16* 18 20 28 30]
2 10 6 12 [16 16* 18 20 28 30]
2 6 10 [12 16 16* 18 20 28 30]
2 6 10 12 16 16* 18 20 28 30]
12 [6 2 10] 12 [28 30 16* 20 16 18]
6 [2] 6 [10] 12 [28 30 16* 20 16 18 ]
28 2 6 10 12 [18 16 16* 20 ] 28 [30 ]
18 2 6 10 12 [16* 16] 18 [20] 28 30
16* 2 6 10 12 16* [16] 18 20 28 30
左子序列递归深度为1,右子序列递归深度为3
2 [12 16 30 28 10 16* 20 6 18]
2 6 [16 30 28 10 16* 20 12 18]
2 6 10 [30 28 16 16* 20 12 18]
2 6 10 12 [28 16 16* 20 30 18]
2 6 10 12 16 [28 16* 20 30 18]
2 6 10 12 16 16* [28 20 30 18]
2 6 10 12 16 16* 18 [20 30 28]
2 6 10 12 16 16* 18 20 [28 30]
2 6 10 12 16 16* 18 20 28 [30]
2 12 16 30 10 28 16 * 20 6 18
2 12 16 30 10 16* 20 28 6 18
2 10 12 16 16* 20 28 30 6 18
2 6 10 12 16 16* 18 20 28 30
答案:
按最低位优先法 →321→156→57→46→28→7→331→33→34→63
分配 [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
321 33 34 156 57 28
331 63 46 7
收集 →321→331→33→63→34→156→46→57→7→28
(3)对输入文件(101,51,19,61,3,71,31,17,19,100,55,20,9,30,50,6,90);当k=6时,使用置换-选择算法,写出建立的初始败者树及生成的初始归并段。
答案:
初始败者树
R2:9,17,19,20,30,50,55,90
R3:6
(1)试以单链表为存储结构,实现简单选择排序算法。
[算法描述]:
void LinkedListSelectSort(LinkedList head)
//本算法一趟找出一个关键字最小的结点,其数据和当前结点进行交换;若要交换指针,则须记下
//当前结点和最小结点的前驱指针
p=head->next;
while(p!=null)
{q=p->next; r=p; //设r是指向关键字最小的结点的指针
while (q!=null)
{if(q->datadata) r=q;
q:=q->next;
}
if(r!=p) r->data<–>p->data;
p=p->next;
}
[算法描述]:
typedef struct node
{ ElemType data;
struct node *prior,*next;
}node,*DLinkedList;
void TwoWayBubbleSort(DLinkedList la)
//对存储在带头结点的双向链表la中的元素进行双向起泡排序。
{int exchange=1; // 设标记
DLinkedList p,temp,tail;
head=la //双向链表头,算法过程中是向下起泡的开始结点
tail=null; //双向链表尾,算法过程中是向上起泡的开始结点
while (exchange)
{p=head->next; //p是工作指针,指向当前结点
exchange=0; //假定本趟无交换
while (p->next!=tail) // 向下(右)起泡,一趟有一最大元素沉底
if (p->data>p->next->data) //交换两结点指针,涉及6条链
{temp=p->next; exchange=1;//有交换
p->next=temp->next;temp->next->prior=p //先将结点从链表上摘下
temp->next=p; p->prior->next=temp; //将temp插到p结点前
temp->prior=p->prior; p->prior=temp;
}
else p=p->next; //无交换,指针后移
tail=p; //准备向上起泡
p=tail->prior;
while (exchange && p->prior!=head)
//向上(左)起泡,一趟有一最小元素冒出
if (p->dataprior->data) //交换两结点指针,涉及6条链
{temp=p->prior; exchange=1; //有交换
p->prior=temp->prior;temp->prior->next=p;
//先将temp结点从链表上摘下
temp->prior=p; p->next->prior=temp; //将temp插到p结点后(右)
temp->next=p->next; p->next=temp;
}
else p=p->prior; //无交换,指针前移
head=p; //准备向下起泡
}// while (exchange)
} //算法结束
[题目分析]利用快速排序思想解决。由于要求“对每粒砾石的颜色只能看一次”,设3个指针i,j和k,分别指向红色、白色砾石的后一位置和待处理的当前元素。从k=n开始,从右向左搜索,若该元素是兰色,则元素不动,指针左移(即k-1);若当前元素是红色砾石,分i>=j(这时尚没有白色砾石)和i
[算法描述]:
void QkSort(rectype r[],int n) {
// r为含有n个元素的线性表,元素是具有红、白和兰色的砾石,用顺序存储结构存储,
//本算法对其排序,使所有红色砾石在前,白色居中,兰色在最后。
int i=1,j=1,k=n,temp;
while (k!=j){
while (r[k].key3) k–;// 当前元素是兰色砾石,指针左移
if (r[k].key1) // 当前元素是红色砾石
if (i>=j){temp=r[k];r[k]=r[i];r[i]=temp; i++;}
//左侧只有红色砾石,交换r[k]和r[i]
else {temp=r[j];r[j]=r[i];r[i]=temp; j++;
//左侧已有红色和白色砾石,先交换白色砾石到位
temp=r[k];r[k]=r[i];r[i]=temp; i++;
//白色砾石(i所指)和待定砾石(j所指)
} //再交换r[k]和r[i],使红色砾石入位。
if (r[k].key==2)
if (i<=j) { temp=r[k];r[k]=r[j];r[j]=temp; j++;}
// 左侧已有白色砾石,交换r[k]和r[j]
else { temp=r[k];r[k]=r[i];r[i]=temp; j=i+1;}
//i、j分别指向红、白色砾石的后一位置
}//while
if (r[k]==2) j++; /* 处理最后一粒砾石
else if (r[k]==1) { temp=r[j];r[j]=r[i];r[i]=temp; i++; j++; }
//最后红、白、兰色砾石的个数分别为: i-1;j-i;n-j+1
}//结束QkSor算法
[算法讨论]若将j(上面指向白色)看作工作指针,将r[1…j-1]作为红色,r[j…k-1]为白色,r[k…n]为兰色。从j=1开始查看,若r[j]为白色,则j=j+1;若r[j]为红色,则交换r[j]与r[i],且j=j+1,i=i+1;若r[j]为兰色,则交换r[j]与r[k];k=k-1。算法进行到j>k为止。
算法片段如下:
int i=1,j=1,k=n;
while(j<=k)
if (r[j]==1) //当前元素是红色
{temp=r[i]; r[i]=r[j]; r[j]=temp; i++;j++; }
else if (r[j]==2) j++; //当前元素是白色
else //(r[j]==3 当前元素是兰色
{temp=r[j]; r[j]=r[k]; r[k]=temp; k–; }
对比两种算法,可以看出,正确选择变量(指针)的重要性。
① 采用顺序存储结构,至多使用一个记录的辅助存储空间;
② 算法的时间复杂度为O(n)。
[算法描述]
void process (int A[n]){
low = 0;
high = n-1;
while ( low
while (low
high++;
if (low
A[low]=A[high];
A[high]=x;
low++;
high–;
}
}
return;
}
[题目分析]把待查记录看作枢轴,先由后向前依次比较,若小于枢轴,则从前向后,直到查找成功返回其位置或失败返回0为止。
[算法描述]
int index (RecType R[],int l,h,datatype key)
{int i=l,j=h;
while (i
if (R[j].keykey) return j;
while (i<=j && R[i].key
}
cout<<“Not find”; return 0;
}//index
① 给出适用于计数排序的顺序表定义;
② 编写实现计数排序的算法;
③ 对于有n个记录的表,关键字比较次数是多少?
④ 与简单选择排序相比较,这种方法是否更好?为什么?
[算法描述]
① typedef struct
{int key;
datatype info
}RecType
② void CountSort(RecType a[],b[],int n)
//计数排序算法,将a中记录排序放入b中
{for(i=0;i
}
}//Count_Sort
③ 对于有n个记录的表,关键码比较n2次。
④ 简单选择排序算法比本算法好。简单选择排序比较次数是n(n-1)/2,且只用一个交换记录的空间;而这种方法比较次数是n2,且需要另一数组空间。
[算法讨论]因题目要求“针对表中的每个记录,扫描待排序的表一趟”,所以比较次数是n2次。若限制“对任意两个记录之间应该只进行一次比较”,则可把以上算法中的比较语句改为:
for(i=0;i
----------- 2022-12-8