数据结构课后习题重点难点易考点(持续更新)

目录

第一章 绪论

第二章 线性表

第三章 栈和队列

第四章 串、数组和广义表

第五章 数和二叉树

第六章 图

第七章 查找

 第八章 排序


第一章 绪论

1、选择题

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

A.存储结构 B.存储实现 C.逻辑结构 D.运算实现

答案:C

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

A.数据具有同一特点

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

C.每个数据元素都一样

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

答案:B

(3)算法的时间复杂度取决于( )。

A.问题的规模  B.待处理数据的初态  C.计算机的配置  D.A和B

答案:D

解释:算法的时间复杂度不仅与问题的规模有关,还与问题的其他因素有关。如某些排序的算法,其执行时间与待排序记录的初始状态有关。为此,有时会对算法有最好、最坏以及平均时间复杂度的评价。

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

数据:是客观事物的符号表示,指所有能输入到计算机中并被计算机程序处理的符号的总称。如数学计算中用到的整数和实数,文本编辑所用到的字符串,多媒体程序处理的图形,图像,声音、动画等通过特殊编码定义后的数据。

数据元素:是数据的基本单位,在计算机中通常作为一个整体进行考虑和处理。

数据项:是组成数据元素的、有独立含义的、不可分割的最小单位。

数据对象:是性质相同的数据元素的集合,是数据的一个子集。

数据结构:是相互之间存在一种或多种特定关系的数据元素集合。

逻辑结构:从逻辑关系上描述数据,它与数据的存储无关,是独立于计算机的。因此,数据的逻辑结构可以看作是从具体问题抽象出来的数学模型。

存储结构:数据对象在计算机中的存储表示,也称为物理结构。

抽象数据类型:由用户定义的,表示应用问题的数学模型,以及定义在这个模型上的一组操作的总称。具体包括三部分:数据对象、数据对象上关系的集合和对数据对象的基本操作的集合。

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

例如有一张学生基本信息表,包括学生的学号、姓名、性别、籍贯、专业等。每个学生基本信息记录对应一个数据元素,学生记录按顺序号排列,形成了学生基本信息记录的线性序列。对于整个表来说,只有一个开始结点(它的前面无记录)和一个终端结点(它的后面无记录),其他的结点则各有一个也只有一个直接前趋和直接后继。学生记录之间的这种关系就确定了学生表的逻辑结构,即线性结构。 这些学生记录在计算机中的存储表示就是存储结构。如果用连续的存储单元(如用数组表示)来存放这些记录,则称为顺序存储结构;如果存储单元不连续,而是随机存放各个记录,然后用指针进行链接,则称为链式存储结构。 即相同的逻辑结构,可以对应不同的存储结构。

 

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

集合结构:数据元素之间除了”属于同一集合“的关系外,别无其他关系。例如,确定一名学生是否为班级成员,只需将班级看作一个集合结构。

线性结构:数据元素之间存在一对一的关系。例如,将学生信息数据按照其入学报到的时间先后顺序进行排列,将组成一个线性结构。

树结构:数据元素之间存在一对多的关系。例如,在班级管理体系中,班长管理多个组长,每位组长管理多名组员,从而构成树形结构。

图结构或网状结构:数据元素之间存在着多对多的关系。例如,多位同学之间的朋友关系,任何两位同学都可以是朋友,从而构成图形结构或网状结构。

其中树结构和图结构都属于非线性结构。

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

顺序存储结构:顺序存储结构是借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系,通常借助程序设计语言的数组类型来描述。

链式存储结构:顺序存储结构要求所有的元素依次存放在一片连续的存储空间中,而链式存储结构,无需占用一整块存储空间。但为了表示结点之间的关系,需要给每个结点附加指针字段,用于存放后继元素的存储地址。所以链式存储通常借助于程序设计语言的指针类型来描述。

第二章 线性表

1、选择题

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

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

答案:C

解释:存储密度是指一个结点数据本身所占存储空间和整个结点所占存储空间之比,假设单链表的一个结点本身所占的空间为D,指针域所占的空间为N,则存储密度为:D/(D+N),一定小于1.

(2) 创建一个包括n个结点的有序单链表的时间复杂度是(    )。

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

答案:C

解释:单链表的创建时间复杂度为O(n),而建立一个有序的单链表,则每生成一个新结点时需要和已有的结点进行比较,确定合适的插入位置,所以时间复杂度为O(n2)。

2、算法设计题

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

【题目分析】合并后的新表使用头指针Lc指向,pa和pb分别时链表La和Lb的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较,当两个链表La和Lb均为到达表尾结点时,依次摘取其中较小者重新链接在Lc表的最后。如果两个表中的元素相等,只摘取La表中的元素,删除Lb表中的元素,这样确保合并后表中无重复的元素。当一个表到达表尾结点,为空时,将非空表的剩余元素直接链接在Lc表的最后。

【算法描述】

