开课实验室:计算机科学与工程实验(电子楼418B) 2022年12月12日
学院 | 计算机科学与网络工程学院 | 年级、专业、班 | 姓名 | 学号 | ||||
实验课程名称 | 数据结构实验 | 成绩 | ||||||
实验项目名称 | 查找和排序算法实现 | 指导老师 |
一、实验目的
掌握线性的定义及基本操作,用链表实现:遍历、查找、插入、删除、翻转。
二、使用仪器、器材
微机一台
操作系统:
编程软件:
三、实验内容
1、各种排序算法的实现
用随机函数生成16个2位正整数(10~99),实现直接插入排序、选择排序、冒泡排序、双向冒泡排序、快速排序、堆排序、二路归并排序等多种排序算法,输出排序中间过程、统计关键字的比较次数和记录的移动次数。
2、各种查找算法实现
(1)顺序查找:使用数组或链表结构。用随机函数生成16个不重复的字母(’a’~’z’),键盘输入待查找的字母,返回查找成功与否,若成功则返回该字母所在的位置(序号),并计算比较次数。
(2)折半查找:用数组实现,查找前元素先排序。计算比较次数。分别用查找成功、不成功进行测试。
(3)二叉排序树:手工输入10个字母,生成一棵二叉排序树,用递归算法打印树结构或分别输出先序和中序遍历序列以确认其结构。键盘输入待查找的字母,计算比较次数。分别用查找成功、不成功进行测试。
四、实验原理
填入自己的内容(思路或算法流程图或伪代码说明等)
1、各种排序算法的实现
(1)生成指定数值范围的随机正整数
创建一个函数ProduceRands用于生成16个2位正整数(10~99)。其原理在实验报告一已经阐述过了,就是使用计算机系统时间来生成随机数,将随机数赋值给整型数组元素中,循环执行16次即可。
(2)排序算法
a. 直接插入排序
创建一个函数InsertSort用于进行直接插入排序。
直接插入排序的基本思路:将无序区的第一个元素R[i]插入到有序区的恰当位置,使得有序区的元素加上该元素按照一个递增的次序进行排列。
具体为:将无序区第一个元素R[i]暂时存放在temp中,在有序区中从后向前找,凡是关键字大于temp.key的元素均向后移一个位置。当找到某个元素R[j],其关键字小于或等于temp.key,则将temp放在其位置之后,即置R[j+1]=temp。
b. 冒泡排序
创建一个函数BubbleSort用于进行冒泡排序。
冒泡排序的基本思路:是通过无序区中相邻元素关键字间的比较和位置的交换使关键字最小的元素如气泡一般逐渐往上“漂浮”直至“水面”。
具体为:算法从数组中最右边的元素开始,对每两个相邻的关键字进行比较,且使关键字较小的元素换至关键字较大的元素的左边,即进行了元素交换,使得经过一趟冒泡排序后关键字最小的元素被送至数组的最左端。接着在剩下的元素中找关键字次小的元素,并把它换至第二个位置上。以此类推,直到所有元素都为递增有序为止。除此之外,若是在执行过程中的某一趟比较完了之后没有发生一次元素交换,则说明此时已排好序,退出执行。即加上一个判别因子进行优化。
c.双向冒泡排序
创建一个函数ShakerBubbleSort用于进行双向冒泡排序。
双向冒泡排序的基本思路是:双向冒泡排序与冒泡排序的思想基本类似,就是将给定元素中最大的冒到数组最右边,然后将元素中最小的冒到数组最左边;接着将次大的冒到数组的下标为n-2的位置,将次小的冒到数组的下标为1的位置,以此类推,直到某一趟中没有发生任何元素交换。
d. 快速排序
创建两个函数Partition和QuickSort用于进行快速排序。函数Partition用以进行快速排序的一趟操作,递归函数QuickSort用以对给定数组的所有元素进行快速排序。
快速排序的基本思路是:在待排序的n个元素中选取第一个元素作为基准,把该元素放入适当位置后,数据序列被此元素划分成两部分。所有关键字比该元素关键字小的元素放置在前一部分,所有比它大的元素放置在后一部分,并把该元素排在这两部分的中间(称为该元素归位),这个过程称为快速排序的一次划分。之后对产生的两个部分分别重复上述过程,直到每部分内只有一个元素或空为止。
一趟划分采用从两头向中间遍历的方法,同时交换与基准元素逆序的元素。具体为:设两个指示器i和j,它们的初值分别为指向无序区中的第一个和最后一个元素。假设无序区中的元素为R[s]、R[s+1]、R[s]....R[t],则i的初值为s,j的初值为t,首先将R移至变量base中作为基准,令j自位置t起向前遍历直到R[j].key<base.key,再将R[j]移至位置i,然后让i向后遍历直到R[i].key>base.key,再将R[i]移至位置j,依此重复,直到i=j,此时所有R[k](k=s,s+1,···,i--1)的关键字都小于base.key,而所有R[k](k=i+1,i+2,··,t)的关键字必大于 base.key,此时再将base元素移至位置i,它将无序区中的元素分割成R[s..i-1]和R[i+1..t],以便分别进行排序。
e. 选择排序
实验要求没有指定用哪种选择排序,这里以简单选择排序进行实验。
创建一个函数SelectSort用于进行简单选择排序。
简单选择排序的基本思路是:第i趟排序开始时,当前有序区和无序区分别为R[0..i-1]和R[i..n-1],该趟排序是从当前无序区中选出关键字最小的元素R[k],将它与无序区的第1个元素R[i]交换,使R[0..i]和R[i+1..n-1]分别变为新的有序区和新的无序区。
具体为:将初始无序区设置为R[0..n-1],则在进行n-1趟之后排序后,有序区均按照递增有序的顺序排列。
f. 堆排序
创建两个函数Sift和HeapSort用于进行堆排序。将筛选区间看作一棵完全二叉树。其中函数Sift用以进行筛选,以达到大者“上浮”,小者被“筛选”下去。函数HeapsSort用以对给定数组的所有元素进行堆排序。
堆排序的基本思路是:Sift算法将完全二叉树从最后一个分支节点开始进行筛选。HeapSort函数首先建立初始堆,然后进行n-1趟完成堆排序,每一趟堆中的元素-1。即将最后一个元素与根进行交换,然后进行筛选,得到结点逐次减1的堆。
g. 二路归并排序
创建三个函数Merge、MergePass和MergeSort用于进行二路归并排序。函数Merge用以将两个有序表归并为一个有序表。函数MergePass用以执行一趟归并。函数MergeSort用以进行二路归并排序。
二路归并排序的基本思路是:将R[0..n-1]看出n个长度为1的有序序列,然后进行两两归并,得到n/2个长度为2的有序序列,然后再进行两两归并,得到n/4个长度为4的有序序列,以此类推,直到得到一个长度为n的序列。
(3)上述排序函数的升级
- 普通的排序函数仅仅对数据进行排序,不会显示中间过程,这就需要在每一个排序函数中增设输出排序前的初始数列、输出排序过程中某一趟的结果的功能。具体为调用函数DisplayRecArray以进行数组中元素关键字的打印。
- 普通的排序函数仅仅对数据进行排序,不会显示元素比较次数和移动次数,这就需要定义全局变量int compare_num=0;和int move_num=0;用以记录。每当调用的排序函数进行了元素比较则compare_num++;每当调用的排序函数进行了元素移动则move_num++,最后在函数结束前输出compare_num和move_num 的值即可。
(4)Main函数:
- 采用switch case语句进行排序方式选择
- 调用排序函数,显示排序的中间过程及其结果(包括关键字的比较次数和移动次数)。
2、各种查找算法实现
(1)生成随机字母
创建一个函数ProduceRandChar用于生成16个不重复的字母。其内核还是生成随机随机数,但需要进行自动类型转换,就可以生成随机字母了。特别需要注意到是,题目要求不重复的字母,这就要求在生成随机字母时,将字母储存起来,每随机生成一个字母就与前面储存的字母进行比较,若与前面生成的字母重复,则重新生成字母,直到与前面的字母不重复才储存该字母。
(2)顺序查找
创建一个函数SeqSearch1用于进行顺序查找。顺序查找算法的基本思路是:从静态查找表的一端向另一端逐个将字元素的关键字与给定值k进行比较,若相同,则查找成功,给出该元素在静态查找表中的逻辑序号,并给出比较次数;若整个静态查找表遍历结束后仍未找到关键字等于k的元素,则查找失败。
(3)折半查找
创建一个函数BinSearch用于进行折半查找。但在进行折半查找前,要求静态查找表中的元素按关键字有序排列。这里采用改良后的冒泡排序BubbleSort进行递增的排列。折半查找算法的基本思路是:设R[low..high]是当前查找区间,首先确定该区间的中间位置mid=(low+high)/2,然后将待查的k值与R[mid].key进行比较:
1、若k=R[mid].key,则查找成功,返回该元素的逻辑序号,计算比较次数。
2、若k<R[mid].key,根据静态查找表的有序性可得,元素k必定在位置mid左边的子表R[low..mid-1],故新的查找区间为左子表R[low..mid-1]。
3、若k>R[mid].key,根据静态查找表的有序性可得,元素k必定在位置mid右边的子表R[mid+1..high],故新的查找区间为右子表R[mid+1..high]。下一次查找是针对新的查找区间进行的。
(4)二叉排序树查找
创建一个函数InsertBST用于对二叉排序树bt插入关键字为k的结点。其基本思路为:若bt为空,则创建一个key域为k的结点bt,将它作为根结点;否则将k和根结点的关键字比较,若k<bt->key,则将k插入bt结点的左子树中,若k>bt->k,则将kk>bt→>key插入bt结点的右子树中,其他情况是k=bt->key,说明树中已有此关键字k,无须插入,最后返回插入后的二叉排序树的根结点bt。
再创建一个函数CreateBST进行创建二叉排序树的操作。其基本思路是:创建一棵二叉排序树是从一个空树开始,每插入一个关键字,就调用一次插入算法将它插入当前已生成的二叉排序树中。
二叉排序树先序遍历函数(递归)InOrderRecur和中序遍历函数(递归)PreOrderRecur用于确定二叉排序树的结构。其算法与之前数据结构实验二所采用的类似。
二叉排序树的查找算法(递归)SearchBST。其基本思路与折半查找类似,都是一个逐步缩小查找范围的国过程。查找关键字为k的结点,成功时返回该结点的地址并计算比较次数,失败是返回NULL。
(5)Main函数
在main函数中,设计一个switch case语句,要求用户用键盘输入数字1、2、3来选择查找方式,其分别表示用顺序查找、折半查找、二叉排序树查找。
五、实验过程原始数据记录
1、实验源代码及注释
(1)各种排序算法的实现
RandNumbers.h
#pragma once
#include<iostream>
using namespace std;
#include "time.h" //时间函数
#include "math.h" //随机函数
#include"SortedAlgorithm.h"
//随机生成指定数值范围的正整数
void ProduceRands(int array[], int n);
SortedAlgorithm.h
#pragma once
#include<iostream>
using namespace std;
typedef int KeyType; //定义关键字类型为int
typedef int InfoType;
typedef struct //元素类型
{
KeyType key; //关键字项
InfoType data; //其他数据项,类型为InfoType
}RecType; //排序元素的类型
//交换函数
void Swap(RecType &r1, RecType &r2);
//初始化排序数组
void CreateRecArray(RecType R[], int array[], int n);
//遍历排序数组
void DisplayRecArray(RecType R[], int n);
//直接插入排序
void InsertSort(RecType R[], int n);
//冒泡排序
void BubbleSort(RecType R[], int n);
//双向冒泡排序
void ShakerBubbleSort(RecType R[], int n);
//快速排序
int Partition(RecType R[], int s, int t, int& c_num, int& m_num);
void QuickSort(RecType R[], int s, int t,int n,int &c_num,int &m_num);
//简单选择排序
void SelectSort(RecType R[], int n);
//堆排序
void Sift(RecType R[], int low, int high);
void HeapSort(RecType R[], int n);
//二路归并排序
void Merge(RecType R[], int low, int mid, int high);
void MergePass(RecType R[], int length, int n);
void MergeSort(RecType R[], int n);
RandNumbers.cpp
#include"RandNumbers.h"
//随机生成指定数值范围的正整数
void ProduceRands(int array[], int n)
{
time_t t; //定义时间变量
srand((unsigned)time(&t)); //由时间确定随机序列,执行一次
for (int i = 0; i < n; i++)
array[i] = (10 + rand() % 90); //随机生成一个2位正整数(10~99)
}
SortedAlgorithm.cpp
#include"SortedAlgorithm.h"
int move_num = 0;
int compare_num = 0;
//交换函数
void Swap(RecType &r1, RecType &r2) //进行两元素的交换
{
RecType temp;
temp = r1;
r1 = r2;
r2 = temp;
}
//初始化排序数组
void CreateRecArray(RecType R[], int array[], int n)
{
for (int i = 0; i < n; i++)
{
R[i].key = array[i];
R[i].data = 0;
}
}
//遍历排序数组
void DisplayRecArray(RecType R[], int n) //输出排序数组的关键字
{
for (int i = 0; i < n; i++)
cout << R[i].key << " ";
printf("\n");
}
//直接插入排序
void InsertSort(RecType R[], int n) //按递增有序进行直接插入排序
{
int i, j;
RecType temp;
printf(" 初始关键字:");
DisplayRecArray(R, n); //输出排序前的关键字序列
for (i = 1; i < n; i++)
{
if (R[i].key < R[i - 1].key) //反序时
{
temp = R[i];
j = i - 1;
compare_num++;
do //找R[i]的插入位置
{
R[j + 1] = R[j]; //比插入元素大的元素后移
move_num++;
j--;
compare_num++;
} while (j >= 0 && R[j].key > temp.key);
R[j + 1] = temp; //插入元素
}
printf(" 第%d趟结果:", i);
DisplayRecArray(R, n); //输出排序中间过程的关键字序列
}
printf("关键字的比较次数为:%d\n", compare_num);
printf("元素的移动次数为:%d", move_num);
}
//冒泡排序
void BubbleSort(RecType R[], int n) //把小的元素冒在数组前面
{
int i, j;
bool exchange;
printf(" 初始关键字:");
DisplayRecArray(R, n); //输出排序前的关键字序列
for (i = 0; i < n - 1; i++)
{
exchange = false; //判别因子
for (j = n - 1; j > i; j--)
if (R[j].key < R[j - 1].key) //相邻元素反序时
{
compare_num++;
Swap(R[j], R[j - 1]); //将两元素交换
move_num++;
exchange = true;
}
if (!exchange) //本趟没有发生交换,中途结束算法
break;
printf(" 第%d趟结果:", i + 1);
DisplayRecArray(R, n); //输出排序中间过程的关键字序列
}
printf("关键字的比较次数为:%d\n", compare_num);
printf("元素的移动次数为:%d", move_num);
}
//双向冒泡排序
void ShakerBubbleSort(RecType R[], int n)
{
int left, right, shift, i;
left = 0;
right = n - 1;
shift = 1;
bool exchange;
printf(" 初始关键字:");
DisplayRecArray(R, n); //输出排序前的关键字序列
while (left < right)
{
exchange = false;
for (i = left; i < right; i++) //将关键字大的元素冒到数组后面
if (R[i].key > R[i + 1].key)
{
compare_num++;
Swap(R[i], R[i + 1]);
move_num++;
exchange = true;
shift = i;
}
printf(" 往右排序:");
DisplayRecArray(R, n);
right = shift;
for (i = right - 1; i >= left; i--) //将关键字小的元素冒到数组前面
if (R[i].key > R[i + 1].key)
{
compare_num++;
Swap(R[i], R[i + 1]);
move_num++;
exchange = true;
shift = i + 1;
}
printf(" 向左排序:");
DisplayRecArray(R, n); //输出排序中间过程的关键字序列
left = shift;
if (exchange == false)
break;
}
printf("关键字的比较次数为:%d\n", compare_num);
printf("元素的移动次数为:%d", move_num);
}
//快速排序
int Partition(RecType R[], int s, int t, int& c_num, int& m_num) //一趟划分
{
int i = s, j = t;
RecType base = R[i]; //以R[i]为基准
while (i < j) //从两端交替向中间扫描,直到i=j为止
{
while (j > i && R[j].key >= base.key) //从右向左遍历,找小于基准的元素进行交换
{
c_num++;
j--;
}
if (i < j)
{
R[i] = R[j]; //将小的元素放置在左区间
move_num++;
i++;
}
while (i < j && R[i].key <= base.key) //从左向右遍历,找大于基准的元素进行交换
{
c_num++;
i++;
}
if (i < j)
{
R[j] = R[i]; //将大的元素放置在右区间
m_num++;
j--;
}
}
R[i] = base; //将基准放置与两区间的中间位置
m_num++;
return i;
}
int t_num = 1; //全局数组,记录快速排序的趟数
void QuickSort(RecType R[], int s, int t, int n, int& c_num, int& m_num) //s、t分别表示无序区第一个元素和最后一个元素的数组下标
{
int i;
if (s < t) //当区间内至少存在两个元素时循环
{
i = Partition(R, s, t, c_num, m_num);
printf(" 第%d次划分:", t_num);
DisplayRecArray(R, n); //输出排序后的关键字序列
t_num++;
QuickSort(R, s, i - 1, n, c_num, m_num); //对左区间进行递归排序
QuickSort(R, i + 1, t, n, c_num, m_num); //对右区间进行递归排序
}
}
//简单选择排序
void SelectSort(RecType R[], int n)
{
int i, j, k;
printf(" 初始关键字:");
DisplayRecArray(R, n); //输出排序前的关键字序列
for (i = 0; i < n - 1; i++)
{
k = i;
for (j = i + 1; j < n; j++) //在当前无序区R[i..n-1]中选出关键字最小的元素
if (R[j].key < R[k].key)
{
compare_num++;
k = j; //记下目前找到的关键字最小的所在位置
}
if (k != i)
{
Swap(R[i], R[k]); //元素交换
move_num++;
}
printf(" 第%d趟结果:", i + 1);
DisplayRecArray(R, n); //输出排序中间过程的关键字序列
}
printf("关键字的比较次数为:%d\n", compare_num);
printf("元素的移动次数为:%d", move_num);
}
//堆排序
void Sift(RecType R[], int low, int high)
{
int i = low, j = 2 * i; //R[j]是R[i]的左孩子
RecType temp = R[i];
while (j <= high)
{
if (j < high && R[j].key < R[j + 1].key) //若右孩子较大,把j指向右孩子
{
compare_num++;
j++;
}
if (temp.key < R[j].key) //若根结点小于最大孩子的关键字
{
compare_num++;
R[i] = R[j]; //将R[j]调整到双亲结点的位置上
i = j; //修改i、j的值,以便继续筛选
j = 2 * i;
}
else
break; //若根结点大于或等于最大孩子的关键字,筛选结束
}
R[i] = temp; //被筛选结点放到最终位置上
}
void HeapSort(RecType R[], int n)
{
int i;
int t_num = 1; //记录堆排序中间过程的趟数
printf(" 初始关键字:");
DisplayRecArray(R, n); //输出排序前的关键字序列
for (i = n / 2 - 1; i >= 0; i--) //循环建立初始堆,从最后一个分支节点直到第一个分支节点反复筛选
Sift(R, i, n);
for (i = n - 1; i >= 1; i--) //进行n-1趟完成堆排序,每一趟堆中元素个数减1
{
Swap(R[0], R[i]); //将最后一个元素(下标最大)与根(下标最小 )进行交换
move_num++;
printf(" 第%d趟结果:", t_num);
DisplayRecArray(R, n); //输出排序中间过程的关键字序列
t_num++;
Sift(R, 0, i - 1); //对R[0..i-2]进行筛选,得到i-1个结点的堆
}
printf("关键字的比较次数为:%d\n", compare_num);
printf("元素的移动次数为:%d", move_num);
}
//二路归并排序
void Merge(RecType R[], int low, int mid, int high) //归并R[low..high]
{
RecType * R1;
int i = low, j = mid + 1, k = 0; //k是R1的下标,i、j分别表示第1、2段的下标
R1 = (RecType*)malloc((high - low + 1) * sizeof(RecType)); //动态分配内存空间
while (i <= mid && j <= high) //在第1、2段均未扫描时循环
{
if (R[i].key <= R[j].key) //将第1段的元素放入R1中
{
compare_num++;
R1[k] = R[i];
move_num++;
i++;
k++;
}
else //将第2段的元素放入R1中
{
compare_num++;
R1[k] = R[j];
move_num++;
j++;
k++;
}
}
while (i <= mid) //将第1段剩余的部分复制到R1
{
R1[k] = R[i];
move_num++;
i++;
k++;
}
while (j <= high) //将第2段剩余的部分复制到R1
{
R1[k] = R[j];
move_num++;
j++;
k++;
}
for (k = 0, i = low; i <= high; k++, i++) //将R1复制到R[low..high]中
R[i] = R1[k];
free(R1);
}
void MergePass(RecType R[], int length, int n) //对整个排序序列进行一趟归并
{
int i;
for (i = 0; i + 2 * length - 1 < n; i = i + 2 * length) //归并length长的两相邻子表
Merge(R, i, i + length - 1, i + 2 * length - 1);
if (i + length - 1 < n - 1) //余下两个子表,后者的长度小于length
Merge(R, i, i + length - 1, n - 1); //归并这两个子表
}
void MergeSort(RecType R[], int n) //采用自底向上的非递归过程
{
int length;
int t_num = 1;
printf(" 初始关键字:");
DisplayRecArray(R, n); //输出排序前的关键字序列
for (length = 1; length < n; length = 2 * length)
{
MergePass(R, length, n);
printf(" 第%d趟结果:", t_num);
DisplayRecArray(R, n); //输出排序中间过程的关键字序列
t_num++;
}
printf("关键字的比较次数为:%d\n", compare_num);
printf("元素的移动次数为:%d", move_num);
}
Main.cpp
#include"RandNumbers.h"
#include"SortedAlgorithm.h"
int main()
{
int Array[16];
RecType RecArray[16];
int Switch = 0; //用于选择排序方式
int num = sizeof(Array) / sizeof(int); //元素的个数
int c_num = 0; //快速排序中关键字的比较次数
int m_num = 0; //快速排序中元素的移动次数
ProduceRands(Array, num); //生成16个随机数,并将随机数存入数组Array中
CreateRecArray(RecArray, Array, num); //创建一个静态排序表
printf("用随机函数生成的16个2位正整数(10~99)为:\n");
DisplayRecArray(RecArray, 16); //遍历输出排序前静态排序表中的关键字
printf("请选择你的排序方式(输入1、2、3、4、5、6、7分别表示直接插入、冒泡、双向冒泡、"
"快速、选择、堆、二路归并排序):");
cin >> Switch; //选择排序方式
switch (Switch)
{
case 1:
printf("进行直接插入排序,其中间过程及结果为:\n");
InsertSort(RecArray, num);
break;
case 2:
printf("进行冒泡排序,其中间过程及结果为:\n");
BubbleSort(RecArray, num);
break;
case 3:
printf("进行双向冒泡排序,其中间过程及结果为:\n");
ShakerBubbleSort(RecArray, num);
break;
case 4:
printf("进行快速排序,其中间过程及结果为:\n");
printf(" 初始关键字:");
DisplayRecArray(RecArray, num); //输出排序前的关键字序列
QuickSort(RecArray, 0, num - 1, num, c_num, m_num); //初始时,无序区的首元素数组下标为0,末尾元素数组下标为num-1
printf("关键字的比较次数为:%d\n", c_num); //因使用递归,快速排序的比较次数和移动次数不好在函数中打印,所以选择在main函数中打印
printf("元素的移动次数为:%d", m_num);
break;
case 5:
printf("进行选择排序,其中间过程及结果为:\n");
SelectSort(RecArray, num);
break;
case 6:
printf("进行堆排序,其中间过程及结果为:\n");
HeapSort(RecArray, num);
break;
case 7:
printf("进行二路归并排序,其中间过程及结果为:\n");
MergeSort(RecArray, num);
}
return 0;
}
(2)各种查找算法实现
Find.h
#pragma once
#include<iostream>
using namespace std;
typedef char KeyType; //定义关键字类型为int
typedef int InfoType;
typedef struct
{
KeyType key; //关键字项
InfoType data; //其他数据项,类型为InfoType
}RecType; //查找元素的类型
typedef struct node //元素类型
{
KeyType Key; //关键字项
InfoType data; //其他数据项
struct node* lchild, * rchild; //左、右孩子指针
}BSTNode; //二叉排序树的结点类型
//交换函数
void Swap(RecType& r1, RecType& r2);
//初始化字符数组
void InitCharArray(char array[], int n);
//遍历字符数组
void DisplayArray(char array[], int n);
//初始化静态查找表(数组)
void InitRecArray(RecType R[], char array[], int n);
//遍历静态查找表中的关键字
void DisplayRecArrayKey(RecType R[], int n);
//根据关键字对静态查找表进行排序
void BubbleSort(RecType R[], int n);
//顺序查找算法
int SeqSearch1(RecType R[], int n, KeyType k, int& counter);
int SeqSearch2(RecType R[], int n, KeyType k, int& counter);
//折半查找算法
int BinSearch(RecType R[], int n, KeyType k, int& counter);
//二叉排序树的插入
BSTNode* InsertBST(BSTNode* bt, KeyType k);
//二叉排序树的创建
BSTNode* CreateBST(KeyType a[], int n);
//先序遍历二叉排序树(递归)
void PreOrderRecur(BSTNode* root);
//中序遍历二叉排序树(递归)
void InOrderRecur(BSTNode* root);
//二叉排序树的查找
BSTNode* SearchBST(BSTNode* bt, KeyType k, int& counter);
RandChar.h
#pragma once
#include<iostream>
using namespace std;
//生成不重复的随机字母
void ProduceRandChar(char array[]);
Find.cpp
#include"Find.h"
//交换函数
void Swap(RecType& r1, RecType& r2)
{
RecType temp;
temp = r1;
r1 = r2;
r2 = temp;
}
//初始化字符数组
void InitCharArray(char array[], int n)
{
for (int i = 0; i < n; i++)
array[i] = ' ';
}
//遍历字符数组
void DisplayArray(char array[], int n)
{
printf("用随机函数生成的16个不重复的字母(’a’~’z’)为:\n");
for (int i = 0; i < n; i++)
printf("%c ", array[i]);
printf("\n");
}
//初始化静态查找表(数组)
void InitRecArray(RecType R[], char array[], int n)
{
for (int i = 0; i < n; i++)
{
R[i].key = array[i];
R[i].data = 0;
}
}
//遍历静态查找表中的关键字
void DisplayRecArrayKey(RecType R[], int n)
{
for (int i = 0; i < n; i++)
printf("%c ", R[i].key);
printf("\n");
}
//根据关键字对静态查找表进行排序
void BubbleSort(RecType R[], int n)
{
int i, j;
bool exchange;
for (i = 0; i < n - 1; i++)
{
exchange = false;
for (j = n - 1; j > i; j--)
if (R[j].key < R[j - 1].key)
{
Swap(R[j], R[j - 1]);
exchange = true;
}
if (!exchange)
return;
}
}
//顺序查找算法
int SeqSearch1(RecType R[], int n, KeyType k, int& counter)
{
int i = 0;
while (i < n && R[i].key != k) //从表头往后找
i++;
counter = i + 1;
if (i >= n) //未找到时返回0
return 0;
else
return i + 1; //找到时返回对应的逻辑序号i+1
}
int SeqSearch2(RecType R[], int n, KeyType k, int& counter)
{
int i = 0;
R[n].key = k; //在R的末尾增加一个关键字为k的元素,称之为哨兵
while (R[i].key != k) //从表头往后找
i++;
counter = i + 1;
if (i == n) //未找到时返回0
return 0;
else
return i + 1; //找到时返回对应的逻辑序号i+1
}
//折半查找算法
int BinSearch(RecType R[], int n, KeyType k, int& counter)
{
int low = 0, high = n - 1, mid = 0, count = 0;
while (low <= high) //当前区间存在元素时循环
{
mid = (low + high) / 2;
if (k == R[mid].key) //查找成功返回其对应的逻辑序号
{
counter = count + 1;
return mid + 1;
}
if (k < R[mid].key) //继续在R[low..mid-1]中查找
high = mid - 1;
else
low = mid + 1; //继续在R[mid+1..high]中查找
count++;
}
return 0; //未找到时返回0(查找失败)
}
//二叉排序树的插入
BSTNode* InsertBST(BSTNode* bt, KeyType k)
{
if (bt == NULL) //原树为空
{
bt = (BSTNode*)malloc(sizeof(BSTNode)); //创建根结点bt
bt->Key = k;
bt->data = 0;
bt->lchild = bt->rchild = NULL;
}
else if (k < bt->Key)
bt->lchild = InsertBST(bt->lchild, k); //插入到左子树中
else if (k > bt->Key)
bt->rchild = InsertBST(bt->rchild, k); //插入到右子树中
return bt; //返回插入后二叉排序树的根结点
}
//二叉排序树的创建
BSTNode* CreateBST(KeyType a[], int n)
{
BSTNode* bt = NULL; //初始时bt为空树
int i = 0;
while (i < n)
{
bt = InsertBST(bt, a[i]); //将关键字a[i]插入到二叉排序树bt中
i++;
}
return bt; //返回建立的二叉排序树的根指针
}
//先序遍历二叉树(递归)
void PreOrderRecur(BSTNode* bt)
{
if (bt != NULL) //结点不为空时
{
printf("%c ", bt->Key); //输出根结点的关键字项
PreOrderRecur(bt->lchild); //先序遍历左子树
PreOrderRecur(bt->rchild); //先序遍历右子树
}
//结点为空时什么操作都不执行
}
//中序遍历二叉树(递归)
void InOrderRecur(BSTNode* bt)
{
if (bt != NULL) //结点不为空时
{
InOrderRecur(bt->lchild); //中序遍历左子树
printf("%c ", bt->Key); //输出根结点的关键字项
InOrderRecur(bt->rchild); //中序遍历右子树
}
//结点为空时什么操作都不执行
}
//二叉排序树的查找
BSTNode* SearchBST(BSTNode* bt, KeyType k, int& counter)
{
if (bt == NULL || bt->Key == k) //递归结束条件
{
counter++;
return bt;
}
if (k < bt->Key)
{
counter++;
return SearchBST(bt->lchild, k, counter); //在左子树中递归查找
}
else
{
counter++;
return SearchBST(bt->rchild, k, counter); //在右子树中递归查找
}
}
RandChar.cpp
#include"RandChar.h"
//生成不重复的随机字母
void ProduceRandChar(char array[])
{
time_t t; //定义时间变量
srand((unsigned)time(&t)); //由时间确定随机序列,执行一次
char ch = 'a';
bool flag = 1;
char temp = ch + rand() % 26; //运算过程中进行了类型转换(int类型转成了char类型)
for (int i = 0; i < 16;) //随机生成16个不重复的字母(’a’~’z’)
{
for (int j = 0; j < i; j++)
{
if (array[j] == temp) //生成了重复的随机字母
{
flag = 0; //将flag置为0,不储存该字母,并重新生成随机字母
break;
}
else
flag = 1;
}
if (flag == 1)
{
array[i] = temp; //储存该字母
i++;
}
temp = ch + rand() % 26;
}
}
Main.cpp
#include"RandChar.h"
#include"Find.h"
int main()
{ //e b a f g d h c i j 实验测试数据
char array[16];
char array2[10];
RecType recArray[16];
char ch = ' ';
int counter = 0; //记录比较次数
int flag = 0;
int Switch = 0;
int num = sizeof(array) / sizeof(char); //静态查找表中元素的个数
int num2 = sizeof(array2) / sizeof(char);; //二叉排序树中元素的个数
BSTNode* bt = NULL, * p = NULL; //指针bt用以唯一标识一棵二叉排序树
printf("请选择你的查找方式(输入1、2、3分别表示顺序查找、折半查找、二叉排序树查找)\n");
cin >> Switch;
switch (Switch)
{
case 1:
InitCharArray(array, num); //初始化数组
ProduceRandChar(array); //将数组的内容赋值为不重复的小写字母
DisplayArray(array, num); //遍历输出随机函数生成的16个不重复的字母
InitRecArray(recArray, array, num); //初始化静态查找表
printf("进行顺序查找,请用键盘输入需要查找的字母:");
cin >> ch;
flag = SeqSearch1(recArray, num, ch, counter); //顺序查找
if (flag == 0)
printf("查找失败\n字母%c不在该静态查找表中\n", ch);
else
printf("查找成功\n字母%c在该静态查找表的位置(序号)为:%d\n所用的比较次数为:%d\n", ch, flag, counter);
break;
case 2:
InitCharArray(array, num); //初始化数组
ProduceRandChar(array); //将数组的内容赋值为不重复的小写字母
DisplayArray(array, num); //遍历输出随机函数生成的16个不重复的字母
InitRecArray(recArray, array, num); //初始化静态查找表
BubbleSort(recArray, num); //对静态查找表进行排序
printf("排序后的这16个的字母为:\n");
DisplayRecArrayKey(recArray, num); //遍历遍历静态查找表中的关键字
printf("进行折半查找,请用键盘输入需要查找的字母:");
cin >> ch;
flag = BinSearch(recArray, num, ch, counter); //折半查找
if (flag == 0)
printf("查找失败\n字母%c不在该静态查找表中\n", ch);
else
printf("查找成功\n字母%c在该静态查找表的位置(序号)为:%d\n所用的比较次数为:%d\n", ch, flag, counter);
break;
case 3:
printf("请用键盘输入10个字母以生成一棵二叉排序树\n");
for (int i = 0; i < 10; i++)
{
cin >> ch;
array2[i] = ch;
}
bt = CreateBST(array2, num2);
printf("采用递归算法分别输出先序和中序遍历序列以确认其结构:\n");
printf("先序遍历:");
PreOrderRecur(bt);
printf("\n中序遍历:");
InOrderRecur(bt);
printf("\n请用键盘输入需要在二叉排序树中查找的字母:");
cin >> ch;
p = SearchBST(bt, ch, counter); //在二叉树排序树中查找关键字为ch的结点
if (p == NULL)
printf("查找失败\n字母%c不在该二叉排序树中\n", ch);
else
printf("查找成功\n查找字母%c所用的比较次数为:%d,其对应的地址是:%p\n", ch, counter, p);
}
return 0;
}
2、实验过程中的错误及原因总结
(1)各种排序算法的实现
1、在写交换函数Swap时,出现了低级错误。
解决措施:将上述代码改为:
RecType temp;
temp = r1;
r1 = r2;
r2 = temp;
总结:写代码时要明确自己的逻辑,避免逻辑出错
2、调用快速排序函数时,程序运行结果未呈现正确的排序
原因:函数调用时的参数设置错误了,导致数组越界访问。快速排序函数QuickSort(RecType R[], int s, int t)中参数s、t分别表示无序区第一个元素和最后一个元素的数组下标,初始时,s应为0,t应为元素总数-1。而上面调用过程中t设置为了元素总数。
解决措施:更改调用函数时的参数
case 4:
printf("进行快速排序,其中间过程及结果为:\n");
QuickSort(RecArray, 0, num - 1);
正确运行结果为:
总结:调用函数前,需要阅读函数的功能,从而确定函数的参数的含义。选择正确的实参进行函数调用。
3、调用堆排序函数时,程序运行错误,得不到正确的排序结果
原因:数组的逻辑序号与下标序号搞错了。与书本上的HeapSort函数中i为数组逻辑序号,而我的程序的i应改为数组下标序号。故需要更改i的取值范围。以下为书本的代码示例:
解决措施:更改的代码如下:
void HeapSort(RecType R[], int n)
{
int i;
for (i = n / 2 - 1; i >= 0; i--) //循环建立初始堆,从最后一个分支节点直到第一个分支节点反复筛选
Sift(R, i, n);
for (i = n - 1; i >= 1; i--) //进行n-1趟完成堆排序,每一趟堆中元素个数减1
{
Swap(R[0], R[i]); //将最后一个元素(下标最大)与根(下标最小 )进行交换
Sift(R, 0, i - 1); //对R[0..i-2]进行筛选,得到i-1个结点的堆
}
}
正确运行结果:
总结:在写程序时,应注意识别使用的是数组的逻辑序号还是数组下标序号。
4、一般排序算法仅仅只是输出排序的最终结果,如图所示:
但实验要求输出排序的中间过程,这就需要在排序算法中使用遍历排序数组的函数,从而输出排序的每一步骤而并非在排序结束后才遍历排序数组。
解决措施:将DisplayRecArray(RecArray, num)这条语句在main函数中删除,写入每个排序算法中。如图所示:
5、在输出二路归并排序的中间过程及其结果时,中间过程的趟数出了问题:
原因:使用了变量length作为趟数
解决措施:定义一个整型变量t作为趟数,输出一次过程则t++
正确运行结果如图所示:
(2)各种查找算法实现
顺序查找
1、在设计初始化数组过程中,仅仅初始化了前4个元素,并未初始化数组的所有元素
原因:在函数中,传进来的形参是一个指针,此时如果调用sizeof(array)则得到了指针的大小4,而并非数组的大小16。
解决措施:在函数中增加一个参数(元素的个数)传入有函数中,将for循环改为如下:
void InitCharArray(char array[], int n)
{
for (int i = 0; i < n; i++)
array[i] = ' ';
}
2、在设计随机生成16个不重复的小写字母函数时,运行结果显示的小写字母个数总是小于16个
原因:在调试中发现
- 设计变量i的变化出了差错;在产生重复字母时将flag设置为0,在产生不重复字母时应将flag改为1。即设计if else语句。
- for循环中i不应该每一个循环增加一次,而应该是储存了字母才增加
解决措施:将上述代码改为如下:
正确的运行结果如图所示:
3、顺序查找的程序可以显示运行结果,但是程序报错:
原因:在调用加入了哨兵的顺序查找算法时,原本数组RecType recArray[16]只有16个元素的位置,但是在加入哨兵的顺序查找算法函数中使用了recArray[16],而recArray最大只能使用到recArray[15],故函数调用出错。
解决办法:暂无。只能先不用这个算法而使用未加入哨兵的顺序查找算法。
(2)各种查找算法的实现
折半查找
1、使用折半查找,若在静态查找表中找到了用户输入的指定字符,所用的比较次数不应该为零。但是调试过程中比较次数的结果始终为零。
原因:通过设置断点,我找出了问题所在:
在while循环过程中,若是查找成功,函数在循环体里面就结束了函数调用,并没有执行后面的 counter = count语句,因此,counter的值始终为初始值0。
解决措施:将counter = count语句的位置设置在查找成功的if语句块中。
if (k == R[mid].key) //查找成功返回其对应的逻辑序号
{
counter = count + 1;
return mid + 1;
}
正确的运行结果如图所示:
二叉排序树查找
1、使用折半查找,若二叉排序树中找到了用户输入的指定字符,所用的比较次数不应该为零。但是调试过程中比较次数的结果始终为零。
原因:通过设置断点,我找出了问题所在:在二叉排序树的查找的函数中,仅仅对临时变量count进行修改,并没有通过形参来改变实参,实参始终不变(为0)。
解决措施:直接对形参进行修改,每比较一次,counter++
if (bt == NULL || bt->Key == k) //递归结束条件
{
counter++;
return bt;
}
if (k < bt->Key)
{
counter++;
return SearchBST(bt->lchild, k, counter); //在左子树中递归查找
}
else
{
counter++;
return SearchBST(bt->rchild, k, counter); //在右子树中递归查找
}
正确的运行结果如图所示:
六、实验结果及分析
截屏的实验结果和结果分析
(1)各种排序算法的实现
直接插入排序
实验结果截屏:
实验结果分析:
- 输出1,执行对元素的直接插入排序。
- 从第二个元素开始与前面的元素进行比较,插入到比它大的元素的后面。
- 每次执行都需要15次,因为从第二个元素开始一直到最后一个元素都需要与其前面的元素进行比较。
- 最终结果为递增有序。
- 关键字的比较次数和移动次数较多。
对该结果的评价:
- 该结果是正确的,能够很好地反映出直接插入排序的全过程。
- 暂未发现可以改进的地方。
冒泡排序
实验结果截屏:
实验结果分析:
1、输出2,执行对元素的冒泡排序。
2、每执行一趟,就从后往前进行两两元素关键字的比较并在需要时进行交换,冒出元素关键字最小的在前面。
3、每次执行的次数都不一定相同。
4、最终结果为递增有序。
5、关键字的比较次数和移动次数较多。
对该结果的评价:
- 该结果是正确的,能够很好地反映出冒泡排序的全过程。
- 暂未发现改进之处。
双向冒泡排序
实验结果截屏:
实验结果分析:
- 输入3,执行对元素的双向冒泡排序。
- 按照元素关键字大的往右冒和元素关键字小的向左冒的次序进行排序,直到所有元素的关键字有序。
- 所用的比较次数与移动次数较多。
对该结果的评价:
- 该结果是正确的,反映了双向冒泡排序的过程及结果。
- 改进之处:该排序没有说明明确的趟数(因为不知道趟数的定义是什么),若是可以加上趟数的说明则程序会更好。
快速排序
实验结果截屏:
实验结果分析:
- 输出4,执行对元素的快速排序。
- 每一次划分都将逐层进行排序。
- 每次调用时划分的次数不一定相同。
- 所用的比较次数和移动次数较少。
对该结果的评价:
- 该结果是正确的
- 改进之处:因快速排序函数是递归的,我不会在其函数中进行排序前关键字的输出以及排序所用的比较次数和移动次数的输出,因此将这些功能的实现写在了main函数中。其中,通过参数的引用来获取比较次数和移动次数。
简单选择排序
实验结果截屏:
实验结果分析:
1、输出5,执行对元素的简单选择排序。
2、每一趟都将无序区中元素关键字最小的纳入有序区的末尾。
3、每次调用时所需的趟数均为15。
4、所用的比较次数和移动次数较少。
对该结果的评价:
1、该结果是正确的
2、暂未发现可改进之处。
堆排序
实验结果截屏:
实验结果分析:
1、输出6,执行对元素的堆排序。
2、每一趟都将关键字最小的元素排到数组末尾。
3、每次调用时所需的趟数均为15。
4、所用的比较次数较多,所用的移动次数较少。
对该结果的评价:
1、该结果是正确的
2、暂未发现可改进之处。
二路归并排序
实验结果截屏:
实验结果分析:
1、输出7,执行对元素的二路归并排序。
2、第一趟将元素两两排序,第二趟将元素四个四个排好了序,第三趟将元素八个八个排好了序,第四趟将所有元素(16个)排序完成。
3、每次调用时所需的趟数均为4。
4、所用的比较次数和移动次数较多。
对该结果的评价:
1、该结果是正确的
2、暂未发现可改进之处。
(2)各种查找算法实现
实验结果截屏:
顺序查找
查找成功案例:
查找失败案例:
实验结果分析:
1、在静态查找表中进行顺序查找,查找所用的比较次数即为所查字母位于该静态查找表中的位置(逻辑序号)。
2、查找字母成功,输出该字母对应的逻辑序号并输出比较次数;查找字母失败,提示查找失败。
对该结果的评价:
- 该结果能够正确地反映出所查找的字母是否在静态查找表中,并能输出对应的逻辑序号和所用的比较次数。
- 待改进的地方:算法设计中所采用的是未加入哨兵的算法,查找效率没有加入哨兵高,但是所用的比较次数基本和加入哨兵的算法相同(除了失败的情况,加入哨兵算法所用的比较次数为元素个数加1)。
折半查找
查找成功案例:
查找失败案例:
实验结果分析:
1、折半查找的效率高,但要求静态查找表为有序的。在静态查找表中进行顺折半查找,查找所用的比较次数为所查字母位于该静态查找表所对应的判定树所在的层数(查找成功,走了一个从根结点到某个内部结点的路径,比较次数为该内部结点的层次)。
2、查找字母成功,输出该字母对应的逻辑序号并输出比较次数;查找字母失败,提示查找失败。
对该结果的评价:
1、该结果能够正确地反映出所查找的字母是否在静态查找表中,并能输出对应的逻辑序号和所用的比较次数
2、待改进的地方:该结果并未输出查找失败所用的比较次数(查找不成功,走了一个从根结点到某个外部结点的路径,比较次数为该外部结点的层次减1)
二叉排序树查找
查找成功案例:
查找失败案例:
实验结果分析:
1、在二叉排序树中进行查找,查找所用的比较次数即为所查字母位于二叉排序树中的层数(查找成功,走了一个从根结点到某个内部结点的路径,比较次数为该内部结点的层次)。
2、查找字母成功,输出查找该字母所用的比较次数,并输出该字母所对应的结点的地址;查找字母失败,提示查找失败。
对该结果的评价:
1、该结果能够正确地反映出所查找的字母是否在该二叉排序树中,并能输出结点对应的地址和所用的比较次数。
2、待改进的地方:该结果并未输出查找失败所用的比较次数(查找不成功,走了一个从根结点到某个外部结点的路径,比较次数为该外部结点的层次减1)
实验思考题
1、给定一棵非空二叉排序树的先序序列,可以唯一确定该二叉排序树吗?为什么?
可以。因为可以先对这个先序序列排序,所得到的排序序列即为该二叉排序树的中序遍历序列。用先序和中序序列就可以唯一确定这棵二叉排序树。
2、基数排序过程中采用单链表和顺序表存放排序元素有何区别?
在基数排序中每个元素多次进出队列,如果采用顺序表储存,需要有大量的元素移动;而采用单链表时只需要修改相应相关的指针域。
- 在设计随机生成16个不重复的小写字母函数时,运行结果显示的小写字母个数总是小于16个
原因:在调试中发现
- 设计变量i的变化出了差错;在产生重复字母时将flag设置为0,在产生不重复字母时应将flag改为1。即设计if else语句。
- for循环中i不应该每一个循环增加一次,而应该是储存了字母才增加
解决措施:将上述代码改为如下:
正确的运行结果如图所示:
- 顺序查找的程序可以显示运行结果,但是程序报错:
原因:在调用加入了哨兵的顺序查找算法时,原本数组RecType recArray[16]只有16个元素的位置,但是在加入哨兵的顺序查找算法函数中使用了recArray[16],而recArray最大只能使用到recArray[15],故函数调用出错。
解决办法:暂无。只能先不用这个算法而使用未加入哨兵的顺序查找算法。
折半查找
1、使用折半查找,若在静态查找表中找到了用户输入的指定字符,所用的比较次数不应该为零。但是调试过程中比较次数的结果始终为零。
原因:通过设置断点,我找出了问题所在:
在while循环过程中,若是查找成功,函数在循环体里面就结束了函数调用,并没有执行后面的 counter = count语句,因此,counter的值始终为初始值0。
解决措施:将counter = count语句的位置设置在查找成功的if语句块中。
if (k == R[mid].key) //查找成功返回其对应的逻辑序号
{
counter = count + 1;
return mid + 1;
}
正确的运行结果如图所示:
二叉排序树查找
1、使用折半查找,若二叉排序树中找到了用户输入的指定字符,所用的比较次数不应该为零。但是调试过程中比较次数的结果始终为零。
原因:通过设置断点,我找出了问题所在:在二叉排序树的查找的函数中,仅仅对临时变量count进行修改,并没有通过形参来改变实参,实参始终不变(为0)。
解决措施:直接对形参进行修改,每比较一次,counter++
if (bt == NULL || bt->Key == k) //递归结束条件
{
counter++;
return bt;
}
if (k < bt->Key)
{
counter++;
return SearchBST(bt->lchild, k, counter); //在左子树中递归查找
}
else
{
counter++;
return SearchBST(bt->rchild, k, counter); //在右子树中递归查找
}
正确的运行结果如图所示: