数据结构实践(有的数据结构课后习题答案),红色是答案

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.试分析下面各程序段的时间复杂度。

1x=90; y=100; 

while(y>0)

if(x>100)

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

else x++;

2for (i=0;  i<n; i++)

for (j=0; j<m; j++)

a[i][j]=0;

3s=0;

     for i=0; i<n; i++)

for(j=0; j<n; j++)

         s+=B[i][j];

sum=s;

4i=1;

     while(i<=n)

        i=i*3;

5x=0;

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

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

x++;

6x=n; //n>1

y=0;

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

    y++;

 

1O1

2Om*n

3On2

4Olog3n

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

6O()


2章  线性表

1选择题

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

A.110            B108         C.100          D.120

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

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

B.在第i个结点后插入一个新结点(1in

C.删除第i个结点(1in

Dn个结点从小到大排序

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

A.8      B63.5        C.63      D.7

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

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

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

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

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

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

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

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

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

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

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

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

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

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

An            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)            CO(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->data<pb->data){ 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;∥设工作指针papb

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<pb->data) {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  DifferenceLinkedList  AB*n

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

{p=A->next                  pq分别是链表AB的工作指针。

 q=B->next pre=A          preAp所指结点的前驱结点的指针。

 whilep!=null && q!=null

ifp->data<q->data{pre=pp=p->next*n++ A链表中当前结点指针后移。

else ifp->data>q->dataq=q->next     B链表中当前结点指针后移。

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

      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->data<maxk)  p=p->next;

                      // 查找第一个值 ≥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  ExchangeLinkedList 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=1j=n),从两端向中间移动,凡遇到值item的数据元素时,直接将右端元素左移至值为item的数据元素位置。

void  DeleteElemType A[ ]int  n

A是有n个元素的一维数组,本算法删除A中所有值为item的元素。

{i=1j=n;∥设置数组低、高端指针(下标)。

 whilei<j

   {whilei<j && A[i]!=itemi++         ∥若值不为item,左移指针。

    ifi<jwhilei<j && A[j]==itemj--;∥若右端元素值为item,指针左移

    ifi<jA[i++]=A[j--]

   }

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


3章  栈和队列

习题

1.选择题

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

A.54321   B.21543    C.4312   D.23541

2)若已知一个栈的入栈序列是123,…,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的初始状态为空,元素e1e2e3e4e5e6依次进入栈S,一个元素出栈后即进入Q,若6个元素出队的序列是e2e4e3e6e5e1,则栈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[0..m]中,则入队时的操作为( )。

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<len/2; 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(“出栈元素是%d\n”,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

      {case0’<=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);//读入表达式中下一个字符。

   }//结束whilex=$’

  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(假定被判定的操作序列已存入一维数组中)。

 

AD是合法序列,B是非法序列。

设被判定的操作序列已存入一维数组A中。

       int Judge(char A[])

        //判断字符数组A中的输入输出序列是否是合法序列。如是,返回true,否则返回false

        {i=0;                //i为下标。

         j=k=0;              //jk分别为I和字母O的的个数。

         while(A[i]!=\0’) //当未到字符数组尾就作。

           {switch(A[i])

             {caseI’: j++; break; //入栈次数增1

              caseO’: 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 <assert.h>

template <class Type> 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, fronttag;//队尾指针、队头指针和队满标志

   Type *Q;//存放队列元素的数组

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

}

构造函数

template <class Type>

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

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

   Q = new Type[m];//创建队列空间

   assert ( Q != 0 );//断言: 动态存储分配成功与否

}

插入函数

template<class Type> 

void Queue<Type:: EnQueue Type &item {

assert ( ! IsFull ( ) );//判队列是否不满,满则出错处理

rear = ( rear + 1 ) % m;//队尾位置进1, 队尾指针指示实际队尾位置

Q[rear] = item;//进队列

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

}

删除函数

template<class Type> 

Type Queue<Type:: DeQueue ( ) {

assert ( ! IsEmpty ( ) );//判断队列是否不空,空则出错处理

front = ( front + 1 ) % m;//队头位置进1, 队头指针指示实际队头的前一位置

tag = 0;//标志改0, 表示栈不满

return Q[front];//返回原队头元素的值

}

读取队头元素函数

template<class Type> 

Type Queue<Type:: 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;

2elemtp delqueue ( cycqueue Q)

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

if (Q.front==Q.rear)  {printf(“队列空”); exit(0);}

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

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

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

void enqueue (cycqueue Q, elemtp x)

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

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

 Q.data[Q.front]=x;        //x 入队列

Q.front=(Q.front-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));

     }//算法结束

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

2int Ackerman( int m, int n)

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

        for(j=0;j<N;j++) akm[0][j];=j+1;

        for(i=1;i<m;i++)

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

           for(j=1;j<N;j++)

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

           }

          return(akm[m][n]);

        }//算法结束

 