void MergeList(LinkList &La,LinkList &Lb,LinkList &Lc)
{//合并链表La和Lb,合并后的新表使用头指针Lc指向
    pa=La->next;  pb=Lb->next; //pa和pb分别是链表La和Lb的工作指针
    Lc=pc=La;  //用La的头结点作为Lc的头结点
    while(pa && pb)
    {
        if(pa->data<pb->data)
        {//取较小者La中的元素,将pa链接在pc的后面,pa指针后移
            pc->next=pa;
            pc=pa;
            pa=pa->next;
        }
        else if(pa->data>pb->data) 
        {//取较小者Lb中的元素,将pb链接在pc的后面,pb指针后移
            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)将两个非递减的有序链表合并为一个非递增的有序链表。要求结果链表仍使用原来两个链表的存储空间,不另外占用其它的存储空间。表中允许有重复的数据。

【题目分析】合并后的新表使用头指针Lc指向,pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较,当两个链表La和Lb均为到达表尾结点时,依次摘取其中较小者重新链接在Lc表的表头结点之后,如果两个表中的元素相等,只摘取La表中的元素,保留Lb表中的元素。当一个表到达表尾结点,为空时,将非空表的剩余元素依次摘取,链接在Lc表的表头结点之后。

【算法描述】

void MergeList(LinkList& La, LinkList& Lb, LinkList& Lc, ) 
{//合并链表La和Lb,合并后的新表使用头指针Lc指向
	pa=La->next;  pb=Lb->next; //pa和pb分别是链表La和Lb的工作指针
  	Lc=pc=La; //用La的头结点作为Lc的头结点 
 	Lc->next=NULL;
  	while(pa||pb)
	{//只要存在一个非空表,用q指向待摘取的元素
	    if(!pa)  
		{//La表为空,用q指向pb,pb指针后移
			q=pb;  
			pb=pb->next;
		}
	    else if(!pb) 
		{//Lb表为空,用q指向pa,pa指针后移
			q=pa;  
			pa=pa->next;
		} 
	    else if(pa->data<=pb->data)  
		{//取较小者(包括相等)La中的元素,用q指向pa,pa指针后移
			q=pa;  
			pa=pa->next;
		}
	    else 
		{//取较小者Lb中的元素,用q指向pb,pb指针后移
			q=pb;  
			pb=pb->next;
		}
	    q->next = Lc->next;
		Lc->next = q;
	}
	delete Lb;             //释放Lb的头结点
}   

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

【题目分析】只有同时出现在两集合中的元素才出现在结果表中,合并后的新表使用头指针Lc指向。pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较,当两个链表La和Lb均为到达表尾结点时,如果两个表中相等的元素时,摘取La表中的元素,删除Lb表中的元素;如果其中一个表中的元素较小时,删除此表中较小的元素,此表的工作指针后移。当链表La和Lb有一个到达表尾结点,为空时,依次删除另一个非空表中的所有元素。

【算法描述】

void Mix(LinkList& La, LinkList& Lb, LinkList& Lc) 
{  
	pa=La->next;pb=Lb->next; //pa和pb分别是链表La和Lb的工作指针
	Lc=pc=La; //用La的头结点作为Lc的头结点
	while(pa&&pb)
	{ 
		if(pa->data==pb->data)//交集并入结果表中。
	   	{ 
		   	pc->next=pa;
			pc=pa;
			pa=pa->next;
	     	u=pb;
			pb=pb->next; 
			delete u;
		}
	  	else if(pa->data<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;  //释放Lb的头结点
}

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

【题目分析】求两个集合A和B的差集是指在A中删除A和B中共有的元素,即删除链表中的相应结点,所以要保存待删除结点的前驱,使用指针pre指向前驱结点。pa和pb分别是链表La和Lb的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较,当两个链表La和Lb均为到达表尾结点时,如果La表中的元素小于Lb表中的元素,pre置为La表的工作指针pa删除Lb表中的元素;如果其中一个表中的元素较小时,删除此表中较小的元素,此表的工作指针后移。当链表La和Lb有一个为空时,依次删除另一个非空表中的所有元素。

【算法描述】

void Difference(LinkList& La, LinkList& Lb,int *n)
{//差集的结果存储于单链表La中,*n是结果集合中元素个数,调用时为0
    pa=La->next; pb=Lb->next; //pa和pb分别是链表La和Lb的工作指针
	pre=La;    //pre为La中pa所指结点的前驱结点的指针
    while(pa&&pb)
    {
        if(pa->data<pb->data)
        {//A链表中当前结点指针后移
            pre=pa;
            pa=pa->next;
            *n++;
        } 
        else if(pa->data>pb->data) 
			pb=pb->next;//B链表中当前结点指针后移
        else 
        {
            pre->next=pa->next; //处理A,B中元素值相同的结点,应删除
            u=pa; 
			pa=pa->next; 
			delete u;
        }  //删除结点
    }
}

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

【算法描述】B表的头结点使用原来A表的头结点,为C表新申请一个头结点。从A表的第一个结点开始,依次取其每个结点p,判断结点p的值是否小于0,利用前插法,将小于0的结点插入B表,大于等于0的结点插入C表。

【算法描述】

void DisCompose(LinkedList A)
{ 	
	B=A;
	B->next= NULL;  //B表初始化
	C=new LNode;  //为C申请结点空间
	C->next=NULL;  //C初始化为空表
	p=A->next;     //p为工作指针
	while(p!= NULL)
	{ 
		r=p->next;     //暂存p的后继
		if(p->data<0)
	    {
			p->next=B->next; 
			B->next=p; 
		}//将小于0的结点链入B表,前插法
	   	else 
		{
			p->next=C->next; 
			C->next=p; 
		}//将大于等于0的结点链入C表,前插法
	   	p=r;//p指向新的待处理结点。
	}
}

(6)设计一个算法,通过一趟遍历在单链表中确定值最大的结点。

【题目分析】假定第一个结点中数据具有最大值,依次与下一个元素比较,若其小于下一个元素,则设其下一个元素为最大值,反复进行比较,直到遍历完该链表。

【算法描述】

ElemType Max (LinkList L )
{
	if(L->next==NULL) return NULL;
	pmax=L->next; //假定第一个结点中数据具有最大值
	p=L->next->next;
	while(p != NULL ){//如果下一个结点存在
		if(p->data > pmax->data) pmax=p;//如果p的值大于pmax的值,则重新赋值
		p=p->next;//遍历链表
	}
	return pmax->data;
}

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

【题目分析】从首元结点开始,逐个地把链表L的当前结点p插入新的链表头部。

【算法描述】

void  inverse(LinkList &L) 
{// 逆置带头结点的单链表 L
    p=L->next;  L->next=NULL;
    while ( p) {
        q=p->next;    // q指向*p的后继
        p->next=L->next;
        L->next=p;       // *p插入在头结点之后
        p = q;
    }
}

(8)设计一个算法,删除递增有序链表中值大于mink且小于maxk的所有元素(mink和maxk是给定的两个参数,其值可以和表中的元素相同,也可以不同)。

【题目分析】分别查找第一个值>mink的结点和第一个值 ≥maxk的结点,再修改指针,删除值大于mink且小于maxk的所有元素。

【算法描述】

void delete(LinkList &L, int mink, int maxk) {
   	p=L->next; //首元结点
   	while (p && p->data<=mink)
    { 
		pre=p;  
		p=p->next; 
	} //查找第一个值>mink的结点
   	if (p) 
	{
		while (p && p->data<maxk)  
			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结点,前驱结点,前驱的前驱结点,后继结点)六条链。

【算法描述】

void  Exchange(LinkList p)
{//p是双向循环链表中的一个结点,本算法将p所指结点与其前驱结点交换。
	q=p->prior;
 	q->prior->next=p;    //p的前驱的前驱之后继为p
 	p->prior=q->prior;   //p的前驱指向其前驱的前驱。
 	q->next=p->next;     //p的前驱的后继为p的后继。
 	q->prior=p;          //p与其前驱交换
 	p->next->prior=q;    //p的后继的前驱指向原p的前驱
 	p->next=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<n && A[i]!=item) i++;//若值不为item,i左移指针。
 	j=i;
 	while(j<n && A[j]==item) j++;//若值为item,j左移指针。
 	while(j<n)
 		A[i++]=A[j++];
}

第三章 栈和队列

1、选择题

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

答案:D

解释:对于非循环队列,尾指针和头指针的差值便是队列的长度,而对于循环队列,差值可能为负数, 所以需要将差值加上 MAXSIZE (本题为 n),然后与 MAXSIZE (本题为 n)求余,即( n+r-f)%n 。

(2) 链式栈结点为: (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 ;

答案:A

解释:x=top->data 将结点的值保存到 x 中,top=top->link 栈顶指针指向栈顶下一结点,即摘除栈顶结点。

(3) 栈在 ()中有所应用。
A.递归调用 B.函数调用 C.表达式求值 D.前三个选项都有

答案:D

解释:递归调用、函数调用、表达式求值军用到了栈的后进先出性质。

(4)为解决计算机主机与打印机间速度不匹配问题,通常设一个打印数据缓冲区。主机
将要输出的数据依次写入该缓冲区,而打印机则依次从该缓冲区中取出数据。该缓冲区的逻
辑结构应该是() 。
A.队列 B .栈 C.线性表 D.有序表

答案:A

解释:解决缓冲区问题应利用一种先进先出的线性表,而队列正是一种先进先出的线性表。

(5)若一个栈以向量 V[1…n] 存储,初始栈顶指针 top 设为 n+1 ,则元素 x 进栈的正确操作
是 ( ) 。
A. top++; V[top]=x;     B. V[top]=x; top++;
C. top–; V[top]=x;      D. V[top]=x; top–;

答案:C

解释:初始栈顶指针 top 为 n+1 ,说明元素从数组向量的高端地址进栈,又因为元素存储在向量空间 V[1…n] 中,所以进栈时 top 指针先下移变为 n,之后将元素 x 存储在 V[n] 。

(6)用链接方式存储的队列,在进行删除运算时( )。
A. 仅修改头指针         B. 仅修改尾指针
C. 头、尾指针都要修改    D. 头、尾指针可能都要修改

答案:D

解释:一般情况下只修改头指针,但是,当删除的是队列中最后一个元素时,队尾指针也丢失了,因此需对队尾指针重新赋值。

(7)循环队列存储在数组 A[0…m] 中,则入队时的操作为( )。
A. rear=rear+1         B. rear=(rear+1)%(m-1)
C. rear=(rear+1)%m     D. rear=(rear+1)%(m+1)

答案:D

解释:数组A[0...m]中含有m+1个元素,故在求模运算时应除以m+1。

(8)一个递归算法必须包括( )。
A. 递归部分     B. 终止条件和递归部分
C. 迭代部分     D. 终止条件和迭代部分

答案:B

2、算法设计题

( 1)将编号为 0 和 1 的两个栈存放于一个数组空间 V[m] 中,栈底分别处于数组的两端。
当第 0 号栈的栈顶指针 top[0] 等于 -1 时该栈为空,当第 1 号栈的栈顶指针 top[1] 等于 m 时该栈为空。两个栈均从两端向中间增长。试编写双栈初始化,判断栈空、栈满、进栈和出栈等算法的函数。双栈数据结构的定义如下:

Typedef struct {
    int top[2],bot[2]; //栈顶和栈底指针
    SElemType *V; //栈数组
    int m; //栈最大可容纳元素个数
}DblStack;

【题目分析】两栈共享向量空间,将两栈底设在向量两端,初始时,左栈顶指针为-1,右栈顶为m。两栈顶指针相邻时为栈满。两栈顶相向、迎面增长,栈顶指针指向栈顶元素。

【算法描述】

(1)栈的初始化

int Init()  {
	S.top[0]=-1; 
	S.top[1]=m; 
	return 1; // 初始化成功
} 

(2)入栈操作

int push(stk S ,int i,int x) 
∥ i 为栈号, i=0 表示左栈, i=1 为右栈, x 是入栈元素。入栈成功返回 1,失败返回 0 
{
	if(i<0||i>1){
		cout<< “栈号输入不对 ”<<endl;exit(0);} 
	if(S.top[1]-S.top[0]==1) {
		cout<< “栈已满 ”<<endl;return(0);} 
	switch(i) {
		case 0: S.V[++S.top[0]]=x; return(1); break; 
		case 1: S.V[--S.top[1]]=x; return(1); 
	} 
} ∥ push 

(3)退栈操作

ElemType pop(stk S,int i) 
∥退栈。 i 代表栈号, i=0 时为左栈, i=1 时为右栈。退栈成功时返回退栈元素
∥否则返回 -1 
{
	if(i<0 || i>1){cout<< “栈号输入错误 ”<<endl ; exit(0);} 
	 switch(i) {
	 	case 0: 
	 		if(S.top[0]==-1) {
	 			cout<< “栈空 ”<<endl ; return ( -1); } 
			else return(S.V[S.top[0]--]); 
		case 1: 
			if(S.top[1]==m {
				cout<< “栈空 ”<<endl; return(-1);} 
			else return(S.V[S.top[1]++]); 
	 } ∥ switch 
} ∥算法结束

(4)判断栈空

int Empty(); 
{
	return (S.top[0]==-1 && S.top[1]==m); 
} 

( 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 个整数序列作处理。
	18 
		{cin>>x); // 从键盘读入整数序列。
	if(x!=-1) // 读入的整数不等于 -1 时入栈。
	{ 
		if(top==maxsize-1)
			{cout<< “栈满” <<endl;exit(0);} 
		else s[++top]=x; //x 入栈。
	}
	else // 读入的整数等于 -1 时退栈。
	{
		if(top==0)
			{cout<< “栈空” <<endl;exit(0);} 
		else cout<< “出栈元素是” <<s[top--]<<endl;} 
	} 
}// 算法结束。

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

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

【算法描述】

float expr( ) 
// 从键盘输入逆波兰表达式,以‘ $’表示输入结束,本算法求逆波兰式表达式的值。
{ 
	float OPND[30]; // OPND 是操作数栈。
	init(OPND); // 两栈初始化。
	float num=0.0; // 数字初始化。
	cin>>x;//x 是字符型变量。
	while(x!= ’$’)
	{
		switch 
	 	{
	 		case ‘0’<=x<=’9’:
				while((x>= ’0’&&x<=’9’)||x== ’. ’) // 拼数
					if(x!= ’. ’) // 处理整数
					{
						num=num*10+ ( ord(x)- ord( ‘0’) ) ; cin>>x;
					} 
					else // 处理小数部分。
					{
						scale=10.0; cin>>x; 
						while(x>= ’0’&&x<=’9’)
							{num=num+(ord(x)- ord( ‘0’)/scale;
						scale=scale*10; cin>>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 
		cin>>x;// 读入表达式中下一个字符。
	 }// 结束 while ( x! =‘$’)
	cout<< “后缀表达式的值为” <<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){ 
	 			cout<< “序列非法” <<ednl ; exit(0);} 
	 	} 
		i++; // 不论 A[i] 是‘ I ’或‘ O’,指针 i 均后移。 
	} 
	if(j!=k) {cout<< “序列非法” <<endl ; return(false);} 
	else {cout<< “序列合法” <<endl ; return(true);} 
 }// 算法结束。

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

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

【题目分析】置空队就是建立一个头结点,并把队头队尾指针都指向头结点,头结点是不存放数据的;判队空就是当头指针等于队尾指针时,队空;入队时,将新的结点插入到链队列的尾部,同时将队尾指针指向这个结点;出队时,删除的是队头结点,要注意队列的长度大于1还是等于1的情况,这个时候要注意尾指针的修改,如果等于1,则要删除尾指针指向的结点。

【算法描述】

//先定义链队结构 : 
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; 
	}
	delete s; 
}// 回收结点空间

(2)判空队

int EmptyQueue( LinkQueue *Q) 
{ // 判队空。当头结点的 next 指针指向自己时为空队
	return Q->rear->next->next==Q->rear->next; 
} 

(3)入队

void EnQueue( LinkQueue *Q, Datatype x) 
{ // 入队。也就是在尾结点处插入元素
	QueueNode *p=new QueueNode;// 申请新结点
	p->data=x; p->next=Q->rear->next;// 初始化新结点并链入
	Q-rear->next=p; 
	Q->rear=p;// 将尾指针移至新结点
} 

(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 
	delete p;// 释放被删结点
	return x; 
} 

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

(1)初始化

SeQueue QueueInit(SeQueue Q) 
{// 初始化队列
	Q.front=Q.rear=0; Q.tag=0; 
	return Q; 
} 

(2)入队

SeQueue QueueIn(SeQueue Q,int e) 
{// 入队列
	if((Q.tag==1) && (Q.rear==Q.front)) 
		cout<<" 队列已满 "<<endl; 
	else 
	{
		Q.rear=(Q.rear+1) % m; 
		Q.data[Q.rear]=e; 
		if(Q.tag==0) Q.tag=1; // 队列已不空
	} 
	return Q; 
} 

(3)出队

ElemType QueueOut(SeQueue Q) 
{// 出队列
	if(Q.tag==0) { 
		cout<<" 队列为空 "<<endl; exit(0);} 
	else 
		{Q.front=(Q.front+1) % m; 
	e=Q.data[Q.front]; 
	if(Q.front==Q.rear) Q.tag=0; // 空队列
	} 
	return(e); 
} 

(8 )如果允许在循环队列的两端都可以进行插入和删除操作。要求:
① 写出循环队列的类型定义;
② 写出“从队尾删除”和“从队头插入”的算法。

【题目分析】用一维数组 v[0…M-1] 实现循环队列,其中 M 是队列长度。设队头指针front 和队尾指针 rear ,约定 front 指向队头元素的前一位置, rear 指向队尾元素。定义front=rear 时为队空,(rear+1)%m=front 为队满。约定队头端入队向下标小的方向发展,队尾端入队向下标大的方向发展。

【算法描述】

#define M 队列可能达到的最大长度
typedef struct 
{
	elemtp data[M]; 
	int front,rear; 
}cycqueue; 

elemtp delqueue ( cycqueue Q) 
 //Q 是如上定义的循环队列,本算法实现从队尾删除,若删除成功,返回被删除元素,
否则给出出错信息。
{
	if (Q.front==Q.rear) {
		cout<<" 队列空 "<<endl; 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) 
		{cout<<" 队满 "<<endl; exit(0);) 
	Q.data[Q.front]=x; //x 入队列
	Q.front=(Q.front-1+M)%M; // 修改队头指针。
}// 结束从队头插入算法。

( 9)已知 f 为单链表的表头指针 , 链表中存储的都是整型数据,试写出实现下列运算
的递归算法:
①求链表中的最大整数;
②求链表的结点个数;
③求所有整数的平均值。

【算法描述】

int GetMax(LinkList p) 
{ 
	if(!p->next) 
		return p->data; 
	else 
	{ 
		int max=GetMax(p->next); 
		return p->data>=max ? p->data:max; 
	} 
} 

int GetLength(LinkList p) 
{ 
	if(!p->next) 
		return 1; 
	else 
	{ 
		return GetLength(p->next)+1; 
	} 
} 

double GetAverage(LinkList p , int n) 
{ 
	if(!p->next) 
		return p->data; 
	else 
	{ 
		double ave=GetAverage(p->next,n-1); 
		return (ave*(n-1)+p->data)/n; 
	} 
}

第四章 串、数组和广义表

1、选择题

(1) 设有数组 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

答案:B

解释:以列序为主,则LOC[5,8]=[(8-1)*8+(5-1)]*3+BA=BA+180。

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

答案:C

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

答案:B

(4)二维数组 A 的每个元素是由 10 个字符组成的串,其行下标 i=0,1, , ,8, 列下标
j=1,2, , ,10 。若 A 按行先存储,元素 A[8,5] 的起始地址与当 A 按列先存储时的元素
()的起始地址相同。设每个字符占一个字节。
A. A[8,5]
B. A[3,10]
C.  A[5,8]
D. A[0,9]

答案:B

解释:设数组从内存首地址 M 开始顺序存放,若数组按行先存储,元素 A[8,5] 的起始地址为: M+[( 8-0 )*10+ (5-1)]*1=M+84 ;若数组按列先存储,易计算出元素 A[3,10] 的起始地址为: M+[ (10-1) *9+ ( 3-0 ) ]*1=M+84 。故选 B。

(5)设二维数组 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. jm+i-1

答案:A

解释:特殊值法。取 i=j=1 ,易知 A[1,1] 的的下标为 1,四个选项中仅有 A 选项能确定的值为 1,故选 A 。

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

答案:B

解释:共有 5*3*3=45 个元素。

2、应用题

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

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

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

【算法描述】

void InvertStore( char A[]) 
// 字符串逆序存储的递归算法。
{ 
	char ch; 
	static int i = 0;// 需要使用静态变量
	cin>>ch; 
	if (ch!= '.') // 规定 '.' 是字符串输入结束标志
	{
		InvertStore(A); 
		A[i++] = ch;// 字符串逆序存储
	} 
	A[i] = '\0'; // 字符串结尾标记
} 

( 3)编写算法,实现下面函数的功能。函数 void insert(chars,chart,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) {
	 	cout<< “pos 参数位置非法” <<endl;exit(0);
	 } 
	while(*p!= ’ 0’&&i<pos) {p++;i++;} // 查 pos 位置
	// 若 pos 小于串 s 长度,则查到 pos 位置时, i=pos 。
	 	if(*p == '/0') {cout<<pos<<" 位置大于字符串 s 的长度 ";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 中。

( 4)已知字符串 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') 	{cout<<" 字符串 s1 为空串或空格串 "<<endl;exit(0); } 
	while( *p!='\0' && i<n)	{*q=*p; q++; p++; i++;} 
	// 字符串 s1 向字符串 s2 中复制
	if(*p =='\0')	{cout<<" 字符串 s1 没有 "<<n<<" 个有效字符 "<<endl; exit(0);} 
	if(*(--q)==' ' ) // 若最后一个字符为空格,则需向后找到第一个非空格字符
	 {
		 p-- ; //p 指针也后退
		 while(*p==' '&&*p!='\0') p++;// 往后查找一个非空格字符作串 s2 的尾字符
		 if(*p=='\0') 
		{
			cout<<"s1 串没有 "<<n<<" 个两端对齐的字符串 "<<endl; exit(0);
		} 
		 *q=*p; // 字符串 s2 最后一个非空字符
		 *(++q)='\0'; // 置 s2 字符串结束标记
	 } 
	*q=s3;p++; // 将 s1 串其余部分送字符串 s3 。
	while (*p!= '\0')	 {*q=*p; q++; p++;} 
	*q='\0'; // 置串 s3 结束标记
} 

( 5)设二维数组 a[1…m, 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<m;i++) 
		for(j=0;j<n-1;j++) 
	 	{
	 		for(p=j+1;p<n;p++) // 和同行其它元素比较
				if(a[i][j]==a[i][p]) 
					{cout<< “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]) {
						cout<< “no”; return(0); 
					} 
	}// for(j=0;j<n-1;j++) 
	cout<< “ yes ”; return(1); // 元素互不相同
}// 算法 JudgEqual 结束

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

(6) 设任意 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<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] 
	}// while(i<j) 
}// 算法 Arrange 结束 . 

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

