开课实验室:计算机科学与工程实验(电子楼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