admin 管理员组

文章数量: 887021


2023年12月17日发(作者:flex布局设置)

第1章 绪论

习题

1.简述下列概念:数据、数据元素、数据项、数据对象、数据结构、逻辑结构、存储结构、抽象数据类型。

2.试举一个数据结构的例子,叙述其逻辑结构和存储结构两方面的含义和相互关系。

3.简述逻辑结构的四种基本关系并画出它们的关系图。

4.存储结构由哪两种基本的存储方法实现?

5.选择题

(1)在数据结构中,从逻辑上可以把数据结构分成( )。

A.动态结构和静态结构 B.紧凑结构和非紧凑结构

C.线性结构和非线性结构 D.内部结构和外部结构

(2)与数据元素本身的形式、内容、相对位置、个数无关的是数据的( )。

A.存储结构 B.存储实现

C.逻辑结构 D.运算实现

(3)通常要求同一逻辑结构中的所有数据元素具有相同的特性,这意味着( )。

A.数据具有同一特点

B.不仅数据元素所包含的数据项的个数要相同,而且对应数据项的类型要一致

C.每个数据元素都一样

D.数据元素所包含的数据项的个数要相等

(4)以下说法正确的是( )。

A.数据元素是数据的最小单位

B.数据项是数据的基本单位

C.数据结构是带有结构的各数据项的集合

D.一些表面上很不相同的数据可以有相同的逻辑结构

(5)以下与数据的存储结构无关的术语是( )。

A.顺序队列 B. 链表 C. 有序表 D. 链栈

( )是非线性数据结构

(6)以下数据结构中,A.树 B.字符串 C.队 D.栈

6.试分析下面各程序段的时间复杂度。

(1)x=90; y=100;

while(y>0)

if(x>100)

{x=x-10;y--;}

else x++;