第五章 数和二叉树

1、选择题

(1)利用二叉链表存储树,则根结点的右指针是() 。
A.指向最左孩子     B .指向最右孩子     C .空     D .非空

答案:C

解释:利用二叉链表存储树时,右指针指向兄弟结点,因为根节点没有兄弟结点,故根节点的右指针指向空。

(2)若二叉树采用二叉链表存储结构,要交换其所有分支结点左、右子树的位置,利用()遍
历方法最合适。
    A.前序     B.中序     C.后序     D .按层次

答案:C

解释:后续遍历和层次遍历均可实现左右子树的交换 ,不过层次遍历的实现消耗比后序大,后序遍历方法最合适。

(3)若 X 是二叉中序线索树中一个有左孩子的结点,且 X 不为根,则 X 的前驱为() 。
A. X 的双亲
B. X 的右子树中最左的结点
C. X 的左子树中最右结点
D. X 的左子树中最右叶结点

答案:C

(4)设 F 是一个森林, B 是由 F 变换得的二叉树。若 F 中有 n 个非终端结点,则 B 中
右指针域为空的结点有( )个。
A.n- 1     B.n     C.n + 1     D.n + 2

答案:C

2、应用题

(1)试找出满足下列条件的二叉树
① 先序序列与后序序列相同②中序序列与后序序列相同
③ 先序序列与中序序列相同④中序序列与层次遍历序列相同

