数据结构(C语言版 第2版严蔚敏)课后全部习题答案(完整版)

目录

第一章 绪论

第二章 线性表

第三章 栈和队列

第四章 串、数组和广义表

第五章 树和二叉树

第六章 图

第七章 查找

第八章 排序


第一章 绪论

1. 简述下列术语:数据、数据元素、数据对象、数据结构、存储结构、数据类型和抽象数据类型

【解答】

        (1)数据:是客观事物的符号表示,是所有能输入到计算机中并被计算机程序处理的符号的总称。

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

        (3)数据项:是组成数据元素的、有独立含义的、不可分割的最小单位。例如,学生基本信息表中的学号、姓名、性别等都是数据项。

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

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

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

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

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

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

【解答】

        如一张学生信息表,其基本信息包括有学生的学号、姓名、性别、籍贯、专业等等。每个学生基本信息记录对应一个数据元素,学生记录按顺序号排列,形成了学生基本信息记录的线性序列。将这些学生记录在计算机中的存储表示就是存储结构。如果用连续的存储单元来存放这些记录,则称为顺序存储结构;如果存储单元不连续,而是随机存放各个记录,然后用指针进行链接,则称为链式存储结构。

即相同的逻辑结构,可以对应不同的存储结构。

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

【解答】

(1)集合结构

        数据元素之间除了“属于同一集合”的关系外,别无其他关系。

(2)线性结构

        数据元素之间存在一对一的关系。

(3)树结构

        数据元素之间存在一对多的关系。

(4)图结构或网状结构

        数据元素之间存在多对多的关系。

        

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

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

【解答】

(1)顺序存储结构

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

(2)链式存储结构

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

5. 选择题

(1)在数据结构中,从逻辑上可以把数据结构分成(   )。
          A. 动态结构和静态结构            B. 紧凑结构和非紧凑结构
          C. 线性结构和非线性结构        D. 内部结构和外部结构

【解答】C

(2)与数据元素本身的形式、内容、相对位置、个数无关的是数据的( )。
          A. 存储结构         B. 存储实现
          C. 逻辑结构         D. 运算实现

【解答】

(3)通常要求同一逻辑结构中的所有数据元素具有相同的特性,这意味着(   )。
          A. 数据具有同一特点
          B. 不仅数据元素所包含的数据项的个数要相同,而且对应数据项的类型要一致
          C. 每个数据元素都一样
          D. 数据元素所包含的数据项的个数要相等

【解答】

(4)以下说法正确的是(    )。
          A. 数据元素是数据的最小单位
          B. 数据项是数据的基本单位
          C. 数据结构是带有结构的各数据项的集合
          D. 一些表面上很不相同的数据可以有相同的逻辑结构

【解答】

        数据元素使数据的基本单位
        数据项是组成数据元素的最小单位
        数据结构是带有结构的数据元素的集合。

(5)算法的时间复杂度取决于(   )。
          A. 问题的规模
          B. 待处理数据的初态
          C. 计算机的配置
          D.  A 和 B

【解答】

(6)以下数据结构中,(   )是非线性数据结构。
          A. 树        B. 字符串        C. 队列        D. 栈

【解答】

 6. 试分析下列各算法的时间复杂度。

(1)

x = 90; y = 100; 
while(y > 0) 
	if(x > 100) 
		{x = x - 10; y--;} 
else x++; 

【解答】O(1)

        该算法的执行时间不随问题规模 n 的增长而增长。

(2)

for(i = 0; i < n; i++) 
	for(j = 0; j < m; j++) 
		a[i][j] = 0; 

【解答】O(nm)

(3)

s = 0; 
for(i = 0; i < n; i++) 
	for(j = 0; j < n; j++) 
		s += B[i][j]; 
sum = s; 

【解答】O(n^{2})

(4)

i = 1; 
while(i <= n) 
	 i = i * 3; 

【解答】O(log_{3}^{n})
设循环体内基本语句的频度为f(n),所以3^{f(n)}<n,所以f(n)\leqslant log_{3}^{n},故时间复杂度为O(log_{3}^{n})
i = i * 3,所以 i 的取值都有:1、3、9、27......,相当于(3^{0},3^{1},3^{2},3^{3}......) 。

(5)

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

【解答】O(n^{2})

        当 i = 1 的时候,内层运行了 (n - 1) 次,
        当 i = 2 的时候,内层运行了 (n - 2) 次,
        当 i = 3 的时候,内层运行了 (n - 3) 次,
        .......
         当 i = n - 1(i != n)的时候,内层运行了(n - (n - 1)) 次
所以加起来就是 (n - 1) + (n - 2) + (n - 3) +.....+ 1 = (n - 1 + 1)(n - 1)/2 = (n^{2}-n)/2故时间复杂度为O(n^{2}).

(6)

x = n; //n >     1 
y = 0; 
while(x >= (y + 1) * (y + 1)) 
	y ++; 

【解答】O\sqrt{n}

        假设语句“y++”的执行次数为 f(n),所以x\geqslant (f(n)+1)^{2},又x = n,所以n\geqslant (f(n)+1)^{2},得出f(n)\leqslant \sqrt{n}-1,故时间复杂度为 O\sqrt{n}.

第二章 线性表

1. 选择题

(1)顺序表中第一个元素的存储地址是100,每 个元素的长度为2,则第5个元素的地址是( )。
          A. 110        B. 108        C. 100        D. 120

【解答】 B
        顺序表中的数据连续存储,LOC(a_{i}) = LOC(a_{1}) + (i - 1) * l

        所以第 5 个元素的地址为: 100 + (5 -1) * 2= 108 。