(10)已知f为单链表的表头指针, 链表中存储的都是整型数据,试写出实现下列运算的递归算法:

① 求链表中的最大整数;

② 求链表的结点个数;

③ 求所有整数的平均值。

#include <iostream.h>//定义在头文件"RecurveList.h"

class List;

class ListNode {//链表结点类

friend class List;

private:

int data;//结点数据

ListNode *link;//结点指针

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

};

class List {//链表类

private:

ListNode *firstcurrent;

int Max ( ListNode *f );

int Num ( ListNode *f );

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

public:

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

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

ListNode* NewNode ( const int item );//创建链表结点其值为item

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

void PrintList ( );//输出链表所有结点数据

int GetMax ( ) { return Max ( first ); }//求链表所有数据的最大值

int GetNum ( ) { return Num first ); }//求链表中数据个数

float GetAvg ( ) { return Avg ( first ); }//求链表所有数据的平均值

};

 

ListNode* List :: NewNode ( const int item ) {//创建新链表结点

ListNode *newnode = new ListNode (item);

return newnode;

}

 

void List :: NewList ( const int retvalue ) {//建立链表以输入retvalue结束

first = NULL;  int value;  ListNode *q;

cout << "Input your data:\n";//提示

cin >> value;//输入

while ( value != retvalue ) {//输入有效

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

if ( first == NULL ) first = current = q;//空表时新结点成为链表第一个结点

else { current->link = q;  current = q; }//非空表时新结点链入链尾

cin >> value;//再输入

}

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

}

void List :: PrintList ( ) {//输出链表

cout << "\nThe List is : \n";

ListNode *p = first;

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

cout << ‘\n’; 

}

int List :: Max ( ListNode *f ) {//递归算法 : 求链表中的最大值

if ( f ->link == NULL ) return f ->data;//递归结束条件

int temp = Max ( f ->link );//在当前结点的后继链表中求最大值

if ( f ->data > temp ) return f ->data;//如果当前结点的值还要大返回当前检点值

else return temp;//否则返回后继链表中的最大值

}

 

int List :: Num ( ListNode *f ) {//递归算法 : 求链表中结点个数

if ( f == NULL ) return 0;//空表返回0

return 1+ Num ( f ->link );//否则返回后继链表结点个数加1

}

float List :: Avg ( ListNode *f int& n ) {//递归算法 求链表中所有元素的平均值

if ( ->link == NULL ) //链表中只有一个结点, 递归结束条件

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

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

}

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

int main ( int argccharargv[ ] ) {

List test;   int finished;

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

cin >> finished;//输入建表结束标志数据 

test.NewList finished );//建立链表

test.PrintList ( );//打印链表

cout << "\nThe Max is : " << test.GetMax ( );

cout << "\nThe Num is : " << test.GetNum ( );

cout << "\nThe Ave is : " << test.GetAve () << '\n';

printf ( "Hello World!\n" );

return 0;

}


4章  串、数组和广义表

习题

1选择题

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

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

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

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

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

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

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

A.012345678999   B.012121111212   C.011234223456    D.0123012322345

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

A010104101      B.010102101      C.010100011       D.010101011 

5)串的长度是指(  )。

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

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

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

A.808            B818             C.1010             D.1020

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

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

8)设有一个10阶的对称矩阵A,采用压缩存储方式,以行序为主存储,a11为第一元素,其存储地址为1,每个元素占一个地址空间,则a85的地址为(  )。

A.13             B33              C.18               D.40

9)若对n阶对称矩阵A以行序为主序方式将其下三角形的元素(包括主对角线上所有元素)依次存放于一维数组B[1..(n(n+1))/2]中,则在B中确定aij(i<j)的位置k的关系为(  )。

A.i*(i-1)/2+j      Bj*(j-1)/2+i      C.i*(i+1)/2+j      D.j*(j+1)/2+i