答案:先序遍历二叉树的顺序是“根—左子树—右子树” ,中序遍历“左子树—根—右
子树”,后序遍历顺序是: “左子树—右子树―根",根据以上原则有
①或为空树,或为只有根结点的二叉树;
② 或为空树,或为任一结点至多只有左子树的二叉树;
③ 或为空树,或为任一结点至多只有右子树的二叉树;
④或为空树,或为任一结点至多只有右子树的二叉树。

(2)设一棵二叉树的先序序列: A B D F C E G H ,中序序列: B F D A G E H C
①画出这棵二叉树。
②画出这棵二叉树的后序线索树。
③将这棵二叉树转换成对应的树(或森林) 。

答案:

 ( 3)假设用于通信的电文仅由 8 个字母组成, 字母在电文中出现的频率分别为 0.07 ,0.19 ,0.02 , 0.06 , 0.32 , 0.03 , 0.21 , 0.10 。
①试为这 8 个字母设计赫夫曼编码。
② 试设计另一种由二进制表示的等长编码方案。
③对于上述实例,比较两种方案的优缺点。

答案:①方案一:哈夫曼编码

先将概率放大 100 倍,以方便构造哈夫曼树。

 

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

 在这里插入图片描述

3、算法设计题 

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

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

【题目分析】如果二叉树为空,返回0,如果二叉树不为空且左右子树为空,返回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)判别两棵树是否相等