(2)在n个节点的顺序表中,算法的时间复杂度是 O(1) 的操作是(   )。
          A. 访问第 i 个节点(1\leqslant i\leqslant n)和求第 i 个节点的直接前驱(2\leqslant i\leqslant n
          B. 在第 i 个节点后插入一个新节点(1\leqslant i\leqslant n
          C. 删除第 i 个节点(1\leqslant i\leqslant n
          D. 将 n 个节点从小到大排序

【解答】 A
        顺序表是一种随机存取结构,访问第 i 个节点和求第 i 个节点的直接前驱都可以直接通过数组的下标直接定位,时间复杂度是 O(1) 。在顺序表中插入和删除一个节点的时间复杂度都是 O(n^{2}),排序的时间复杂度为 O(n^{2})O(nlog_{2}^{n})。。

(3)向一个有 127 个元素的顺序表中插入一个新元素并保持原来顺序不变, 平均要移动的元素个数为(   )。
          A. 8        B. 63.5        C. 63        D. 7

【解答】B
        平均要移动约表中一半的元素 。

(4)链接存储的存储结构所占存储空间(   )。
          A. 分两部分,一部分存放节点值,另一部分存放表示节点间关系的指针
          B. 只有一部分,存放节点值
          C. 只有一部分,存储表示节点间关系的指针
          D. 分两部分,一部分存放节点值,另一部分存放节点所占单元数

【解答】A

(5)线性表若采用链式存储结构时,要求内存中可用存储单元的地址(   )。
          A. 必须是连续的                B. 部分地址必须是连续的
          C. 一定是不连续的            D. 连续或不连续都可以

【解答】D

(6)线性表L在(   )情况下适用于使用链式结构实现。
          A. 需经常修改L中的节点值         B. 需不断对L进行删除插入
          C. L中含有大量的节点                D. L中结点结构复杂

【解答】B
        链表的插入或删除操作无须移动数据,只需要直接修改指针。

(7)单链表的存储密度(   )。
          A. 大于 1        B. 等于 1        C. 小于 1        D. 不能确定

【解答】C
        链表的存储密度小于1,顺序表的存储密度等于1。

(8)将两个各有 n 个元素的有序表归并成一个有序表,其最少的比较次数是(   )。
          A. n        B. 2n-1        C. 2n        D. n-1

【解答】A
        假设这两个有序表对应的数组为 A[0, 1,...., n-1],B[0,1....., n-1],且假设A中数据都比B中的小,那么比较的顺序就为A中的元素从第一个开始依次与B中的第一个元素比较,即A[0] < B[0], A[1] < B[0], A[2] < B[0], ......., A[n-1] < B[0],所以比较的次数应该是n次。

        比如 表A(1, 2, 3) 与 表B(4, 5, 6) 合并,则1, 2, 3依次与4比较,比较3次然后合并。

(9)在一个长度为 n 的顺序表中,在第 i 个元素(1\leqslant i\leqslant n+1)之前插入一个新元素时
须向后移动(   )个元素。
          A. n - i        B. n - i + 1        C. n - i - 1        D. i

【解答】B

(10)线性表 L = ( a_{1}, a_{2},..., a_{n}),下列说法正确的是(   )。
          A. 每个元素都有一个直接前驱和一个直接后继
          B. 线性表中至少有一个元素
          C. 表中诸元素的排列必须是由小到大或由大到小
          D. 除第一个和最后一个元素外,其余每个元素都有一个且仅有一个直接前驱和直接后继

【解答】D

        A、第一个数据元素没有直接前驱,最后一个数据元素没有直接后继

        B、线性表可以为空

        C、线性表没有要求顺序

(11)创建一个包括 n 个节点的有序单链表的时间复杂度是(   )。
          A. O(1)        B. O(n)        C. O(n^{2})        D. O(nlog_{2}^{n})

【解答】C

        可以理解成将n个元素依次插入到空链表中,每一个元素插入需要遍历之前已经有序的链表,找到合适的位置,复杂度是O(n)。一共有n个元素,所以就是O(n^{2})。

        单链表创建的时间复杂度是 O(n) ,而要建立一个有序的单链表,则每生成一个新节点时需要和已有的节点进行比较,确定合适的插入位置,这里也是O(n),所以时间复杂度是O(n^{2}) 。

(12)以下说法错误的是(   )。
          A. 求表长、定位这两种运算在采用顺序存储结构时实现的效率不比采用链式存储结构时实现的效率低
          B. 顺序存储的线性表可以随机存取
          C. 由于顺序存储要求连续的存储区域,所以在存储管理上不够灵活
          D. 线性表的链式存储结构优于顺序存储结构

【解答】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;

【解答】D

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

【解答】A

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

【解答】C

2. 算法设计题

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

【算法描述】

void MergeList(LinkList &LA, LinkList &LB, LinkList &LC){
	LinkList pa = LA->next;               
	LinkList pb = LB->next;			//pa和pb的初值分别指向两个表的第一个结点                   
	LC = LA;                        //用La的头结点作为Lc的头结点
	LinkList pc = LC;               //pc的初值指向LC的头结点
    pc->next = NULL;
	LinkList q;
	while(pa && pb){ 				//两个链表LA和LB均未到达表尾结点
		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;            //将非空表的剩余元素直接链接在LC的最后面
	delete LB;                      //释放LB的头结点
}

【完整代码】

#include <stdio.h>
#define OK 1
using namespace std;
typedef int Status;
typedef int ElemType;

//单链表的存储结构
typedef struct LNode{
	ElemType data;	     
	struct LNode *next;	
}LNode, *LinkList;    
 
//初始化单链表 
Status InitList(LinkList &L){
	L = new LNode;     
	L->next = NULL;  
	return OK; 
} 

//创建单链表 
LinkList CreateList(LinkList &L){     
	int x, n, i;
    L = new LNode;
    LinkList r = L;
    printf("请输入单链表的长度: ", n);
    scanf("%d", &n);
    printf("请依次输入%d个数(空格隔开):",n);
    for(i = 0; i < n; i++){
        scanf("%d", &x);
        LinkList p = new LNode;
        p->data = x;
        p->next = NULL;
        r->next = p;
        r = r->next;		//或者 r = p;
    }
    return L;
}

//打印链表 
void PrintList(LinkList L){
	for(LinkList p = L->next; p != NULL; p = p->next){
		if(p->next == NULL)
			printf("%d", p->data);
		else
			printf("%d->", p->data);
	}
	printf("\n"); 
}

//合并链表 
void MergeList(LinkList &LA, LinkList &LB, LinkList &LC){
	LinkList pa = LA->next;               
	LinkList pb = LB->next;			//pa和pb的初值分别指向两个表的第一个结点                   
	LC = LA;                        //用La的头结点作为Lc的头结点
	LinkList pc = LC;               //pc的初值指向LC的头结点
    pc->next = NULL;
	LinkList q;
	while(pa && pb){ 				//两个链表LA和LB均未到达表尾结点
		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;            //将非空表的剩余元素直接链接在LC的最后面
	delete LB;                      //释放LB的头结点
}

int main(){	
	int n;
	LinkList LA, LB, LC; 
	InitList(LA);
	InitList(LB);
	InitList(LC);
	printf("=====对于单链表 LA ===== \n");
	CreateList(LA);
	printf("=====对于单链表 LB ===== \n");
	CreateList(LB);
	MergeList(LA, LB, LC);
	printf("合并表LA和表LB:");
	PrintList(LC);
	return 0; 
} 

【运行结果】

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

【算法描述】

void MergeList(LinkList &LA, LinkList &LB, LinkList &LC){
	LinkList pa = LA->next;
    LinkList pb = LB->next;			//pa和pb的初值分别指向两个表的第一个结点 
    LC = new LNode;  				//为LC创建新的头节点
    LC->next = NULL;
    LinkList pc = LC;
    while(pa || pb){
        if(pa == NULL){			
			//如果LA已遍历完,将LB的元素依次插入到LC头部
            LinkList temp = pb;
            pb = pb->next;
            temp->next = pc->next;
            pc->next = temp;
        }
        else if(pb == NULL){		
			//如果LB已遍历完,将LA的元素依次插入到LC头部
            LinkList temp = pa;
            pa = pa->next;
            temp->next = pc->next;
            pc->next = temp;
        }
        else if(pa->data <= pb->data){	
			//取LA元素,将其插入到LC头部
            LinkList temp = pa;
            pa = pa->next;
            temp->next = pc->next;
            pc->next = temp;
        }
        else{						
			//取LB元素,将其插入到LC头部
            LinkList temp = pb;
            pb = pb->next;
            temp->next = pc->next;
            pc->next = temp;
        }
    }
    //释放LA和LB的头节点
    delete LA;
    delete LB;
}

【完整代码】

#include <stdio.h>
#define OK 1
using namespace std;
typedef int Status;
typedef int ElemType;

//单链表的存储结构
typedef struct LNode{
	ElemType data;	     
	struct LNode *next;	
}LNode, *LinkList;    
 
//初始化单链表 
Status InitList(LinkList &L){
	L = new LNode;     
	L->next = NULL;  
	return OK; 
} 

//创建单链表 
LinkList CreateList(LinkList &L){     
	int x, n, i;
    L = new LNode;
    LinkList r = L;
    printf("请输入单链表的长度: ", n);
    scanf("%d", &n);
    printf("请依次输入%d个数(空格隔开):",n);
    for(i = 0; i < n; i++){
        scanf("%d", &x);
        LinkList p = new LNode;
        p->data = x;
        p->next = NULL;
        r->next = p;
        r = r->next;		//或者 r = p;
    }
    return L;
}

//打印链表 
void PrintList(LinkList L){
	for(LinkList p = L->next; p != NULL; p = p->next){
		if(p->next == NULL)
			printf("%d", p->data);
		else
			printf("%d->", p->data);
	}
	printf("\n"); 
}

//合并链表 
void MergeList(LinkList &LA, LinkList &LB, LinkList &LC){
	LinkList pa = LA->next;
    LinkList pb = LB->next;			//pa和pb的初值分别指向两个表的第一个结点 
    LC = new LNode;  				//为LC创建新的头节点
    LC->next = NULL;
    LinkList pc = LC;
    while(pa || pb){
        if(pa == NULL){			
			//如果LA已遍历完,将LB的元素依次插入到LC头部
            LinkList temp = pb;
            pb = pb->next;
            temp->next = pc->next;
            pc->next = temp;
        }
        else if(pb == NULL){		
			//如果LB已遍历完,将LA的元素依次插入到LC头部
            LinkList temp = pa;
            pa = pa->next;
            temp->next = pc->next;
            pc->next = temp;
        }
        else if(pa->data <= pb->data){	
			//取LA元素,将其插入到LC头部
            LinkList temp = pa;
            pa = pa->next;
            temp->next = pc->next;
            pc->next = temp;
        }
        else{						
			//取LB元素,将其插入到LC头部
            LinkList temp = pb;
            pb = pb->next;
            temp->next = pc->next;
            pc->next = temp;
        }
    }
    //释放LA和LB的头节点
    delete LA;
    delete LB;
}

int main(){	
	int n;
	LinkList LA, LB, LC; 
	InitList(LA);
	InitList(LB);
	InitList(LC);
	printf("=====对于单链表 LA ===== \n");
	CreateList(LA);
	printf("=====对于单链表 LB ===== \n");
	CreateList(LB);
	MergeList(LA, LB, LC);
	printf("合并表LA和表LB:");
	PrintList(LC);
	return 0; 
} 

【运算结果】

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

【算法描述】

 void Mix(LinkList &LA, LinkList &LB, LinkList &LC){
	
	LinkList pa = LA->next; 
	LinkList pb = LB->next;			//pa和pb的初值分别指向两个表的第一个结点
	LC = LA;	                    //用LA的头结点作为LC的头结点
	LinkList pc = LC;               //pc的初值指向LC的头结点
	LinkList q;
	while(pa && pb){ 				//两个链表LA和LB均未到达表尾结点
		if(pa->data == pb->data){
			//取LA中的元素,将pa链接在pc的后面,pa指针后移 
			pc->next = pa;
			pc = pa;
			pa = pa->next;
			//删除LB中对应的相等元素
			q = pb;
			pb = pb->next;
			delete q; 
		}
		else if(pa->data < pb->data){			//删除较小者LA中的元素
			q = pa;
			pa = pa->next;
			delete q; 
		}
		else{									//删除较小者LB中的元素
			q = pb;
			pb = pb->next;
			delete q;
		}		
	}
	while(pa){									//LB为空,删除非空表LA中的所有元素
		q = pa;
		pa = pa->next;
		delete q; 
	}
	while(pb){									//LA为空,删除非空表LB中的所有元素
		q = pb;
		pb = pb->next;
		delete q; 
	}
	pc->next == NULL;							//置链表LC尾标记 
	delete LB;									//释放LB的头结点 		
}

【完整代码】

#include <stdio.h>
#define OK 1
using namespace std;
typedef int Status;
typedef int ElemType;

//单链表的存储结构
typedef struct LNode{
	ElemType data;	     
	struct LNode *next;	
}LNode, *LinkList;    
 
//初始化单链表 
Status InitList(LinkList &L){
	L = new LNode;     
	L->next = NULL;  
	return OK; 
} 

//创建单链表 
LinkList CreateList(LinkList &L){     
	int x, n, i;
    L = new LNode;
    LinkList r = L;
    printf("请输入单链表的长度: ", n);
    scanf("%d", &n);
    printf("请依次输入%d个数(空格隔开):",n);
    for(i = 0; i < n; i++){
        scanf("%d", &x);
        LinkList p = new LNode;
        p->data = x;
        p->next = NULL;
        r->next = p;
        r = r->next;		//或者 r = p;
    }
    return L;
}

//打印链表 
void PrintList(LinkList L){
	for(LinkList p = L->next; p != NULL; p = p->next){
		if(p->next == NULL)
			printf("%d", p->data);
		else
			printf("%d->", p->data);
	}
	printf("\n"); 
}
 
//求两表的交集 
void Mix(LinkList &LA, LinkList &LB, LinkList &LC){
	LinkList pa = LA->next; 
	LinkList pb = LB->next;			//pa和pb的初值分别指向两个表的第一个结点
	LC = LA;	                    //用LA的头结点作为LC的头结点
	LinkList pc = LC;               //pc的初值指向LC的头结点
	LinkList q;
	while(pa && pb){ 				//两个链表LA和LB均未到达表尾结点
		if(pa->data == pb->data){
			//取LA中的元素,将pa链接在pc的后面,pa指针后移 
			pc->next = pa;
			pc = pa;
			pa = pa->next;
			//删除LB中对应的相等元素
			q = pb;
			pb = pb->next;
			delete q; 
		}
		else if(pa->data < pb->data){			//删除较小者LA中的元素
			q = pa;
			pa = pa->next;
			delete q; 
		}
		else{									//删除较小者LB中的元素
			q = pb;
			pb = pb->next;
			delete q;
		}		
	}
	while(pa){									//LB为空,删除非空表LA中的所有元素
		q = pa;
		pa = pa->next;
		delete q; 
	}
	while(pb){									//LA为空,删除非空表LB中的所有元素
		q = pb;
		pb = pb->next;
		delete q; 
	}
	pc->next == NULL;							//置链表LC尾标记 
	delete LB;									//释放LB的头结点 		
} 

int main(){	
	int n;
	LinkList LA, LB, LC; 
	InitList(LA);
	InitList(LB);
	InitList(LC);
	printf("=====对于单链表 LA ===== \n");
	CreateList(LA);
	printf("=====对于单链表 LB ===== \n");
	CreateList(LB);
	Mix(LA, LB, LC);
	printf("LA和LB的交集为:");
	PrintList(LC);
    return 0;
} 

【运行结果】

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

【算法描述】

int DifferenceSet(LinkList &LA, LinkList &LB, LinkList &LC){
    InitList(LC);
    LinkList pa = LA->next;
    LinkList pc = LC;			//pc指针指向LC的头节点
    int count = 0;				//用于存储差集中元素的个数 
    while(pa){
        LinkList pb = LB->next;
        int found = 0;			//found标志,用于标记是否在LB中找到与p所指元素相同的元素
        while(pb){
            if(pa->data == pb->data){
                found = 1;
                break;
            }
            pb = pb->next;		//将pb指针后移,继续比较下一个元素
        }
        //如果在LB中没有找到与pa所指元素相同的元素
        if(!found){
//			LinkList temp = new LNode; 
            LinkList temp = (LinkList)malloc(sizeof(LNode));
            temp->data = pa->data;
            temp->next = NULL;
            pc->next = temp;
            pc = temp;
            count++;
        }
        pa = pa->next;
    }
    return count;
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#define OK 1
using namespace std;
typedef int Status;
typedef int ElemType;

//单链表的存储结构
typedef struct LNode{
	ElemType data;	     
	struct LNode *next;	
}LNode, *LinkList;    
 
//初始化单链表 
Status InitList(LinkList &L){
	L = new LNode;     
	L->next = NULL;  
	return OK; 
}

//创建单链表 
LinkList CreateList(LinkList &L){     
	int x, n, i;
    L = new LNode;
    LinkList r = L;
    printf("请输入单链表的长度: ", n);
    scanf("%d", &n);
    printf("请依次输入%d个数(空格隔开):",n);
    for(i = 0; i < n; i++){
        scanf("%d", &x);
        LinkList p = new LNode;
        p->data = x;
        p->next = NULL;
        r->next = p;
        r = r->next;		//或者 r = p;
    }
    return L;
}

//打印链表 
void PrintList(LinkList L){
	for(LinkList p = L->next; p != NULL; p = p->next){
		if(p->next == NULL)
			printf("%d", p->data);
		else
			printf("%d->", p->data);
	}
	printf("\n"); 
}

//差集
int DifferenceSet(LinkList &LA, LinkList &LB, LinkList &LC){
    InitList(LC);
    LinkList pa = LA->next;
    LinkList pc = LC;			//pc指针指向LC的头节点
    int count = 0;				//用于存储差集中元素的个数 
    while(pa){
        LinkList pb = LB->next;
        int found = 0;			//found标志,用于标记是否在LB中找到与p所指元素相同的元素
        while(pb){
            if(pa->data == pb->data){
                found = 1;
                break;
            }
            pb = pb->next;		//将pb指针后移,继续比较下一个元素
        }
        //如果在LB中没有找到与pa所指元素相同的元素
        if(!found){
			LinkList temp = new LNode; 
//          LinkList temp = (LinkList)malloc(sizeof(LNode));
            temp->data = pa->data;
            temp->next = NULL;
            pc->next = temp;
            pc = temp;
            count++;
        }
        pa = pa->next;
    }
    return count;
}


int main(){
    LinkList LA, LB, LC;
    InitList(LA);
    InitList(LB);
    InitList(LC);
    printf("=====对于单链表 LA ===== \n");
	CreateList(LA);
	printf("=====对于单链表 LB ===== \n");
	CreateList(LB);
    int num = DifferenceSet(LA, LB, LC);
    printf("差集LC为:");
    PrintList(LC);
    printf("差集元素个数:%d\n", num);
    return 0;
}

【运算结果】

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

【算法描述】

void Fenjie(LinkList &LA, LinkList &LB, LinkList &LC){
	InitList(LB);
    InitList(LC);
    LinkList pa = LA->next;
    LinkList pb = LB;
    LinkList pc = LC;
    while(pa){
	    LinkList temp = pa;
	    pa = pa->next;
	    if(temp->data < 0){
	        temp->next = pb->next;
	        pb->next = temp;
	        pb = temp;
	    } 
		else if(temp->data > 0){
	        temp->next = pc->next;
	        pc->next = temp;
	        pc = temp;
	    }
	}
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#define OK 1
using namespace std;
typedef int Status;
typedef int ElemType;

//单链表的存储结构
typedef struct LNode{
	ElemType data;	     
	struct LNode *next;	
}LNode, *LinkList;    
 
//初始化单链表 
Status InitList(LinkList &L){
	L = new LNode;    
	L->next = NULL;  
	return OK; 
}

//创建单链表 
LinkList CreateList(LinkList &L){     
	int x, n, i;
    L = new LNode;
    LinkList r = L;
    printf("请输入单链表的长度: ", n);
    scanf("%d", &n);
    printf("请依次输入%d个数(空格隔开):",n);
    for(i = 0; i < n; i++){
        scanf("%d", &x);
        LinkList p = new LNode;
        p->data = x;
        p->next = NULL;
        r->next = p;
        r = r->next;		//或者 r = p;
    }
    return L;
}

//打印链表 
void PrintList(LinkList L){
	for(LinkList p = L->next; p != NULL; p = p->next){
		if(p->next == NULL)
			printf("%d", p->data);
		else
			printf("%d->", p->data);
	}
	printf("\n"); 
}

//分解 
void Fenjie(LinkList &LA, LinkList &LB, LinkList &LC){
	InitList(LB);
    InitList(LC);
    LinkList pa = LA->next;
    LinkList pb = LB;
    LinkList pc = LC;
    while(pa){
	    LinkList temp = pa;
	    pa = pa->next;
	    if(temp->data < 0){
	        temp->next = pb->next;
	        pb->next = temp;
	        pb = temp;
	    } 
		else if(temp->data > 0){
	        temp->next = pc->next;
	        pc->next = temp;
	        pc = temp;
	    }
	}
}

int main(){
    LinkList LA, LB, LC;
    InitList(LA);
    InitList(LB);
    InitList(LC);
    printf("=====对于单链表 LA ===== \n");
	CreateList(LA);
    Fenjie(LA, LB, LC);
    printf("链表LB为:");
    PrintList(LB);
    printf("链表LC为:");
    PrintList(LC);
    return 0;
}

【运算结果】

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

【算法描述】

Status Max(LinkList L){
//    if(L->next == NULL)    return NULL;
	if(L->next != NULL){
	    LinkList max = L->next;
	    LinkList p = L->next->next;
	    while(p != NULL){
	        if(p->data > max->data)
	            max = p;
	        p = p->next;
		}
		return max->data; 
	}
}

【完整代码】

#include <stdio.h>
#define OK 1
using namespace std;
typedef int Status;
typedef int ElemType;

//单链表的存储结构
typedef struct LNode{
	ElemType data;	     
	struct LNode *next;	
}LNode, *LinkList;    
 
//初始化单链表 
Status InitList(LinkList &L){
	L = new LNode;     
	L->next = NULL;  
	return OK; 
} 

//创建单链表 
LinkList CreateList(LinkList &L){     
	int x, n, i;
    L = new LNode;
    LinkList r = L;
    printf("请输入单链表L的长度: ", n);
    scanf("%d", &n);
    printf("请依次输入%d个数(空格隔开):",n);
    for(i = 0; i < n; i++){
        scanf("%d", &x);
        LinkList p = new LNode;
        p->data = x;
        p->next = NULL;
        r->next = p;
        r = r->next;		//或者 r = p;
    }
    return L;
}
 
//求最大值
Status Max(LinkList L){
//    if(L->next == NULL)    return NULL;
	if(L->next != NULL){
	    LinkList max = L->next;
	    LinkList p = L->next->next;
	    while(p != NULL){
	        if(p->data > max->data)
	            max = p;
	        p = p->next;
		}
		return max->data; 
	}
}

int main(){	
	int n;
	LinkList L; 
	InitList(L);
	CreateList(L);
	int m = Max(L);
	printf("L表中最大的结点的数据元素:%d\n", m);
    return 0;
} 

【运行结果】

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

【算法描述】

void ReverList(LinkList &L){
	LNode *p = L->next;
 	LNode *q;                  	//生成新结点指向p的后继结点
	L->next = NULL;
	while (p){
		q = p->next;           	//q结点作为辅助结点来记录p的直接后继结点的位置
		p->next = L->next;		//将p所指的结点插入到新链表中
		L->next = p;			//因为后继结点已经存入q中,所以p仍然可以找到后继   //*p插在头结点之后 (头插法)
		p = q; 					//此时的新开始结点
	}
}

【完整代码】

#include <stdio.h>
#define OK 1
using namespace std;
typedef int Status;
typedef int ElemType;

//单链表的存储结构
typedef struct LNode{
	ElemType data;	     
	struct LNode *next;	
}LNode, *LinkList;    
 
//初始化单链表 
Status InitList(LinkList &L){
	L = new LNode;     
	L->next = NULL;  
	return OK; 
}

//创建单链表 
LinkList CreateList(LinkList &L){     
	int x, n, i;
    L = new LNode;
    LinkList r = L;
    printf("请输入单链表的长度: ", n);
    scanf("%d", &n);
    printf("请依次输入%d个数(空格隔开):",n);
    for(i = 0; i < n; i++){
        scanf("%d", &x);
        LinkList p = new LNode;
        p->data = x;
        p->next = NULL;
        r->next = p;
        r = r->next;		//或者 r = p;
    }
    return L;
}

//打印链表 
void PrintList(LinkList L){
	for(LinkList p = L->next; p != NULL; p = p->next){
		if(p->next == NULL)
			printf("%d", p->data);
		else
			printf("%d->", p->data);
	}
	printf("\n"); 
}

//逆转链表 
void ReverList(LinkList &L){
	LNode *p = L->next;
 	LNode *q;                  	//生成新结点指向p的后继结点
	L->next = NULL;
	while (p){
		q = p->next;           	//q结点作为辅助结点来记录p的直接后继结点的位置
		p->next = L->next;		//将p所指的结点插入到新链表中
		L->next = p;			//因为后继结点已经存入q中,所以p仍然可以找到后继   //*p插在头结点之后 (头插法)
		p = q; 					//此时的新开始结点
	}
}

int main(){	
	int n;
	LinkList L; 
	InitList(L);
	CreateList(L);
	ReverList(L);
	printf("逆转之后的链表为 :");
	PrintList(L);
	return 0; 
} 

【运算结果】

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

【算法描述】

void RangeList(LinkList &L, int mink, int maxk){
	LinkList pre, q, s;
	LinkList p = L->next;
	while(p && p->data <= mink){
		pre = p;					//pre指向前驱结点
		p = p->next; 
	}
	while(p && p->data < maxk){
		p = p->next;
	}
	q = pre->next;
	pre->next = p;					//修改待删除节点的指针 
	while(q != p){					//释放空间 
		s = q->next;
		delete q;
		q = s;
	}
}

【完整代码】

#include <stdio.h>
#define OK 1
using namespace std;
typedef int Status;
typedef int ElemType;

//单链表的存储结构
typedef struct LNode{
	ElemType data;	     
	struct LNode *next;	
}LNode, *LinkList;    
 
//初始化单链表 
Status InitList(LinkList &L){
	L = new LNode;     
	L->next = NULL;  
	return OK; 
}

//创建单链表 
LinkList CreateList(LinkList &L){     
	int x, n, i;
    L = new LNode;
    LinkList r = L;
    printf("请输入单链表的长度: ", n);
    scanf("%d", &n);
    printf("请依次输入%d个数(空格隔开):",n);
    for(i = 0; i < n; i++){
        scanf("%d", &x);
        LinkList p = new LNode;
        p->data = x;
        p->next = NULL;
        r->next = p;
        r = r->next;		//或者 r = p;
    }
    return L;
}

//打印链表 
void PrintList(LinkList L){
	for(LinkList p = L->next; p != NULL; p = p->next){
		if(p->next == NULL)
			printf("%d", p->data);
		else
			printf("%d->", p->data);
	}
	printf("\n"); 
}

void RangeList(LinkList &L, int mink, int maxk){
	LinkList pre, q, s;
	LinkList p = L->next;
	while(p && p->data <= mink){
		pre = p;					//pre指向前驱结点
		p = p->next; 
	}
	while(p && p->data < maxk){
		p = p->next;
	}
	q = pre->next;
	pre->next = p;					//修改待删除节点的指针 
	while(q != p){					//释放空间 
		s = q->next;
		delete q;
		q = s;
	}
}

int main(){	
	int n, mink, maxk;
	LinkList L; 
	InitList(L);
	CreateList(L);
	printf("请输入mink和maxk的值(用空格隔开): ", mink, maxk);
    scanf("%d %d", &mink, &maxk);
	RangeList(L, mink, maxk);
	printf("删除递增有序链表L中值大于%d且小于%d的所有元素后的数据元素:\n", mink, maxk);
	PrintList(L);
	return 0; 
} 

【运算结果】

(9)已知 p 指向双向循环链表中的一个结点,其结点结构为data、prior、next三个域,写出算法change(p),交换 p 所指向的结点和它的前缀结点的顺序。

【算法描述】

void change(DuLinkList p){
    DuLinkList q = p->prior;
    
	q->prior->next = p;
	p->prior = q->prior;
	
	q->next = p->next;
	p->next->prior = q;
	
	q->prior = p;
	p->next = q;
}

【完整代码】

#include <stdio.h>
#define OK 1
using namespace std;
typedef int Status;
typedef int ElemType;

//双向循环链表的存储结构
typedef struct DuLNode{
    ElemType data;         //数据域
    struct DuLNode *prior; //指向直接前驱
    struct DuLNode *next;  //指向直接后继
} DuLNode, *DuLinkList;

//初始化双向循环链表
Status InitDuList(DuLinkList &L){
    L = new DuLNode;
    L->next = L;
    L->prior = L;
    return OK;
}

//创建双向循环链表
DuLinkList CreateDuList(DuLinkList &L){
	int i, n, x;
    L = new DuLNode;
    L->next = L;
    L->prior = L;
    DuLinkList r = L;
    printf("请输入链表长度:");
    scanf("%d", &n);
    printf("依次输入 %d 个数(用空格隔开):", n);
    for (i = 0; i < n; i++){
        scanf("%d", &x);
        DuLinkList p = new DuLNode;
        p->next = L;
        p->data = x;
        r->next = p;
        p->prior = r;
        r = r->next;
    }
    return L;
}

//打印双向循环链表
void PrintDuList(DuLinkList L){
    DuLinkList p = L->next;
    while (p != L){
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}

//算法change(p)
void change(DuLinkList p){
    DuLinkList q = p->prior;
    
	q->prior->next = p;
	p->prior = q->prior;
	
	q->next = p->next;
	p->next->prior = q;
	
	q->prior = p;
	p->next = q;
}

int main(){
    DuLinkList L;
    InitDuList(L);
    CreateDuList(L);
    printf("原始链表: ");
    PrintDuList(L);
    
    int n;
    printf("请输入要交换的节点(第几个节点,从1开始): ");
    scanf("%d", &n);
    //获取要交换的节点
    DuLinkList p = L->next;
    for(int i = 1; i < n && p != NULL; i++){
        p = p->next;
    }
    change(p);

    printf("交换后的链表: ");
    PrintDuList(L);

    return 0;
}

【运算结果】

(10)已知长度为 n 的线性表A采用顺序存储结构,请写一时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除线性表中所有值为 item 的数据元素。

【算法描述】

void DeleteElem(SqList &L, int item){
	int i = 0;
    int j = 0;
    while(i < L.length){
        if(L.data[i] != item){  	//如果当前元素不等于item,保留该元素
            L.data[j++] = L.data[i];
        }
        i++;
    }
    L.length = j;
}

【完整代码】

#include <stdio.h>

//顺序表的存储结构
#define MAXSIZE 100
typedef struct{
	int data[MAXSIZE];			
	int length;		
} SqList;				//顺序表的结构类型为SqList 

//初始化顺序表
void InitList(SqList &L){
	L.length = 0;		//空表长度为0
}

//插入 
void InsertList(SqList &L){
    int num = 0;
    printf("请输入要入元素的个数:");
    scanf("%d", &num);
    printf("请依次输入%d元素的值:", num);
    for (int i = 0; i < num; i++){
        scanf("%d", &L.data[L.length]);
        L.length++;
    }
}

//打印顺序表 
void PrintList(SqList &L){
    for(int i = 0; i < L.length; i++){
        printf("%d ", L.data[i]);
    }
    printf("\n");
}

//删除线性表中所有值为 item 的数据元素
void DeleteElem(SqList &L, int item){
	int i = 0;
    int j = 0;
    while(i < L.length){
        if(L.data[i] != item){  	//如果当前元素不等于item,保留该元素
            L.data[j++] = L.data[i];
        }
        i++;
    }
    L.length = j;
}

int main(){
    SqList L;
    InitList(L);
    InsertList(L);
    PrintList(L);
    int deleteNum;
    printf("请输入要删除的数:");
	scanf("%d", &deleteNum); 
    DeleteElem(L, deleteNum);
    printf("删除%d之后打印顺序表:", deleteNum);
    PrintList(L);
}

【运算结果】

第三章 栈和队列

1. 选择题

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

【解答】C

        栈是后进先出的线性表,然而C选项中 1 比 2 先出栈,违背了栈的后进先出原则,所以不可能出现C选项所示的情况。

        A、1, 2, 3, 4, 5 依次全部进栈,再出栈,得到 5, 4, 3, 2, 1

        B、1, 2 先进栈,然后出栈得到 2, 1,再 3, 4, 5 进栈,再出栈 5, 4, 3,合起来就是 2, 1, 5, 4, 3

        D、1, 2 先进栈,然后 2 出栈,再 3, 4, 5 进栈,得到 2, 5, 4, 3,最后 1 出栈,就是 2, 5, 4, 3, 1

(2)若已知一个栈的入栈序列是 1,2,3… n,其输出序列为p_{1}, p_{2}, p_{3},...p_{n}, 若p_{1}=n,则 p_{i} 为(   )。
          A. i
          B. n - i
          C. n - i + 1
          D. 不确定

【解答】C

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

【解答】D
        对于非循环队列,尾指针和头指针的差值便是队列的长度,而对于循环队列,差值可能为负数, 所以需要将差值加上MAXSIZE,然后与 MAXSIZE求余,即 ( r - f + n) % 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 ;

【解答】A
        先把将删除节点的值保存到 x 当中,即 x = top->data; 然后栈顶指针往后移,指向下一个节点,就是 top = 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

【解答】A

        n = 0,===> fact(0)

        n = 1,===> fact(1)-->fact(0)

        n = 2,===> fact(2)-->fact(1)-->fact(0)

        ..........

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

【解答】D

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

【解答】A
        解决缓冲区问题应利用一种先进先出的线性表,所以选队列。

(8)设栈 S 和队列 Q 的初始状态为空,元素e_{1}, e_{2}, e_{3}, e_{4}, e_{5}e_{6} 依次进入栈 S,
一个元素出栈后即进入 Q,若 6 个元素出队的序列是 e_{2}, e_{4}, e_{3}, e_{6}, e_{5}e_{1},则栈 S 的容
量至少应该是(   )。
A. 2        B. 3        C. 4        D. 6

【解答】B

        e_{1}, e_{2}进栈,栈里有e_{1}, e_{2} (2个) ,然后e_{2}出栈;

        e_{3}, e_{4}进栈,栈里有e_{1}, e_{3}, e_{4} (3个) ,然后e_{4}, e_{3}出栈;

        e_{5},e_{6}进栈,栈里有e_{1},e_{5},e_{6} (3个) ,然后e_{6},e_{5},e_{1}出栈。

        所以栈的容量至少是 3 。

(9)若一个栈以向量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,那么这个数组是从后往前压的,应该先 top--,再压栈,不然第一个数就会溢出。

(10)设计一个判别表达式中左,右括号是否配对出现的算法,采用(   )数据结构最佳。
          A. 线性表的顺序存储结构         B. 队列
          C. 线性表的链式存储结构         D. 栈

【解答】D
        利用栈的后进先出原则。

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

【解答】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)

【解答】D
        数组 A[0…m] 中一共含有 m + 1 个元素,故在求模运算时应除以 m + 1。

(13)最大容量为 n 的循环队列, 队尾指针是 rear ,队头是 front ,则队空的条件是(   )。
          A. (rear+1)%n == front        B. rear == front
          C. rear+1 == front               D. (rear-l)%n == front

【解答】B
        最大容量为n的循环队列,队满条件是 (rear+1)%n==front,队空条件是rear==front 。

(14)栈和队列的共同点是(   )。
          A. 都是先进先出                                         B. 都是先进后出
          C. 只允许在端点处插入和删除元素            D. 没有共同点

【解答】C
        栈只允许在栈顶处进行插入和删除元素,队列只允许在队尾插入元素和在队头删除元素。

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

【解答】B

2. 算法设计题

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

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

【算法描述】

1、双栈初始化

初始化时,栈 0 的栈顶指针 top[0] 设置为 -1,栈 1 的栈顶指针 top[1] 设置为 m。栈 0 从数组的起始位置向中间增长,栈 1 从数组的末尾向中间增长。

int Init(DblStack *S){
    S.top[0]=-1; 
	S.top[1]=m; 
	return 1;
} 

2、判断栈空

由题目:当第 0 号栈的栈顶指针 top[0] 等于 -1 时该栈为空,当第 1 号栈的栈顶指针 top[1] 等于 m 时,该栈为空。

也就是说栈 0 为空的条件是 top[0] == -1,栈 1 为空的条件是  top[1]  == m。

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

3、判断栈满

栈满的条件是两个栈的栈顶指针相邻,即 top[0] + 1 == top[1]。

int IsFull(DblStack *S){
    return S.top[0] + 1 == S.top[1];
}

4、进栈

int Push(DblStack *S, int stackNum, SElemType e){
//stackNum=0表示左栈,stackNum=1表示右栈
    if (IsFull(S)){
        return 0;    //栈满,无法进栈
    }
    if (stackNum == 0){
        S.V[++S.top[0]] = e;    //栈0的栈顶指针加1,然后存入元素
    } 
    else if (stackNum == 1){
        S.V[--S.top[1]] = e;    //栈1的栈顶指针减1,然后存入元素
    }
    else{
        return -1;    //无效的栈编号
    }
    return 0;    //进栈成功
}

5、出栈

int Pop(DblStack *S, int stackNum, SElemType e){
    if(IsEmpty(S)){
        return 0;     //栈空,无法出栈
    }
    if(stackNum == 0){
        *e = S->V[S->top[0]--];    //取出栈0的栈顶元素,然后栈顶指针减1
    } 
    else if (stackNum == 1){
        *e = S->V[S->top[1]++];    //取出栈1的栈顶元素,然后栈顶指针加1
    } 
    else{
        return -1;    //无效的栈编号
    }
    return 1;    //出栈成功
}

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

【算法描述】

//判断字符序列是否是回文
int IsPalindrome(char *str){
    LinkStack S;
    InitStack(&S);
    int len = strlen(str);	//获取字符串长度
    int i;
    //将前半部分字符入栈
    for(i = 0; i < len / 2; i++){
//        Push(&S, str[i]);
		if(Push(&S, str[i]) == ERROR) return ERROR;
    }
    //如果字符串长度为奇数,跳过中间字符
    if(len % 2 != 0){
        i++;
    }
    //依次出栈并与后半部分字符比较
    while(i < len){
        ElemType e;
        Pop(&S, &e);
        if(e != str[i]){
            return ERROR;	//不匹配,不是回文
        }
        i++;
    }
    return OK;
}

【完整代码】

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define OK 1
#define ERROR -1
typedef int ElemType;
//链栈的存储结构
typedef struct StackNode{
	ElemType data;
	struct StackNode *next;
}StackNode, *LinkStack;
//初始化链栈
int InitStack (LinkStack *S){
	*S = NULL;
	return OK;
}
//入栈
int Push(LinkStack *S, ElemType e){
//	p = new StackNode;	//生成新节点 
	StackNode *p = (StackNode *)malloc(sizeof(StackNode));
	p->data = e;		//将新节点数据域置为e 
	p->next = *S;		//将新节点插入栈顶 
	*S = p;				//修改栈顶指针为p 
	return OK;
}
//出栈
int Pop(LinkStack *S, ElemType *e){
	if(*S == NULL)	return ERROR;	//栈空
	*e = (*S)->data;		//将栈顶元素赋给e
	StackNode *p = *S;	    //用p临时保存栈顶元素空间,以备释放
	*S = (*S)->next;		//修改栈顶指针
//	delete p;			//释放原栈顶元素的空间(C++) 
	free(p);			//C语言 
	return OK; 
} 
//判断字符序列是否是回文
int IsPalindrome(char *str){
    LinkStack S;
    InitStack(&S);
    int len = strlen(str);	//获取字符串长度
    int i;
    //将前半部分字符入栈
    for(i = 0; i < len / 2; i++){
//        Push(&S, str[i]);
		if(Push(&S, str[i]) == ERROR) return ERROR;
    }
    //如果字符串长度为奇数,跳过中间字符
    if(len % 2 != 0){
        i++;
    }
    //依次出栈并与后半部分字符比较
    while(i < len){
        ElemType e;
        Pop(&S, &e);
        if(e != str[i]){
            return ERROR;	//不匹配,不是回文
        }
        i++;
    }
    return OK;
}
int main(){
    char str[100];
    printf("请输入一个字符串: ");
    scanf("%s", str);

    if(IsPalindrome(str) == OK){
        printf("该字符串是回文。\n");
    }
	else{
        printf("该字符串不是回文。\n");
    }
    return 0;
}

【运算结果】

(3)设从键盘输入一整数的序列:a_{1},a_{2},a_{3},\cdots ,a_{n},试设计算法实现:用栈结构存储输入的整数,当 a_{i}\neq -1 时,将 a_{i} 进栈;当 a_{i}= -1 时,输出栈顶整数并出栈。算法应对异常情况(入栈满等)给出相应的信息。

【算法描述】

void InOutS(LinkStack *S){
	int num;
	printf("请输入一个整数序列,以0表示结束输入:\n");
	while(1){
		scanf("%d", &num);
		if(num == 0) break;	//输入0时结束输入

		if(num == -1){
			ElemType e;
			if(Pop(S, &e) == OK){
				printf("出栈元素:%d\n", e);
			}
		}
        else{
			if(Push(S, num) == ERROR){
				//处理入栈失败的情况
				break;
			}
		}
	}
}

【完整代码】

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define OK 1
#define ERROR -1
typedef int ElemType;
//链栈的存储结构
typedef struct StackNode{
	ElemType data;
	struct StackNode *next;
}StackNode, *LinkStack;
//初始化链栈
int InitStack (LinkStack *S){
	*S = NULL;
	return OK;
}
//入栈
int Push(LinkStack *S, ElemType e){
//	p = new StackNode;	//生成新节点 
	StackNode *p = (StackNode *)malloc(sizeof(StackNode));
	p->data = e;		//将新节点数据域置为e 
	p->next = *S;		//将新节点插入栈顶 
	*S = p;				//修改栈顶指针为p 
	return OK;
}
//出栈
int Pop(LinkStack *S, ElemType *e){
	if(*S == NULL)	return ERROR;	//栈空
	*e = (*S)->data;		//将栈顶元素赋给e
	StackNode *p = *S;		//用p临时保存栈顶元素空间,以备释放
	*S = (*S)->next;		//修改栈顶指针
//	delete p;			//释放原栈顶元素的空间(C++) 
	free(p);			//C语言 
	return OK; 
} 
void InOutS(LinkStack *S){
	int num;
	printf("请输入一个整数序列,以0表示结束输入:\n");
	while(1){
		scanf("%d", &num);
		if(num == 0) break;	//输入0时结束输入

		if(num == -1){
			ElemType e;
			if(Pop(S, &e) == OK){
				printf("出栈元素:%d\n", e);
			}
		}
        else{
			if(Push(S, num) == ERROR){
				//处理入栈失败的情况
				break;
			}
		}
	}
}
int main(){
	LinkStack S;
	InitStack(&S);
	InOutS(&S);
	return 0;
}

【运算结果】

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

【算法描述】

double Calculate(){
    LinkStack opnd;
    InitStack(&opnd);
    char ch, data[50] = {0};
    int i = 0;
    ElemType a, b, num;

    ch = getchar();
    while(ch != '$'){
        i = 0;
        while(isdigit(ch) || ch == '.'){
            data[i++] = ch;
            ch = getchar();
        }
        if(i > 0){
            data[i] = '\0';
            num = atof(data);
            Push(&opnd, num);
        }
        switch(ch){
            case '+':
                Pop(&opnd, &b);
                Pop(&opnd, &a);
                Push(&opnd, a + b);
                break;
            case '-':
                Pop(&opnd, &b);
                Pop(&opnd, &a);
                Push(&opnd, a - b);
                break;
            case '*':
                Pop(&opnd, &b);
                Pop(&opnd, &a);
                Push(&opnd, a * b);
                break;
            case '/':
                Pop(&opnd, &b);
                if(b == 0){
                    printf("Error: Division by zero\n");
                    return ERROR;
                }
                Pop(&opnd, &a);
                Push(&opnd, a / b);
                break;
        }
        if(ch != '$') ch = getchar();
    }
    return GetTop(&opnd);
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#define OK 1
#define ERROR -1
typedef double ElemType;
//链栈的存储结构
typedef struct StackNode{
    ElemType data;
    struct StackNode *next;
} StackNode, *LinkStack;
//初始化链栈
int InitStack(LinkStack *S){
    *S = NULL;
    return OK;
}
//判断栈空
int isEmpty(LinkStack *S){
    return *S == NULL;
}
//入栈
int Push(LinkStack *S, ElemType e){
    StackNode *p = (StackNode *)malloc(sizeof(StackNode)); //生成新节点 
    if (!p) return ERROR;
    p->data = e;		//将新节点数据域置为e 
    p->next = *S;		//将新节点插入栈顶 
    *S = p;				//修改栈顶指针为p 
    return OK;
}
//出栈
int Pop(LinkStack *S, ElemType *e){
	if(*S == NULL)	return ERROR;	//栈空
	*e = (*S)->data;		//将栈顶元素赋给e
	StackNode *p = *S;		//用p临时保存栈顶元素空间,以备释放
	*S = (*S)->next;		//修改栈顶指针
//	delete p;			//释放原栈顶元素的空间(C++) 
	free(p);			//C语言 
	return OK; 
}
//获取栈顶元素
ElemType GetTop(LinkStack *S){
    if (*S == NULL) return ERROR;
    return (*S)->data;
}

double Calculate(){
    LinkStack opnd;
    InitStack(&opnd);
    char ch, data[50] = {0};
    int i = 0;
    ElemType a, b, num;

    ch = getchar();
    while(ch != '$'){
        i = 0;
        while(isdigit(ch) || ch == '.'){
            data[i++] = ch;
            ch = getchar();
        }
        if(i > 0){
            data[i] = '\0';
            num = atof(data);
            Push(&opnd, num);
        }
        switch(ch){
            case '+':
                Pop(&opnd, &b);
                Pop(&opnd, &a);
                Push(&opnd, a + b);
                break;
            case '-':
                Pop(&opnd, &b);
                Pop(&opnd, &a);
                Push(&opnd, a - b);
                break;
            case '*':
                Pop(&opnd, &b);
                Pop(&opnd, &a);
                Push(&opnd, a * b);
                break;
            case '/':
                Pop(&opnd, &b);
                if(b == 0){
                    printf("Error: Division by zero\n");
                    return ERROR;
                }
                Pop(&opnd, &a);
                Push(&opnd, a / b);
                break;
        }
        if(ch != '$') ch = getchar();
    }
    return GetTop(&opnd);
}

int main(){
    printf("请输入一个后缀表达式,以'$'结束:\n");
    double sum = Calculate();
    printf("%.2f\n", sum);
    return 0;
}

【运算结果】

(5)假设以 I 和 O 分别表示入栈和出栈操作。栈的初态和终态均为空,入栈和出栈的操作序列可表示为仅由 I 和 O 组成的序列, 称可以操作的序列为合法序列, 否则称为非法序列。
        ① 下面所示的序列中哪些是合法的?
        A、IOIIOIOO        B、IOOIOIIO        C、IIIOIOIO        D、IIIOOIOO
        ② 通过对①的分析, 写出一个算法, 判定所给的操作序列是否合法。 若合法, 返回 true ,否则返回 false (假定被判定的操作序列已存入一维数组中)。

  ① 选 A 和 D。

【算法描述】

int isLegalSequence(char *seq){
    int counter = 0;
    for(int i = 0; i < strlen(seq); i++){
        if(seq[i] == 'I'){
            counter++;
        } 
		else if(seq[i] == 'O'){
            counter--;
            if(counter < 0){
                return false;   //栈为空时尝试出栈,返回非法
            }
        } 
		else{
            return false;   //非法字符
        }
    }
    return counter == 0 ? true : false;   //如果计数器不为0,则表示入栈和出栈次数不匹配
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define true 1
#define false 0

int isLegalSequence(char *seq){
    int counter = 0;
    for(int i = 0; i < strlen(seq); i++){
        if(seq[i] == 'I'){
            counter++;
        } 
		else if(seq[i] == 'O'){
            counter--;
            if(counter < 0){
                return false;   //栈为空时尝试出栈,返回非法
            }
        } 
		else{
            return false;   //非法字符
        }
    }
    return counter == 0 ? true : false;   //如果计数器不为0,则表示入栈和出栈次数不匹配
}

int main(){
    char *seq = (char *)malloc(100*sizeof(char));
    scanf("%99s", seq);
    printf("%s\n", isLegalSequence(seq) ? "true" : "false");
    return 0;
}

【运算结果】

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

【算法描述】

1、置空队

int InitQueue(LinkQueue *Q){
    Q->rear = (QueuePtr)malloc(sizeof(QueueNode));
    if (!Q->rear) return ERROR;
    Q->rear->next = Q->rear; 	//初始化为循环链表,指向自身
    return OK;
}

2、判断队列是否为空

int IsEmptyQueue(LinkQueue *Q){
    return Q->rear->next == Q->rear;
}

3、入队

int EnQueue(LinkQueue *Q, ElemType e){
    QueueNode *p = (QueueNode *)malloc(sizeof(QueueNode));
    if (!p) return ERROR;
    p->data = e;
    p->next = Q->rear->next; 	//新节点指向头结点
    Q->rear->next = p; 		//尾节点的next指向新节点
    Q->rear = p;	 //尾节点指向新节点
    return OK;
}

4、出队

int DeQueue(LinkQueue *Q, ElemType *e){
    if (Q->rear->next == Q->rear) return ERROR; 	//队列为空
    QueueNode *p = Q->rear->next->next; 	//p指向队首元素
    *e = p->data;
    Q->rear->next->next = p->next; 		//结点指向下一个节点
    if (p == Q->rear) Q->rear = Q->rear->next; 		//若队列只有一个元素,则修改队尾指针
    free(p);
    return OK;
}

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

【算法描述】

//插入(入队)操作
int enqueue(CycQueue *Q, ElemType x){
    if(Q->front == Q->rear && Q->tag == 1){
        //队列满
        return ERROR;
    }
    Q->data[Q->rear] = x;
    Q->rear = (Q->rear + 1) % MAXSIZE;
    if(Q->front == Q->rear){
        //队列满
        Q->tag = 1;
    }
    return OK;
}
//删除(出队)操作
int dlqueue(CycQueue *Q, ElemType *x){
    if(Q->front == Q->rear && Q->tag == 0){
        //队列空
        return ERROR;
    }
    *x = Q->data[Q->front];
    Q->front = (Q->front + 1) % MAXSIZE;
    if(Q->front == Q->rear){
        //队列空
        Q->tag = 0;
    }
    return OK;
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR -1
#define MAXSIZE 100 	 //定义队列的最大容量
typedef int ElemType;	 //定义队列中元素的数据类型
typedef struct{
    ElemType data[MAXSIZE]; 	//存储队列元素的数组
    int front; 		//队头指针
    int rear; 		//队尾指针
    int tag; 		//标志,0表示队列空,1表示队列满
} CycQueue;
//初始化队列
void InitQueue(CycQueue *Q){
    Q->front = 0;
    Q->rear = 0;
    Q->tag = 0; 	//初始化为空队列
}
//插入(入队)操作
int enqueue(CycQueue *Q, ElemType x){
    if(Q->front == Q->rear && Q->tag == 1){
        //队列满
        return ERROR;
    }
    Q->data[Q->rear] = x;
    Q->rear = (Q->rear + 1) % MAXSIZE;
    if(Q->front == Q->rear){
        //队列满
        Q->tag = 1;
    }
    return OK;
}
//删除(出队)操作
int dlqueue(CycQueue *Q, ElemType *x){
    if(Q->front == Q->rear && Q->tag == 0){
        //队列空
        return ERROR;
    }
    *x = Q->data[Q->front];
    Q->front = (Q->front + 1) % MAXSIZE;
    if(Q->front == Q->rear){
        //队列空
        Q->tag = 0;
    }
    return OK;
}
//打印队列
void PrintQueue(CycQueue *Q){
    int i = Q->front;
    if(Q->tag == 0 && Q->front == Q->rear){
        printf("队列空\n");
        return;
    }
    printf("队列元素:");
    while(i != Q->rear){
        printf("%d ", Q->data[i]);
        i = (i + 1) % MAXSIZE;
    }
    printf("\n");
}
int main(){
    CycQueue Q;
    InitQueue(&Q);
    ElemType e;
    int num;

    //插入
    printf("请输入要入队的元素数量:");
    scanf("%d", &num);
    printf("请输入 %d 个元素:", num);
    for(int i = 0; i < num; i++){
        scanf("%d", &e);
        if(enqueue(&Q, e) == ERROR){
            printf("队列已满,无法插入元素 %d\n", e);
            break;
        }
    }
    PrintQueue(&Q);

    //删除 
    printf("请输入要出队的元素数量:");
    scanf("%d", &num);
    for(int i = 0; i < num; i++){
        if(dlqueue(&Q, &e) == ERROR){
            printf("队列已空,无法删除元素\n");
            break;
        }
        printf("从队头删除:%d\n", e);
    }
    PrintQueue(&Q);

    return 0;
}

【运算结果】

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

 ① 循环队列的类型定义

【算法描述】

#define MAXSIZE 100       //定义队列的最大容量
typedef double ElemType;  //定义队列中元素的数据类型

typedef struct{
    ElemType data[MAXSIZE];  //存储队列元素的数组
    int front;  //队头指针
    int rear;   //队尾指针
}Deque;

② 1、从队尾删除的算法

【算法描述】

//从队尾删除元素
int DequeDeleteRear(Deque *Q, ElemType *e){
    if(IsEmpty(Q)){
        return ERROR;   //队列为空,无法删除
    }
    Q->rear = (Q->rear - 1 + MAXSIZE) % MAXSIZE;   //队尾指针向前移动
    *e = Q->data[Q->rear];   //获取队尾元素
    return OK;
}

  2、从队头插入的算法

【算法描述】

//从队头插入元素
int DequeInsertFront(Deque *Q, ElemType e){
    if(IsFull(Q)){
        return ERROR;   //队列已满,无法插入
    }
    Q->front = (Q->front - 1 + MAXSIZE) % MAXSIZE;   //队头指针向前移动
    Q->data[Q->front] = e;   //插入元素
    return OK;
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100  //定义队列的最大容量
#define OK 1
#define ERROR -1
typedef int ElemType;  //定义队列中元素的数据类型

typedef struct{
    ElemType data[MAXSIZE];  //存储队列元素的数组
    int front;  //队头指针
    int rear;   //队尾指针
} Deque;
//初始化队列
void InitDeque(Deque *Q){
    Q->front = 0;
    Q->rear = 0;
}
//判断队列是否为空
int IsEmpty(Deque *Q){
    return Q->front == Q->rear;
}
//判断队列是否满
int IsFull(Deque *Q){
    return (Q->rear + 1) % MAXSIZE == Q->front;
}
//打印队列
void PrintDeque(Deque *Q){
    int i = Q->front;
    printf("队列当前元素:");
    while(i != Q->rear){
        printf("%d ", Q->data[i]);
        i = (i + 1) % MAXSIZE;
    }
    printf("\n");
}
//从队尾删除元素
int DequeDeleteRear(Deque *Q, ElemType *e){
    if(IsEmpty(Q)){
        return ERROR;  //队列为空,无法删除
    }
    Q->rear = (Q->rear - 1 + MAXSIZE) % MAXSIZE;  //队尾指针向前移动
    *e = Q->data[Q->rear];  //获取队尾元素
    return OK;
}
//从队头插入元素
int DequeInsertFront(Deque *Q, ElemType e){
    if(IsFull(Q)){
        return ERROR;  //队列已满,无法插入
    }
    Q->front = (Q->front - 1 + MAXSIZE) % MAXSIZE;  //队头指针向前移动
    Q->data[Q->front] = e;  //插入元素
    return OK;
}
int main(){
    Deque Q1, Q2;
    ElemType e;
    int n, i;
    InitDeque(&Q1);
    InitDeque(&Q2);

    printf("输入第一个队列的元素数量: ");
    scanf("%d", &n);
    printf("输入%d个元素到第一个队列(从队尾): \n", n);
    for(i = 0; i < n; i++){
        scanf("%d", &e);
        if(DequeInsertFront(&Q1, e) == ERROR){
            printf("队列已满。无法插入更多元素。\n");
            break;
        }
    }
    PrintDeque(&Q1);

    //从队尾删除元素
    if(DequeDeleteRear(&Q1, &e) == OK){
        printf("从队尾删除:%d\n", e);
    } else{
        printf("队列已空。无法从队尾删除。\n");
    }
    PrintDeque(&Q1);

    //从键盘输入第二个队列的元素数量
    printf("输入第二个队列的元素数量:");
    scanf("%d", &n);
    printf("输入%d个元素到第二个队列(到前端):\n", n);
    for(i = 0; i < n; i++){
        scanf("%d", &e);
        if(DequeInsertFront(&Q2, e) == ERROR){
            printf("队列已满。无法插入更多元素。\n");
            break;
        }
    }
    PrintDeque(&Q2);

    //从队头插入元素
    printf("输入一个元素以插入到第二个队列的前端: ");
    scanf("%d", &e);
    if(DequeInsertFront(&Q2, e) == OK){
        printf("前端插入:%d\n", e);
    } else{
        printf("队列已满。无法在前端插入。\n");
    }
    PrintDeque(&Q2);

    return 0;
}

【运算结果】

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

        Ack(m, n)\left\{\begin{matrix} n+1& &m=0 \\ Ack(m-1,1)& &m\neq 0, n=0 \\ Ack(m-1, Ack(m,n-1)) & &m\neq 0,n\neq 0 \end{matrix}\right.
        ① 写出计算 Ack(m, n) 的递归算法,并根据此算法给出出 Ack(2, 1) 的计算过程;
        ② 写出计算 Ack(m, n) 的非递归算法。

 ①  Ack(m, n) 的递归算法

【算法描述】

int Ack(int m, int 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, n - 1));
	}
}

【完整代码】

#include <stdio.h>
int Ack(int m, int 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, n - 1));
	}
}
int main(){
	int a, b;
	scanf("%d %d", &a, &b);
	int sum = Ack(a, b);
	printf("%d\n", sum);
	return 0;
}

【运算结果】

 ②  Ack(m, n) 的非递归算法

【算法描述】

int Ackermann(int m, int n){
    StackFrame *stack = (StackFrame*)malloc(sizeof(StackFrame) * 1000); 	//假设栈足够大
    int sp = -1; 	//栈指针
    int result;

    //初始化栈
    stack[++sp] = (StackFrame){m, n, 0};

    while(sp >= 0){
        StackFrame current = stack[sp];
        switch(current.state){
            case 0: 	//初始状态
                if (current.m == 0){
                    result = current.n + 1;
                    sp--; 	 //弹栈
                }
				else if 
				(current.m != 0 && current.n == 0){
                    stack[sp] = (StackFrame){current.m - 1, 1, 0}; 	//替换当前帧
                } 
				else if(current.m != 0 && current.n != 0){
                    stack[sp].state = 1; 	//更新状态
                    stack[++sp] = (StackFrame){current.m, current.n - 1, 0}; 	//压栈
                }
                break;
            case 1: 	//等待A(m, n-1)的结果
                stack[sp] = (StackFrame){current.m - 1, result, 0}; 	//替换当前帧
                break;
        }
    }

    free(stack); 	//释放栈内存
    return result;
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
typedef struct{
    int m;
    int n;
    int state;
}StackFrame;

int Ackermann(int m, int n){
    StackFrame *stack = (StackFrame*)malloc(sizeof(StackFrame) * 1000); 	//假设栈足够大
    int sp = -1; 	//栈指针
    int result;

    //初始化栈
    stack[++sp] = (StackFrame){m, n, 0};

    while(sp >= 0){
        StackFrame current = stack[sp];
        switch(current.state){
            case 0: 	//初始状态
                if (current.m == 0){
                    result = current.n + 1;
                    sp--; 	 //弹栈
                }
				else if 
				(current.m != 0 && current.n == 0){
                    stack[sp] = (StackFrame){current.m - 1, 1, 0}; 	//替换当前帧
                } 
				else if(current.m != 0 && current.n != 0){
                    stack[sp].state = 1; 	//更新状态
                    stack[++sp] = (StackFrame){current.m, current.n - 1, 0}; 	//压栈
                }
                break;
            case 1: 	//等待A(m, n-1)的结果
                stack[sp] = (StackFrame){current.m - 1, result, 0}; 	//替换当前帧
                break;
        }
    }

    free(stack); 	//释放栈内存
    return result;
}

int main(){
    int a, b;
    scanf("%d %d", &a, &b);
    int sum = Ackermann(a, b);
    printf("%d\n", sum);
    return 0;
}

【运算结果】

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

【算法描述】

 ① 求链表中的最大整数

//求链表中的最大整数
int findMax(LNode *head){
    if(head == NULL){
        return INT_MIN; 	//链表为空时返回最小整数
    }
    int maxInRest = findMax(head->next);
    return (head->data > maxInRest) ? head->data : maxInRest;
}


 ② 求链表的结点个数

//求链表的结点个数
int countNodes(LNode *head){
    if(head == NULL){
        return 0;
    }
    return 1 + countNodes(head->next);
}


 ③ 求所有整数的平均值

//求所有整数的平均值
double findAverage(LinkList L){
    int length = countNodes(L->next);
    if(length == 0){
        return 0.0; 	//防止除以0
    }
    double sum = findSum(L->next);
    return sum / length;
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#include <limits.h> 	//包含INT_MIN的定义
#define OK 1
using namespace std;
typedef int Status;
typedef int ElemType;
//单链表的存储结构
typedef struct LNode{
	ElemType data;	     
	struct LNode *next;	
}LNode, *LinkList;    
//初始化单链表 
Status InitList(LinkList &L){
	L=new LNode;     
	L->next=NULL;  
	return OK; 
}
//创建单链表 
LinkList CreateList(LinkList &L){     
	int x, n, i;
    L = new LNode;
    LinkList r = L;
    printf("请输入单链表L的长度: ", n);
    scanf("%d", &n);
    printf("请依次输入%d个数(空格隔开):",n);
    for(i = 0; i < n; i++){
        scanf("%d", &x);
        LinkList p = new LNode;
        p->data = x;
        p->next = NULL;
        r->next = p;
        r = r->next;		//或者 r = p;
    }
    return L;
}
//打印链表 
void PrintList(LinkList L){
	for(LinkList p = L->next; p != NULL; p = p->next){
		if(p->next == NULL)
			printf("%d", p->data);
		else
			printf("%d->", p->data);
	}
	printf("\n"); 
}
//求链表中的最大整数
int findMax(LNode *head){
    if(head == NULL){
        return INT_MIN; 	//链表为空时返回最小整数
    }
    int maxInRest = findMax(head->next);
    return (head->data > maxInRest) ? head->data : maxInRest;
}
//求链表的结点个数
int countNodes(LNode *head){
    if(head == NULL){
        return 0;
    }
    return 1 + countNodes(head->next);
}
//求所有整数的总和
double findSum(LNode *head){
    if (head == NULL) {
        return 0.0;
    }
    return head->data + findSum(head->next);
}
//求所有整数的平均值
double findAverage(LinkList L){
    int length = countNodes(L->next);
    if(length == 0){
        return 0.0; 	//防止除以0
    }
    double sum = findSum(L->next);
    return sum / length;
}
int main(){
    LinkList L;
    InitList(L);
    CreateList(L);

    //链表中的最大整数
    int max = findMax(L->next);
    printf("链表中的最大整数是:%d\n", max);

    //求链表的结点个数
    int count = countNodes(L->next);
    printf("链表的结点个数是:%d\n", count);

    //求所有整数的平均值
    double average = findAverage(L);
    printf("所有整数的平均值是:%.2f\n", average);

    //释放链表内存
    LNode *current = L;
    while(current != NULL){
        LNode *temp = current;
        current = current->next;
        free(temp);
    }
    return 0;
}

【运算结果】

第四章 串、数组和广义表

1. 选择题

(1)串是一种特殊的线性表,其特殊性体现在(   )。
        A. 可以顺序存储        B. 数据元素是一个字符
        C. 可以链式存储        D. 数据元素可以是多个字符若

【解答】B

(2)串下面关于串的的叙述中,(   )是不正确的。
        A. 串是字符的有限序列                       B. 空串是由空格构成的串
        C. 模式匹配是串的一种重要运算        D. 串既可以采用顺序存储,也可以采用链式存储

【解答】B
        空格常常是串的字符集合中的一个元素,由一个或多个空格组成的串成为空格
串,零个字符的串成为空串,其长度为零。

(3)串“ ababaaababaa ”的 next 数组为(   )。
        A. 012345678999        B. 012121111212         C. 011234223456         D. 0123012322345

【解答】C

首先 j = 1 的时候,next[1] = 0;

j = 2,1<k<2,next[2] = 1;

j = 3,1<k<3,k = 2,然后比较t_{1}t_{2}是否相等,该题中t_{1}=a,t_{2}=b,则 next[3] = 1;

j = 4,1<k<4,k = 2、3;当 k = 2 时,比较t_{1}t_{3}t_{1}=a=t_{3} ;
                                         当 k = 3 时,比较t_{1}t_{2}t_{2}t_{3},即a b\neq b a

                                          综上,所以 next[4] = 2;

......

(4)串“ ababaabab ”的 nextval 为(   )。
        A. 010104101        B. 010102101        C. 010100011         D. 010101011

【解答】A

        该题前六位元素"ababaa"用上题的next[j] : 011234

首先默认 j = 1 时,nextval[1] = 0

j = 2 时,该元素(b)对应的 next 值为 1,则用该元素(b) 与 j = 1 对应的元素(a)比较,不相等,则 它对应的 nextval 值 就等于 其 next 值,即 nextval[2] = next[2] = 1

j = 3 时,该元素(a)对应的 next 值为 1,则用该元素(a) 与 j = 1 对应的元素(a)比较,相等,则 它对应的 nextval 值 就等于 j = 1的 nextval 值,即 nextval[3] = nextval[0] = 0

......

j = 6 时,该元素(a)对应的 next 值为 4,则用该元素(a) 与 j = 4 对应的元素(b)比较,不相等,则 它对应的 nextval 值 就等于其 next 值,即 nextval[6] = next[6] = 4;

......

(5)串的长度是指(   )。
        A. 串中所含不同字母的个数         B. 串中所含字符的个数
        C. 串中所含不同字符的个数         D. 串中所含非空格字符的个数

【解答】B
        串中字符的数目n称为串的长度。

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

【解答】B
        以行序为主,则 LOC[5,5]=[(5-1)*100+(5-1)]*2+10=818 。

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

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

        a_{11}=BA

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

【解答】C

        a_{85}中 i = 8, j = 5, i > j,所以根据公式 i(i - 1)/2 + (j - 1) + 1,得出答案选C。

(9)若对 n 阶对称矩阵 A 以行序为主序方式将其下三角形的元素 (包括主对角线上所有元素 ) 依次存放于一维数组 B[1…(n(n + 1)) / 2] 中,则在 B 中确定 a_{ij}( 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

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

(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. jm + i - 1

【解答】A
        由题意可知 A[1, 1] = 1

        所以 A[i, j] 在一维数组 B 中的下标为 (i - 1)*n + (j - 1) + 1(首地址),即选A。

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

【解答】B

        我们知道一个二维数组 Array[m][n]的元素个数为 m\times n

        该数组是一个三维数组,可以理解为一个长方体,0 ~ 4 共有 5 行,-1 ~ -3 共有 3 列,5 ~ 7 共有 3 纵,所以一共有 5 * 3 * 3 = 45 个元素。

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

【解答】D

        首先 Tail(A) = (b, (c, d), (e, (f, g)));

        然后 Tail(Tail(A)) = ((c, d), (e, (f, g)));

        然后 Head(Tail(Tail(A))) = (c, d);

        Tail(Head(Tail(Tail(A)))) = (d);

        Head(Tail(Head(Tail(Tail(A))))) = d

(14)广义表 ((a, b, c, d)) 的表头是() ,表尾是(   )。
          A. a         B. ( )        C. (a,b,c,d)        D. (b,c,d)

【解答】C、 B

(15)设广义表 L=((a, b, c)) ,则 L 的长度和深度分别为(   )。
           A. 1 和 1         B. 1 和 3         C. 1 和 2         D. 2 和 3

【解答】C
        广义表的长度是指广义表中所含元素的个数,广义表的深度是指广义表中展开后所含括号的层数。所以该题中 L 的长度为 1,深度为 2。

2.应用题

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

【解答】

(2)设目标为 t = “ abcaabbabcabaacbacba ”,模式为 p = “ abcabaa ”。
        ① 计算模式 p 的 naxtval 函数值;
        ② 画出利用 KMP算法进行模式匹配时每一趟的匹配过程。

【解答】

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

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

     

(3)数组 A 中,每个元素 A[i, j] 的长度均为 32 个二进制位,行下标从 -1 ~ 9,列下标从 1 到 11,从首地址 S 开始连续存放主存储器中,主存储器字长为 16 位。求:
        ① 存放该数组所需多少单元?
        ② 存放数组第 4 列所有元素至少需多少单元?
        ③ 数组按行存放时,元素 A[7, 4] 的起始地址是多少?
        ④ 数组按列存放时,元素 A[4, 7] 的起始地址是多少?

【解答】

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

                主存储器字长 16 位,即 2 个字节,-1 ~ 9 一共 11 行,1~11一个 11 列,故该数组存放需要单位数为:11*11*2 = 242。
        ② 存放数组第 4 列所有元素至少需多少单元?

                11*2 = 22 个单元。
        ③ 数组按行存放时,元素 A[7, 4] 的起始地址是多少?

                (8 * 11 + 3) * 2 + s = s + 182
        ④ 数组按列存放时,元素 A[4, 7] 的起始地址是多少?

                (6 * 11 + 5) * 2 + s = s + 142

(4)请将香蕉(banana)用工具 H( ) — Head( ) , T( ) — Tail( ) 从 L 中取出。
        L = (apple, (orange, (strawberry, (banana)), peach), pear)

【解答】

故答案是 H ( H ( T ( H ( T ( H ( T(L) ) ) ) ) )

3. 算法设计题

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

【算法描述】

void countFrequency(char* str, FILE* outputFile){
    int num[36] = {0};  	//0-25存储A-Z,26-35存储0-9

    for(int i = 0; str[i] != '\0'; i++){
        if(str[i] >= 'A' && str[i] <= 'Z'){
            num[str[i] - 'A']++;
        } 
		else if(str[i] >= '0' && str[i] <= '9'){
            num[str[i] - '0' + 26]++;
        }
    }

    //将结果输出到文件
    fprintf(outputFile, "字符频度统计结果:\n");
    for(int i = 0; i < 26; i++){
        if(num[i] > 0){
            fprintf(outputFile, "%c: %d\n", 'A' + i, num[i]);
        }
    }
    for(int i = 26; i < 36; i++){
        if(num[i] > 0){
            fprintf(outputFile, "%c: %d\n", '0' + (i - 26), num[i]);
        }
    }
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LENGTH 1000

//统计字符频度的函数
void countFrequency(char* str, FILE* outputFile){
    int num[36] = {0};  	//0-25存储A-Z,26-35存储0-9

    for(int i = 0; str[i] != '\0'; i++){
        if(str[i] >= 'A' && str[i] <= 'Z'){
            num[str[i] - 'A']++;
        } 
		else if(str[i] >= '0' && str[i] <= '9'){
            num[str[i] - '0' + 26]++;
        }
    }

    //将结果输出到文件
    fprintf(outputFile, "字符频度统计结果:\n");
    for(int i = 0; i < 26; i++){
        if(num[i] > 0){
            fprintf(outputFile, "%c: %d\n", 'A' + i, num[i]);
        }
    }
    for(int i = 26; i < 36; i++){
        if(num[i] > 0){
            fprintf(outputFile, "%c: %d\n", '0' + (i - 26), num[i]);
        }
    }
}


int main(){
    char inputString[MAX_LENGTH];
    char filename[100];

    //输入字符串
    printf("请输入字符串:\n");
    fgets(inputString, MAX_LENGTH, stdin);
    inputString[strcspn(inputString, "\n")] = '\0';  //去除fgets读取的换行符

    //输入文件名
    printf("请输入输出文件名:\n");
    scanf("%s", filename);

    //打开文件
    FILE* outputFile = fopen(filename, "w");	//写入模式 "w"会创建一个新文件(如果文件不存在的话),如果文件已经存在,会将文件内容截断(即删除文件中原有的内容)
    //FILE* outputFile = fopen(filename, "a");	//追加模式 "a"在文件末尾追加内容而不删除原有的内容
	if(outputFile == NULL){
        printf("无法打开文件。\n");
        return 1;
    }

    //统计字符频度并将结果输出到文件
    countFrequency(inputString, outputFile);

    //关闭文件
    fclose(outputFile);
    return 0;
}

【运算结果】

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

【算法描述】

void reverseString(char* str, int start, int end){
    char tempChar;
    if(start >= end){
        return;
    }
    //交换首尾字符
    tempChar = str[start];
    str[start] = str[end];
    str[end] = tempChar;
    //递归调用
    reverseString(str, start + 1, end - 1);
}

【完整代码】

#include <stdio.h>
#include <string.h>

//递归函数实现字符串逆序存储
void reverseString(char* str, int start, int end){
    char tempChar;
    if(start >= end){
        return;
    }
    //交换首尾字符
    tempChar = str[start];
    str[start] = str[end];
    str[end] = tempChar;
    //递归调用
    reverseString(str, start + 1, end - 1);
}

int main(){
    char str[100];
    //从键盘输入字符串
    printf("请输入字符串: ");
    fgets(str, sizeof(str), stdin);
    //去除 fgets 读取的换行符
    str[strcspn(str, "\n")] = '\0';
    int len = strlen(str);
    //调用递归函数进行字符串逆序存储
    reverseString(str, 0, len - 1);
    printf("逆序后的字符串: %s\n", str);
    return 0;
}

【运算结果】

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

【算法描述】

void insert(char* s, char* t, int pos){
    int i, j;
    int len_s = 0, len_t = 0;
    while(s[len_s]!= '\0'){
        len_s++;
    }
    while(t[len_t]!= '\0'){
        len_t++;
    }
    //将s中从pos开始的字符向后移动len_t个位置
    for(i = len_s; i >= pos; i--){
        s[i + len_t] = s[i];
    }
    //将t插入到s的pos位置
    for (i = pos, j = 0; j < len_t; i++, j++){
        s[i] = t[j];
    }
}

【完整代码】

#include <stdio.h>
#include <string.h>

//将字符串t插入到字符串s中,插入位置为pos
void insert(char* s, char* t, int pos){
    int i, j;
    int len_s = 0, len_t = 0;
    while(s[len_s]!= '\0'){
        len_s++;
    }
    while(t[len_t]!= '\0'){
        len_t++;
    }
    //将s中从pos开始的字符向后移动len_t个位置
    for(i = len_s; i >= pos; i--){
        s[i + len_t] = s[i];
    }
    //将t插入到s的pos位置
    for (i = pos, j = 0; j < len_t; i++, j++){
        s[i] = t[j];
    }
}

int main(){
    char s[1000], t[1000];
    int pos;
    printf("请输入字符串s: ");
    fgets(s, sizeof(s), stdin);
    s[strcspn(s, "\n")] = '\0';
    printf("插入前的字符串s: %s\n", s);
    printf("请输入字符串t: ");
    fgets(t, sizeof(t), stdin);
    t[strcspn(t, "\n")] = '\0';
    printf("请输入位置pos:");
	scanf("%d", &pos); 
    insert(s, t, pos);
    printf("插入后的字符串s: %s", s);
    return 0;
}

【运算结果】

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

【算法描述】

void format(char* s1, char* s2, char* s3, int n){
    int len = strlen(s1);
    if(len <= n){
        //原字符串长度小于等于n,直接复制到s2并添加空格填充
        int spaces = n - len;		//计算需要填充的空格总数。
        int left_spaces = spaces / 2;
        int right_spaces = spaces - left_spaces;		//计算左右两侧的空格数
        for(int i = 0; i < left_spaces; i++){
            s2[i] = ' ';
        }
        strcpy(s2 + left_spaces, s1);
        for(int i = len + left_spaces; i < n; i++){
            s2[i] = ' ';
        }
        s2[n] = '\0';
        s3[0] = '\0';
    } 
	else{
        //原字符串长度大于 n,截取前n个字符到s2,其余放入s3
        strncpy(s2, s1, n);				//将s1的前n个字符复制到s2
        s2[n] = '\0';
        strcpy(s3, s1 + n);				//将s1的第n个字符开始的部分复制到s3
    }
}

【完整代码】

#include <stdio.h>
#include <string.h>

void format(char* s1, char* s2, char* s3, int n){
    int len = strlen(s1);
    if(len <= n){
        //原字符串长度小于等于n,直接复制到s2并添加空格填充
        int spaces = n - len;		//计算需要填充的空格总数。
        int left_spaces = spaces / 2;
        int right_spaces = spaces - left_spaces;		//计算左右两侧的空格数
        for(int i = 0; i < left_spaces; i++){
            s2[i] = ' ';
        }
        strcpy(s2 + left_spaces, s1);
        for(int i = len + left_spaces; i < n; i++){
            s2[i] = ' ';
        }
        s2[n] = '\0';
        s3[0] = '\0';
    } 
	else{
        //原字符串长度大于 n,截取前n个字符到s2,其余放入s3
        strncpy(s2, s1, n);				//将s1的前n个字符复制到s2
        s2[n] = '\0';
        strcpy(s3, s1 + n);				//将s1的第n个字符开始的部分复制到s3
    }
}

int main(){
    char s1[] = "Hello, World!";
    printf("请输入字符串s1:");
	fgets(s1, sizeof(s1), stdin);
    char s2[100];
    char s3[100];
    int n;
    printf("请输入长度 n: ");
    scanf("%d", &n);
    getchar(); 		//消耗掉输入n后的换行符
    format(s1, s2, s3, n);
    printf("Formatted string s2: %s\n", s2);
    printf("Extra characters s3: %s\n", s3);
    return 0;
}

【运算结果】

(5)设二维数组 a[1…m, 1…n] 含有 m*n 个整数。
        ① 写一个算法判断 a 中所有元素是否互不相同 ?输出相关信息 (yes / no) ;
        ② 试分析算法的时间复杂度。

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

【算法描述】

int fun(int a[100][100], int m, int n){
    for(int i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            for(int x = 0; x < m; x++){
                for(int y = 0; y < n; y++){
                    if((i!= x || j!= y) && a[i][j] == a[x][y]){
                        return 0;
                    }
                }
            }
        }
    }
    return 1;
}

【完整代码】

#include <stdio.h>

//断二维数组中的元素是否互不相同
int fun(int a[100][100], int m, int n){
    for(int i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            for(int x = 0; x < m; x++){
                for(int y = 0; y < n; y++){
                    if((i!= x || j!= y) && a[i][j] == a[x][y]){
                        return 0;
                    }
                }
            }
        }
    }
    return 1;
}

int main(){
    int m, n;
    int a[100][100];

    //输入二维数组的行数
    printf("请输入二维数组的行数: ");
    scanf("%d", &m);

    //输入二维数组的列数
    printf("请输入二维数组的列数: ");
    scanf("%d", &n);

    //输入二维数组的元素
    printf("请输入二维数组的元素:\n");
    for(int i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            scanf("%d", &a[i][j]);
        }
    }

    //判断元素是否互不相同
    if(fun(a, m, n)){
        printf("no\n");
    } 
    else{
        printf("yes\n");
    }
    return 0;
}

【运算结果】

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

      该算法的时间复杂度为m\times n\times m\times n=m^{2} \times n^{2}

      因为最外层的for循环 for(int i = 0; i < m; i++) 执行 m 次,
      第二层for循环 for(int j = 0; j < n; j++) 执行 n 次,后面两层也是依次执行 m、n次。

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

【算法描述】

void fun(int a[], int n){
    int i = 0;
    int j = n - 1;
    int temp; 

    while(i < j){
        //从左向右找到一个负数
        while (i < j && a[i] >= 0){
            i++;
        }

        //从右向左找到一个正数
        while(i < j && a[j] < 0){
            j--;
        }

        //交换正、负数
        if(i < j){
            temp = a[i];
			a[i++] = a[j];
			a[j--] = temp;
        }
    }
}

【完整代码】

#include <stdio.h>
#define MAX_SIZE 100

void fun(int a[], int n){
    int i = 0;
    int j = n - 1;
    int temp; 

    while(i < j){
        //从左向右找到一个负数
        while (i < j && a[i] >= 0){
            i++;
        }

        //从右向左找到一个正数
        while(i < j && a[j] < 0){
            j--;
        }

        //交换正、负数
        if(i < j){
            temp = a[i];
			a[i++] = a[j];
			a[j--] = temp;
        }
    }
}


int main(){
    int a[MAX_SIZE];
    int n;

    //输入数组的长度
    printf("请输入数组的长度 (不超过 %d): ", MAX_SIZE);
    scanf("%d", &n);

    //输入数组元素
    printf("请输入数组元素: ");
    for(int i = 0; i < n; i++){
        scanf("%d", &a[i]);
    }
    fun(a, n);

    //输出排后的数组
    printf("排后的数组: ");
    for(int i = 0; i < n; i++){
        printf("%d ", a[i]);
    }
    return 0;
}

【运算结果】

第五章 树和二叉树

1. 选择题

(1)把一棵树转换为二叉树后,这棵二叉树的形态是( ) 。
        A. 唯一的
        B. 有多种
        C. 有多种,但根节点都没有左孩子
        D. 有多种,但根节点都没有右孩子

【解答】A
        因为二叉树有左孩子、右孩子之分,故一棵树转换为二叉树后,这棵二叉树的
形态是唯一的。

(2)由 3 个结点可以构造出多少种不同的二叉树?( )
        A. 2        B. 3        C. 4        D. 5

【解答】D

        二叉树有左右之分。

(3)一棵完全二叉树上有 1001 个节点,其中叶子节点的个数是( )。
        A. 250        B. 500        C. 254        D. 501

【解答】D
        设 n_{0} 是度为 0 节点(叶子节点)个数,n_{1}是度为 1 的节点个数,n_{2}是度为 2 的节点个数,因此可得 n_{0}=n_{2}+1,且 n_{0}+n_{1}+n_{2}=1001,化简得到2n_{2}+n_{1}=1000,由完全二叉树的性质可得n_{1} = 0 或 1,又因为n_{2}为整数,所以 n_{1} = 0,n_{2} = 500,n_{0} = 501,即有 501 个叶子节点。

(4)一个具有 1025 个节点的二叉树的高 h 为( )。
        A. 11        B. 10        C. 11 ~ 1025         D. 10 ~ 1024 

【解答】C
        若每层仅有一个节点,则树高 h 为 1025 ;

        根据二叉树的性质 4 :具有 n 个节点的完全二叉树的深度为 \left \lfloor log_{2}^{n} \right \rfloor+1,可知该二叉树最小数高为\left \lfloor log_{2}^{1025} \right \rfloor+1=11, 即选C。

(5)深度为 h 的满 m叉树的第 k 层有()个节点。(1\leqslant k\leqslant h)
        A. m^{k-1}        B. m^{k}-1        C. m^{h-1}        D. m^{h}-1

【解答】A
        深度为 h 的满 m 叉树共有 m^{h}-1 个节点,第 k 层有 m^{k-1} 个节点。

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

【解答】C
        利用二叉链表表示法存储树时,右指针指向兄弟节点,因为根节点没有兄弟节点,故根节点的右指针指向空。

(7)对二叉树的节点从 1 开始进行连续编号,要求每个节点的编号大于其左、右孩子的编号,同一节点的左右孩子中,其左孩子的编号小于其右孩子的编号,可采用( )遍历实现编号。
        A. 先序        B. 中序        C. 后序        D. 从根开始按层次遍历

【解答】C

        若是从大到小,则是中、右、左的顺序,

        若是从小到大,则是左、右、中的顺序,即后序遍历。

(8)在一棵度为 4 的树 T 中,若有 20 个度为 4 的节点,10 个度为 3 的节点,1 个度为 2 的节点,10 个度为 1 的节点,则树 T 的叶节点个数是( )。
        A. 41        B. 82        C. 113        D. 122

【解答】B
        设树 T 的总叶子个数为 n,叶子节点的个数为 n_{0},度为 2 的节点的个数为 n_{2},度为 3 的节点的个数为 n_{3},度为 4 的节点的个数为 n_{4}

所以n=n_{4}+n_{3}+n_{2}+n_{1}+n_{0}=20+10+1+10+n_{0}=41+n_{0}

又因为 n = B(分支总数) + 1,

可算出B=4n_{4}+3n_{3}+2n_{2}+n_{1}=4\times20+3\times10+2\times1+10=122

所以 n = 122 + 1 = 123 = 41 +n_{0},由此可算出叶子节点个数为82。

(9)在下列存储结构表示法中,( )不是树的存储结构表示法。
        A. 双亲表示法        B. 孩子链表表示法        C. 孩子兄弟表示法        D. 顺序存储表示法

【解答】D
        树的存储结构有三种:双亲表示法、孩子表示法、孩子兄弟表示法,其中孩子兄弟表示法是常用的表示法,任意一棵树都能通过孩子兄弟表示法转换为二叉树进行存储。

(10)一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足( )。
        A. 所有的节点均无左孩子
        B. 所有的节点均无右孩子
        C. 只有一个叶子节点
        D. 是任意一棵二叉树

【解答】C
        因为先序遍历结果是“中左右” ,后序遍历结果是“左右中” ,当没有左子树时,就是“中右”和“右中” ;当没有右子树时,就是“中左”和“左中” 。则所有的节点均无左孩子或所有的节点均无右孩子均可,但题目问的是一定满足的条件,所以 A、B 不能选,又所有的节点均无左孩子与所有的结点均无右孩子时,均只有一个叶子节点,故选 C。

(11)设哈夫曼树中有 199 个节点,则该哈夫曼树中有( )个叶子节点。
        A. 99        B. 100        C. 101        D. 102

【解答】B
        在哈夫曼树中没有度为 1 的节点,只有度为 0(叶子节点)和度为 2 的节点。设叶子节点的个数为 n_{0},度为 2 的节点的个数为 n_{2},由二叉树的性质 n_{0}=n_{2}+1,则总节点数  n=n_{0}+n_{2}=2n_{0}-1,得到 n_{0}=100

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

【解答】C

例如下图中的二叉树,X的前驱为 F 。

(13)引入二叉线索树的目的是( )。
        A. 加快查找节点的前驱或后继的速度
        B. 为了能在二叉树中方便的进行插入与删除
        C. 为了能方便的找到双亲
        D. 使二叉树的遍历结果唯一

【解答】A

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

【解答】C

        森林转换成二叉树,即用到孩子兄弟表示法。        

        非终端节点有 n 个,设森林总节点个数为 m,则终端节点(即叶子节点)个数为 m - n,一个节点有两个指针域,左孩子右兄弟,所以总指针域个数为 2m 个。

        根据二叉树的特性,可知在 m 个节点的二叉链表中,有 m + 1 个空指针域

      【除根节点外,每出现一个节点就会占用其父节点的一个指针域,所以占据 m - 1个指针域,即使用的指针域为 m - 1,所以空指针域为 2m - (m - 1) = m + 1】(这里不需要考虑孩子兄弟表示法,就是普通的一个二叉树)

        终端节点转化为二叉树后,该节点没有左孩子,左指针域就为空,即 m - n

        因为森林转二叉树后,左孩子是第一个孩子,右孩子是兄弟,所以终端节点转化后一定无左孩子,右孩子可能有也可能没有,也就是说转化后有两种可能,一是仍为叶子节点,二为不再是叶子节点,但只有右孩子。

        右指针域为空个数 = 总的空指针域个数 - 左指针域为空的个数

        所以右指针域为空的个数为:(m + 1) - (m - n) = n + 1。

(15)n(n\geqslant 2)个权值均不相同的字符构成哈夫曼树, 关于该树的叙述中, 错误的是( )。
        A. 该树一定是一棵完全二叉树
        B. 树中一定没有度为 1 的节点
        C. 树中两个权值最小的节点一定是兄弟节点
        D. 树中任一非叶节点的权值一定不小于下一层任一节点的权值

【解答】A
        哈夫曼树的构造过程是每次都选取权值最小的树作为左右子树构造一棵新的二叉树,所以树中一定没有度为 1 的节点、两个权值最小的结点一定是兄弟节点、任一非叶节点的权值一定不小于下一层任一节点的权值。

2. 应用题

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

【解答】
        先序遍历:中—左—右;

        中序遍历:左—中—右;

        后序遍历:左—右―中。

        层次遍历就是一层一层从左往右依次遍历,相当于“中—左—右”,即先序遍历。

① 先序和后序相同,那么左右子树一样,没有根节点,故该二叉树或为空树,或为只有根结点的二叉树;
② 中序和后序相同,那么左中子树一样,故该二叉树或为空树,或为任一结点至多只有左子树的二叉树;
③ 先序和中序相同,那么中右子树一样,故该二叉树或为空树,或为任一结点至多只有右子树的二叉树;
④ 与③一样。

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

【解答】

       ①
      

      ② 该二叉树的后序遍历(左-右-中)为:F D B G H E C A 。

           则后序线索树如下:

     

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

           将随便一棵树转化为二叉树采用了左孩子右兄弟法。反过来。

    

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

【解答】
        ① 试为这 8 个字母设计哈夫曼编码。

        
        ② 试设计另一种由二进制表示的等长编码方案。

        因为有八位数,所以二进制编码是三位,即设从左到右 0.07,0.19,0.02,0.06,0.32,0.03,0.21,0.10 的二进制等长编码依次依次是000、001、010、011、100、101、110、111。
        ③ 对于上述实例,比较两种方案的优缺点。

        对于第 ① 种,

                其WPL = (0.02+0.03)*5+(0.06+0.07+0.10)*4+(0.32+0.21+0.19)*2 = 2.61      

        对于第 ② 种,

                其WPL = (0.07+0.19+0.02+0.06+0.32+0.03+0.21+0.10)*3 = 

        综上比较哈夫曼编码优于等长二进制编码。

(4)已知下列字符 A、B、C、D、E、F、G 的权值分别为 3、12 、7、4、2、8,11,试填写出其对应哈夫曼树 HT 的存储结构的初态和终态。

【解答】
       对应的哈夫曼树如下:

       

HT 的初态:

HT 的终态:

3. 算法设计题

以二叉链表作为二叉树的存储结构,设计以下算法。

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

【算法描述】

int NodeCount(BiTree T){
	if(T == NULL)	return 0;		//如果是空树,则节点个数为0,递归结束
	else return NodeCount(T->lchild) + NodeCount(T->rchild) + 1;
	//否则节点个数为左子树的节点个数+右子树的节点个数+1 
} 

【完整代码】

#include <stdio.h>
#include <stdlib.h>

//二叉树的二叉链表存储表示
typedef struct BiTNode{
	int data;
	struct BiTNode *lchild, *rchild;	//左右孩子指针
}BiTNode, *BiTree;

//先序遍历的顺序建立二叉链表
void CreateBiTree(BiTree &T){
	char ch; 
	printf("请输入二叉树中节点的值(单字符):"); 
	scanf(" %c", &ch);
	if(ch == '#')	T = NULL;		//递归结束,建立空树	 
	else{							//递归创建二叉树 
		T = new BiTNode;			//生成根节点
		T->data = ch;				//根节点数据域置为ch
		CreateBiTree(T->lchild);	//递归创建左子树
		CreateBiTree(T->rchild);	//递归创建右子树
	}
} 

//统计二叉树中节点的个数
int NodeCount(BiTree T){
	if(T == NULL)	return 0;		//如果是空树,则节点个数为0,递归结束
	else return NodeCount(T->lchild) + NodeCount(T->rchild) + 1;
	//否则节点个数为左子树的节点个数+右子树的节点个数+1 
} 

int main(){
	BiTree root = NULL;

    //先序遍历的顺序建立二叉链表
    CreateBiTree(root);

    //统计二叉树中节点的个数
    int nodeCount = NodeCount(root);
    printf("二叉树中的节点个数:%d\n", nodeCount);
    return 0;
}

【运算结果】

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

【算法描述】

int isEqual(BiTree T1, BiTree T2){
    if(T1 == NULL && T2 == NULL){
        return 1;
    }
    if(T1 == NULL || T2 == NULL){
        return 0;
    }
    if(T1->data!= T2->data){
        return 0;
    }
    return isEqual(T1->lchild, T2->lchild) && isEqual(T1->rchild, T2->rchild);
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>

//二叉树的二叉链表存储表示
typedef struct BiTNode{
	int data;
	struct BiTNode *lchild, *rchild;	//左右孩子指针
}BiTNode, *BiTree;

//先序遍历的顺序建立二叉链表
void CreateBiTree(BiTree &T){
	char ch; 
	printf("请输入二叉树中节点的值(单字符):"); 
	scanf(" %c", &ch);
	if(ch == '#')	T = NULL;		//递归结束,建立空树	 
	else{							//递归创建二叉树 
		T = new BiTNode;			//生成根节点
		T->data = ch;				//根节点数据域置为ch
		CreateBiTree(T->lchild);	//递归创建左子树
		CreateBiTree(T->rchild);	//递归创建右子树
	}
} 

//判别两棵树是否相等
int isEqual(BiTree T1, BiTree T2){
    if(T1 == NULL && T2 == NULL){
        return 1;
    }
    if(T1 == NULL || T2 == NULL){
        return 0;
    }
    if(T1->data!= T2->data){
        return 0;
    }
    return isEqual(T1->lchild, T2->lchild) && isEqual(T1->rchild, T2->rchild);
}

int main(){
	BiTree root1 = NULL;
    BiTree root2 = NULL;
	printf("请输入第一棵树:\n"); 
    CreateBiTree(root1);
	printf("请输入第二棵树:\n"); 
    CreateBiTree(root2);
    // 判别两棵树是否相等
    if(isEqual(root1, root2)){
        printf("两棵树相等\n");
    } 
	else{
        printf("两棵树不相等\n");
    }
    return 0;
}

【运算结果】

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

【算法描述】

void PreOrderTraverse(BiTree T){
	if(T){
		printf("%c ", T->data);
		PreOrderTraverse(T->lchild);
		PreOrderTraverse(T->rchild);
	}
} 

【完整代码】

#include <stdio.h>
#include <stdlib.h>

//二叉树的二叉链表存储表示
typedef struct BiTNode{
	int data;
	struct BiTNode *lchild, *rchild;	//左右孩子指针
}BiTNode, *BiTree;

//先序遍历的顺序建立二叉链表
void CreateBiTree(BiTree &T){
	char ch; 
	printf("请输入二叉树中节点的值(单字符):"); 
	scanf(" %c", &ch);
	if(ch == '#')	T = NULL;		//递归结束,建立空树	 
	else{							//递归创建二叉树
		T = new BiTNode;			//生成根节点
		T->data = ch;				//根节点数据域置为ch
		CreateBiTree(T->lchild);	//递归创建左子树
		CreateBiTree(T->rchild);	//递归创建右子树
	}
} 

//交换二叉树每个节点的左孩子和右孩子
void swapChildren(BiTree T){
    if(T == NULL)	return;
    //交换左右孩子
    BiTree temp = T->lchild;
    T->lchild = T->rchild;
    T->rchild = temp;
    //递归交换左右子树的孩子节点
    swapChildren(T->lchild);
    swapChildren(T->rchild);
}

//先序遍历二叉树
void PreOrderTraverse(BiTree T){
	if(T){
		printf("%c ", T->data);
		PreOrderTraverse(T->lchild);
		PreOrderTraverse(T->rchild);
	}
} 


int main(){
	BiTree root = NULL;	
    //先序遍历的顺序建立二叉链表
    CreateBiTree(root);
    printf("交换前的先序遍历结果:");
    PreOrderTraverse(root);
    printf("\n");
    //交换二叉树每个节点的左孩子和右孩子
    swapChildren(root);
    printf("交换后的先序遍历结果:");
    PreOrderTraverse(root);
    printf("\n");
    return 0;
}

【运算结果】

(4)设计二叉树的双序遍历算法(双序遍历是指对于二叉树的每一个节点来说,先访问
这个节点,再按双序遍历它的左子树,然后再一次访问这个节点,接下来按双序遍历它的右
子树)。

【算法描述】

void DoubleOrderTraverse(BiTree T){
    if(T){
		printf("%c ", T->data);
		DoubleOrderTraverse(T->lchild);
		printf("%c ", T->data);
		DoubleOrderTraverse(T->rchild);
	}
}

【完整代码】

#include <stdio.h>

//二叉树的二叉链表存储表示
typedef struct BiTNode{
	int data;
	struct BiTNode *lchild, *rchild;	//左右孩子指针
}BiTNode, *BiTree;

//先序遍历的顺序建立二叉链表
void CreateBiTree(BiTree &T){
	char ch; 
	printf("请输入二叉树中节点的值(单字符):"); 
	scanf(" %c", &ch);
	if(ch == '#')	T = NULL;		//递归结束,建立空树	 
	else{							//递归创建二叉树
		T = new BiTNode;			//生成根节点
		T->data = ch;				//根节点数据域置为ch
		CreateBiTree(T->lchild);	//递归创建左子树
		CreateBiTree(T->rchild);	//递归创建右子树
	}
}

//双序遍历二叉树
void DoubleOrderTraverse(BiTree T){
    if(T){
		printf("%c ", T->data);
		DoubleOrderTraverse(T->lchild);
		printf("%c ", T->data);
		DoubleOrderTraverse(T->rchild);
	}
}

int main(){
    BiTree root = NULL;
    CreateBiTree(root);
    //双序遍历二叉树
    printf("双序遍历结果:");
    DoubleOrderTraverse(root);
    printf("\n");
    return 0;
}

【运算结果】

(5)计算二叉树最大的宽度 (二叉树的最大宽度是指二叉树所有层中节点个数的最大值) 。

【算法描述】

int MaxWidthTree(BiTree T){
	int depth = 0;
    int MaxDepth = 0;
    if(T == NULL){	//二叉树为空,最大宽度为0
        return 0;
    }
	int count[1010] = {0};		//存储每层节点数

    traverse(T, 0, count, &MaxDepth);

    int MaxWidth = 0;		//初始化最大宽度为0
    for(int i = 0; i <= MaxDepth; i++){
        if(count[i] > MaxWidth){
            MaxWidth = count[i];
        }
    }
    return MaxWidth;
}

【完整代码】

#include <stdio.h>

//二叉树的二叉链表存储表示
typedef struct BiTNode{
	int data;
	struct BiTNode *lchild, *rchild;	//左右孩子指针
}BiTNode, *BiTree;

//先序遍历的顺序建立二叉链表
void CreateBiTree(BiTree &T){
	char ch; 
	printf("请输入二叉树中节点的值(单字符):"); 
	scanf(" %c", &ch);
	if(ch == '#')	T = NULL;		//递归结束,建立空树	 
	else{							//递归创建二叉树 
		T = new BiTNode;			//生成根节点
		T->data = ch;				//根节点数据域置为ch
		CreateBiTree(T->lchild);	//递归创建左子树
		CreateBiTree(T->rchild);	//递归创建右子树
	}
}

//先序遍历函数
void traverse(BiTree T, int level, int *count, int *MaxDepth){		//level表示当前遍历的节点所在的层次 
	if(T == NULL){
        return;
    }
    if(level > *MaxDepth){ 
        *MaxDepth = level;
    }
    count[level]++;
    traverse(T->lchild, level + 1, count, MaxDepth);
    traverse(T->rchild, level + 1, count, MaxDepth);
}

//计算二叉树的最大宽度
int MaxWidthTree(BiTree T){
	int depth = 0;
    int MaxDepth = 0;
    if(T == NULL){	//二叉树为空,最大宽度为0
        return 0;
    }
	int count[1010] = {0};		//存储每层节点数

    traverse(T, 0, count, &MaxDepth);

    int MaxWidth = 0;		//初始化最大宽度为0
    for(int i = 0; i <= MaxDepth; i++){
        if(count[i] > MaxWidth){
            MaxWidth = count[i];
        }
    }
    return MaxWidth;
}

int main(){
    BiTree T = NULL;
    CreateBiTree(T);
    int width = MaxWidthTree(T);
    printf("二叉树的最大宽度是:%d\n", width);
    return 0;
}

【运算结果】

(6)用按层次顺序遍历二叉树的方法,统计树中度为 1 的节点数目。

【算法描述】

  “度为 1 的节点”指的是只有一个子节点的节点。

int countDegreeOneNodes(BiTree T){
	if(T == NULL){
        return 0;
    }
    int depth = Depth(T);
    int count[1010] = {0};
    int level = 0; 
    int degree = 0;
    
    if(T->lchild != NULL){
        degree++;
    }
    if(T->rchild != NULL){
        degree++;
    }
    if(degree == 1){
        count[level]++;
    }
    count[level] += countDegreeOneNodes(T->lchild);
    count[level] += countDegreeOneNodes(T->rchild);
    int totalCount = 0;
    for(int i = 0; i < depth; i++){
        totalCount += count[i];
    }
    return totalCount;
}

【完整代码】

#include <stdio.h>

//二叉树的二叉链表存储表示
typedef struct BiTNode{
	int data;
	struct BiTNode *lchild, *rchild;	//左右孩子指针
}BiTNode, *BiTree;

//先序遍历的顺序建立二叉链表
void CreateBiTree(BiTree &T){
	char ch; 
	printf("请输入二叉树中节点的值(单字符):"); 
	scanf(" %c", &ch);
	if(ch == '#')	T = NULL;		//递归结束,建立空树	 
	else{							//递归创建二叉树 
		T = new BiTNode;			//生成根节点
		T->data = ch;				//根节点数据域置为ch
		CreateBiTree(T->lchild);	//递归创建左子树
		CreateBiTree(T->rchild);	//递归创建右子树
	}
}

int Depth(BiTree T){
    if (T == NULL)	return 0;		//如果是空树,深度为0,递归结束
	else{
		int leftDepth = Depth(T->lchild);		//递归计算左子树的深度 
		int rightDepth = Depth(T->rchild);		//递归计算右子树的深度
		if(leftDepth > rightDepth) 	return leftDepth+1;
		else return rightDepth+1;
	}
}

//统计度为1的节点数目
int countDegreeOneNodes(BiTree T){
	if(T == NULL){
        return 0;
    }
    int depth = Depth(T);
    int count[1010] = {0};
    int level = 0; 
    int degree = 0;
    
    if(T->lchild != NULL){
        degree++;
    }
    if(T->rchild != NULL){
        degree++;
    }
    if(degree == 1){
        count[level]++;
    }
    count[level] += countDegreeOneNodes(T->lchild);
    count[level] += countDegreeOneNodes(T->rchild);
    int totalCount = 0;
    for(int i = 0; i < depth; i++){
        totalCount += count[i];
    }
    return totalCount;
}

int main(){
    BiTree T = NULL;
    CreateBiTree(T);
    int num = countDegreeOneNodes(T);
    printf("度为1的节点数目是:%d\n", num);
    return 0;
}

【运算结果】

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

【算法描述】

//DFS遍历二叉树,寻找最长路径
void DFS(BiTree T, int depth){
    if(T == NULL) {
        return;
    }
    //将当前节点加入路径
    current_path[depth] = T->data;
    //如果是叶子节点,检查路径长度
    if(T->lchild == NULL && T->rchild == NULL){
        if(depth + 1 > max_length){
            max_length = depth + 1;
            for(int i = 0; i <= depth; i++){
                longest_path[i] = current_path[i];
            }
        }
    }
    //递归遍历左子树和右子树
    DFS(T->lchild, depth + 1);
    DFS(T->rchild, depth + 1);
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>

//二叉树的二叉链表存储表示
typedef struct BiTNode{
	int data;
	struct BiTNode *lchild, *rchild;	//左右孩子指针
}BiTNode, *BiTree;

//先序遍历的顺序建立二叉链表
void CreateBiTree(BiTree &T){
	char ch; 
	printf("请输入二叉树中节点的值(单字符):"); 
	scanf(" %c", &ch);
	if(ch == '#')	T = NULL;		//递归结束,建立空树	 
	else{							//递归创建二叉树 
		T = new BiTNode;			//生成根节点
		T->data = ch;				//根节点数据域置为ch
		CreateBiTree(T->lchild);	//递归创建左子树
		CreateBiTree(T->rchild);	//递归创建右子树
	}
}
//用于存储最长路径的全局变量
int max_length = 0;
int longest_path[100]; 	//假设路径长度不超过100
int current_path[100]; 	//当前路径

//DFS遍历二叉树,寻找最长路径
void DFS(BiTree T, int depth){
    if(T == NULL){
        return;
    }
    //将当前节点加入路径
    current_path[depth] = T->data;
    //如果是叶子节点,检查路径长度
    if(T->lchild == NULL && T->rchild == NULL){
        if(depth + 1 > max_length){
            max_length = depth + 1;
            for(int i = 0; i <= depth; i++){
                longest_path[i] = current_path[i];
            }
        }
    }
    //递归遍历左子树和右子树
    DFS(T->lchild, depth + 1);
    DFS(T->rchild, depth + 1);
}

int main(){
    BiTree T;
    CreateBiTree(T);
    //初始化最长路径
    max_length = 0;

    //从根节点开始DFS遍历
    DFS(T, 0);
    //输出最长路径
    printf("最长路径的长度为:%d\n", max_length);
    printf("路径上的节点值为:");
    for(int i = 0; i < max_length; i++){
        printf("%c ", longest_path[i]);
    }
    printf("\n");
    return 0;
}

【运算结果】

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

【算法描述】

//输出从叶子节点到根节点的路径
void PrintPath(BiTree T, char path[], int pathLen){
    if (T == NULL)
        return;
    //将当前节点加入路径
    path[pathLen] = T->data;
    pathLen++;
    //如果是叶子节点,输出路径
    if(T->lchild == NULL && T->rchild == NULL){
        for(int i = pathLen - 1; i >= 0; i--){
            printf("%c", path[i]);
            if(i > 0)
                printf(" -> ");
        }
        printf("\n");
    }
    else{
        //递归遍历左子树和右子树
        PrintPath(T->lchild, path, pathLen);
        PrintPath(T->rchild, path, pathLen);
    }
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>

//二叉树的二叉链表存储表示
typedef struct BiTNode{
	int data;
	struct BiTNode *lchild, *rchild;	//左右孩子指针
}BiTNode, *BiTree;

//先序遍历的顺序建立二叉链表
void CreateBiTree(BiTree &T){
	char ch; 
	printf("请输入二叉树中节点的值(单字符):"); 
	scanf(" %c", &ch);
	if(ch == '#')	T = NULL;		//递归结束,建立空树	 
	else{							//递归创建二叉树
		T = new BiTNode;			//生成根节点
		T->data = ch;				//根节点数据域置为ch
		CreateBiTree(T->lchild);	//递归创建左子树
		CreateBiTree(T->rchild);	//递归创建右子树
	}
}
//输出从叶子节点到根节点的路径
void PrintPath(BiTree T, char path[], int pathLen){
    if (T == NULL)
        return;
    //将当前节点加入路径
    path[pathLen] = T->data;
    pathLen++;
    //如果是叶子节点,输出路径
    if(T->lchild == NULL && T->rchild == NULL){
        for(int i = pathLen - 1; i >= 0; i--){
            printf("%c", path[i]);
            if(i > 0)
                printf(" -> ");
        }
        printf("\n");
    }
    else{
        //递归遍历左子树和右子树
        PrintPath(T->lchild, path, pathLen);
        PrintPath(T->rchild, path, pathLen);
    }
}

int main(){
    BiTree T;
    CreateBiTree(T);
    char path[100]; 	//假设路径长度不超过100
    int pathLen = 0;
    printf("从每个叶子节点到根节点的路径如下:\n");
    PrintPath(T, path, pathLen);
    return 0;
}

【运算结果】

第六章 图

1. 选择题

(1)在一个无向图中,所有顶点的度数之和等于图的边数的( )倍。
        A. 1/2        B. 1        C. 2        D. 4

【解答】C

        每条边连接两个顶点,因此每条边对应顶点的度数为2,因此所有顶点的度数之和等于所有边数的2倍。

(2)在一个有向图中,所有顶点的入度之和等于所有顶点的出度之和的( )倍。
        A. 1/2        B. 1        C. 2        D. 4

【解答】B
        有向图中所有顶点入度之和等于所有顶点出度之和。

(3)具有 n 个顶点的有向图最多有( )条边。
        A. n        B. n(n-1)        C. n(n+1)        D. n^{2}

【解答】B

        每个顶点都可以有 (n - 1) 条边,又因为有向图有方向之分,然后 n 个顶点,就最多有 n(n - 1)条边。

        若是具有 n 个顶点的无向图,则最多有 n(n - 1)/2 条边。因为无向图没有方向。

(4)n 个顶点的连通图用邻接距阵表示时,该距阵至少有( )个非零元素。
        A. n        B. 2(n-1)        C. n/2        D. n^{2}

【解答】B
        所谓连通图一定是无向图,有向的叫做强连通图。

        连通图:任意一个顶点到另一个顶点直接都有路径。(注!!路径并非指一条边)

        完全图:任意两个点都有一条边相连。

        连通图 \neq 完全图

        连通图有 n 个顶点,则至少只需要 n - 1 条边。由于无向图的每条边同时关联了两个顶点,因此邻接矩阵中每条边被存储了两次(也就是对称矩阵),因此至少有 2(n - 1) 个非零元素。

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

【解答】C
        与第(3)题类似。        

        若是具有 n 个顶点的无向图,则最多有 n(n - 1)/2 条边。

        8 个顶点的无向图最多有 8(8 - 1)/2 = 28 条边,此时的话就是连通图,所以需要再添加一个点即构成非连通无向图,故至少有 9 个顶点。第九个节点是孤立的,不与任何节点通。

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

【解答】B
        首先题目中是无向图,所以排除 C、D 两个选项。        

        从该无向图任意一个顶点出发有到各个顶点的路径, 所以该无向图是连通图。

(7)下面( )算法适合构造一个稠密图 G 的最小生成树。
        A. 普里姆算法(Prim)                B. 克鲁斯卡尔算法(Kruskal)       
        C. 弗洛伊德算法(Floyd)           D. 迪杰斯特拉算法(Dijkstra)

【解答】A
        普里姆算法适合构造一个稠密图 G 的最小生成树。(归并顶点)

        克鲁斯卡尔算法适合构造一个稀疏图 G 的最小生成树。(归并边)

        弗洛伊德算法是求每一对顶点之间的最短路径。

        迪杰斯特拉算法是就从某个源点到其余各顶点的最短路径。

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

【解答】B

        广度优先遍历通常借助队列来实现算法,深度优先遍历通常借助栈来实现算法。

        深度优先遍历使用递归实现,故用到了栈。

        广度优先遍历,每次需要确保当前层的所有结点被访问到,要用队列存储。

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

【解答】A

(10)深度优先遍历类似于二叉树的( )。
        A. 先序遍历        B. 中序遍历        C. 后序遍历        D. 层次遍历

【解答】A

(11)广度优先遍历类似于二叉树的( )。
        A. 先序遍历        B. 中序遍历        C. 后序遍历        D. 层次遍历

【解答】D

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

【解答】C

        BFS:广度优先遍历        DFS:深度优先遍历
        对于一些特殊的图,比如只有一个顶点的图,其 BFS 生成树的树高和 DFS 生成树的树高相等。一般的图,根据图的 BFS 生成树和 DFS 树的算法思想, BFS 生成树的树高比 DFS 生成树的树高小。

(13)已知图的邻接矩阵如图 6.30 所示, 则从顶点 v_{0} 出发按深度优先遍历的结果是( )。

【解答】C

        按深度优先遍历:

        从 v_{0} 开始,看第一行,假设它先遇到的是 v_{1}(也可以是v_{2}v_{3}v_{4}v_{6});

        然后再看 v_{1} 这一行,这里v_{1}v_{0}这个就不需要再看了,因为v_{0}的时候已经算过了,再假设先遇到的顶点是 v_{3}(也可以是v_{6});

        再看 v_{3} 这一行,先遇到 v_{4},再看 v_{4} 这一行,先遇到 v_{2},再看 v_{2} 这一行,发现 0 和 4 都遍历过了,退回到 v_{4} ,v_{3} 也遍历过了,遇到 v_{5},看 v_{5} 这一行,遇到 v_{6},结束。

        综上就是 0 1 3 4 2 5 6 ,所以选 C。

        邻接矩阵唯一 =====> 深度遍历结果不唯一

        再以选项 A 为例:

        首先 v_{0} 到 v_{2} ,然后看 v_{2} 这一行,到 v_{4},再到 v_{3},再到 v_{1},然后看 v_{1} 这一行,发现 v_{1} 到 v_{5} 是没有路径的,所以 A 选项错误。


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


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

【解答】D、D

        由该链表可知,此图为无向图。

        广度优先遍历,则先从 v_{0} 出发,直接读取邻接表第一行,即 0 1 2 3。

        深度优先遍历,从 v_{0} 出发,到 v_{1} ,再看 v_{1} 那一行,到 v_{2},最后再到 v_{3},即 0 1 2 3。

        如果根据该链表将此无向图画出来,再根据图按广度优先遍历的话,C 选项也可以,但是该题已经给出了链表,所以已经确定了遍历的顺序,故只能先遍历 v_{1}

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

【解答】B

2. 应用题

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

        ① 每个顶点的入度和出度;
        ② 邻接矩阵;
        ③ 邻接表;
        ④ 逆邻接表。

【解答】

         ① 每个顶点的入度和出度

                顶点 1 的入度为 3,出度为 0;        顶点 2 的入度为 2,出度为 2;

                顶点 3 的入度为 1,出度为 2;        顶点 4 的入度为 1,出度为 3;

                顶点 5 的入度为 2,出度为 1;        顶点 6 的入度为 2,出度为 3;

        ② 邻接矩阵

               

        ③ 邻接表

          

        ④ 逆邻接表

         

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

        ① 邻接矩阵;
        ② 邻接表;
        ③ 最小生成树

【解答】

        ① 邻接矩阵

          

        ② 邻接表                                                   
        ③ 最小生成树

           根据普里姆算法或者克鲁斯卡尔算法可以得到对应的最小生成树。

          

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

【解答】

                   

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

【解答】

由上图可知顶点 a 到各个顶点的最短路径为:b----15、c----2、d----11、e----10、f----6、g----14。

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


        ① 求这个工程最早可能在什么时间结束;
        ② 求每个活动的最早开始时间和最迟开始时间;
        ③ 确定哪些活动是关键活动。

【解答】

ve(0) = 0, ve(i) = max\left \{ ve(k) + w_{k, i} \right \}

vl(n-1) = ve(n-1), vl(i) = min\left \{ vi(k)-w_{i,k} \right \}

顶点的发生时间
顶点ve(i)vl(i)
100
21919
31515
42937
53838
64343

e(i) = ve(j), l(i) = vl(k) - w_{j, k}

活动的开始时间
活动e(i)l(i)l(i) - e(i)
<1, 2>01717
<1, 3>000
<3, 2>15150
<2, 4>19278
<2, 5>19190
<3, 5>152712
<4, 6>29378
<5, 6>38380

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

        由第一张表可以看出,该工程最早完成结束的时间为 43。
② 求每个活动的最早开始时间和最迟开始时间

        见第二张表,e(i) 表示活动的最早开始时间,l(i) 表示活动最晚开始时间。
③ 确定哪些活动是关键活动

        对于关键活动,e(i) = l(i),即 l(i) - e(i) = 0,根据第二张表,可看出关键活动有:

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

1、以邻接矩阵作为存储结构

【算法描述】

// ①增加一个新顶点 v,函数为 InsertVex(G,v);
void InsertVex(AMGraph *G, char v){
    if(G->numVertices >= MAXVEX){
        printf("图已满,无法添加新顶点\n");
        return;
    }
    G->vexs[G->numVertices] = v;
    for(int i = 0; i <= G->numVertices; i++){
        G->arc[G->numVertices][i] = INFINITY;
        G->arc[i][G->numVertices] = INFINITY;
    }
    G->numVertices++;
}

// ②删除顶点 v 及其相关的边,函数为 DeleteVex(G,v);
void DeleteVex(AMGraph *G, char v){
    int index = LocateVex(*G, v);
    if(index == -1){
        printf("顶点不存在\n");
        return;
    }
    for(int i = index; i < G->numVertices - 1; i++){
        G->vexs[i] = G->vexs[i + 1];
    }
    for(int i = index; i < G->numVertices - 1; i++){
        for(int j = 0; j < G->numVertices; j++){
            G->arc[i][j] = G->arc[i + 1][j];
        }
    }
    for(int i = 0; i < G->numVertices; i++){
        for(int j = index; j < G->numVertices - 1; j++){
            G->arc[i][j] = G->arc[i][j + 1];
        }
    }
    G->numVertices--;
}

// ③ 增加一条边 <v, w>,函数为 InsertArc(G,v,w);
void InsertArc(AMGraph *G, char v, char w){
    int vIndex = LocateVex(*G, v);
    int wIndex = LocateVex(*G, w);
    if(vIndex == -1 || wIndex == -1){
        printf("顶点不存在\n");
        return;
    }
    G->arc[vIndex][wIndex] = 1;
}

// ④ 删除一条边 <v, w>,函数为 DeleteArc(G , v, w)。
void DeleteArc(AMGraph *G, char v, char w){
    int vIndex = LocateVex(*G, v);
    int wIndex = LocateVex(*G, w);
    if(vIndex == -1 || wIndex == -1){
        printf("顶点不存在\n");
        return;
    }
    G->arc[vIndex][wIndex] = INFINITY;
}

2、以邻接表作为存储结构

【算法描述】

// ①增加一个新顶点 v,函数为 InsertVex(G,v);
void InsertVex(ALGraph *G, VertexType v){
    if(G->numVertices >= MAXVEX){
        printf("图已满,无法添加新顶点\n");
        return;
    }
    G->vertices[G->numVertices].data = v;
    G->vertices[G->numVertices].firstarc = NULL;
    G->numVertices++;
}

// ②删除顶点 v 及其相关的边,函数为 DeleteVex(G,v);
void DeleteVex(ALGraph *G, VertexType v){
    int index = LocateVex(*G, v);
    if(index == -1){
        printf("顶点不存在\n");
        return;
    }
    for(int i = 0; i < G->numVertices; i++){
        if(i != index){
            DeleteArc(G, i, index);
        }
    }
    for(int i = index; i < G->numVertices - 1; i++){
        G->vertices[i] = G->vertices[i + 1];
    }
    G->numVertices--;
}
// ③ 增加一条边 <v, w>,函数为 InsertArc(G,v,w);
void InsertArc(ALGraph *G, char v, char w){
    iint vIndex = LocateVex(G, v);
    int wIndex = LocateVex(G, w);
    if(vIndex == -1 || wIndex == -1){
        printf("顶点不存在\n");
        return;
    }
    ArcNode *e = (ArcNode *)malloc(sizeof(ArcNode));
    e->adjvex = wIndex;
    e->nextarc = G->vertices[vIndex].firstarc;
    G->vertices[vIndex].firstarc = e;
    G->numEdges++;
}

// ④ 删除一条边 <v, w>,函数为 DeleteArc(G, v, w)。
void DeleteArc(ALGraph *G, char v, char w){
    int vIndex = LocateVex(G, v);
    int wIndex = LocateVex(G, w);
    if(vIndex == -1 || wIndex == -1){
        printf("顶点不存在\n");
        return;
    }
    ArcNode *p = G->vertices[vIndex].firstarc;
    ArcNode *pre = NULL;
    while(p != NULL && p->adjvex != wIndex){
        pre = p;
        p = p->nextarc;
    }
    if(p != NULL){
        if (pre == NULL){
            G->vertices[vIndex].firstarc = p->nextarc;
        }
        else{
            pre->nextarc = p->nextarc;
        }
        free(p);
        G->numEdges--;
    }
}

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

【算法描述】

void DFS(ALGraph G, int v){
    int visited[MVNum] = {0}; 	//访问标记数组
    Stack S;
    InitStack(&S);
    Push(&S, v); 	//起始顶点入栈

    while(!IsEmpty(&S)){
        int u = Pop(&S);
        if(!visited[u]){ 		//如果顶点u未被访问
            printf("%d ", u); 	//访问顶点u
            visited[u] = 1; 	//标记顶点u已访问
        }
        //遍历顶点u的所有邻接点
        for(ArcNode *p = G.vertices[u].firstarc; p != NULL; p = p->nextarc){
            if (!visited[p->adjvex]) { 		//如果邻接点未被访问
                Push(&S, p->adjvex); 		//邻接点入栈
            }
        }
    }
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>

typedef int VerTexType; 	//顶点数据类型为int
typedef int OtherInfo; 		//边相关的信息类型
 
//图的邻接表存储表示
#define MVNum 100	//最大顶点数
typedef struct ArcNode{		//边节点 
	int adjvex;			//该边所指向的顶点的位置
	struct ArcNode * nextarc;		//指向下一条边的指针
	OtherInfo info;		//和边相关的信息 
}ArcNode;
typedef struct VNode{		//顶点信息 
	VerTexType data;
	ArcNode *firstarc;		//指向第一条依附该顶点的边的指针 
}VNode, AdjList[MVNum];		//AdjList表示邻接表类型
typedef struct{			//邻接表 
	AdjList vertices;
	int vexnum, arcnum;		//图的当前顶点数和边数 
}ALGraph;
//栈的定义
typedef struct{
    int data[MVNum];
    int top;
} Stack;
//初始化栈
void InitStack(Stack *S){
    S->top = -1;
}
//入栈
void Push(Stack *S, int v){
    if (S->top == MVNum - 1) return; //栈满
    S->data[++S->top] = v;
}
//出栈
int Pop(Stack *S){
    if (S->top == -1) return -1; //栈空
    return S->data[S->top--];
}
//判断栈是否为空
int IsEmpty(Stack *S){
    return S->top == -1;
}
//创建图
void CreateGraph(ALGraph *G){
    printf("输入顶点数和边数:");
    scanf("%d %d", &G->vexnum, &G->arcnum);
    for(int i = 0; i < G->vexnum; ++i){
        G->vertices[i].firstarc = NULL;
    }
    for(int i = 0; i < G->arcnum; ++i){
        int u, v;
        printf("输入边(u, v):");
        scanf("%d %d", &u, &v);
        ArcNode *edge = (ArcNode *)malloc(sizeof(ArcNode));
        edge->adjvex = v;
        edge->nextarc = G->vertices[u].firstarc;
        G->vertices[u].firstarc = edge;
    }
}
//深度优先遍历的非递归实现
void DFS(ALGraph G, int v){
    int visited[MVNum] = {0}; 	//访问标记数组
    Stack S;
    InitStack(&S);
    Push(&S, v); 	//起始顶点入栈

    while(!IsEmpty(&S)){
        int u = Pop(&S);
        if(!visited[u]){ 		//如果顶点u未被访问
            printf("%d ", u); 	//访问顶点u
            visited[u] = 1; 	//标记顶点u已访问
        }
        //遍历顶点u的所有邻接点
        for(ArcNode *p = G.vertices[u].firstarc; p != NULL; p = p->nextarc){
            if (!visited[p->adjvex]) { 		//如果邻接点未被访问
                Push(&S, p->adjvex); 		//邻接点入栈
            }
        }
    }
}
int main(){
    ALGraph G;
    CreateGraph(&G);
    int startVertex;
    printf("输入深度优先搜索的起始顶点:");
    scanf("%d", &startVertex);
    DFS(G, startVertex);
    return 0;
}

【运算结果】

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

【算法描述】

int FindMax(ALGraph G, int v){
    int visited[MVNum];
    int dist[MVNum];
    memset(visited, 0, sizeof(visited));
    memset(dist, 0, sizeof(dist));
    Queue Q;
    InitQueue(&Q);
    EnQueue(&Q, v);
    visited[v] = 1;
    int farthestVertex = v;
    int maxDist = 0;
    while(!IsEmpty(&Q)){
        int u = DeQueue(&Q);
        ArcNode *p = G.vertices[u].firstarc;
        while(p){
            int w = p->adjvex;
            if(!visited[w]){
                visited[w] = 1;
                dist[w] = dist[u] + 1;
                EnQueue(&Q, w);
                if(dist[w] > maxDist){
                    maxDist = dist[w];
                    farthestVertex = w;
                }
            }
            p = p->nextarc;
        }
    }
    return farthestVertex;
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef int VerTexType; 	//顶点数据类型为int
typedef int OtherInfo; 		//边相关的信息类型

//图的邻接表存储表示
#define MVNum 100	//最大顶点数
typedef struct ArcNode{		//边节点 
	int adjvex;			//该边所指向的顶点的位置
	struct ArcNode * nextarc;		//指向下一条边的指针
	OtherInfo info;		//和边相关的信息 
}ArcNode;
typedef struct VNode{		//顶点信息 
	VerTexType data;
	ArcNode *firstarc;		//指向第一条依附该顶点的边的指针 
}VNode, AdjList[MVNum];		//AdjList表示邻接表类型
typedef struct{			//邻接表 
	AdjList vertices;
	int vexnum, arcnum;		//图的当前顶点数和边数 
}ALGraph;
//队列的存储结构
typedef struct{
	int data[MVNum];
	int front, rear;
}Queue;
//初始化队列
void InitQueue(Queue *Q){
    Q->front = Q->rear = 0;
}
//判断队列是否为空
int IsEmpty(Queue *Q){
    return Q->front == Q->rear;
}
//入队
void EnQueue(Queue *Q, int v){
    Q->data[Q->rear++] = v;
}
//出队
int DeQueue(Queue *Q){
    return Q->data[Q->front++];
}
//创建图
void CreateGraph(ALGraph *G){
    printf("请输入顶点数和边数:");
    scanf("%d %d", &G->vexnum, &G->arcnum);
    for(int i = 0; i < G->vexnum; i++){
        G->vertices[i].data = i;
        G->vertices[i].firstarc = NULL;
    }
    for(int i = 0; i < G->arcnum; i++){
        int u, v;
        printf("请输入边 (u, v):");
        scanf("%d %d", &u, &v);
        ArcNode *newNode = (ArcNode *)malloc(sizeof(ArcNode));
        newNode->adjvex = v;
        newNode->nextarc = G->vertices[u].firstarc;
        G->vertices[u].firstarc = newNode;
    }
}
int FindMax(ALGraph G, int v){
    int visited[MVNum];
    int dist[MVNum];
    memset(visited, 0, sizeof(visited));
    memset(dist, 0, sizeof(dist));
    Queue Q;
    InitQueue(&Q);
    EnQueue(&Q, v);
    visited[v] = 1;
    int farthestVertex = v;
    int maxDist = 0;
    while(!IsEmpty(&Q)){
        int u = DeQueue(&Q);
        ArcNode *p = G.vertices[u].firstarc;
        while(p){
            int w = p->adjvex;
            if(!visited[w]){
                visited[w] = 1;
                dist[w] = dist[u] + 1;
                EnQueue(&Q, w);
                if(dist[w] > maxDist){
                    maxDist = dist[w];
                    farthestVertex = w;
                }
            }
            p = p->nextarc;
        }
    }
    return farthestVertex;
}
int main(){
    ALGraph G;
    CreateGraph(&G);
    int v;
    printf("请输入起始顶点 v:");
    scanf("%d", &v);
    int farthest = FindFMax(G, v);
    printf("距离顶点 %d 最短路径长度最大的顶点是:%d\n", v, farthest);
    return 0;
}

【运算结果】

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

【算法描述】

int HasPath(ALGraph *G, int i, int j){
    if(i == j){
        return 0;
    }
    int visited[MVNum];
    memset(visited, 0, sizeof(visited));
    return DFS(G, i, j, visited);
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef int VerTexType; 	//顶点数据类型为int
typedef int OtherInfo; 		//边相关的信息类型

//图的邻接表存储表示
#define MVNum 100	//最大顶点数
typedef struct ArcNode{		//边节点 
    int adjvex;			//该边所指向的顶点的位置
    struct ArcNode * nextarc;		//指向下一条边的指针
    OtherInfo info;		//和边相关的信息 
}ArcNode;
typedef struct VNode{		//顶点信息 
    VerTexType data;
    ArcNode *firstarc;		//指向第一条依附该顶点的边的指针 
}VNode, AdjList[MVNum];		//AdjList表示邻接表类型
typedef struct{			//邻接表 
    AdjList vertices;
    int vexnum, arcnum;		//图的当前顶点数和边数 
}ALGraph;
//创建图
void CreateGraph(ALGraph *G){
    printf("请输入顶点数和边数:");
    scanf("%d %d", &G->vexnum, &G->arcnum);
    for(int i = 0; i < G->vexnum; i++){
        G->vertices[i].data = i;
        G->vertices[i].firstarc = NULL;
    }
    for(int i = 0; i < G->arcnum; i++){
        int u, v;
        printf("请输入边 (u, v):");
        scanf("%d %d", &u, &v);
        ArcNode *newNode = (ArcNode *)malloc(sizeof(ArcNode));
        newNode->adjvex = v;
        newNode->nextarc = G->vertices[u].firstarc;
        G->vertices[u].firstarc = newNode;
    }
}
//深度优先搜索
int DFS(ALGraph *G, int i, int j, int *visited){
    if(i == j){
        return 1; //找到目标顶点
    }
    visited[i] = 1; 	//标记当前顶点为已访问
    ArcNode *p = G->vertices[i].firstarc;
    while(p != NULL){
        int k = p->adjvex;
        if(!visited[k]){
            if(DFS(G, k, j, visited)){
                return 1; 	//递归搜索找到路径
            }
        }
        p = p->nextarc;
    }
    return 0; 	//未找到路径
}
//判别是否存在由顶点i到顶点j的路径
int HasPath(ALGraph *G, int i, int j){
    if(i == j){
        return 0;
    }
    int visited[MVNum];
    memset(visited, 0, sizeof(visited));
    return DFS(G, i, j, visited);
}
int main(){
    ALGraph G;
    CreateGraph(&G);
    int i, j;
    printf("请输入起始顶点 i 和目标顶点 j:");
    scanf("%d %d", &i, &j);
    if(HasPath(&G, i, j)){
        printf("存在由顶点 %d 到顶点 %d 的路径。\n", i, j);
    } 
	else{
        printf("不存在由顶点 %d 到顶点 %d 的路径。\n", i, j);
    }
    return 0;
}

【运算结果】

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

【算法描述】

int HasLengthKPath(ALGraph *G, int start, int end, int k){
    int visited[MVNum];
    memset(visited, 0, sizeof(visited));
    return DFS(G, start, end, k, visited);
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef int VerTexType; 	//顶点数据类型为int
typedef int OtherInfo; 		//边相关的信息类型

//图的邻接表存储表示
#define MVNum 100	//最大顶点数
typedef struct ArcNode{		//边节点 
    int adjvex;			//该边所指向的顶点的位置
    struct ArcNode * nextarc;		//指向下一条边的指针
    OtherInfo info;		//和边相关的信息 
}ArcNode;
typedef struct VNode{		//顶点信息 
    VerTexType data;
    ArcNode *firstarc;		//指向第一条依附该顶点的边的指针 
}VNode, AdjList[MVNum];		//AdjList表示邻接表类型
typedef struct{			//邻接表 
    AdjList vertices;
    int vexnum, arcnum;		//图的当前顶点数和边数 
}ALGraph;
//创建图
void CreateGraph(ALGraph *G){
    printf("请输入顶点数和边数:");
    scanf("%d %d", &G->vexnum, &G->arcnum);
    for(int i = 0; i < G->vexnum; i++){
        G->vertices[i].data = i;
        G->vertices[i].firstarc = NULL;
    }
    for(int i = 0; i < G->arcnum; i++){
        int u, v;
        printf("请输入边 (u, v):");
        scanf("%d %d", &u, &v);
        //无向图需要添加两条边
        ArcNode *newNode1 = (ArcNode *)malloc(sizeof(ArcNode));
        newNode1->adjvex = v;
        newNode1->nextarc = G->vertices[u].firstarc;
        G->vertices[u].firstarc = newNode1;

        ArcNode *newNode2 = (ArcNode *)malloc(sizeof(ArcNode));
        newNode2->adjvex = u;
        newNode2->nextarc = G->vertices[v].firstarc;
        G->vertices[v].firstarc = newNode2;
    }
}
//深度优先搜索
int DFS(ALGraph *G, int start, int end, int k, int *visited){
    if(k == 0){
        return start == end;
    }
    visited[start] = 1;
    ArcNode *p = G->vertices[start].firstarc;
    while(p != NULL){
        int next = p->adjvex;
        if(!visited[next]){
            if(DFS(G, next, end, k - 1, visited)){
                visited[start] = 0;
                return 1;
            }
        }
        p = p->nextarc;
    }
    visited[start] = 0;
    return 0;
}
//判断是否存在长度为 k 的简单路径
int HasLengthKPath(ALGraph *G, int start, int end, int k){
    int visited[MVNum];
    memset(visited, 0, sizeof(visited));
    return DFS(G, start, end, k, visited);
}
int main(){
    ALGraph G;
    CreateGraph(&G);
    int start, end, k;
    printf("请输入起始顶点、目标顶点和路径长度 k:");
    scanf("%d %d %d", &start, &end, &k);
    if(HasLengthKPath(&G, start, end, k)){
        printf("存在长度为 %d 的简单路径从顶点 %d 到顶点 %d。\n", k, start, end);
    } 
	else{
        printf("不存在长度为 %d 的简单路径从顶点 %d 到顶点 %d。\n", k, start, end);
    }
    return 0;
}

【运算结果】

第七章 查找

1. 选择题

(1)对 n 个元素的表做顺序查找时, 若查找每个元素的概率相同, 则平均查找长度为( )。
        A. (n-1) / 2
        B. n / 2
        C. (n+1) / 2
        D. n

【解答】C
        总查找次数N = 1 + 2 + 3 + ...... + n = n(n+1) / 2 ,则平均查找长度为 N / n = (n+1) / 2。

        因为每个元素查找的概率相同,所以P_{i}=\frac{1}{n}

        故ASL=\sum_{i=1}^{n}P_{i}C_{i}=\frac{1}{n}\sum_{i=1}^{n}C_{i}=\frac{1}{n}(1+2+3+......+n)=\frac{n+1}{2}

(2)适用于折半查找的表的存储方式及元素排列要求为( )。
        A. 链接方式存储,元素无序
        B. 链接方式存储,元素有序
        C. 顺序方式存储,元素无序
        D. 顺序方式存储,元素有序

【解答】D
        折半查找要求线性表必须采用顺序存储结构, 而且表中元素按关键字有序排列。

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

【解答】C


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

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

【解答】A
        表中共 10 个元素, 第一次取 mid=(1+10)/2=5,与第五个元素 20 比较, 58 大于 20,low=mid+1,即看[6,10],再取 (6+10)/2=8,与第八个元素 70 比较,依次类推再与 30 、 50 比较,最终查找失败。

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

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

至少需要4次,
第一次:与第11个位置上的数进行比较 mid=(1+22)/2=11,查找失败进入[1,10]区域进行查找
第二次:与第5个位置上的数进行比较 mid=(1+10)/2=5,查找失败进入[1,4]区域进行查找
第三次:与第2个位置上的数进行比较 mid=(1+4)/2=2,查找失败进入[1,2]区域进行查找
第四次:与第1个位置上的数进行比较 查找失败说明不存在该关键字

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

【解答】C

(7)分别以下列序列构造二叉排序树,与用其它三个序列所构造的结果不同的是( )。
        A.( 100 , 80, 90 , 60, 120 , 110 , 130)
        B.( 100 , 120 , 110 , 130 , 80, 60 , 90)
        C.( 100 , 60, 80 , 90, 120 , 110 , 130)
        D.(100 , 80 , 60, 90 , 120 , 130 , 110)

【解答】C
        A 、 B、 C、 D 四个选项构造二叉排序树都以 100 为根,易知 A 、 B、 D 三个序列中第一个比 100 小的关键字为 80,即 100 的左孩子为 80 ,而 C 选项中 100 的左孩子为 60,故选 C。

        A、B、D所构造的二叉排序树如下:

        而 C 选项对应的二叉排序树如下:

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

【解答】C

        该题最简单的插入后的树如下:

        由图可知,是在右孩子的左孩子进行插入,故应进行 RL 旋转调整。

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

【解答】C

        根节点关键字数量最少为 1,最多为 m - 1。

        其他非叶节点关键字数量最少为 m / 2 (向上取整),最多为 m - 1。

        根节点可以为终端节点,那么没有子树。

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

【解答】C

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

【解答】B

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

【解答】C

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

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

(14)设哈希表长为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。

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

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

2. 应用题

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

【解答】

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

    
② 若查找元素 54,需依次与哪些元素比较?

        若查找元素 54,需依次与30、63、42、54元素比较。
③ 若查找元素 90,需依次与哪些元素比较?

        若查找元素 90,需依次与30、63、87、95元素比较。’
④ 假定每个元素的查找概率相等,求查找成功时的平均查找长度。                                              ASL = \frac{1}{12}(1\times 1+2\times2+4\times3+5\times4)\approx 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)。
        ① 试按表中元素的顺序依次插入一棵初始为空的二叉排序树,画出插入完成之后的二叉排序树,并求其在等概率的情况下查找成功的平均查找长度。
        ② 若对表中元素先进行排序构成有序表,求在等概率的情况下对此有序表进行折半查找时查找成功的平均查找长度。
        ③ 按表中元素顺序构造一棵平衡二叉排序树,并求其在等概率的情况下查找成功的平均查找长度。

【解答】

          二叉排序树如下:

          

        其平均查找长度 ASL = 1/12 (1*1+2*2+3*3+3*4+2*5+1*6) = 3.5。

        ② 经过排序后(通过字母先后)的有序表为:(8

        折半查找:

        

        故查找成功的平均查找长度为:

        ASL_{succ} = \frac{1}{12}(1\times1+2\times2+3\times4+4\times5)=\frac{37}{12}

        ③ 平衡二叉树:每个子树的左右高度差都不超过一。                         

        

        故查找成功的平均查找长度为:

        ASL_{succ} = \frac{1}{12}(1\times1+2\times2+3\times3+4\times4+5\times1)=\frac{19}{6}

(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,需要依次与哪些关键字进行比较?
        ④ 假定每个关键字的查找概率相等,求查找成功时的平均查找长度。

【解答】

由题意,表长为18,即 m = 18,线性探测法,d = 1, 2, 3, ......, m-1。

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

        63 % 16 = 15,所以 63 要与地址 15 内存放的关键字进行比较,即 63 和 31 比较,不匹配,往后移(用线性探测法)。(15+1) % 18 = 16,依次类推,故要查找关键字 63,需要依次与 31、36、47、32、17、63 比较,一共 6 次比较。

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

        与第二题类似,要查找关键字 60,60 % 16 = 12,所以 60 要与地址 12 内存放的关键字进行比较,而地址 12 存放的内容为空,即只需比较一次。

 评均查找长度 ASL=\frac{1}{11}(1\times6+6+3\times3+2)=\frac{23}{11}

(6)设有一组关键字 (9,01,23,14,55,20,84,27),采用散列函数 H(key) = key % 7,表长为 10,用开放地址法的二次探测法处理冲突。要求:对该关键字序列构造散列表,并计算查找成功的平均查找长度。限定 d_{i} 取值为 1^{2},2^{2},\cdots ,k^{2}(k\leqslant m/2)

【解答】

查找成功的平均查找长度为:

        ASL_{succ}=\frac{1}{8}(1\times4+2\times2+3+4)=\frac{15}{8}

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

【解答】

① 线性探测法。

        ASL_{succ}=\frac{1}{8}(1\times5+2\times3)=\frac{11}{8}

        ASL_{unsucc}=\frac{1}{11}(1+2+1+8+7+6+5+4+3+2+1)=\frac{40}{11}

② 链地址法。

        ASL_{succ}=\frac{1}{8}(1\times5+2\times3)=\frac{11}{8}

        ASL_{unsucc}=\frac{1}{11}(1+2+1+2+3+1+3+1+3+1+1)=\frac{19}{11}

3. 算法设计题

(1)试设计折半查找的递

折半查找 即 二分查找,查找过程:从中间记录开始,如果给定值和中间记录的关键字相等,则查找成功;如果大于或小于中间记录的关键字,则在大于或小于中间记录的那一半查找,直到查找成功。

【算法描述】

//折半查找的递归函数
int Search(int arr[], int left, int right, int target){
    //如果左边界大于右边界
    if(left > right){
        return -1;
    }

    //计算中间记录的索引
    int mid = left + (right - left) / 2;

    //如果给定值等于中间记录的关键字,返回索引
    if(arr[mid] == target){
        return mid;
    }
    //果给定值大于中间记录的关键字,在左半部分继续查找
    else if(arr[mid] > target){
        return Search(arr, left, mid - 1, target);
    }
    //果给定值小于于中间记录的关键字,在右半部分继续查找
    else{
        return Search(arr, mid + 1, right, target);
    }
}

【完整代码】

#include <stdio.h>

//折半查找的递归函数
int Search(int arr[], int left, int right, int target){
    //如果左边界大于右边界
    if(left > right){
        return -1;
    }

    //计算中间记录的索引
    int mid = left + (right - left) / 2;

    //如果给定值等于中间记录的关键字,返回索引
    if(arr[mid] == target){
        return mid;
    }
    //果给定值大于中间记录的关键字,在左半部分继续查找
    else if(arr[mid] > target){
        return Search(arr, left, mid - 1, target);
    }
    //果给定值小于于中间记录的关键字,在右半部分继续查找
    else{
        return Search(arr, mid + 1, right, target);
    }
}

int main(){
    int n;
    printf("请输入数组的元素个数: ");
    scanf("%d", &n);
    int arr[100];
    printf("请输入%d个有序的整数(从小到大): ", n);
    for(int i = 0; i < n; i++){
        scanf("%d", &arr[i]);
    }
    int target;
    printf("请输入要查找的给定值: ");
    scanf("%d", &target);
    int result = Search(arr, 0, n - 1, target);
    if(result != -1){
        printf("元素%d在数组中的索引是 %d。\n", target, result);
    } 
	else{
        printf("元素%d不在数组中。\n", target);
    }
    return 0;
}

【运算结果】

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

二叉排序树的定义:

(1)若它的左子树不空,则左子树上所有节点的值均小于它的根结点的值;

(2)若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;

(3)它的左、右子树也分别为二叉排序树。 

【算法描述】

int isBSTtree(BSTree root, int *prev){
    if(root == NULL){
        return 1;
    }
    //递归检查左子树
    if(!isBSTtree(root->lchild, prev)){
        return 0;
    }
    //检查当前节点值是否大于前一个节点值
    if(root->data <= *prev){
        return 0;
    }
    *prev = root->data;
    //递归检查右子树
    return isBSTtree(root->rchild, prev);
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
//定义二叉树节点结构
typedef struct BSTNode{
    int data;
    struct BSTNode *lchild, *rchild;
}BSTNode, *BSTree;
//根据前序遍历输入构建二叉树
BSTree createBSTree(){
    char ch;
    scanf("%c", &ch);
    if(ch == '#'){
        return NULL;
    }
	else{
        BSTree node = (BSTree)malloc(sizeof(BSTNode));
        node->data = ch - '0';  	//将字符转换为整数
        node->lchild = createBSTree();
        node->rchild = createBSTree();
        return node;
    }
}
//中序遍历检查是否为二叉排序树
int isBSTtree(BSTree root, int *prev){
    if(root == NULL){
        return 1;
    }
    //递归检查左子树
    if(!isBSTtree(root->lchild, prev)){
        return 0;
    }
    //检查当前节点值是否大于前一个节点值
    if(root->data <= *prev){
        return 0;
    }
    *prev = root->data;
    //递归检查右子树
    return isBSTtree(root->rchild, prev);
}
//判断二叉树是否为二叉排序树
int isBST(BSTree root){
    int prev = INT_MIN;
    return isBSTtree(root, &prev);
}
int main(){
    printf("请以前序遍历的方式输入二叉树节点值,用 # 表示空节点:\n");
    BSTree root = createBSTree();
    if(isBST(root)){
        printf("该二叉树是二叉排序树。\n");
    }
	else{
        printf("该二叉树不是二叉排序树。\n");
    }
    return 0;
}

【运算结果】

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

【算法描述】

void printNodesGreaterEqualX(BSTree T, int x){
    if(T == NULL) return;
    //先递归处理左子树
    printNodesGreaterEqualX(T->lchild, x);
    //如果当前节点的值大于等于 x,则输出该节点的值
    if(T->data >= x){
        printf("%d ", T->data);
    }
    //再递归处理右子树
    printNodesGreaterEqualX(T->rchild, x);
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
//定义二叉树节点结构
typedef struct BSTNode{
    int data;
    struct BSTNode *lchild, *rchild;
} BSTNode, *BSTree;
//插入节点到二叉排序树
BSTree insertNode(BSTree T, int key){
    if(T == NULL){
        BSTree newNode = (BSTree)malloc(sizeof(BSTNode));
        newNode->data = key;
        newNode->lchild = newNode->rchild = NULL;
        return newNode;
    }
    if(key < T->data){
        T->lchild = insertNode(T->lchild, key);
    }
	else if(key > T->data){
        T->rchild = insertNode(T->rchild, key);
    }
    return T;
}
//从小到大输出二叉排序树中所有数据值大于等于 x 的节点的数据
void printNodesGreaterEqualX(BSTree T, int x){
    if(T == NULL) return;
    //先递归处理左子树
    printNodesGreaterEqualX(T->lchild, x);
    //如果当前节点的值大于等于 x,则输出该节点的值
    if(T->data >= x){
        printf("%d ", T->data);
    }
    //再递归处理右子树
    printNodesGreaterEqualX(T->rchild, x);
}
int main(){
    BSTree T = NULL;
    int n, key, x;
    //输入节点数量
    printf("请输入二叉排序树的节点数量: ");
    scanf("%d", &n);
    //输入节点数据并构建二叉排序树
    printf("请依次输入 %d 个节点的数据:\n", n);
    for(int i = 0; i < n; i++){
        scanf("%d", &key);
        T = insertNode(T, key);
    }
    printf("请输入 x 的值: ");
    scanf("%d", &x);
    printf("二叉排序树中所有数据值大于等于 %d 的节点的数据为: ", x);
    printNodesGreaterEqualX(T, x);
    printf("\n");
    return 0;
}

【运算结果】

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

【算法描述】

Node* insert(Node* root, int data, int* count){
    Node* current = root;
    Node* parent = NULL;
    while(current != NULL){
        if(current->data == data){
            current->count++;
            *count = current->count;
            return root;
        }
        parent = current;
        if(data < current->data){
            current = current->llink;
        }
		else{
            current = current->rlink;
        }
    }
    Node* newNode = createNode(data);
    if(parent == NULL){
        root = newNode;
    }
	else if(data < parent->data){
        parent->llink = newNode;
    }
	else{
        parent->rlink = newNode;
    }
    *count = 0;
    return root;
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
//定义二叉树节点结构体
typedef struct Node{
    struct Node *llink;
    int data;
    int count;
    struct Node *rlink;
}Node;
//创建新节点
Node* createNode(int data){
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->count = 0;
    newNode->llink = NULL;
    newNode->rlink = NULL;
    return newNode;
}
//插入节点到二叉排序树
Node* insert(Node* root, int data, int* count){
    Node* current = root;
    Node* parent = NULL;
    while(current != NULL){
        if(current->data == data){
            current->count++;
            *count = current->count;
            return root;
        }
        parent = current;
        if(data < current->data){
            current = current->llink;
        }
		else{
            current = current->rlink;
        }
    }
    Node* newNode = createNode(data);
    if(parent == NULL){
        root = newNode;
    }
	else if(data < parent->data){
        parent->llink = newNode;
    }
	else{
        parent->rlink = newNode;
    }
    *count = 0;
    return root;
}
//中序遍历打印二叉树
void inorderPrint(Node *root){
    if(root != NULL){
        inorderPrint(root->llink);
        printf("%d ", root->data);
        inorderPrint(root->rlink);
    }
}
int main(){
    Node* root = NULL;
    int value, X, count = 0;
    printf("请输入二叉树的节点值(输入-1结束): \n");
    while(1){
        scanf("%d", &value);
        if(value == -1) break;
        root = insert(root, value, &count);
    }
    printf("请输入要查找或插入的值X: ");
    scanf("%d", &X);
    //查找或插入节点
    root = insert(root, X, &count);
    //如果找到了节点,输出count值
    if(count >= 1){
        printf("值为%d的节点已存在,计数加1,当前计数为: %d\n", X, count);
    }
	else{
        printf("值为%d的节点不存在,已作为新节点插入树中。\n", X);
	    printf("更新后的二叉树的中序遍历结果: ");
	    inorderPrint(root);
	    printf("\n");
    }
    return 0;
}

【运算结果】

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

平衡因子:该节点的左子树和右子树的深度之差(左 - 右),平衡因子只可能是 -1、0和1。

【算法描述】

int height(Node* node){
    if(node == NULL){
        return 0;
    }
    int leftHeight = height(node->left);
    int rightHeight = height(node->right);
    return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
//定义二叉树节点结构体
typedef struct Node{
    int data;
    int b; 		//平衡因子
    struct Node *left;
    struct Node *right;
}Node;
//创建新节点
Node* createNode(int data, int b){
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->b = b;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}
//计算二叉树的高度
int height(Node* node){
    if(node == NULL){
        return 0;
    }
    int leftHeight = height(node->left);
    int rightHeight = height(node->right);
    return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}
//插入节点到二叉排序树
Node* insert(Node* node, int data, int b){
    if(node == NULL){
        return createNode(data, b);
    }
    if (data < node->data) {
        node->left = insert(node->left, data, b);
    } else if (data > node->data) {
        node->right = insert(node->right, data, b);
    }
    return node;
}
int main(){
    int n, data, b;
    Node* root = NULL;
    printf("请输入节点数量: ");
    scanf("%d", &n);
    for(int i = 0; i < n; i++){
        printf("请输入第 %d 个节点的数据值和平衡因子: ", i + 1);
        scanf("%d %d", &data, &b);
        root = insert(root, data, b);
    }
    printf("平衡二叉树的高度为: %d\n", height(root));
    return 0;
}

【运算结果】

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

【算法描述】

//插入关键字
void insert(HashTable* table, int key){
    int index = H(key, table->size);
    HashNode* newNode = (HashNode*)malloc(sizeof(HashNode));
    newNode->key = key;
    newNode->next = table->buckets[index];
    table->buckets[index] = newNode;
}
//删除关键字
void remove(HashTable* table, int key){
    int index = H(key, table->size);
    HashNode* current = table->buckets[index];
    HashNode* prev = NULL;
    while(current != NULL && current->key != key){
        prev = current;
        current = current->next;
    }
    if(current == NULL){
        printf("关键字 %d 不存在。\n", key);
        return;
    }
    if(prev == NULL){
        table->buckets[index] = current->next;
    }
	else{
        prev->next = current->next;
    }
    free(current);
    printf("关键字 %d 已删除。\n", key);
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
//定义散列表节点结构体
typedef struct HashNode{
    int key;              	//关键字
    struct HashNode* next; 	//指向下一个节点的指针
}HashNode;
//定义散列表结构体
typedef struct HashTable{
    HashNode** buckets; 	//散列地址数组,每个散列地址是一个链表的头指针
    int size;           	//散列表的大小
}HashTable;
//散列函数
int H(int key, int size){
    return key % size;
}
//创建散列表
HashTable* createHashTable(int size){
    HashTable* table = (HashTable*)malloc(sizeof(HashTable));
    table->size = size;
    table->buckets = (HashNode**)malloc(sizeof(HashNode*) * size);
    for (int i = 0; i < size; i++) {
        table->buckets[i] = NULL;
    }
    return table;
}
//插入关键字
void insert(HashTable* table, int key){
    int index = H(key, table->size);
    HashNode* newNode = (HashNode*)malloc(sizeof(HashNode));
    newNode->key = key;
    newNode->next = table->buckets[index];
    table->buckets[index] = newNode;
}
//删除关键字
void remove(HashTable* table, int key){
    int index = H(key, table->size);
    HashNode* current = table->buckets[index];
    HashNode* prev = NULL;
    while(current != NULL && current->key != key){
        prev = current;
        current = current->next;
    }
    if(current == NULL){
        printf("关键字 %d 不存在。\n", key);
        return;
    }
    if(prev == NULL){
        table->buckets[index] = current->next;
    }
	else{
        prev->next = current->next;
    }
    free(current);
    printf("关键字 %d 已删除。\n", key);
}
//打印散列表
void printHashTable(HashTable* table){
    for(int i = 0; i < table->size; i++){
        printf("散列地址%d: ", i);
        HashNode* current = table->buckets[i];
        while(current != NULL){
            printf("%d ", current->key);
            current = current->next;
        }
        printf("\n");
    }
}
//释放散列表
void freeHashTable(HashTable* table){
    for(int i = 0; i < table->size; i++){
        HashNode* current = table->buckets[i];
        while (current != NULL) {
            HashNode* temp = current;
            current = current->next;
            free(temp);
        }
    }
    free(table->buckets);
    free(table);
}
int main(){
    int size, key, operation;
    printf("请输入散列表的大小: ");
    scanf("%d", &size);
    HashTable* table = createHashTable(size);
    while(1){
        printf("请选择操作(1-插入,2-删除,0-退出): ");
        scanf("%d", &operation);
        if(operation == 0){
            break;
        }
        printf("请输入关键字: ");
        scanf("%d", &key);
        if(operation == 1){
            insert(table, key);
            printf("关键字 %d 已插入。\n", key);
        }
		else if(operation == 2){
            remove(table, key);
        }
        printHashTable(table);
    }
    freeHashTable(table);
    return 0;
}

【运算结果】

第八章 排序

1. 选择题

(1)从未排序序列中依次取出元素与已排序序列中的元素进行比较, 将其放入已排序序
列的正确位置上的方法,这种排序方法称为( )。
        A. 归并排序        B. 冒泡排序        C. 插入排序        D. 选择排序

【解答】C

(2)从未排序序列中挑选元素,并将其依次放入已排序序列(初始时为空)的一端的方
法,称为( )。
        A. 归并排序        B. 冒泡排序        C. 插入排序        D. 选择排序

【解答】D

(3)对 n 个不同的关键字由小到大进行冒泡排序,在下列( )情况下比较的次数最多。
        A. 从小到大排列好的        B. 从大到小排列好的        B. 元素无序        B. 元素基本有序

【解答】B
        对关键字进行冒泡排序,关键字逆序时比较次数最多。

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

【解答】D
        比较次数最多时,第一次比较 n-1 次,第二次比较 n-2 次, 最后一次比较 1 次,

        即 (n-1) + (n-2) +......+ 1 = n(n-1)/2

(5)快速排序在下列( )情况下最易发挥其长处。
        A. 被排序的数据中含有多个相同排序码
        B. 被排序的数据已基本有序
        B. 被排序的数据完全无序
        B. 被排序的数据中的最大值和最小值相差悬殊

【解答】C
        B 选项是快速排序的最坏情况。

(6)对 n 个关键字作快速排序,在最坏情况下,算法的时间复杂度是( )。
        A. O(n)       B. O(n^{2})       B. O(nlog_{2}^{n})        B. O(n^{3})

【解答】B
        快速排序的平均时间复杂度为 O(nlog_{2}^{n}) ,但在最坏情况下,即关键字基本排好
序的情况下,时间复杂度为 O(n^{2})。

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

【解答】C

(8)下列关键字序列中,( )是堆。
        A. 16 , 72, 31, 23 , 94, 53
        B. 94, 23 , 31 , 72 , 16, 53
        B. 16 , 53, 23 , 94, 31, 72
        B. 16 , 23, 53 , 31, 94, 72

【解答】D
        D 选项为小根堆

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

【解答】B

(10)堆的形状是一棵( )。
        A. 二叉排序树        B. 满二叉树        B. 完全二叉树        B. 平衡二叉树

【解答】C

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

【解答】B

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

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

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

【解答】C
        不稳定排序有:希尔排序、简单选择排序、快速排序、堆排序。

        稳定排序有:直接插入排序、折半插入排序、冒泡排序、归并排序、基数排序。

(14)数据表中有 10000 个元素,如果仅要求求出其中最大的 10 个元素,则采用( )算法最节省时间。
        A. 冒泡排序        B. 快速排序        C. 简单选择排序        D. 堆排序

【解答】D

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

【解答】A
        快速排序的每趟排序能将作为枢轴的元素放到最终位置。

        冒泡排序的每趟排序能将最大或最小的元素放到最终位置。

        堆排序的每趟排序能将最大或最小的元素放到最终位置。

2. 应用题

(1)设待排序的关键字序列为 {12,2,16,30,28,10,16* ,20,6,18} ,试分别写出使用以下排序方法,每趟排序结束后关键字序列的状态。
        ① 直接插入排序;
        ② 折半插入排序;
        ③ 希尔排序(增量选取 5、3和1);
        ④ 冒泡排序;
        ⑤ 快速排序;
        ⑥ 简单选择排序;
        ⑦ 堆排序;
        ⑧ 二路归并排序。

【解答】

① 直接插入排序; 

        初始关键字:(12),2,16,30,28,10,16* ,20,6,18

                  i = 1 (2,12),16,30,28,10,16* ,20,6,18

                  i = 2 (2,12,16),30,28,10,16* ,20,6,18

                  i = 3 (2,12,16,30),28,10,16* ,20,6,18

                  i = 4 (2,12,16,28,30),10,16* ,20,6,18

                  i = 5 (2,10,12,16,28,30),16* ,20,6,18

                  i = 6 (2,10,12,16,16*,28,30),20,6,18

                  i = 7 (2,10,12,16,16*,20,28,30),6,18

                  i = 8 (2,6,10,12,16,16*,20,28,30),18

                  i = 9 (2,6,10,12,16,16*,18,20,28,30)

② 折半插入排序;             

        初始关键字:(12),2,16,30,28,10,16* ,20,6,18

                  i = 1 (2,12),16,30,28,10,16* ,20,6,18

                  i = 2 (2,12,16),30,28,10,16* ,20,6,18

                  i = 3 (2,12,16,30),28,10,16* ,20,6,18

                  i = 4 (2,12,16,28,30),10,16* ,20,6,18

                  i = 5 (2,10,12,16,28,30),16* ,20,6,18

                  i = 6 (2,10,12,16,16*,28,30),20,6,18

                  i = 7 (2,10,12,16,16*,20,28,30),6,18

                  i = 8 (2,6,10,12,16,16*,20,28,30),18

                  i = 9 (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)

      

④ 冒泡排序;

         初始关键字:12,2,16,30,28,10,16* ,20,6,18

                第一趟: 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   2   16   30   28   10   16*   20   6   18 }

             第一趟:{ 6   2   10 }  12  { 28   30   16*   20   16   18 }

             第二趟:{ 2 }  6  { 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

            


⑥ 简单选择排序

        初始关键字: 12   2   16   30   28   10   16*   20   6   18 

               第一趟:(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)   30   28

               第九趟:(2   6   10   12   16   16*   18   20   28)   30

⑦ 堆排序;

        初始关键字: 12   2   16   30   28   10   16*   20   6   18

               第一趟: 18   12   16   2   28   10   16*   20   6   30

               第二趟: 6   18   16   20   12   10   16*   2   28   30

               第三趟: 2   6   16   18   12   10   16*   20   28   30

               第四趟: 16*   2   16   6   12   10   18   20   28   30

               第五趟: 10   6   16   2   12   16*   18   20   28   30

               第六趟: 6   12   10   2   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

               第一趟: 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},试按链式基数排序方法,列出每一趟分配和收集的过程。

【解答】

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

【解答】
 

初始归并段:R_{1}:3,19,31,51,61,71,100,101

                      R_{2}:9,17,19,20,30,50,55,90

                      R_{3}:6

3. 算法设计题

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

【算法描述】

void selectionSort(Node* head){
    Node* current = head;
    while(current != NULL){
        Node* minNode = current;
        Node* temp = current->next;
        while(temp != NULL){
            if(temp->data < minNode->data){
                minNode = temp;
            }
            temp = temp->next;
        }
        //交换当前节点和最小节点的数据
        if(minNode != current){
            swap(current, minNode);
        }
        current = current->next;
    }
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
//定义链表节点结构
typedef struct Node{
    int data;
    struct Node* next;
}Node;
//创建新节点
Node* createNode(int data){
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}
//插入节点到链表尾部
void insertNode(Node** head, int data){
    Node* newNode = createNode(data);
    if(*head == NULL){
        *head = newNode;
    }
	else{
        Node* temp = *head;
        while(temp->next != NULL){
            temp = temp->next;
        }
        temp->next = newNode;
    }
}
//打印链表
void printList(Node* head){
    Node* temp = head;
    while(temp != NULL){
        printf("%d ", temp->data);
        temp = temp->next;
    }
    printf("\n");
}
//交换两个节点的数据
void swap(Node* a, Node* b){
    int temp = a->data;
    a->data = b->data;
    b->data = temp;
}
//简单选择排序
void selectionSort(Node* head){
    Node* current = head;
    while(current != NULL){
        Node* minNode = current;
        Node* temp = current->next;
        while(temp != NULL){
            if(temp->data < minNode->data){
                minNode = temp;
            }
            temp = temp->next;
        }
        //交换当前节点和最小节点的数据
        if(minNode != current){
            swap(current, minNode);
        }
        current = current->next;
    }
}
int main(){
    Node* head = NULL;
    int n, data;
    printf("请输入元素个数: ");
    scanf("%d", &n);
    printf("请输入 %d 个整数: ", n);
    for(int i = 0; i < n; i++){
        scanf("%d", &data);
        insertNode(&head, data);
    }
    printf("排序前的链表: ");
    printList(head);
    selectionSort(head);
    printf("排序后的链表: ");
    printList(head);
    return 0;
}

【运算结果】

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

【算法描述】

void BubbleSort(Node* head) {
    if (head == NULL || head->next == NULL) return;
    int swapped;
    Node* left = head->next;
    Node* right = NULL;
    do{
        swapped = 0;
        //从左到右冒泡
        Node* current = left;
        while(current->next != right){
            if(current->data > current->next->data){
                int temp = current->data;
                current->data = current->next->data;
                current->next->data = temp;
                swapped = 1;
            }
            current = current->next;
        }
        right = current;
        if(!swapped) break;
        swapped = 0;
        //从右到左冒泡
        current = right->prev;
        while(current != left->prev){
            if(current->data > current->next->data){
                int temp = current->data;
                current->data = current->next->data;
                current->next->data = temp;
                swapped = 1;
            }
            current = current->prev;
        }
        left = current->next;
    }while(swapped);
}

【完整代码】

#include <stdio.h>
#include <stdlib.h>
//定义双向链表节点结构
typedef struct Node{
    int data;
    struct Node *prev;
    struct Node *next;
} Node;
//创建新节点
Node* createNode(int data){
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}
//在链表尾部插入节点
void insert(Node* head, int data){
    Node* newNode = createNode(data);
    Node* temp = head;
    while(temp->next != NULL){
        temp = temp->next;
    }
    temp->next = newNode;
    newNode->prev = temp;
}
//双向冒泡排序
void BubbleSort(Node* head) {
    if (head == NULL || head->next == NULL) return;
    int swapped;
    Node* left = head->next;
    Node* right = NULL;
    do{
        swapped = 0;
        //从左到右冒泡
        Node* current = left;
        while(current->next != right){
            if(current->data > current->next->data){
                int temp = current->data;
                current->data = current->next->data;
                current->next->data = temp;
                swapped = 1;
            }
            current = current->next;
        }
        right = current;
        if(!swapped) break;
        swapped = 0;
        //从右到左冒泡
        current = right->prev;
        while(current != left->prev){
            if(current->data > current->next->data){
                int temp = current->data;
                current->data = current->next->data;
                current->next->data = temp;
                swapped = 1;
            }
            current = current->prev;
        }
        left = current->next;
    }while(swapped);
}
//打印链表
void printList(Node* head){
    Node* temp = head->next;
    while(temp != NULL){
        printf("%d ", temp->data);
        temp = temp->next;
    }
    printf("\n");
}
//释放链表内存
void freeList(Node* head){
    Node* temp;
    while(head != NULL){
        temp = head;
        head = head->next;
        free(temp);
    }
}
int main(){
    int n;
    printf("请输入记录的数量: ");
    scanf("%d", &n);
    Node* head = createNode(0);
    printf("请依次输入 %d 个记录:\n", n);
    for(int i = 0; i < n; i++){
        int data;
        scanf("%d", &data);
        insert(head, data);
    }
    printf("排序前的链表: ");
    printList(head);
    BubbleSort(head);
    printf("排序后的链表: ");
    printList(head);
    freeList(head);
    return 0;
}

【运算结果】

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

【算法描述】

void Sort(rectype r[], int n){
    int i = 0, j = 0, k = n - 1;
    while(j <= k){
        //红色 
		if(r[j].key == 'R'){
            rectype temp = r[i];
            r[i] = r[j];
            r[j] = temp;
            i++;
            j++;
        }
        //白色 
		else if(r[j].key == 'W'){
            j++;
        }
        //蓝色 r[j].key == 'B'
		else{
            rectype temp = r[j];
            r[j] = r[k];
            r[k] = temp;
            k--;
        }
    }
}

【完整代码】

#include <stdio.h>
typedef struct{
    char key; 	//R-红色,W-白色,B-蓝色
} rectype;
void Sort(rectype r[], int n){
    int i = 0, j = 0, k = n - 1;
    while(j <= k){
        //红色 
		if(r[j].key == 'R'){
            rectype temp = r[i];
            r[i] = r[j];
            r[j] = temp;
            i++;
            j++;
        }
        //白色 
		else if(r[j].key == 'W'){
            j++;
        }
        //蓝色 r[j].key == 'B'
		else{
            rectype temp = r[j];
            r[j] = r[k];
            r[k] = temp;
            k--;
        }
    }
}
int main(){
    int n;
    printf("请输入桶的数量: ");
    scanf("%d", &n);
    rectype arr[n];
    printf("请输入每个桶中砾石的颜色(R-红色,W-白色,B-蓝色): ");
    for(int i = 0; i < n; i++){
        scanf(" %c", &arr[i].key);
    }
    Sort(arr, n);
    printf("重新安排后的砾石颜色: ");
    for(int i = 0; i < n; i++){
        printf("%c ", arr[i].key);
    }
    printf("\n");
    return 0;
}

【运算结果】

(4)设计算法,整理对 n 个关键字取整数值的记录序列进行整理,以使所有关键字为负值的记录排在关键字为非负值的记录之前,要求:
        ① 采用顺序存储结构,至多使用一个记录的辅助存储空间;
        ② 算法的时间复杂度为 O(n)。

【算法描述】

void Range(int arr[], int n){
    int j = 0;
    for(int i = 0; i < n; i++){
    	//如果是负数
        if arr[i] < 0){ 
            if(i != j){
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
            j++;
        }
    }
}

【完整代码】

#include <stdio.h>
void Range(int arr[], int n){
    int j = 0;
    for(int i = 0; i < n; i++){
    	//如果是负数
        if arr[i] < 0){ 
            if(i != j){
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
            j++;
        }
    }
}
int main(){
    int n;
    printf("请输入记录的数量: ");
    scanf("%d", &n);
    int arr[n];
    printf("请输入每个记录的关键字: ");
    for(int i = 0; i < n; i++){
        scanf("%d", &arr[i]);
    }
    Range(arr, n);
    printf("整理后的记录序列: ");
    for(int i = 0; i < n; i++){
        printf("%d ", arr[i]);
    }
    printf("\n"); 
    return 0;
}

【运算结果】

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

【算法描述】

//分区函数
int Part(Record r[], int low, int high){
    int pivot = r[low].key;
    while(low < high){
        while(low < high && r[high].key > pivot) high--;
        if(low < high){
            Record temp = r[low];
            r[low] = r[high];
            r[high] = temp;
            low++;
        }
        while(low < high && r[low].key < pivot) low++;
        if(low < high){
            Record temp = r[low];
            r[low] = r[high];
            r[high] = temp;
            high--;
        }
    }
    r[low].key = pivot;
    return low;
}
//查找函数
void SearchSort(Record r[], int low, int high, int key){
    if(low <= high){
        int pos = Part(r, low, high);
        if(r[pos].key == key){
            printf("查找成功!位置: %d, 关键字值: %d\n", r[pos].index + 1, r[pos].key);
            return;
        }
		else if(r[pos].key > key){
            SearchSort(r, low, pos - 1, key);
        }
		else{
            SearchSort(r, pos + 1, high, key);
        }
    }
	else{
        printf("not find\n");
    }
}

【完整代码】

#include <stdio.h>
typedef struct{
    int key;
    int index;
} Record;
//分区函数
int Part(Record r[], int low, int high){
    int pivot = r[low].key;
    while(low < high){
        while(low < high && r[high].key > pivot) high--;
        if(low < high){
            Record temp = r[low];
            r[low] = r[high];
            r[high] = temp;
            low++;
        }
        while(low < high && r[low].key < pivot) low++;
        if(low < high){
            Record temp = r[low];
            r[low] = r[high];
            r[high] = temp;
            high--;
        }
    }
    r[low].key = pivot;
    return low;
}
//查找函数
void SearchSort(Record r[], int low, int high, int key){
    if(low <= high){
        int pos = Part(r, low, high);
        if(r[pos].key == key){
            printf("查找成功!位置: %d, 关键字值: %d\n", r[pos].index + 1, r[pos].key);
            return;
        }
		else if(r[pos].key > key){
            SearchSort(r, low, pos - 1, key);
        }
		else{
            SearchSort(r, pos + 1, high, key);
        }
    }
	else{
        printf("not find\n");
    }
}
int main(){
    int n, key;
    printf("请输入记录的数量: ");
    scanf("%d", &n);
    Record r[n];
    printf("请输入每个记录的关键字: ");
    for(int i = 0; i < n; i++){
        scanf("%d", &r[i].key);
        r[i].index = i;
    }
    printf("请输入要查找的关键字: ");
    scanf("%d", &key);
    SearchSort(r, 0, n - 1, key);
    return 0;
}

【运算结果】

(6)有一种简单的排序算法,叫做计数排序。这种排序算法对一个待排序的表进行排序,并将排序结果存放到另一个新的表中。必须注意的是,表中所有待排序的关键字互不相同,计数排序算法针对表中的每个记录,查找待排序的表一趟,统计表中有多少个记录的关键字比该记录的关键小。假设针对某一个记录,统计出的计数值为 c,那么,这个记录在新的有序表中的合适的存放位置即为 c。
        ① 给出适用于计数排序的顺序表定义;
        ② 设计实现计数排序的算法;
        ③ 对于有 n 个记录的表,关键字比较次数是多少?
        ④ 与简单选择排序相比较,这种方法是否更好?为什么?

【算法描述】

// ①给出适用于计数排序的顺序表定义;
typedef struct{
    int key;
}Record;
// ②设计实现计数排序的算法;
void CountingSort(Record arr[], Record sortedArr[], int n){
    for(int i = 0; i < n; i++){
        int count = 0;
        for(int j = 0; j < n; j++){
            if(arr[j].key < arr[i].key){
                count++;
            }
        }
        sortedArr[count] = arr[i];
    }
}

【完整代码】

#include <stdio.h>
// ①给出适用于计数排序的顺序表定义;
typedef struct{
    int key;
}Record;
// ②设计实现计数排序的算法;
void CountingSort(Record arr[], Record sortedArr[], int n){
    for(int i = 0; i < n; i++){
        int count = 0;
        for(int j = 0; j < n; j++){
            if(arr[j].key < arr[i].key){
                count++;
            }
        }
        sortedArr[count] = arr[i];
    }
}
int main(){
    int n;
    printf("请输入记录的数量: ");
    scanf("%d", &n);
    Record arr[n], sortedArr[n];
    printf("请输入每个记录的关键字: ");
    for(int i = 0; i < n; i++){
        scanf("%d", &arr[i].key);
    }
    CountingSort(arr, sortedArr, n);
    printf("排序后的记录关键字: ");
    for(int i = 0; i < n; i++){
        printf("%d ", sortedArr[i].key);
    }
    printf("\n");
    return 0;
}

【运算结果】

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

     外层循环遍历每个记录,执行 n 次。

     内层循环对每个记录再次遍历整个表,执行 n 次。

     则关键字比较的总次数为:n\times n = n^{2}

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

      简单选择排序算法比本算法好。

      简单选择排序比较次数是 \frac{n(n- 1)}{2},且只用一个交换记录的空间;而这种方法比较次数是 n^{2},且需要另一数组空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值