(2)for (i=0; i

for (j=0; j

a[i][j]=0;

(3)s=0;

for i=0; i

for(j=0; j

s+=B[i][j];

sum=s;

(4)i=1;

while(i<=n)

i=i*3;

(5)x=0;

for(i=1; i

for (j=1; j<=n-i; j++)

x++;

(6)x=n; //n>1

y=0;

while(x≥(y+1)* (y+1))

y++;

(1)O(1)

(2)O(m*n)

(3)O(n2)

(4)O(log3n)

(5)因为x++共执行了n-1+n-2+……+1= n(n-1)/2,所以执行时间为O(n2)

(6)O(n)

第2章 线性表

1.选择题

(1)一个向量第一个元素的存储地址是100,每个元素的长度为2,则第5个元素的地址是( )。

A.110 B.108 C.100 D.120

(2)在n个结点的顺序表中,算法的时间复杂度是O(1)的操作是( )。

A.访问第i个结点(1≤i≤n)和求第i个结点的直接前驱(2≤i≤n)

B.在第i个结点后插入一个新结点(1≤i≤n)

C.删除第i个结点(1≤i≤n)

D.将n个结点从小到大排序

(3) 向一个有127个元素的顺序表中插入一个新元素并保持原来顺序不变,平均要移。

动 的元素个数为( )A.8 B.63.5 C.63 D.7

(4)链接存储的存储结构所占存储空间( )。

A.分两部分,一部分存放结点值,另一部分存放表示结点间关系的指针

B.只有一部分,存放结点值

C.只有一部分,存储表示结点间关系的指针

D.分两部分,一部分存放结点值,另一部分存放结点所占单元数

(5)线性表若采用链式存储结构时,要求内存中可用存储单元的地址( )。

A.必须是连续的 B.部分地址必须是连续的

C.一定是不连续的 D.连续或不连续都可以

(6)线性表L在( )情况下适用于使用链式结构实现。

A.需经常修改L中的结点值 B.需不断对L进行删除插入

C.L中含有大量的结点 D.L中结点结构复杂

(7)单链表的存储密度( )。

A.大于1 B.等于1 C.小于1 D.不能确定

(8)将两个各有n个元素的有序表归并成一个有序表,其最少的比较次数是( )。

A.n B.2n-1

C.2n D.n-1

(9)在一个长度为n的顺序表中,在第i个元素(1≤i≤n+1)之前插入一个新元素时须向后移动( )个元素。

A.n-i B.n-i+1 C.n-i-1 D.i

(10) 线性表L=(a1,a2,……an),下列说法正确的是( )A.每个元素都有一个直接前驱和一个直接后继

B.线性表中至少有一个元素

C.表中诸元素的排列必须是由小到大或由大到小

D.除第一个和最后一个元素外,其余每个元素都有一个且仅有一个直接前驱和直接后继。

(11) 若指定有n个元素的向量,则建立一个有序单链表的时间复杂性的量级是( )。

A.O(1) B.O(n) C.O(n2) D.O(nlog2n)

(12) 以下说法错误的是( )。

A.求表长、定位这两种运算在采用顺序存储结构时实现的效率不比采用链式存储结构时实现的效率低

B.顺序存储的线性表可以随机存取

C.由于顺序存储要求连续的存储区域,所以在存储管理上不够灵活

D.线性表的链式存储结构优于顺序存储结构

(13) 在单链表中,要将s所指结点插入到p所指结点之后,其语句应为( )。

A.s->next=p+1; p->next=s;

B.(*p).next=s; (*s).next=(*p).next;

C.s->next=p->next; p->next=s->next;

D.s->next=p->next; p->next=s;

(14) 在双向链表存储结构中,删除p所指的结点时须修改指针( )。

A.p->next->prior=p->prior; p->prior->next=p->next;

B.p->next=p->next->next; p->next->prior=p;

C.p->prior->next=p; p->prior=p->prior->prior;

D.p->prior=p->next->next; p->next=p->prior->prior;

(15) 在双向循环链表中,在p指针所指的结点后插入q所指向的新结点,其修改指针的操作是( )。

A.p->next=q; q->prior=p; p->next->prior=q; q->next=q;

B.p->next=q; p->next->prior=q; q->prior=p; q->next=p->next;

C.q->prior=p; q->next=p->next; p->next->prior=q; p->next=q;

D.q->prior=p; q->next=p->next; p->next=q; p->next->prior=q;

2.算法设计题

(1)将两个递增的有序链表合并为一个递增的有序链表。要求结果链表仍使用原来两个链表的存储空间, 不另外占用其它的存储空间。表中不允许有重复的数据。

void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc){

pa=La->next; pb=Lb->next;

Lc=pc=La; //用La的头结点作为Lc的头结点

while(pa && pb){

if(pa->datadata){ pc->next=pa;pc=pa;pa=pa->next;}

else if(pa->data>pb->data) {pc->next=pb; pc=pb; pb=pb->next;}

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)将两个非递减的有序链表合并为一个非递增的有序链表。要求结果链表仍使用原来两个链表的存储空间, 不另外占用其它的存储空间。表中允许有重复的数据。

void union(LinkList& La, LinkList& Lb, LinkList& Lc, ) {

pa = La->next; pb = Lb->next; // 初始化

Lc=pc=La; //用La的头结点作为Lc的头结点

Lc->next = NULL;

while ( pa || pb ) {

if ( !pa ) { q = pb; pb = pb->next; }

else if ( !pb ) { q = pa; pa = pa->next; }

else if (pa->data <= pb->data ) { q = pa; pa = pa->next; }

else { q = pb; pb = pb->next; }

q->next = Lc->next; Lc->next = q; // 插入

}

delete Lb; //释放Lb的头结点}

(3)已知两个链表A和B分别表示两个集合,其元素递增排列。请设计算法求出A与B的交集,并存放于A链表中。

void Mix(LinkList& La, LinkList& Lb, LinkList& Lc, ) {

pa=la->next;pb=lb->next;∥设工作指针pa和pb;

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->datadata) {u=pa;pa=pa->next; delete u;}

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; ∥注: 本算法中也可对B表不作释放空间的处理

(4)已知两个链表A和B分别表示两个集合,其元素递增排列。请设计算法求出两个集合A和B 的差集(即仅由在A中出现而不在B中出现的元素所构成的集合),并以同样的形式存储,同时返回该集合的元素个数。

void Difference(LinkedList A,B,*n)

∥A和B均是带头结点的递增有序的单链表,分别存储了一个集合,本算法求两集合的差集,存储于单链表A中,*n是结果集合中元素个数,调用时为0

{p=A->next; ∥p和q分别是链表A和B的工作指针。

q=B->next; pre=A; ∥pre为A中p所指结点的前驱结点的指针。

while(p!=null && q!=null)

if(p->datadata){pre=p;p=p->next;*n++;} ∥ A链表中当前结点指针后移。

else if(p->data>q->data)q=q->next; ∥B链表中当前结点指针后移。

else {pre->next=p->next; ∥处理A,B中元素值相同的结点,应删除。

u=p; p=p->next; delete u;} ∥删除结点

(5)设计算法将一个带头结点的单链表A分解为两个具有相同结构的链表B、C,其中B表的结点为A表中值小于零的结点,而C表的结点为A表中值大于零的结点(链表A的元素类型为整型,要求B、C表利用A表的结点)。

(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=p->next;

}

return pmax->data;

(7)设计一个算法,通过遍历一趟,将链表中所有结点的链接方向逆转,仍利用原表的存储空间。

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是给定的两个参数,其值可以和表中的元素相同,也可以不同 )。

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->datanext;

// 查找第一个值 ≥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所指向的结点和它的前缀结点的顺序。

知道双向循环链表中的一个结点,与前驱交换涉及到四个结点(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的数据元素。

[题目分析] 在顺序存储的线性表上删除元素,通常要涉及到一系列元素的移动(删第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

{while(i

if(i

if(i

}

[算法讨论] 因元素只扫描一趟,算法时间复杂度为O(n)。删除元素未使用其它辅助空间,最后线性表中的元素个数是j。

第3章 栈和队列

习题

1.选择题

(1)若让元素1,2,3,4,5依次进栈,则出栈次序不可能出现在( )种情况。

A.5,4,3,2,1 B.2,1,5,4,3 C.4,3,1,2,5 D.2,3,5,4,1

(2)若已知一个栈的入栈序列是1,2,3,…,n,其输出序列为p1,p2,p3,…,pn,若p1=n,则pi为( )。

A.i B.n-i C.n-i+1 D.不确定

(3)数组Q[n]用来表示一个循环队列,f为当前队列头元素的前一位置,r为队尾元素的位置,假定队列中元素的个数小于n,计算队列中元素个数的公式为( )。

A.r-f B.(n+f-r)%n C.n+r-f D.(n+r-f)%n

(4)链式栈结点为:(data,link),top指向栈顶.若想摘除栈顶结点,并将删除结点的值保存到x中,则应执行操作( )。

A.x=top->data;top=top->link; B.top=top->link;x=top->link;

C.x=top;top=top->link; D.x=top->link;

(5)设有一个递归算法如下

int fact(int n) { //n大于等于0

if(n<=0) return 1;

else return n*fact(n-1); }

则计算fact(n)需要调用该函数的次数为( )。

A. n+1 B.n-1 C. n D. n+2

(6)栈在 ( )中有所应用。

A.递归调用 B.函数调用 C.表达式求值 D.前三个选项都有

(7)为解决计算机主机与打印机间速度不匹配问题,通常设一个打印数据缓冲区。主机将要输出的数据依次写入该缓冲区,而打印机则依次从该缓冲区中取出数据。该缓冲区的逻辑结构应该是( )。

A.队列 B.栈 C. 线性表 D.有序表

(8)设栈S和队列Q的初始状态为空,元素e1、e2、e3、e4、e5和e6依次进入栈S,一个元素出栈后即进入Q,若6个元素出队的序列是e2、e4、e3、e6、e5和e1,则栈S的容量至少应该是( )。

A.2 B.3 C.4 D. 6

(9)在一个具有n个单元的顺序栈中,假设以地址高端作为栈底,以top作为栈顶指针,则当作进栈处理时,top的变化为( )。

A.top不变 B.top=0 C.top-- D.top++

(10)设计一个判别表达式中左,右括号是否配对出现的算法,采用( )数据结构最佳。

A.线性表的顺序存储结构 B.队列

C. 线性表的链式存储结构 D. 栈

(11)用链接方式存储的队列,在进行删除运算时( )。

A. 仅修改头指针 B. 仅修改尾指针

C. 头、尾指针都要修改 D. 头、尾指针可能都要修改

(12)循环队列存储在数组]中,则入队时的操作为( )。

A. rear=rear+1 B. rear=(rear+1)%(m-1)

C. rear=(rear+1)%m D. rear=(rear+1)%(m+1)

(13)最大容量为n的循环队列,队尾指针是rear,队头是front,则队空的条件是( )。

A. (rear+1)%n==front B. rear==front

C.rear+1==front D. (rear-l)%n==front

(14)栈和队列的共同点是( )。

A. 都是先进先出 B. 都是先进后出

C. 只允许在端点处插入和删除元素 D. 没有共同点

(15)一个递归算法必须包括( )。

A. 递归部分 B. 终止条件和递归部分

C. 迭代部分 D. 终止条件和迭代部分

(2)回文是指正读反读均相同的字符序列,如“abba”和“abdba”均是回文,但“good”不是回文。试写一个算法判定给定的字符向量是否为回文。(提示:将一半字符入栈)

根据提示,算法可设计为:

//以下为顺序栈的存储结构定义

#define StackSize 100 //假定预分配的栈空间最多为100个元素

typedef char DataType;//假定栈元素的数据类型为字符

typedef struct{

DataType data[StackSize];

int top;

}SeqStack;

int IsHuiwen( char *t)

{//判断t字符向量是否为回文,若是,返回1,否则返回0

SeqStack s;

int i , len;

char temp;

InitStack( &s);

len=strlen(t); //求向量长度

for ( i=0; i

Push( &s, t[i]);

while( !EmptyStack( &s))

{// 每弹出一个字符与相应字符比较

temp=Pop (&s);

if( temp!=S[i]) return 0 ;// 不等则返回0

else i++;

}

return 1 ; // 比较完毕均相等则返回 1

}

(3)设从键盘输入一整数的序列:a1, a2, a3,…,an,试编写算法实现:用栈结构存储输入的整数,当ai≠-1时,将ai进栈;当ai=-1时,输出栈顶整数并出栈。算法应对异常情况(入栈满等)给出相应的信息。

#define maxsize 栈空间容量

void InOutS(int s[maxsize])

//s是元素为整数的栈,本算法进行入栈和退栈操作。

{int top=0; //top为栈顶指针,定义top=0时为栈空。

for(i=1; i<=n; i++) //n个整数序列作处理。

{scanf(“%d”,&x); //从键盘读入整数序列。

if(x!=-1) // 读入的整数不等于-1时入栈。

if(top==maxsize-1){printf(“栈满n”);exit(0);}else s[++top]=x; //x入栈。

else //读入的整数等于-1时退栈。

{if(top==0){printf(“栈空n”);exit(0);} else printf(“出栈元素是%dn”,s[top--]);}}

}//算法结束。

(4)从键盘上输入一个后缀表达式,试编写算法计算表达式的值。规定:逆波兰表达式的长度不超过一行,以$符作为输入结束,操作数之间用空格分隔,操作符只可能有+、-、*、/四种运算。例如:234 34+2*$。

[题目分析]逆波兰表达式(即后缀表达式)求值规则如下:设立运算数栈OPND,对表达式从左到右扫描(读入),当表达式中扫描到数时,压入OPND栈。当扫描到运算符时,从OPND退出两个数,进行相应运算,结果再压入OPND栈。这个过程一直进行到读出表达式结束符$,这时OPND栈中只有一个数,就是结果。

float expr( )

//从键盘输入逆波兰表达式,以‘$’表示输入结束,本算法求逆波兰式表达式的值。

{float OPND[30]; // OPND是操作数栈。

init(OPND); //两栈初始化。

float num=0.0; //数字初始化。

scanf (“%c”,&x);//x是字符型变量。

while(x!=’$’)

{switch

{case‘0’<=x<=’9’:while((x>=’0’&&x<=’9’)||x==’.’) //拼数

if(x!=’.’) //处理整数

{num=num*10+(ord(x)-ord(‘0’));

scanf(“%c”,&x);}

else //处理小数部分。

{scale=10.0; scanf(“%c”,&x);

while(x>=’0’&&x<=’9’)

{num=num+(ord(x)-ord(‘0’)/scale;

scale=scale*10; scanf(“%c”,&x); }

}//else

push(OPND,num); num=0.0;//数压入栈,下个数初始化

case x=‘ ’:break; //遇空格,继续读下一个字符。

case x=‘+’:push(OPND,pop(OPND)+pop(OPND));break;

case x=‘-’:x1=pop(OPND);x2=pop(OPND);push(OPND,x2-x1);break;

case x=‘*’:push(OPND,pop(OPND)*pop(OPND));break;

case x=‘/’:x1=pop(OPND);x2=pop(OPND);push(OPND,x2/x1);break;

default: //其它符号不作处理。

}//结束switch

scanf(“%c”,&x);//读入表达式中下一个字符。

}//结束while(x!=‘$’)

printf(“后缀表达式的值为%f”,pop(OPND));

}//算法结束。

[算法讨论]假设输入的后缀表达式是正确的,未作错误检查。算法中拼数部分是核心。若遇到大于等于‘0’且小于等于‘9’的字符,认为是数。这种字符的序号减去字符‘0’的序号得出数。对于整数,每读入一个数字字符,前面得到的部分数要乘上10再加新读入的数得到新的部分数。当读到小数点,认为数的整数部分已完,要接着处理小数部分。小数部分的数要除以10(或10的幂数)变成十分位,百分位,千分位数等等,与前面部分数相加。在拼数过程中,若遇非数字字符,表示数已拼完,将数压入栈中,并且将变量num恢复为0,准备下一个数。这时对新读入的字符进入‘+’、‘-’、‘*’、‘/’及空格的判断,因此在结束处理数字字符的case后,不能加入break语句。

(5)假设以I和O分别表示入栈和出栈操作。栈的初态和终态均为空,入栈和出栈的操作序列可表示为仅由I和O组成的序列,称可以操作的序列为合法序列,否则称为非法序列。

①下面所示的序列中哪些是合法的?

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){printf(“序列非法n”);exit(0);}

}

i++; //不论A[i]是‘I’或‘O’,指针i均后移。}

if(j!=k) {printf(“序列非法n”);return(false);}

else {printf(“序列合法n”);return(true);}

}//算法结束。

[算法讨论]在入栈出栈序列(即由‘I’和‘O’组成的字符串)的任一位置,入栈次数(‘I’的个数)都必须大于等于出栈次数(即‘O’的个数),否则视作非法序列,立即给出信息,退出算法。整个序列(即读到字符数组中字符串的结束标记‘0’),入栈次数必须等于出栈次数(题目中要求栈的初态和终态都为空),否则视为非法序列。

(6)假设以带头结点的循环链表表示队列,并且只设一个指针指向队尾元素站点(注意不设头指针) ,试编写相应的置空队、判队空 、入队和出队等算法。

算法如下:

//先定义链队结构:

typedef struct queuenode{

Datatype data;

struct queuenode *next;

}QueueNode; //以上是结点类型的定义

typedef struct{

queuenode *rear;

}LinkQueue; //只设一个指向队尾元素的指针

(1)置空队

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;

free(s);

}//回收结点空间

}

(2)判队空

int EmptyQueue( LinkQueue *Q)

{ //判队空

//当头结点的next指针指向自己时为空队

return Q->rear->next->next==Q->rear->next;

}

(3)入队

void EnQueue( LinkQueue *Q, Datatype x)

{ //入队

//也就是在尾结点处插入元素

QueueNode *p=(QueueNode *) malloc (sizeof(QueueNode));//申请新结点

p->data=x; p->next=Q->rear->next;//初始化新结点并链入

Q-rear->next=p;

Q->rear=p;//将尾指针移至新结点

}

(4)出队

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

free(p);//释放被删结点

return x;

}

(7)假设以数组Q[m]存放循环队列中的元素, 同时设置一个标志tag,以tag == 0和tag == 1来区别在队头指针(front)和队尾指针(rear)相等时,队列状态为“空”还是“满”。试编写与此结构相应的插入(enqueue)和删除(dlqueue)算法。

【解答】

循环队列类定义

#include

template class Queue {

public:

Queue ( int=10 );

~Queue ( ) { delete [ ] Q; }

void EnQueue ( Type & item );

Type DeQueue ( );

Type GetFront ( );

void MakeEmpty ( ) { front = rear = tag = 0; } //置空队列

int IsEmpty ( ) const { return front == rear && tag == 0; } //判队列空否

int IsFull ( ) const { return front == rear && tag == 1; } //判队列满否

private:

int rear, front, tag;

Type *Q;

int m;

}

//队尾指针、队头指针和队满标志

//存放队列元素的数组

//队列最大可容纳元素个数

//循环队列的类定义

构造函数

template

Queue:: Queue ( int sz ) : rear (0), front (0), tag(0), m (sz) {

//建立一个最大具有m个元素的空队列。

Q = new Type[m];

assert ( Q != 0 );

}

//创建队列空间

//断言: 动态存储分配成功与否

插入函数

template

void Queue :: EnQueue ( Type &item ) {

//判队列是否不满,满则出错处理

//队尾位置进1, 队尾指针指示实际队尾位置

//进队列

//标志改1,表示队列不空

assert ( ! IsFull ( ) );

}

rear = ( rear + 1 ) % m;

Q[rear] = item;

tag = 1;

删除函数

template

Type Queue :: DeQueue ( ) {

//判断队列是否不空,空则出错处理

//队头位置进1, 队头指针指示实际队头的前一//标志改0, 表示栈不满

//返回原队头元素的值

assert ( ! IsEmpty ( ) );

位置

tag = 0;

}

return Q[front];

front = ( front + 1 ) % m;

读取队头元素函数

template

Type Queue :: GetFront ( ) {

//判断队列是否不空,空则出错处理

//返回队头元素的值

assert ( ! IsEmpty ( ) );

}

return Q[(front + 1) % m];

(8)如果允许在循环队列的两端都可以进行插入和删除操作。要求:

① 写出循环队列的类型定义;

② 写出“从队尾删除”和“从队头插入”的算法。

[题目分析] 用一维数组 v[0..M-1]实现循环队列,其中M是队列长度。设队头指针

front和队尾指针rear,约定front指向队头元素的前一位置,rear指向队尾元素。定义front=rear时为队空,(rear+1)%m=front 为队满。约定队头端入队向下标小的方向发展,队尾端入队向下标大的方向发展。

(1)#define M 队列可能达到的最大长度

typedef struct

{ elemtp data[M];

int front,rear;

} cycqueue;

(2)elemtp delqueue ( cycqueue Q)

//Q是如上定义的循环队列,本算法实现从队尾删除,若删除成功,返回被删除元素,否则给出出错信息。

{ if (==) {printf(“队列空”); exit(0);}

=(-1+M)%M; //修改队尾指针。

return([(+1+M)%M]); //返回出队元素。

}//从队尾删除算法结束

void enqueue (cycqueue Q, elemtp x)

// Q是顺序存储的循环队列,本算法实现“从队头插入”元素x。

{if (==(-1+M)%M) {printf(“队满”; exit(0);)

[]=x; //x 入队列

=(-1+M)%M; //修改队头指针。

}// 结束从队头插入算法。

(9)已知Ackermann函数定义如下:

① 写出计算Ack(m,n)的递归算法,并根据此算法给出出Ack(2,1)的计算过程。

② 写出计算Ack(m,n)的非递归算法。

int Ack(int m,n)

{if (m==0) return(n+1);

else if(m!=0&&n==0) return(Ack(m-1,1));

else return(Ack(m-1,Ack(m,m-1));

}//算法结束

(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而得

(2)int Ackerman( int m, int n)

{int akm[M][N];int i,j;

for(j=0;j

for(i=1;i

{akm[i][0]=akm[i-1][1];

for(j=1;j

akm[i][j]=akm[i-1][akm[i][j-1]];

}

return(akm[m][n]);

}//算法结束

(10)已知f为单链表的表头指针,

链表中存储的都是整型数据,试写出实现下列运算的递归算法:

① 求链表中的最大整数;

② 求链表的结点个数;

求所有整数的平均值。

#include

class List;

//链表结点类 class ListNode {

friend class List;

private:

};

class List {

private:

ListNode* List :: NewNode ( const int item ) {

}

void List :: NewList ( const int retvalue ) {

cout << "Input your data:n";

cin >> value;

while ( value != retvalue ) {

//建立链表, 以输入retvalue结束

//提示

first = NULL; int value; ListNode *q;

//输入

//输入有效

ListNode *newnode = new ListNode (item);

return newnode;

//创建新链表结点

ListNode *first, current;

int Max ( ListNode *f );

int Num ( ListNode *f );

float Avg ( ListNode *f, int& n );

List ( ) : first(NULL), current (NULL) { } //构造函数

~List ( ){ } //析构函数

//创建链表结点, 其值为item

//输出链表所有结点数据

//求链表所有数据的最大值

//求链表中数据个数

//求链表所有数据的平均值

ListNode* NewNode ( const int item );

void PrintList ( );

//链表类

int data;

//结点数据

//结点指针 ListNode *link;

//定义在头文件"RecurveList.h"中

ListNode ( const int item ) : data(item), link(NULL) { } //构造函数

public:

void NewList ( const int retvalue ); //建立链表, 以输入retvalue结束

int GetMax ( ) { return Max ( first ); }

int GetNum ( ) { return Num ( first ); }

float GetAvg ( ) { return Avg ( first ); }

};

}

}

q = NewNode ( value ); //建立包含value的新结点

if ( first == NULL ) first = current = q;

else { current->link = q; current = q; }

cin >> value;

//空表时, 新结点成为链表第一个结点

//非空表时, 新结点链入链尾

//再输入

//链尾封闭 current->link = NULL;

void List :: PrintList ( ) {

}

ListNode *p = first;

//输出链表

cout << "nThe List is : n";

while ( p != NULL ) { cout << p->data << ' '; p = p->link;

cout << ‘n’;

}

int List :: Max ( ListNode *f ) {

}

int List :: Num ( ListNode *f ) {

}

//递归算法 : 求链表中的最大值

//递归结束条件

//在当前结点的后继链表中求最大值

//如果当前结点的值还要大, 返回当前检点值

//否则返回后继链表中的最大值

if ( f ->link == NULL ) return f ->data;

if ( f ->data > temp ) return f ->data;

int temp = Max ( f ->link );

else return temp;

//递归算法 : 求链表中结点个数

//空表, 返回0

//否则, 返回后继链表结点个数加1

//递归算法 : 求链表中所有元素的平均值

//链表中只有一个结点, 递归结束条件

if ( f == NULL ) return 0;

return 1+ Num ( f ->link );

float List :: Avg ( ListNode *f , int& n ) {

if ( f ->link == NULL )

{ n = 1; return ( float ) (f ->data ); }

else { float Sum = Avg ( f ->link, n ) * n; n++; return ( f ->data + Sum ) / n; }

}

#include "RecurveList.h" //定义在主文件中

int main ( int argc, char* argv[ ] ) {

List test; int finished;

cout << “输入建表结束标志数据 :”;

cin >> finished;

}

ist ( );

//输入建表结束标志数据

//建立链表

//打印链表

t ( finished );

cout << "nThe Max is : " << ( );

cout << "nThe Num is : " << ( );

cout << "nThe Ave is : " << () << 'n';

printf ( "Hello World!n" );

return 0;

第4章 串、数组和广义表

习题

1.选择题

(1)串是一种特殊的线性表,其特殊性体现在( )。

A.可以顺序存储 B.数据元素是一个字符

C.可以链式存储 D.数据元素可以是多个字符若

(2)串下面关于串的的叙述中,( )是不正确的?

A.串是字符的有限序列 B.空串是由空格构成的串

C.模式匹配是串的一种重要运算 D.串既可以采用顺序存储,也可以采用链式存储

(3)串“ababaaababaa”的next数组为( )。

A. B. C. D.5

(4)串“ababaabab”的nextval为( )。

A.010104101 B.010102101 C.010100011 D.010101011

(5)串的长度是指( )。

A.串中所含不同字母的个数 B.串中所含字符的个数

C.串中所含不同字符的个数 D.串中所含非空格字符的个数

假设以行序为主序存储二维数组A=array[1..100,1..100],设每个数据元素占2个存(6)储单元,基地址为10,则LOC[5,5]=( )。

A.808 B.818 C.1010 D.1020

(7)设有数组A[i,j],数组的每个元素长度为3字节,i的值为1到8,j的值为1到10,数组从内存首地址BA开始顺序存放,当用以列为主存放时,元素A[5,8]的存储首地址为( )。

A.BA+141 B.BA+180 C.BA+222 D.BA+225

(8)设有一个10阶的对称矩阵A,采用压缩存储方式,以行序为主存储,a11为第一元。

素,其存储地址为1,每个元素占一个地址空间,则a85的地址为( )A.13 B.33 C.18 D.40

(9)若对n阶对称矩阵A以行序为主序方式将其下三角形的元素(包括主对角线上所有。

元素)依次存放于一维数组B[1..(n(n+1))/2]中,则在B中确定aij(i

N]是对称矩阵,将下面三角(包括对角线)以行序存储到一维数组T[N(N+1)/2]A[N,(10)。

中,则对任一上三角元素a[i][j]对应T[k]的下标k是( )A.i(i-1)/2+j B.j(j-1)/2+i C.i(j-i)/2+1

D.j(i-1)/2+1

(11)设二维数组A[1.. m,1.. n](即m行n列)按行存储在数组B[1.. m*n]中,则二。

维数组元素A[i,j]在一维数组B中的下标为( )A.(i-1)*n+j B.(i-1)*n+j-1 C.i*(j-1) D.j*m+i-1

(12)数组A[0..4,-1..-3,5..7]中含有元素的个数( )A.55 B.45 C.36

D.16

(13)广义表A=(a,b,(c,d),(e,(f,g))),则Head(Tail(Head(Tail(Tail(A)))))的值为( )。

A.(g) B.(d) C.c D.d

(14)广义表((a,b,c,d))的表头是( ),表尾是( )。

A.a B.( ) C.(a,b,c,d)

D.(b,c,d)

(15)设广义表L=((a,b,c)),则L的长度和深度分别为( )。

A.1和1 B.1和3 C.1和2 D.2和3

(1)已知模式串t=‘abcaabbabcab’写出用KMP法求得的每个字符对应的next和nextval函数值。

模式串t的next和nextval值如下:

j

t串

next[j]

nextval[j]

11 12

1 2 3 4

5 6 7 8 9 10

a b c a a b b a b c a b0 1 1 1 2 2 3 1 2 3 4 5

0 1 1 0 2 1 3 0 1 1 0 5

(2)设目标为t=“abcaabbabcabaacbacba”,模式为p=“abcabaa”

① 计算模式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)

(3)数组A中,每个元素A[i,j]的长度均为32个二进位,行下标从-1到9,列下标从1到11,从首地址S开始连续存放主存储器中,主存储器字长为16位。求:

① 存放该数组所需多少单元?

② 存放数组第4列所有元素至少需多少单元?

③ 数组按行存放时,元素A[7,4]的起始地址是多少?

④ 数组按列存放时,元素A[4,7]的起始地址是多少?

每个元素32个二进制位,主存字长16位,故每个元素占2个字长,行下标可平移至1到11。

(1)242 (2)22 (3)s+182 (4)s+142

(4)请将香蕉banana用工具 H( )—Head( ),T( )—Tail( )从L中取出。

L=(apple,(orange,(strawberry,(banana)),peach),pear)

H(H(T(H(T(H(T(L)))))))

(5)写一个算法统计在输入字符串中各个不同字符出现的频度并将结果存入文件(字符串中的合法字符为A-Z这26个字母和0-9这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++) // 输出数字字符的个数

printf(“数字%d的个数=%dn”,i,num[i]) for(i=10;i<36;i++)// 求出字母字符的个数

printf(“字母字符%c的个数=%dn”,i+55,num[i])}// 算法结束。

(6)写一个递归算法来实现字符串逆序存储,要求不另设串存储空间。

[题目分析]实现字符串的逆置并不难,但本题“要求不另设串存储空间”来实现字符串逆序存储,即第一个输入的字符最后存储,最后输入的字符先存储,使用递归可容易做到。

void InvertStore(char A[])

//字符串逆序存储的递归算法。

{ char ch;

static int i = 0;//需要使用静态变量

scanf ("%c",&ch);

if (ch!= '.') //规定'.'是字符串输入结束标志

{InvertStore(A);

A[i++] = ch;//字符串逆序存储

}

A[i] = '0'; //字符串结尾标记

}//结束算法InvertStore。

(7)编写算法,实现下面函数的功能。函数void insert(char*s,char*t,int pos)将字符串t插入到字符串s中,插入位置为pos。假设分配给字符串s的空间足够让字符串t插入。(说明:不得使用任何库函数)

[题目分析]本题是字符串的插入问题,要求在字符串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) {printf(“pos参数位置非法n”);exit(0);}

while(*p!=’0’&&i

//若pos小于串s长度,则查到pos位置时,i=pos。

if(*p == '/0') {printf("%d位置大于字符串s的长度",pos);exit(0);}

else //查找字符串的尾

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中。

(8)已知字符串S1中存放一段英文,写出算法format(s1,s2,s3,n),将其按给定的长度n格式化成两端对齐的字符串S2, 其多余的字符送S3。

[题目分析]本题要求字符串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') {printf("字符串s1为空串或空格串n");exit(0); }

while( *p!='0' && i

if(*p =='0'){ printf("字符串s1没有%d个有效字符n",n); exit(0);}

if(*(--q)==' ' ) //若最后一个字符为空格,则需向后找到第一个非空格字符

{p-- ; //p指针也后退

while(*p==' '&&*p!='0') p++;//往后查找一个非空格字符作串s2的尾字符

if(*p=='0') {printf("s1串没有%d个两端对齐的字符串n",n); exit(0); }

*q=*p; //字符串s2最后一个非空字符

*(++q)='0'; //置s2字符串结束标记

}

*q=s3;p++; //将s1串其余部分送字符串s3。

while (*p!= '0') {*q=*p; q++; p++;}

*q='0'; //置串s3结束标记

}

(9)设二维数组, 1..n] 含有m*n 个整数。

① 写一个算法判断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(j=0;j

{ for(p=j+1;p

if(a[i][j]==a[i][p]) {printf(“no”); return(0); }

//只要有一个相同的,就结论不是互不相同

for(k=i+1;k

for(p=0;p

if(a[i][j]==a[k][p]) {printf(“no”); return(0); }

}// for(j=0;j

printf(yes”); return(1); //元素互不相同

}//算法JudgEqual结束

(2)二维数组中的每一个元素同其它元素都比较一次,数组中共m*n个元素,第1个元素同其它m*n-1个元素比较,第2个元素同其它m*n-2 个元素比较,……,第m*n-1个元素同最后一个元素(m*n)比较一次,所以在元素互不相等时总的比较次数为 (m*n-1)+(m*n-2)+…+2+1=(m*n)(m*n-1)/2。在有相同元素时,可能第一次比较就相同,也可能最后一次比较时相同,设在(m*n-1)个位置上均可能相同,这时的平均比较次数约为(m*n)(m*n-1)/4,总的4时间复杂度是O(n)。

(10)设任意n个整数存放于数组A(1:n)中,试编写算法,将所有正数排在所有负数前面(要求算法复杂性为0(n))。

[题目分析]本题属于排序问题,只是排出正负,不排出大小。可在数组首尾设两个指针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(i0) i++;

while(i

if(i

}

}//算法Arrange结束.

[算法讨论]对数组中元素各比较一次,比较次数为n。最佳情况(已排好,正数在前,负数在后)不发生交换,最差情况(负数均在正数前面)发生n/2次交换。用类c编写,数组界偶是0..n-1。空间复杂度为O(1).

第5章 树和二叉树

1.选择题

(1)把一棵树转换为二叉树后,这棵二叉树的形态是( )。

A.唯一的 B.有多种

C.有多种,但根结点都没有左孩子 D.有多种,但根结点都没有右孩子

(2)由3 个结点可以构造出多少种不同的二叉树?( )

A.2 B.3 C.4 D.5

(3)一棵完全二叉树上有1001个结点,其中叶子结点的个数是( )。

A.250 B. 500 C.254 D.501

(4)一个具有1025个结点的二叉树的高h为( )。

A.11 B.10 C.11至1025之间 D.10至1024之间

(5)深度为h的满m叉树的第k层有( )个结点。(1=

k-1 kh-1hA.m B.m-1 C.m D.m-1

(6)利用二叉链表存储树,则根结点的右指针是( )。

A.指向最左孩子 B.指向最右孩子 C.空 D.非空

(7)对二叉树的结点从1开始进行连续编号,要求每个结点的编号大于其左、右孩子的编号,同一结点的左右孩子中,其左孩子的编号小于其右孩子的编号,可采用( )遍历实现编号。

A.先序 B. 中序 C. 后序 D. 从根开始按层次遍历

(8)若二叉树采用二叉链表存储结构,要交换其所有分支结点左、右子树的位置,利用( )遍历方法最合适。

A.前序 B.中序 C.后序 D.按层次

(9)在下列存储形式中,( )不是树的存储形式?

A.双亲表示法 B.孩子链表表示法 C.孩子兄弟表示法 D.顺序存储表示法

(10)一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足( )。

A.所有的结点均无左孩子 B.所有的结点均无右孩子

C.只有一个叶子结点 D.是任意一棵二叉树

(11)某二叉树的前序序列和后序序列正好相反,则该二叉树一定是( )的二叉树。

A.空或只有一个结点 B.任一结点无左子树

C.高度等于其结点数 D.任一结点无右子树

(12)若X是二叉中序线索树中一个有左孩子的结点,且X不为根,则X的前驱为( )。

A.X的双亲 B.X的右子树中最左的结点

C.X的左子树中最右结点 D.X的左子树中最右叶结点

(13)引入二叉线索树的目的是( )。

A.加快查找结点的前驱或后继的速度 B.为了能在二叉树中方便的进行插入与删除

C.为了能方便的找到双亲 D.使二叉树的遍历结果唯一

(14)线索二叉树是一种( )结构。

A.逻辑 B. 逻辑和存储 C.物理 D.线性

(15)设F是一个森林,B是由F变换得的二叉树。若F中有n个非终端结点,则B中右指针域为空的结点有( )个。

A. n-1 B.n C. n+1 D. n+2

2.应用题

(1)试找出满足下列条件的二叉树

① 先序序列与后序序列相同 ②中序序列与后序序列相同

③ 先序序列与中序序列相同 ④中序序列与层次遍历序列相同

先序遍历二叉树的顺序是“根—左子树—右子树”,中序遍历“左子树—根—右子树”,后序遍历顺序是:“左子树—右子树―根",根据以上原则,本题解答如下:

(1)

若先序序列与后序序列相同,则或为空树,或为只有根结点的二叉树

(2)

若中序序列与后序序列相同,则或为空树,或为任一结点至多只有左子树的二叉树.

(3)

若先序序列与中序序列相同,则或为空树,或为任一结点至多只有右子树的二叉树.(4)

若中序序列与层次遍历序列相同,则或为空树,或为任一结点至多只有右子树的二叉树

(2)设一棵二叉树的先序序列: A B D F C E G H ,中序序列: B F D A G E H C

①画出这棵二叉树。

②画出这棵二叉树的后序线索树。

③将这棵二叉树转换成对应的树(或森林)。

A

A

BC

BCAC

BDEDEE

DnullFG

FGH

FGH

(3) (1) (2)

(3) 假设用于通信的电文仅由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)

0 1

(40) (60)

19 21 32 (28)

0 1 0 1

19

H21 32

0 1

0 1 0 1

7 10 6

0 1

2 3

(17) (11)

7 10 6 (5)

2 3

方案比较:

字母对应出现字母对应出现

编号 编码 频率 编号 编码 频率

1 1100 0.07 1 000 0.07

2 00 0.19 2 001 0.19

3 11110 0.02 3 010 0.02

4 1110 0.06 4 011 0.06

5 100 0.32

5 10 0.32

6101003

方案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的存储结构的初态和终态。

初态:

weight parent lchild rchild

1 3 0 0 0

2 12 0 0 0

3 7 0 0 0

4 4 0 0 0

5 2 0 0 0

6 8 0 0 0

7 11 0 0 0

8 0 0 0

9 0 0 0

10 0 0 0

11 0 0 0

12 0 0 0

13 0 0 0

终态

1

2

3

4

5

6

7

8

9

10

11

12

13

weight

3

12

7

4

2

8

11

5

9

15

20

27

47

parent

8

12

10

9

8

10

11

9

11

12

13

13

0

lchild

0

0

0

0

0

0

0

5

4

3

9

2

11

rchild

0

0

0

0

0

0

0

1

8

6

7

10

12

3.算法设计题

以二叉链表作为二叉树的存储结构,编写以下算法:

(1)统计二叉树的叶结点个数。

int LeafNodeCount(BiTree T)

{

if(T==NULL)

return 0; //如果是空树,则叶子结点个数为0

else if(T->lchild==NULL&&T->rchild==NULL)

return 1; //判断该结点是否是叶子结点(左孩子右孩子都为空),若是则返回1

else

return LeafNodeCount(T->lchild)+LeafNodeCount(T->rchild);

}

(2)判别两棵树是否相等。

(3)交换二叉树每个结点的左孩子和右孩子。

void ChangeLR(BiTree &T)

{

BiTree temp;

if(T->lchild==NULL&&T->rchild==NULL)

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->lchild==NULL&&T->rchild==NULL)

cout<data;

else

{

cout<data;

DoubleTraverse(T->lchild);

cout<data;

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

(6)用按层次顺序遍历二叉树的方法,统计树中具有度为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); printf(p->data); //出队,访问结点

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); //非空右子女入队

} }//if(bt)

return(num); }//返回度为1的结点的个数

(7)求任意二叉树中第一条最长的路径长度,并输出此路径上各结点的值。

[题目分析]因为后序遍历栈中保留当前结点的祖先的信息,用一变量保存栈的最高栈顶指针,每当退栈时,栈顶指针高于保存最高栈顶指针的值时,则将该栈倒入辅助栈中,辅助栈始终保存最长路径长度上的结点,直至后序遍历完毕,则辅助栈中内容即为所求。

void LongestPath(BiTree bt)//求二叉树中的第一条最长路径长度

{BiTree p=bt,l[],s[]; //l, s是栈,元素是二叉树结点指针,l中保留当前最长路径中的结点

int i,top=0,tag[],longest=0;

while(p || top>0)

{ while(p) {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

(8)输出二叉树中从每个叶子结点到根结点的路径。

[题目分析]采用先序遍历的递归方法,当找到叶子结点*b时,由于*b叶子结点尚未添加到path中,因此在输出路径时还需输出b->data值。对应的递归算法如下:

void AllPath(BTNode *b,ElemType path[],int pathlen)

{

int i;

if (b!=NULL)

{

if (b->lchild==NULL && b->rchild==NULL) //*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--; //恢复环境

}

}

}

第6章 图

1.选择题

(1)在一个图中,所有顶点的度数之和等于图的边数的( )倍。

A.1/2 B.1 C.2 D.4

(2)在一个有向图中,所有顶点的入度之和等于所有顶点的出度之和的( )倍。

A.1/2 B.1 C.2 D.4

(3)具有n个顶点的有向图最多有( )条边。

A.n B.n(n-1) C.n(n+1) D.n2

(4)n个顶点的连通图用邻接距阵表示时,该距阵至少有( )个非零元素。

A.n B.2(n-1) C.n/2 D.n2

(5)G是一个非连通无向图,共有28条边,则该图至少有( )个顶点。

A.7 B.8 C.9 D.10

(6)若从无向图的任意一个顶点出发进行一次深度优先搜索可以访问图中所有的顶点,则该图一定是( )图。

A.非连通 B.连通 C.强连通 D.有向

(7)下面( )算法适合构造一个稠密图G的最小生成树。

A. Prim算法 B.Kruskal算法 C.Floyd算法 D.Dijkstra算法

(8)用邻接表表示图进行广度优先遍历时,通常借助( )来实现算法。

A.栈 B. 队列 C. 树 D.图

(9)用邻接表表示图进行深度优先遍历时,通常借助( )来实现算法。

A.栈 B. 队列 C. 树 D.图

(10)深度优先遍历类似于二叉树的( )。

A.先序遍历

B.中序遍历 C.后序遍历 D.层次遍历

(11)广度优先遍历类似于二叉树的( )。

A.先序遍历 B.中序遍历 C.后序遍历 D.层次遍历

(12)图的BFS生成树的树高比DFS生成树的树高( )。

A.小 B.相等 C.小或相等 D.大或相等

(13)已知图的邻接矩阵如图6.25所示,则从顶点0出发按深度优先遍历的结果是01111011100010A.0 2 4 3 1 5 6

B.0 1 3 6 5 4 2

C.0 1 3 4 2 5 6

D.0 3 6 1 5 4 2

( )。

图6.25 邻接矩阵

(14)已知图的邻接表如图6.26所示,则从顶点0出发按广度优先遍历的结果是( ),按深度优先遍历的结果是( )。

A.0 1 3 2 B.0 2 3 1

C.0 3 2 1 D.0 1 2 3

图6.26 邻接表

(15)下面( )方法可以判断出一个有向图是否有环。

A.深度优先遍历 B.拓扑排序 C.求最短路径 D.求关键路径

2.应用题

(1)已知如图6.27所示的有向图,请给出:

① 每个顶点的入度和出度;

② 邻接矩阵;

③ 邻接表;

④ 逆邻接表。

图6.27 有

(2)已知如图6.28所示的无向网,请给出:

① 邻接矩阵;

② 邻接表;

③ 最小生成树

43

4

5

593

5

5

5



557654973

632

526546

a

b

c

d

e

f

g

h

b 4

→→

a 4

→→

a 3

→→

b 5

→→

b 9

→→

d 6

→→

d 5

→→

c 5

→c

3

^^

c 5

d 5

→b 5

d 5

→c 5

e 7

→e 9h 5f 6

→h4^

图6.28 无→g5d 7

f 3 ^

e 3

g 2 ^

f 2

h 6 ^

d 4

g 6 ^

(3)已知图的邻接矩阵如6.29所示。试分别画出自顶点1出发进行遍历所得的深度优先生成树和广度优先生成树。

图6.29 邻接矩(4)有向网如图6.30所示,试用迪杰斯特拉算法求出从顶点a到其他各顶点间的最短路径,完成表6.9。

图6.30 有向网

D

终点

b 15 15

(a,b)

c

2

(a,c)

(a,d)

e

f

g

S

终点集

{a,c} {a,c,f} {a,c,f,e} {a,c,f,e,d} {a,c,f,e,d,g} {a,c,f,e,d,g,b}

(a,b)

15

(a,b)

11

(a,c,f,d)

10

(a,c,e)

16

(a,c,f,g)

15

(a,b)

11

(a,c,f,d)

16

(a,c,f,g)

15

(a,b)

14

(a,c,f,d,g)

15

(a,b)

i=1 i=2 i=3 i=4 i=5 i=6

d 12 12

(a,d)

10

(a,c,e)

6

(a,c,f)

(5)试对图6.31所示的AOE-网:

① 求这个工程最早可能在什么时间结束;

② 求每个活动的最早开始时间和最迟开始时间;

③ 确定哪些活动是关键活动

图6.31 AOE-网

【解答】按拓扑有序的顺序计算各个顶点的最早可能开始时间Ve和最迟允许开始时间Vl。然后再计算各个活动的最早可能开始时间e和最迟允许开始时间l,根据l - e = 0? 来确定关键活动,从而确定关键路径。

 

1

 

2

 

15

15

<3, 2>

15

0

-e 17 0

0

15

8

27

0

19

19

12

37

<2, 4>

19

27

8

29

38

<2, 5>

15

37

0

3

 

4

 

38

43

<3, 5>

29

38

<4, 6>

38

<5, 6>

5

 

43

6

0

Ve 19

0

Vl 19

<1, 2>

e

l

0

17

0

<1, 3>

此工程最早完成时间为43。关键路径为<1, 3><3, 2><2, 5><5, 6>

3.算法设计题

(1)分别以邻接矩阵和邻接表作为存储结构,实现以下图的基本操作:

① 增添一个新顶点v,InsertVex(G, v);

② 删除顶点v及其相关的边,DeleteVex(G,

v);

③ 增加一条边,InsertArc(G,

v,

w);

④ 删除一条边,DeleteArc(G,

v,

w)。

//本题中的图G均为有向无权图,其余情况容易由此写出

Status Insert_Vex(MGraph &G, char v)//在邻接矩阵表示的图G上插入顶点v

{

if(+1)>MAX_VERTEX_NUM return INFEASIBLE;

[++]=v;

return OK;

}//Insert_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(![j].adj)

{

[j].adj=1;

++;

}

return OK;

}//Insert_Arc

Status Delete_Vex(MGraph &G,char v)//在邻接矩阵表示的图G上删除顶点v

{

n=;

if((m=LocateVex(G,v))<0) return ERROR;

[m]<->[n]; //将待删除顶点交换到最后一个顶点

for(i=0;i

{

[m]=[n];

[m]=[n]; //将边的关系随之交换

}

[m][m].adj=0;

--;

return OK;

}//Delete_Vex

分析:如果不把待删除顶点交换到最后一个顶点的话,算法将会比较复杂,而伴随着大量元素的移动,时间复杂度也会大大增加.

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([j].adj)

{

[j].adj=0;

--;

}

return OK;

}//Delete_Arc

//为节省篇幅,本题只给出Insert_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=(ArcNode*)malloc(sizeof(ArcNode));

p->adjvex=j;p->nextarc=NULL;

if(!rc) rc=p;

else

{

for(q=rc;q->q->nextarc;q=q->nextarc)

if(q->adjvex==j) return ERROR; //边已经存在

q->nextarc=p;

}

++;

return OK;

}//Insert_Arc

(2)一个连通图采用邻接表作为存储结构,设计一个算法,实现从顶点v出发的深度优先遍历的非递归过程。

数据结构考研指导232页7.3.7

(3)设计一个算法,求图G中距离顶点v的最短路径长度最大的一个顶点,设v可达其余各个顶点。

数据结构考研指导232页7.3.8

(4)试基于图的深度优先搜索策略写一算法,判别以邻接表方式存储的有向图中是否。

存在由顶点vi到顶点vj的路径(i≠j)解1:

int visited[MAXSIZE]; //指示顶点是否在当前路径上

int exist_path_DFS(ALGraph G,int i,int j)//深度优先判断有向图G中顶点i到顶点j

是否有路径,是则返回1,否则返回0

{

if(i==j) return 1; //i就是j

else

{

visited[i]=1;

for(p=es[i].firstarc;p;p=p->nextarc)

{

k=p->adjvex;

if(!visited[k]&&exist_path(k,j)) return 1;//i下游的顶点到j有路径

}//for

}//else

}//exist_path_DFS

解2:(以上算法似乎有问题:如果不存在路径,则原程序不能返回0。我的解决方式是在原程序的中引入一变量level来控制递归进行的层数。具体的方法我在程序中用红色标记出来了。)

int visited[MAXSIZE]; //指示顶点是否在当前路径上

int level=1;//递归进行的层数

int exist_path_DFS(ALGraph G,int i,int j)//深度优先判断有向图G中顶点i到顶点j

是否有路径,是则返回1,否则返回0

{

if(i==j) return 1; //i就是j

else

{

visited[i]=1;

for(p=es[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 (level==1) return 0;

}//exist_path_DFS

(5)采用邻接表存储结构,编写一个算法,判别无向图中任意给定的两个顶点之间是否存在一条长度为为k的简单路径。

(注1:一条路径为简单路径指的是其顶点序列中不含有重现的顶点。

注2:此题可参见严题集P207-208中有关按“路径”遍历的算法基本框架。)

int visited[MAXSIZE];

int exist_path_len(ALGraph G,int i,int j,int k)//判断邻接表方式存储的有向图G

的顶点i到j是否存在长度为k的简单路径

{

{

if(i==j&&k==0) return 1; //找到了一条路径,且长度符合要求

else if(k>0)

{

visited[i]=1;

for(p=es[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

第7章 查找

1.选择题

(1)对n个元素的表做顺序查找时,若查找每个元素的概率相同,则平均查找长度为( )。

A.(n-1)/2 B. n/2 C.(n+1)/2 D.n

(2)适用于折半查找的表的存储方式及元素排列要求为( )。

A.链接方式存储,元素无序 B.链接方式存储,元素有序

C.顺序方式存储,元素无序 D.顺序方式存储,元素有序

(3)当在一个有序的顺序表上查找一个数据时,既可用折半查找,也可用顺序查找,但前者比后者的查找速度( )。

A.必定快 B.不一定

C.在大部分情况下要快 D.取决于表递增还是递减

(4)折半查找有序表(4,6,10,12,20,30,50,70,88,100)。若查找表中元素58,则它将依次与表中( )比较大小,查找结果是失败。

A.20,70,30,50 B.30,88,70,50

C.20,50 D.30,88,50

(5)对22个记录的有序表作折半查找,当查找失败时,至少需要比较( )次关键字。

A.3 B.4 C.5 D.6

(6)折半搜索与二叉排序树的时间性能( )。

A.相同 B.完全不同

C.有时不相同 D.数量级都是O(log2n)

(7)分别以下列序列构造二叉排序树,与用其它三个序列所构造的结果不同的是( )。

A.(100,80, 90, 60, 120,110,130)

B.(100,120,110,130,80, 60, 90)

C.(100,60, 80, 90, 120,110,130)

D.(100,80, 60, 90, 120,130,110)

(8)在平衡二叉树中插入一个结点后造成了不平衡,设最低的不平衡结点为A,并已知A的左孩子的平衡因子为0右孩子的平衡因子为1,则应作( )型调整以使其平衡。

A.LL B.LR C.RL

D.RR

(9)下列关于m阶B-树的说法错误的是( )。

A.根结点至多有m棵子树

B.所有叶子都在同一层次上

C.非叶结点至少有m/2 (m为偶数)或m/2+1(m为奇数)棵子树

D.根结点中的数据是有序的

(10)下面关于B-和B+树的叙述中,不正确的是( )。

A.B-树和B+树都是平衡的多叉树 B.B-树和B+树都可用于文件的索引结构

C.B-树和B+树都能有效地支持顺序检索 D.B-树和B+树都能有效地支持随机检索

(11)m阶B-树是一棵( )。

A.m叉排序树 B.m叉平衡排序树

C.m-1叉平衡排序树 D.m+1叉平衡排序树

(12)下面关于哈希查找的说法,正确的是( )。

A.哈希函数构造的越复杂越好,因为这样随机性好,冲突小

B.除留余数法是所有哈希函数中最好的

C.不存在特别好与坏的哈希函数,要视情况而定

D.哈希表的平均查找长度有时也和记录总数有关

(13)下面关于哈希查找的说法,不正确的是( )。

A.采用链地址法处理冲突时,查找一个元素的时间是相同的

B.采用链地址法处理冲突时,若插入规定总是在链首,则插入任一个元素的时间是相同的

C.用链地址法处理冲突,不会引起二次聚集现象

D.用链地址法处理冲突,适合表长不确定的情况

(14)设哈希表长为14,哈希函数是H(key)=key%11,表中已有数据的关键字为15,38,61,84共四个,现要将关键字为49的元素加到表中,用二次探测法解决冲突,则放入的位置是( )。

A.8 B.3 C.5 D.9

(15)采用线性探测法处理冲突,可能要探测多个位置,在查找成功的情况下,所探测的这些位置上的关键字 ( )。

A.不一定都是同义词 B.一定都是同义词

C.一定都不是同义词 D.都相同

2.应用题

(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

(2)在一棵空的二叉排序树中依次插入关键字序列为12,7,17,11,16,2,13,9,21,4,请画出所得到的二叉排序树。

12

7 17

2 11 16 21

4 9 13

验算方法: 用中序遍历应得到排序结果: 2,4,7,9,11,12,13,16,17,21

(3)已知如下所示长度为12的表:(Jan, Feb, Mar, Apr, May, June, July, Aug, Sep, Oct, Nov,

Dec)

① 试按表中元素的顺序依次插入一棵初始为空的二叉排序树,画出插入完成之后的二叉排序树,并求其在等概率的情况下查找成功的平均查找长度。

② 若对表中元素先进行排序构成有序表,求在等概率的情况下对此有序表进行折半查找时查找成功的平均查找长度。

③ 按表中元素顺序构造一棵平衡二叉排序树,并求其在等概率的情况下查找成功的平均查找长度。

解:

(4)对下面的3阶B-树,依次执行下列操作,画出各步操作的结果。

① 插入90 ② 插入25 ③ 插入45 ④ 删除60

50308 2035 406080100

(5)设哈希表的地址范围为0~17,哈希函数为:H(key)=key%16。用线性探测法处理冲突,输入关键字序列:(10,24,32,17,31,30,46,47,40,63,49),构造哈希表,试回答下列问题:

① 画出哈希表的示意图;

② 若查找关键字63,需要依次与哪些关键字进行比较?

③ 若查找关键字60,需要依次与哪些关键字比较?

④ 假定每个关键字的查找概率相等,求查找成功时的平均查找长度。

①画表如下:

0 1 2 3 4 5 6 7 8 9 1011121314 15 16 1732 17 63 49 244010 30 31 46 47②查找63,首先要与H(63)=63%16=15号单元内容比较,即63 vs 31 ,no;

然后顺移,与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

(6)设有一组关键字(9,01,23,14,55,20,84,27),采用哈希函数:H(key)=key

%7 ,表长为10,用开放地址法的二次探测法处理冲突。要求:对该关键字序列构造哈希表,并计算查找成功的平均查找长度。

散列地址 0

关键字 14

比较次数 1

1

01

1

2

9

1

3

23

2

4

84

5

27

6

55

1

7

20

2

8

9

3 4

平均查找长度:ASLsucc=(1+1+1+2+3+4+1+2)/8=15/8

以关键字27为例:H(27)=27%7=6(冲突) H1=(6+1)%10=7(冲突)

23H2=(6+2)%10=0(冲突) H3=(6+3)%10=5 所以比较了4次。

(7)设哈希函数H(K)=3 K mod 11,哈希地址空间为0~10,对关键字序列(32,13,49,24,38,21,4,12),按下述两种解决冲突的方法构造哈希表,并分别求出等概率下查找成功时和查找失败时的平均查找长度ASLsucc和ASLunsucc。

① 线性探测法;

② 链地址法。

散列地址 0 1

关键字 4

1 比较次数

2

3

12

1

4

49

1

5

38

2

6

13

1

7

24

2

8

32

1

9

21

2

10

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

ASLsucc =(1*5+2*3)/8=11/8

ASLunsucc=(1+2+1+2+3+1+3+1+3+1+1)/11=19/11

(5)设哈希表的地址范围为0~17,哈希函数为:H(key)=key%16。用线性探测法处理冲突,输入关键字序列:(10,24,32,17,31,30,46,47,40,63,49),构造哈希表,试回答下列问题:

① 画出哈希表的示意图;

② 若查找关键字63,需要依次与哪些关键字进行比较?

③ 若查找关键字60,需要依次与哪些关键字比较?

④ 假定每个关键字的查找概率相等,求查找成功时的平均查找长度。

解: (1)画表如下:

0 1 2 3 4 5 6 7 8 9 1011121314 15 16 1732 17 63 49 244010 30 31 46 47(2) 查找63,首先要与H(63)=63%16=15号单元内容比较,即63 vs 31 ,no;

然后顺移,与46,47,32,17,63相比,一共比较了6次!

(3)查找60,首先要与H(60)=60%16=12号单元内容比较,但因为12号单元为空(应当有空标记),所以应当只比较这一次即可。

(4) 对于黑色数据元素,各比较1次;共6次;

对红色元素则各不相同,要统计移位的位数。“63”需要6次,“49”需要3次,“40”需要2次,“46”需要3次,“47”需要3次,

所以ASL=1/11(6+2+3×3+6)=23/11

(6)设有一组关键字(9,01,23,14,55,20,84,27),采用哈希函数:H(key)=key %7 ,表长为10,用开放地址法的二次探测法处理冲突。要求:对该关键字序列构造哈希表,并计算查找成功的平均查找长度。

散列地址 0

关键字 14

比较次数 1

1

01

1

2

9

1

3

23

2

4

84

5

27

6

55

1

7

20

2

8

9

3 4

平均查找长度:ASLsucc=(1+1+1+2+3+4+1+2)/8=15/8

以关键字27为例:H(27)=27%7=6(冲突) H1=(6+1)%10=7(冲突)

23H2=(6+2)%10=0(冲突) H3=(6+3)%10=5 所以比较了4次。

(7)设哈希函数H(K)=3 K mod 11,哈希地址空间为0~10,对关键字序列(32,13,49,24,38,21,4,12),按下述两种解决冲突的方法构造哈希表,并分别求出等概率下查找成功时和查找失败时的平均查找长度ASLsucc和ASLunsucc。

① 线性探测法;

② 链地址法。

散列地址 0 1 2 3 4 5 6 7 8 9 10

关键字 4

1

12

1

49

1

38

2

13

1

24

2

32

1

21

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

第8章 排序

1.选择题

(1)从未排序序列中依次取出元素与已排序序列中的元素进行比较,将其放入已排序序列的正确位置上的方法,这种排序方法称为( )。

A.归并排序 B.冒泡排序 C.插入排序 D.选择排序

(2)从未排序序列中挑选元素,并将其依次放入已排序序列(初始时为空)的一端的方法,称为( )。

A.归并排序 B.冒泡排序 C.插入排序 D.选择排序

(3)对n个不同的关键字由小到大进行冒泡排序,在下列( )情况下比较的次数最多。

A.从小到大排列好的 B.从大到小排列好的

C.元素无序 D.元素基本有序

(4)对n个不同的排序码进行冒泡排序,在元素无序的情况下比较的次数最多为( )。

A.n+1 B.n C.n-1 D.n(n-1)/2

(5)快速排序在下列( )情况下最易发挥其长处。

A.被排序的数据中含有多个相同排序码

B.被排序的数据已基本有序

C.被排序的数据完全无序

D.被排序的数据中的最大值和最小值相差悬殊

(6)对n个关键字作快速排序,在最坏情况下,算法的时间复杂度是( )。

A.O(n)

B.O(n2) C.O(nlog2n) D.O(n3)

(7)若一组记录的排序码为(46, 79,56,38,40,84),则利用快速排序的方法,以第一个记录为基准得到的一次划分结果为( )。

A.38,40,46,56,79,84 B.40,38,46,79,56,84

C.40,38,46,56,79,84 D.40,38,46,84,56,79

(8)下列关键字序列中,( )是堆。

A.16,72,31,23,94,53 B.94,23,31,72,16,53

C.16,53,23,94,31,72 D.16,23,53,31,94,72

(9)堆是一种( )排序。

A.插入 B.选择 C.交换 D.归并

(10)堆的形状是一棵( )。

A.二叉排序树 B.满二叉树 C.完全二叉树 D.平衡二叉树

(11)若一组记录的排序码为(46,79,56,38,40,84),则利用堆排序的方法建立的初始堆为( )。

A.79,46,56,38,40,84 B.84,79,56,38,40,46

C.84,79,56,46,40,38 D.84,56,79,40,46,38

(12)下述几种排序方法中,要求内存最大的是( )。

A.希尔排序 B.快速排序 C.归并排序 D.堆排序

(13)下述几种排序方法中,( )是稳定的排序方法。

A.希尔排序 B.快速排序 C.归并排序 D.堆排序

(14)数据表中有10000个元素,如果仅要求求出其中最大的10个元素,则采用( )算法最节省时间。

A.冒泡排序 B.快速排序 C.简单选择排序 D.堆排序

(15)下列排序算法中,( )不能保证每趟排序至少能将一个元素放到其最终的位置上。

A.希尔排序 B.快速排序 C.冒泡排序 D.堆排序

2.应用题

(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]

② 折半插入排序 排序过程同①

③ 希尔排序(增量选取5,3,1)

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 [30 ]

28 2 6 10 12 [18 16 16* 20

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

⑦ 堆排序

第一步,形成初始大根堆(详细过程略),第二步做堆排序。

12

2

3020 6 18

28 10

16

16*2206122818

30

1610

16*

初始排序 不是大根堆 形成初始大根堆

12

28 16

20 18 10 16*2 6 30

交换1与10对象

6

20 16

12 18 10 16*2 28

30

交换1与9对象

2

18 16

12 6 10 16*20 28 30

交换1与8对象

16*

12 16

2 6 10 1820 28 30

交换1与7对象

28

20161218 10

16*2630 从1到9重新形成堆

20

1816126 10 16*22830 从1到8重新形成堆

18

1216212 10 16*202830 从1到7重新形成堆

16*

121626 10 18202830 从1到6重新形成堆


本文标签: 结点 元素 算法 序列