【算法描述】

int compareTree(TreeNode* tree1, TreeNode* tree2) 
//用分治的方法做,比较当前根,然后比较左子树和右子树
{
	bool tree1IsNull = (tree1==NULL); 
	bool tree2IsNull = (tree2==NULL); 
	if(tree1IsNull != tree2IsNull) 
	{ 
		return 1; 
	} 
	if(tree1IsNull && tree2IsNull) 
	{// 如果两个都是 NULL ,则相等
		return 0; 
	}// 如果根节点不相等,直接返回不相等,否则的话,看看他们孩子相等不相等
	if(tree1->c != tree2->c) 
	{ 
		return 1; 
	} 
	return (compareTree(tree1->left,tree2->left)&compareTree(tree1->right,tree2->right)) 
	(compareTree(tree1->left,tree2->right)&compareTree(tree1->right,tree2->left)); 
}// 算法结束

(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 的结点数目。

【题目分析】若某个结点左子树空右子树非空或者右子树空左子树非空,则该结点为度为 1 的结点

【算法描述】

int Level(BiTree bt) // 层次遍历二叉树,并统计度为 1 的结点的个数
{
	int num=0; //num 统计度为 1 的结点的个数
	if(bt)
	{
		QueueInit(Q); QueueIn(Q,bt);//Q 是以二叉树结点指针为元素的队列
		while(!QueueEmpty(Q)) 
		{
			p=QueueOut(Q); cout<<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); // 非空右子女入队
		} //while(!QueueEmpty(Q)) 
	}//if(bt) 
	return(num); 
}// 返回度为 1 的结点的个数

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

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

【算法描述】

// 递归求树的深度
int Depth(BiTNode *p)
{
    if (p == NULL)
        return (0);
    return 1 + (Depth(p->lchild) > Depth(p->rchild) ? Depth(p->lchild) : Depth(p->rchild));
}

// 递归打印路径
void echoPath(BiTree T)
{
    if (T)
    {
        //访问节点
        visit(T);

        // 往下走
        if (Depth(T->lchild) > Depth(T->rchild))
            echoPath(T->lchild);
        else
            echoPath(T->rchild);
    }
}

( 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--; //恢复环境
		} 
	}//if (b!=NULL) 
}// 算法结束

第六章 图

1、选择题

(1) G 是一个非连通无向图,共有 28 条边,则该图至少有()个顶点。
    A.7     B.8     C.9     D.10

答案:C

解释:8 个顶点的无向图最多有 8*7/2=28 条边,再添加一个点即构成非连通无向图,故至少有 9 个顶点。

(2)下面( )算法适合构造一个稠密图 G 的最小生成树。
    A.Prim 算法     B.Kruskal 算法     C.Floyd 算法     D.Dijkstra 算法

答案:A

解释: Prim 算法适合构造一个稠密图 G 的最小生成树, Kruskal 算法适合构造一个稀疏图 G 的最小生成树。

(3)用邻接表表示图进行广度优先遍历时,通常借助()来实现算法。
    A.栈     B. 队列     C. 树     D .图

答案: B
解释:广度优先遍历通常借助队列来实现算法,深度优先遍历通常借助栈来实现算法。

(4)图的 BFS 生成树的树高比 DFS 生成树的树高() 。
     A.小     B.相等     C.小或相等     D.大或相等

答案: C
解释:对于一些特殊的图,比如只有一个顶点的图,其 BFS 生成树的树高和 DFS 生成树的树高相等。一般的图,根据图的 BFS 生成树和 DFS 树的算法思想, BFS 生成树的树高比 DFS 生成树的树高小。

(5)已知图的邻接矩阵如图 6.30 所示, 则从顶点 v0 出发按深度优先遍历的结果是 ()。 
     A. 0243156    B. 0136542    C. 0134256    D. 0361542

 答案:C

(6)已知图的邻接表如图 6.31 所示,则从顶点 v0 出发按广度优先遍历的结果是()
    A . 0 1 3 2     B. 0 2 3 1     C. 0 3 2 1     D . 0 1 2 3

在这里插入图片描述

 答案:D

(7)下面()方法可以判断出一个有向图是否有环。
    A.深度优先遍历     B.拓扑排序     C.求最短路径     D .求关键路径

答案:C

2、应用题

(1)已知图 6.32 所示的有向图,请给出:
①每个顶点的入度和出度;
②邻接矩阵;
③邻接表;
④逆邻接表。

 答案:

 在这里插入图片描述

 ( 2)已知如图 6.33 所示的无向网,请给出:
①邻接矩阵;
②邻接表;
③最小生成树

答案:

 

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

在这里插入图片描述

 答案:在这里插入图片描述

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

答案:

在这里插入图片描述

 ( 5)试对图 6.36 所示的 AOE- 网:
① 求 这 个 工 程 最早 可 能 在 什么时间结束;
②求每个活动的最早开始时间和最迟开始时间;
③确定哪些活动是关键活动

在这里插入图片描述

 答案:按拓扑有序的顺序计算各个顶点的最早可能开始时间 Ve 和最迟允许开始时间 Vl 。然后再计算各个活动的最早可能开始时间 e 和最迟允许开始时间 l ,根据 l-e = 0? 来确定关键活动,从而确定关键路径。在这里插入图片描述

 3、算法设计题

( 1)分别以邻接矩阵和邻接表作为存储结构,实现以下图的基本操作:
①增加一个新顶点 v, InsertVex(G, v) ;
②删除顶点 v 及其相关的边, DeleteVex(G , v);
③增加一条边 <v , w>, InsertArc(G , v, w);
④删除一条边 <v , w>, DeleteArc(G , v, w)

【算法描述】

假设图 G 为有向无权图,以邻接矩阵作为存储结构四个算法分别如下:
①增加一个新顶点 v

Status Insert_Vex(MGraph &G, char v)// 在邻接矩阵表示的图 G 上插入顶点 v 
{ 
	if(G.vexnum+1)>MAX_VERTEX_NUM return INFEASIBLE; 
	G.vexs[++G.vexnum]=v; 
	return OK; 
}//Insert_Vex 
	

②删除顶点 v 及其相关的边

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 

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

③增加一条边 <v , w>

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 

④删除一条边 <v , w>

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=new ArcNode; 
	p->adjvex=j;p->nextarc=NULL; 
	if(!G.vertices.firstarc) G.vertices.firstarc=p; 
	else 
	{ 
		for(q=G.vertices.firstarc;q->q->nextarc;q=q->nextarc) 
			if(q->adjvex==j) return ERROR; // 边已经存在
		q->nextarc=p; 
	} 
	G.arcnum++; 
	return OK; 
}//Insert_Arc 

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

【算法描述】

Void DFSn(Graph G,int v) 
{ // 从第 v 个顶点出发非递归实现深度优先遍历图 G 
	Stack s; 
	SetEmpty(s); 
	Push(s,v); 
	While(!StackEmpty(s)) 
	{ //栈空时第 v 个顶点所在的连通分量已遍历完
		Pop(s,k); 
		if(!visited[k]) 
		{
			visited[k]=TRUE; 
			VisitFunc(k); // 访问第 k 个顶点
			// 将第 k 个顶点的所有邻接点进栈
			for(w=FirstAdjVex(G,k);w;w=NextAdjVex(G,k,w)) 
			{ 
				if(!visited[w]&&w!=GetTop(s)) Push(s,w); // 图中有环时 w==GetTop(s) 
			} 
		}
	} 
} 

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

【题目分析】利用 Dijkstra 算法求 v0 到其它所有顶点的最短路径,分别保存在数组 D[i] 中,然后求出D[i] 中值最大的数组下标 m 即可。

【算法描述】

int ShortestPath _ MAX(AMGraph G,int v0){ 
//用 Dijkstra 算法求距离顶点 v0 的最短路径长度最大的一个顶点 m 
	n=G.vexnum; //n 为 G 中顶点的个数
	for(v = 0; v<n;++v)
	{ //n 个顶点依次初始化
		S[v] = false; //S 初始为空集
		D[v] = G.arcs[v0][v]; //将 v0 到各个终点的最短路径长度初始化
		if(D[v]< MaxInt) Path [v]=v0; // 如果 v0 和 v 之间有弧,则将 v 的前驱置为 v0 
		else Path [v]=-1; //如果 v0 和 v 之间无弧,则将 v 的前驱置为 -1 
	}//for 
	S[v0]=true; //将 v0 加入 S 
	D[v0]=0; // 源点到源点的距离为 0 
	/* 开始主循环,每次求得 v0 到某个顶点 v 的最短路径,将 v 加到 S 集 */ 
	for(i=1;i<n; ++i){ //对其余 n- 1 个顶点,依次进行计算
		min= MaxInt; 
		for(w=0;w<n; ++w) 
			if(!S[w]&&D[w]<min) 
				{v=w; min=D[w];} //选择一条当前的最短路径,终点为 v 
		S[v]=true; //将 v 加入 S 
		for(w=0;w<n; ++w) // 更新从 v0 到 V- S 上所有顶点的最短路径长度
			if(!S[w]&&(D[v]+G.arcs[v][w]<D[w])){ 
				D[w]=D[v]+G.arcs[v][w]; //更新 D[w] 
				Path [w]=v; //更改 w 的前驱为 v 
			}//if 
	}//for 
	/* 最短路径求解完毕,设距离顶点 v0 的最短路径长度最大的一个顶点为 m */ 
	Max=D[0]; 
	m=0; 
	for(i=1;i<n;i++) 
		if(Max<D[i]) m=i; 
	return m; 
} 

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

【题目分析】引入一变量 level 来控制递归进行的层数

【算法描述】

int visited[MAXSIZE]; // 指示顶点是否在当前路径上
int level = 1;// 递归进行的层数
int exist_path_DFS(ALGraph G,int i,int j)// 深度优先判断有向图 G 中顶点 i 到顶点 j 是否有路径 ,是则返回 1,否则返回 0 
{ 
	if(i==j) return 1; //i 就是 j 
	else 
	{ 
		visited[i]=1; 
		for(p=G.vertices[i].firstarc;p;p=p->nextarc , level--) 
		{
			level++; 
			k=p->adjvex; 
			if(!visited[k]&&exist_path(k,j)) return 1;//i 下游的顶点到 j 有路径
		}//for 
	}//else 
	if (level==1) return 0; 
}//exist_path_DFS 

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