10A[N,N]是对称矩阵,将下面三角(包括对角线)以行序存储到一维数组T[N(N+1)/2]中,则对任一上三角元素a[i][j]对应T[k]的下标k是(  )。

A.i(i-1)/2+j       Bj(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            B45             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            Dd

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          C12          D.2和3 

 

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

 

模式串tnextnextval值如下:

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

 

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

① 计算模式p的naxtval函数值;

② 不写出算法,只画出利用KMP算法进行模式匹配时每一趟的匹配过程。

 

 pnextval函数值为0110132。(pnext函数值为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个字长,行下标可平移至111

1242   222   3s+182   4s+142

 

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

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

HHTHTHTL)))))))

 

 

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

 

void Count()

//统计输入字符串中数字字符和字母字符的个数。

int inum[36]

char ch

fori0i<36i++num[i]=0;// 初始化

while((chgetchar())!=#’)  //#’表示输入字符串结束。

if(‘0<=ch<=9’){i=ch48;num[i]++;}        // 数字字符

  elseif(‘A<=ch<=Z’){i=ch-65+10;num[i]++;}// 字母字符

fori=0i<10i++  // 输出数字字符的个数

printf(“数字%d的个数=%d\n”,inum[i]);

fori10i<36i++// 求出字母字符的个数

printf(“字母字符%c的个数=%d\n”,i55num[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插入。(说明:不得使用任何库函数)

[题目分析]本题是字符串的插入问题,要求在字符串spos位置,插入字符串t。首先应查找字符串spos位置,将第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;  //pq分别为字符串st的工作指针

 if(pos<1) {printf(“pos参数位置非法\n”);exit(0);}

while(*p!=’\0’&&i<pos) {p++;i++;} //pos位置

    //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--;}//spos后的子串右移,空出串t的位置。

 q--;  //指针q回退到串t的最后一个字符

 for(j=1;j<=x;j++) *p--=*q--;  //t串插入到spos位置上

 [算法讨论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<n){*q=*p; q++; p++; i++;}//字符串s1向字符串s2中复制

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)设二维数组a[1..m, 1..n] 含有m*n 个整数。

① 写一个算法判断a中所有元素是否互不相同?输出相关信息(yes/no);

② 试分析算法的时间复杂度。

[题目分析]判断二维数组中元素是否互不相同,只有逐个比较,找到一对相等的元素,就可结论为不是互不相同。如何达到每个元素同其它元素比较一次且只一次?在当前行,每个元素要同本行后面的元素比较一次(下面第一个循环控制变量pfor循环),然后同第i+1行及以后各行元素比较一次,这就是循环控制变量kp的二层for循环。

int JudgEqual(ing a[m][n],int m,n)  

 //判断二维数组中所有元素是否互不相同,如是,返回1;否则,返回0

{for(i=0;i<m;i++)

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

   { for(p=j+1;p<n;p++) //和同行其它元素比较

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

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

     for(k=i+1;k<m;k++)  //和第i+1行及以后元素比较

      for(p=0;p<n;p++)

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

}// for(j=0;j<n-1;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,总的时间复杂度是O(n4)

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

[题目分析]本题属于排序问题,只是排出正负,不排出大小。可在数组首尾设两个指针iji自小至大搜索到负数停止,j自大至小搜索到正数停止。然后ij所指数据交换,继续以上过程,直到 i=j为止。

void Arrange(int A[],int n) 

//n个整数存于数组A中,本算法将数组中所有正数排在所有负数的前面

 {int i=0,j=n-1,x;  //用类C编写,数组下标从0开始

  while(i<j)

{while(i<j && A[i]>0)  i++;

while(i<j && A[j]<0)  j--;

  if(i<j) {x=A[i]; A[i++]=A[j]; A[j--]=x; }//交换A[i] A[j]

}

     }//算法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          D5   

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

A.250         B. 500          C.254        D501   

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

A.11          B.10             C111025之间       D.10至1024之间

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

Amk-1          B.mk-1            C.mh-1        D.mh-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的右子树中最左的结点 

CX的左子树中最右结点          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

①画出这棵二叉树。

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

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

 

 

 

 

 

 

 

 

 

 

        (1)                           (2)

(3) 假设用于通信的电文仅由8个字母组成,字母在电文中出现的频率分别为0.070.190.020.060.320.030.210.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

 

 

方案比较:

 

 

 

 

 

 

 

 

 

方案1WPL2(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

方案2WPL3(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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

weight

parent

lchild

rchild

1

3

8

0

0

2

12

12

0

0

3

7

10

0

0

4

4

9

0

0

5

2

8

0

0

6

8

10

0

0

7

11

11

0

0

8

5

9

5

1

9

9

11

4

8

10

15

12

3

6

11

20

13

9

7

12

27

13

2

10

13

47

0

11

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<<T->data;

else

{

cout<<T->data;

DoubleTraverse(T->lchild);

cout<<T->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 itop=0,tag[],longest=0;

  while(p || top>0)

   { while(p) {s[++top]=ptag[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             C2             D.4 

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

  A1/2            B1             C.2             D.4 

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

An              Bn(n-1)         C.n(n+1)        D.n2 

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

An              B2(n-1)         C.n/2           D.n2 

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

A7              B.8             C9             D.10 

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

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

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

A Prim算法      BKruskal法   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出发按深度优先遍历的结果是(   )。

6.25  邻接矩阵

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

 

6.26  邻接表

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

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

2.应用题

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

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

② 邻接矩阵;

③ 邻接表;

④ 逆邻接表。             

 

 

                      

 

 

 

 


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

① 邻接矩阵;    

② 邻接表;

③ 最小生成树

            

 

 

            

                                      

 

  

 

 

 

 

 

a

b

4

c

3

 

 

 

 

 

 

 

 

 

 

 

 

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

^

 

 

 

 

 

 

 

 

h

c

5

d

4

g

6

^

 

 

 

 

 

 

 

 

 

 

 

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

 

 

 

 

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

 

 

 

 

 

 

D

终点

i=1

i=2

i=3

i=4

i=5

i=6

b

15

(a,b)

15

(a,b)

15

(a,b)

15

(a,b)

15

(a,b)

15

(a,b)

c

2

(a,c)

 

 

 

 

 

d

12

(a,d)

12

(a,d)

11

(a,c,f,d)

11

(a,c,f,d)

 

 

e

 

10

(a,c,e)

10

(a,c,e)

 

 

 

f

 

6

(a,c,f)

 

 

 

 

g

 

 

16

(a,c,f,g)

16

(a,c,f,g)

14

(a,c,f,d,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}

    

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

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

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

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

 

 

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

 

 

 1  

 2 ¸

 3 ·

 4 ¹

 5 º

 6 »

 Ve

 0

 19

 15

 29

 38 

 43

 Vl

 0

 19

 15

 37

 38

 43

 

 

<1, 2>

<1, 3>

<3, 2>

<2, 4>

<2, 5>

<3, 5>

<4, 6>

<5, 6>

 e

  0

  0

 15

 19

 19

 15

 29

 38

 l

 17

  0

 15

 27

 19

 27

 37

 38

l-e

 17

  0

  0

  8

  0

 12

  8

  0

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

      

3.算法设计题

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

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

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

③ 增加一条边<v,w>InsertArc(G, v, w);

④ 删除一条边<v,w>DeleteArc(G, v, w)。

//本题中的图G均为有向无权图,其余情况容易由此写出
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 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_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<n;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 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
//为节省篇幅,本题只给出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(!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

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

数据结构考研指导2327.3.7

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

数据结构考研指导2327.3.8

(4)试基于图的深度优先搜索策略写一算法,判别以邻接表方式存储的有向图中是否存在由顶点vi到顶点vj的路径(i≠j)。

1

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

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

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

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

  else 

  { 

    visited[i]=1; 

    for(p=G.vertices[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 level1;//递归进行的层数

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

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

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

  else 

  { 

    visited[i]=1; 

    for(p=G.vertices[i].firstarc;p;p=p->nextarclevel--) 

    { 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)//判断邻接表方式存储的有向图

的顶点ij是否存在长度为k的简单路径 

  if(i==j&&k==0) 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

 


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折半查找有序表(4610122030507088100)。若查找表中元素58,则它将依次与表中(   )比较大小,查找结果是失败。

A20703050                    B30887050   

C2050                            D308850

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

A3            B4          C5           D6

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

  A.相同                              B.完全不同        

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

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

A.(10080, 90, 60, 120110130) 

B.(10012011013080, 60, 90

C10060, 80 90, 120110130

D(10080, 60, 90, 120130110)

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

ALL           BLR          CRL          DRR

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+树都能有效地支持随机检索

11m阶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的元素加到表中,用二次探测法解决冲突,则放入的位置是(   )。

      A8              B3              C5             D

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

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

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

 

2.应用题

1假定对有序表:(34572430425463728795)进行折半查找,试回答下列问题:

① 画出描述折半查找过程的判定树;

② 若查找元素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层共查找12×24×3=17次;

但最后一层未满,不能用8×4,只能用5×4=20次,

所以ASL1/121720)=37/123.08

 

2在一棵空的二叉排序树中依次插入关键字序列为1271711162139214请画出所得到的二叉排序树。

 

 

 

                 12

    17

       2     11        16    21              

        4   9        13

 

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

 

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

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

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

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

 

解:

 

 

 

 

    

 

 

    

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

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

                       

 

 

 

 

 

 

 

 

 

5设哈希表的地址范围为017,哈希函数为:Hkey=key%16。用线性探测法处理冲突,输入关键字序列:(1024321731304647406349),构造哈希表,试回答下列问题:

① 画出哈希表的示意图;

② 若查找关键字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 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/11623×3+6)=23/11

 

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

 

散列地址

0

1

2

3

4

5

6

7

8

9

关键字

14

01

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

 

(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

 

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

 

 ②

 

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

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

5设哈希表的地址范围为017,哈希函数为:Hkey=key%16。用线性探测法处理冲突,输入关键字序列:(1024321731304647406349),构造哈希表,试回答下列问题:

① 画出哈希表的示意图;

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

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

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

 

 

解: (1)画表如下:

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

(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/11623×3+6)=23/11

 

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

 

散列地址

0

1

2

3

4

5

6

7

8

9

关键字

14

01

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

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

 

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

 


8章  排序

 

1.选择题

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

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

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

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

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

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

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

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

An+1            B.n               C.n-1              Dn(n-1)/2

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

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

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

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

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

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

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

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

A384046567984               B.403846795684

C.403846567984               D.403846845679

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

A167231239453               B.942331721653 

C.16532394,3172               D162353319472

9堆是一种(   )排序。

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

10堆的形状是一棵(   )。

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

11若一组记录的排序码为(467956384084),则利用堆排序的方法建立的初始堆为(   )。

A794656384084               B.847956384046          

C.847956464038               D.845679404638

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

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

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

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

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

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

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

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

2.应用

1设待排序的关键字序列为{1221630281016*20618},试分别写出使用以下排序方法,每趟排序结束后关键字序列的状态。

① 直接插入排序

② 折半插入排序

③ 希尔排序(增量选取531

④ 冒泡排序

⑤ 快速排序

⑥ 简单选择排序

⑦ 堆排序

⑧ 二路归并排序

 

①直接插入排序

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

 

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

 

③ 希尔排序(增量选取531

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

 

⑦ 堆排序

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

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

交换110对象                                      从19重新形成堆

交换19对象                                      18重新形成堆

交换18对象                                      从17重新形成堆

交换17对象                                      从16重新形成堆

交换16对象                                      从15重新形成堆

交换15对象                                      从14重新形成堆

交换14对象                                      从13重新形成堆

交换13对象                                         从12重新形成堆

 

交换12对象                                      得到结果

 

2给出如下关键字序列{321,156,57,46,28,7,331,33,34,63},试按链式基数排序方法,列出每一趟分配和收集的过程。

3.算法设计

(1)试以单链表为存储结构,实现简单选择排序算法。

void LinkedListSelectSort(LinkedList head)

//本算法一趟找出一个关键字最小的结点,其数据和当前结点进行交换;若要交换指针,则须记下

//当前结点和最小结点的前驱指针

p=head->next; 

while(p!=null)

    {q=p->next;  r=p;    //设r是指向关键字最小的结点的指针

     while (q!=null)

      {if(q->data<r->data) r=q;

 q:=q->next;

}

     if(r!=p)  r->data<-->p->data;

     p=p->next;

    }

2)有n个记录存储在带头结点的双向链表中,现用双向冒泡排序法对其按上升序进行排序,请写出这种排序的算法。(注:双向冒泡排序即相邻两趟排序向相反方向冒泡)。   

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->data<p->prior->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)设有顺序放置的n个桶,每个桶中装有一粒砾石,每粒砾石的颜色是红,白,蓝之一。要求重新安排这些砾石,使得所有红色砾石在前,所有白色砾石居中,所有蓝色砾石居后,重新安排时对每粒砾石的颜色只能看一次,并且只允许交换操作来调整砾石的位置。

[题目分析]利用快速排序思想解决。由于要求“对每粒砾石的颜色只能看一次”,设3个指针ijk,分别指向红色、白色砾石的后一位置和待处理的当前元素。从k=n开始,从右向左搜索,若该元素是兰色,则元素不动,指针左移(即k-1);若当前元素是红色砾石,分i>=j(这时尚没有白色砾石)和i<j两种情况。前一情况执行第i个元素和第k个元素交换,之后i+1;后一情况,i所指的元素已处理过(白色),j所指的元素尚未处理,应先将ij所指元素交换,再将ik所指元素交换。对当前元素是白色砾石的情况,也可类似处理。

为方便处理,将三种砾石的颜色用整数123表示。

void QkSort(rectype r[],int n)

// r为含有n个元素的线性表,元素是具有红、白和兰色的砾石,用顺序存储结构存储,

//本算法对其排序,使所有红色砾石在前,白色居中,兰色在最后。

{int i=1,j=1,k=n,temp;

 while (k!=j)

  {while (r[k].key==3) k--;// 当前元素是兰色砾石,指针左移 

   if (r[k].key==1)        // 当前元素是红色砾石

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

//ij分别指向红、白色砾石的后一位置

  }//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+1i=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--; }

对比两种算法,可以看出,正确选择变量(指针)的重要性。

4)编写算法,对n个关键字取整数值的记录序列进行整理,以使所有关键字为负值的记录排在关键字为非负值的记录之前,要求:

① 采用顺序存储结构,至多使用一个记录的辅助存储空间;

② 算法的时间复杂度为O(n)

 

5)借助于快速排序的算法思想,在一组无序的记录中查找给定关键字值等于key的记录。设此组记录存放于数组r[l..n]中。若查找成功,则输出该记录在r数组中的位置及其值,否则显示“not find”信息。请简要说明算法思想并编写算法。

[题目分析]把待查记录看作枢轴,先由后向前依次比较,若小于枢轴,则从前向后,直到查找成功返回其位置或失败返回0为止。

     int index (RecType R[],int l,h,datatype key)

     {  int i=l,j=h;

        while (i<j)

        { while (i<=j && R[j].key>key) j--;

          if (R[j].key==key) return  j;

          while (i<=j && R[i].key<key) i++;

          if (R[i].key==key) return  i;

         }

        printf(“Not find”) ; return  0;

     }//index

 

6)有一种简单的排序算法,叫做计数排序。这种排序算法对一个待排序的表进行排序,并将排序结果存放到另一个新的表中。必须注意的是,表中所有待排序的关键字互不相同,计数排序算法针对表中的每个记录,扫描待排序的表一趟,统计表中有多少个记录的关键字比该记录的关键字小。假设针对某一个记录,统计出的计数值为c,那么,这个记录在新的有序表中的合适的存放位置即为c。

 ① 给出适用于计数排序的顺序表定义;

 ② 编写实现计数排序的算法;

 ③ 对于有n个记录的表,关键字比较次数是多少?

 ④ 与简单选择排序相比较,这种方法是否更好?为什么?

typedef struct

int key; datatype info}RecType

void CountSort(RecType a[],b[],int n) //计数排序算法,将a中记录排序放入b

for(i=0;i<n;i++) //对每一个元素

  { for(j=0,cnt=0;j<n;j++)

 if(a[j].key<a[i].key) cnt++; //统计关键字比它小的元素个数

    b[cnt]=a[i];

  }

}//Count_Sort

(3) 对于有n个记录的表,关键码比较n2次。

(4) 简单选择排序算法比本算法好。简单选择排序比较次数是n(n-1)/2,且只用一个交换记录的空间;而这种方法比较次数是n2,且需要另一数组空间。

[算法讨论]因题目要求“针对表中的每个记录,扫描待排序的表一趟”,所以比较次数是n2次。若限制“对任意两个记录之间应该只进行一次比较”,则可把以上算法中的比较语句改为:

for(i=0;i<n;i++) a[i].count=0;//各元素再增加一个计数域,初始化为0

for(i=0;i<n;i++)

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

 if(a[i].key<a[j].key) a[j].count++; else a[i].count++;

 

 

 

 

posted @ 2013-10-12 08:44  ZhangAihua  阅读(12233)  评论(0编辑  收藏  举报