int visited[MAXSIZE]; 
int exist_path_len(ALGraph G,int i,int j,int k) 
//判断邻接表方式存储的有向图 G 的顶点 i 到 j 是否存在长度为 k 的简单路径
{
	if(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

第七章 查找

 1、选择题

(1)如果要求一个线性表既能较快的查找,又能适应动态变化的要求,最好采用 ( )查找法。
    A .顺序查找     B.折半查找
    C.分块查找      D.哈希查找

答案: C
解释: 分块查找的优点是: 在表中插入和删除数据元素时, 只要找到该元素对应的块,就可以在该块内进行插入和删除运算。由于块内是无序的,故插入和删除比较容易,无需进行大量移动。如果线性表既要快速查找又经常动态变化,则可采用分块查找。

(2)对 22 个记录的有序表作折半查找,当查找失败时,至少需要比较()次关键字。
    A.3    B.4    C.5    D.6

答案: B
解释: 22 个记录的有序表,其折半查找的判定树深度为 log22 + 1=5 ,且该判定树不是满二叉树,即查找失败时至多比较 5 次,至少比较 4 次。

(3)折半搜索与二叉排序树的时间性能() 。
    A.相同
    B.完全不同
    C.有时不相同
    D.数量级都是 O(log 2n)

答案:C

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

答案: C

(5)下列关于 m 阶 B- 树的说法错误的是() 。
A.根结点至多有 m 棵子树
B.所有叶子都在同一层次上
C.非叶结点至少有 m/2 (m 为偶数 )或 m/2+1 ( m 为奇数)棵子树
D.根结点中的数据是有序的

答案:C

(6)下面关于 B- 和 B+ 树的叙述中,不正确的是() 。
A. B- 树和 B+ 树都是平衡的多叉树
B. B- 树和 B+ 树都可用于文件的索引结构
C. B- 树和 B+ 树都能有效地支持顺序检索
D. B- 树和 B+ 树都能有效地支持随机检索

答案:C

(7) m 阶 B- 树是一棵() 。
A. m 叉排序树
B. m 叉平衡排序树
C. m-1 叉平衡排序树
D. m+1 叉平衡排序树

答案:B

(8)下面关于哈希查找的说法,正确的是() 。
A.哈希函数构造的越复杂越好,因为这样随机性好,冲突小
B.除留余数法是所有哈希函数中最好的
C.不存在特别好与坏的哈希函数,要视情况而定
D.哈希表的平均查找长度有时也和记录总数有关

答案:C

(9)下面关于哈希查找的说法,不正确的是() 。
A.采用链地址法处理冲突时,查找一个元素的时间是相同的
B.采用链地址法处理冲突时,若插入规定总是在链首,则插入任一个元素的时间是相同的
C.用链地址法处理冲突,不会引起二次聚集现象
D.用链地址法处理冲突,适合表长不确定的情况

答案:A

解释:在同义词构成的单链表中,查找该单链表表中不同元素,所消耗的时间不同。

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

答案: D
解释:关键字 15 放入位置 4,关键字 38 放入位置 5,关键字 61 放入位置 6,关键字 84放入位置 7,再添加关键字 49,计算得到地址为 5,冲突,用二次探测法解决冲突得到新地址为 6,仍冲突,再用用二次探测法解决冲突,得到新地址为 4,仍冲突,再用用二次探测法解决冲突,得到新地址为 9,不冲突,即将关键字 49 放入位置 9。

(11)采用线性探测法处理冲突,可能要探测多个位置,在查找成功的情况下,所探测的这
些位置上的关键字 ( )。
A.不一定都是同义词
B.一定都是同义词
C.一定都不是同义词
D.都相同

答案: A
解释:所探测的这些关键字可能是在处理其它关键字冲突过程中放入该位置的。

2、应用题

( 1)假定对有序表: ( 3, 4,5, 7, 24 , 30, 42,54 ,63, 72, 87, 95)进行折半查找,试回答下列问题:
①画出描述折半查找过程的判定树;
②若查找元素 54,需依次与哪些元素比较?
③若查找元素 90,需依次与哪些元素比较?
④假定每个元素的查找概率相等,求查找成功时的平均查找长度。

答案:

①判定树如下(注(1+12)/2=6):

②查找元素54,需要依次与30,63,42,54元素比较;

③查找元素90,需要依次与30,63,87,95元素比较;

④求ASL之前,需要统计每个元素的查找次数。判定树的前三层共查找1+2*2+4*3=17次;但最后一层未满,不能用8*4,只能用5*4=20次,所以ASL=(17+20)/12≈3.08

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

答案:

 ( 3)已知如下所示长度为 12 的表:( Jan, Feb, Mar, Apr, May, June, July, Aug, Sep, Oct, Nov, Dec )
①试按表中元素的顺序依次插入一棵初始为空的二叉排序树,画出插入完成之后的二叉排序树,并求其在等概率的情况下查找成功的平均查找长度。
②若对表中元素先进行排序构成有序表,求在等概率的情况下对此有序表进行折半查找时查找成功的平均查找长度。
③按表中元素顺序构造一棵平衡二叉排序树,并求其在等概率的情况下查找成功的平均查找长度。

答案:

在这里插入图片描述

 在这里插入图片描述

( 4)对图 7.31 所示的 3 阶 B- 树,依次执行下列操作,画出各步操作的结果。
①插入90 ② 插入25 ③ 插入45 ④删除60

在这里插入图片描述

 答案:

在这里插入图片描述

 在这里插入图片描述

 ( 5)设哈希表的地址范围为 0~ 17 ,哈希函数为: H ( key ) =key%16 。用线性探测法处理冲突,输入关键字序列: ( 10 , 24 , 32, 17 , 31 , 30, 46 , 47 , 40, 63 , 49),构造哈希表,试回答下列问题:
①画出哈希表的示意图;
②若查找关键字 63 ,需要依次与哪些关键字进行比较?
③若查找关键字 60 ,需要依次与哪些关键字比较?
④假定每个关键字的查找概率相等,求查找成功时的平均查找长度。

答案:

①画表: 在这里插入图片描述

②查找63,首先要与H (63)=63%16=15 号单元内容比较,即 63 与 31 比较 , 不匹配 ;然后顺移,与 46,47,32,17,63 相比,一共比较了 6 次!

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

④对于黑色数据元素,各比较 1 次;共 6 次;对红色元素则各不相同,要统计移位的位数。 “ 63 ”需要 6 次,“ 49”需要 3 次,“ 40”需要 2 次,“ 46 ”需要 3 次,“ 47 ”需要 3 次,
所以 ASL=1/11 ( 6+ 2+ 3×3+6 )= 23/11

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

答案:

在这里插入图片描述

 平均查找长度: ASLsucc =(1+1+1+2+3+4+1+2 ) /8=15/8
以关键字 27 为例: H( 27 ) =27%7=6(冲突) H 1=( 6+1 ) %10=7(冲突)H2=( 6+2*2) %10=0(冲突) H 3=( 6+3*3) %10=5 所以比较了 4 次。

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

答案:

在这里插入图片描述

 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 = ( 15+23 ) /8=11/8
ASLunsucc=(1+2+1+2+3+1+3+1+3+1+1 ) /11=19/11

3、算法设计题

( 1)试写出折半查找的递归算法。

【算法描述】

int BinSrch(rectype r[ ],int k ,low ,high )
// 在长为 n 的有序表中查找关键字 k,若查找成功,返回 k 所在位置,查找失败返回 0。
{
	if(low ≤ high)//low 和 high 分别是有序表的下界和上界
	{
		mid= (low+high) /2 ;
		if(r[mid].key==k) return mid;
		else if(r[mid].key>k) return (BinSrch(r,k ,mid+1,high)); 
		else return BinSrch(r,k,low,mid-1)); 
	} 
	else return 0; // 查找失败。
}// 算法结束

( 2)试写一个判别给定二叉树是否为二叉排序树的算法。

【题目分析】根据二叉排序树中序遍历所得结点值为增序的性质,在遍历中将当前遍历结点与其前驱结点值比较,即可得出结论,为此设全局指针变量pre(初始值为NULL)和全局变量flag,初始值为true。若非二叉排序树,则置flag为false。

【算法描述】

#define true 1 
#define false 0 
typedef struct node 
{datatype data; struct node *lchild,*rchild;} *BTree; 
void JudgeBST (BTree T,int flag )
// 判断二叉树是否是二叉排序树,本算法结束后,在调用程序中由 flag 得出结论。
{ 
	if ( T!=null && flag )
	{ 
		 Judgebst (T->lchild,flag ); // 中序遍历左子树
		 if (pre==NULL) pre=T ; // 中序遍历的第一个结点不必判断
		 else if (pre->data<T->data) pre=T ; // 前驱指针指向当前结点
		 else{flag=flase ;
	} // 不是完全二叉树
	Judgebst (T->rchild,flag); // 中序遍历右子树
}//JudgeBST 算法结束

( 3) 已 知 二 叉 排 序 树 采 用 二 叉 链 表 存 储 结 构 , 根 结 点 的 指 针 为 T , 链 结 点 的 结 构 为 ( lchild,data,rchild ),其中 lchild , rchild 分别指向该结点左、右孩子的指针, data 域存放结点的数据信息。 请写出递归算法, 从小到大输出二叉排序树中所有数据值 >=x 的结点的数据。要求先找到第一个满足条件的结点后,再依次输出其他满足条件的结点。

【题目分析】( 3) 已 知 二 叉 排 序 树 采 用 二 叉 链 表 存 储 结 构 , 根 结 点 的 指 针 为 T , 链 结 点 的 结 构 为 ( lchild,data,rchild ),其中 lchild , rchild 分别指向该结点左、右孩子的指针, data 域存放结点的数据信息。 请写出递归算法, 从小到大输出二叉排序树中所有数据值 >=x 的结点的数据。要求先找到第一个满足条件的结点后,再依次输出其他满足条件的结点。

void Print (BSTree t)
 // 中序输出以 t 为根的二叉排序树的结点
 { 
	if (t) 
	{
		Print (t->lchild);
		Cout<<t-data ;
		Print (t->rchild);
 	} 
 } 
void PrintAllx(BSTree bst ,datatype x)
 // 在二叉排序树 bst 中,查找值≥ x 的结点并输出
{
	p=bst;
	if (p)
	{ 
		 while (p && p->data<x) p=p->rchild; // 沿右分枝找第一个值≥ x 的结点
		 bst=p; //bst 所指结点是值≥ x 的结点的树的根
		if (p)
		{
			 f=p; p=p->lchild ; // 找第一个值 <x 的结点
			while (p && p->data ≥ x) // 沿左分枝向下,找第一个值 <x 的结点
			{
				f=p;  p=p->lchild; } //f 是 p 的双亲结点的指针,指向第一个值≥ x 的结点
				if (p) f->lchild=null; // 双亲与找到的第一个值 <x 的结点断开
				Print(bst);  // 输出以 bst 为根的子树
			 }// while
		 }// 内层 if ( p)
	 }// 第一层 if ( p)
 }

( 4)已知二叉树 T 的结点形式为( lling,data,count,rlink ),在树中查找值为 X 的结点,若找到,则记数( count )加 1,否则,作为一个新结点插入树中,插入后仍为二叉排序树,写出其非递归算法

【算法描述】

void SearchBST(BiTree &T,int target){ 
	BiTree s,q,f;// 以数据值 target, 新建结点 s 
	s=new BiTNode; 
	s->data.x=target; 
	s->data.count=0; 
	s->lchild=s->rchild=NULL; 
	if(!T){ 
		T=s; 
		return ; 
	} //如果该树为空则跳出该函数
	f=NULL; 
	q=T; 
	while (q){ 
		if (q->data.x==target){ 
			q->data.count++; 
			return ; 
		} // 如果找到该值则计数加一
		f=q; 
		if (q->data.x>target) // 如果查找值比目标值大,则为该树左孩子
			q=q->lchild; 
		else // 否则为右孩子
			q=q->rchild; 
		}// 将新结点插入树中
	if(f->data.x>target) 
		f->lchild=s; 
	else 
		f->rchild=s; 
}

( 5)假设一棵平衡二叉树的每个结点都表明了平衡因子 b,试设计一个算法,求平衡二叉树的高度。

【题目分析】因为二叉树各结点已标明了平衡因子 b,故从根结点开始记树的层次。根结点的层次为 1,每下一层,层次加 1,直到层数最大的叶子结点,这就是平衡二叉树的高度。当结点的平衡因子 b 为 0 时,任选左右一分枝向下查找,若 b 不为 0,则沿左(当 b=1 时)或右(当 b=-1 时)向下查找。

【算法描述】

int Height (BSTree t)
 // 求平衡二叉树 t 的高度
{
	level=0; p=t;
	while (p)
	{
		level++; // 树的高度增 1 
		if (p->bf<0) p=p->rchild; //bf=-1 沿右分枝向下
		//bf 是平衡因子,是二叉树 t 结点的一个域,因篇幅所限,没有写出其存储定义
		else p=p->lchild; //bf>=0 沿左分枝向下
	}// while
	return level; // 平衡二叉树的高度
} // 算法结束

( 6)分别写出在散列表中插入和删除关键字为 K 的一个记录的算法,设散列函数为 H,解决冲突的方法为链地址法。

【算法描述】

bool insert(){ 
	int data; 
	cin>>data; 
	int ant=hash(data); 
	LinkList p=HT[ant]; // 初始化散列表
	while (p->next){ 
		if(p->next->data==data) 
			return false; 
		p=p->next; 
	} //找到插入位置
	LinkList s; 
	s=new LNode; 
	s->data=data; 
	s->next=p->next; 
	p->next=s; //插入该结点
	return true; 
} 
bool deletes(){ 
	int data; 
	cin>>data; 
	int ant=hash(data); 
	LinkList p=HT[ant]; // 初始化散列表
	while (p->next){ 
		if(p->next->data==data){ 
			LinkList s=p->next; 
			p->next=s->next; 
			delete s; //删除该结点
			return true; 
		} // 找到删除位置
		p=p->next ; // 遍历下一个结点
	} 
	return false; 
}

 第八章 排序

1、选择题

(1)堆是一种()排序。
A.插入    B.选择    C.交换    D.归并

答案:B

(2)若一组记录的排序码为( 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

答案: B

解释:从底层到顶层依次调整元素位置。

(3)下述几种排序方法中,要求内存最大的是() 。
A.希尔排序    B.快速排序    C.归并排序    D.堆排序

答案: C
解释:堆排序、希尔排序的空间复杂度为 O(1) ,快速排序的空间复杂度为 O(log 2n),
归并排序的空间复杂度为 O(n) 。

(4)下述几种排序方法中, ()是稳定的排序方法。
A.希尔排序    B.快速排序    C.归并排序    D.堆排序

答案: C
解释:不稳定排序有希尔排序、简单选择排序、快速排序、堆排序(快些选一堆朋友来玩);稳定排序有直接插入排序、折半插入排序、冒泡排序、归并排序、基数排序。

(5)下列排序算法中, ()不能保证每趟排序至少能将一个元素放到其最终的位置上。
A.希尔排序 B .快速排序 C.冒泡排序 D.堆排序

答案: A
解释:快速排序的每趟排序能将作为枢轴的元素放到最终位置;冒泡排序的每趟排序能将最大或最小的元素放到最终位置;堆排序的每趟排序能将最大或最小的元素放到最终位置。

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]

⑤快速排序
12         2         16          30          28        10         16*        20         6            18

6           2         10          [12]        28         30        16*        20         16          18

2          [6]        10          [12]        18         16        16*        20         [28]        30

[2]        [6]        [10 ]       [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]

⑥简单选择排序
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

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

答案:
按最低位优先法→ 321 → 156→ 57→ 46 → 28 → 7→ 331→ 33 → 34→ 63
分配         [0]         [1]         [2]         [3]         [4]         [5]         [6]         [7]         [8]         [9]
                321                                 33         34                     156        57         28
                331                                 63                                   46          7
收集→ 321 → 331 → 33→ 63→ 34 → 156 → 46 → 57→ 7→ 28

                [0]         [1]         [2]         [3]         [4]         [5]         [6]         [7]         [8]         [9]
                 7                      321       331        46        156        63        

                                          28         33                      57

                                                       34

收集→ 7 → 321 → 28 → 331 → 33 → 34 → 46 → 156 → 57 → 63

               [0]         [1]         [2]         [3]         [4]         [5]         [6]         [7]         [8]         [9]

                7         156                     321

                28                                  331

                33

                34

                46

                57

                63

收集→ 7 → 28 → 33 → 34 → 46 → 57 → 63 → 156 → 321 → 331

( 3)对输入文件( 101 , 51 , 19, 61 , 3, 71 , 31, 17, 19 , 100, 55 , 20, 9, 30 , 50 , 6, 90 );当 k=6 时,使用置换 - 选择算法,写出建立的初始败者树及生成的初始归并段。

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

typedefstruct 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个指针 i , j 和 k,分别指向红色、白色砾石的后一位置和待处理的当前元素。从 k=n 开始,从右向左搜索,若该元素是兰色, 则元素不动, 指针左移 (即 k-1 );若当前元素是红色砾石,分 i>=j (这时尚没有白色砾石)和 i<j 两种情况。前一情况执行第 i 个元素和第 k 个元素交换,之后 i+1 ;后一况, i 所指的元素已处理过(白色) ,j 所指的元素尚未处理,应先将 i 和 j 所指元素交换,再将 i 和 k所指元素换。对当前元素是白色砾石的情况,也可类似处理。为方便处理,将三种砾石的颜色用整数 1、 2 和 3 表示。

【算法描述】

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;
	 		} 
	//i 、 j 分别指向红、白色砾石的后一位置
	 	}//while 
	 	if (r[k]==2) j++; /* 处理最后一粒砾石
	 	else if (r[k]==1) { temp=r[j];r[j]=r[i];r[i]=temp; i++; j++; } 
	 // 最后红、白、兰色砾石的个数分别为 : i-1;j-i;n-j+1 
}// 结束 QkSor 算法

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

①采用顺序存储结构,至多使用一个记录的辅助存储空间;
②算法的时间复杂度为 O(n)

void process(int A[n])
{
    low=0;
    high=n-1;
    while(low<high){
        while(low<high && A[low]<0)
            low++;
        while(low<high && A[low]>0)
            high++;
        if(low<high){
            x=A[loq];
            A[low]=A[high];
            A[high]=x;
            low++;
            high--;
        }
    }
    return ;
}

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

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

【算法描述】

int index(RecType R[],int l,int 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[j].key<key)
            i++;
        if(R[j].key==key) return i;
    }
    cout<<"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 

③ 对于有 n 个记录的表,关键码比较 n2 次。
④ 简单选择排序算法比本算法好。简单选择排序比较次数是 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++;

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值