当然这也是数据结构的最后一个实验了,通过以前9次实验给我们带来了很深的思考,现在对于编程算法应该来说有了很大的提升,对于做题也是非常好的,我想现在对于我来说,编程能力有了质的飞跃,很感谢这门数据结构课程,让我理解了很多!当然后面《算法设计与分析》这门课也十分重要(尽管很多专业并不会开,在我们这好像就软件工程才开,所以需要自学了),是编程能力的又一突破,我也希望学完这门课的同学也可以学习算法这门课,推荐教材:李春葆,《算法设计与分析(第二版)》,清华大学出版社。
最后,对前面的数据结构实验进行汇总吧!
实验一 链表的应用(城市信息)
实验二 栈的应用(进货信息)
实验三 二叉树的算法(递归与非递归)
实验四 哈夫曼编码
实验五 图的遍历(邻接矩阵与邻接表)
实验六 AOE关键路径
实验七 查找的应用(姓名查找)
实验八 查找的算法(平衡二叉树+Hash)
本次数据结构实验为排序算法,书上讲了大概7种排序,本人比较推荐的是快速排序和堆排序,这两个排序也是C++ STL sort函数的排序方法,这里列举了各种排序方法性能比较,目前整理出六种排序算法,其中基数排序等过段时间更新以下,让我缓缓!(明天还得搞六级。。)
百度网盘提取算法测试代码提取码:ss6l
排序方法 | 平均时间复杂度 | 辅助存储空间 |
---|---|---|
简单排序 | O ( n 2 ) O(n^{2}) O(n2) | O(1) |
快速排序 | O ( n log n ) O(n\log_{}{n} ) O(nlogn) | O(log(n) |
堆排序 | O ( n log n ) O(n\log_{}{n} ) O(nlogn) | O(1) |
归并排序 | O ( n log n ) O(n\log_{}{n} ) O(nlogn) | O(n) |
基数排序 | O(d(n+rd)) | O(rd) |
首先给出结构体代码
typedef struct{
int key;
}RecordList;
以下代码中均附有详细注释,故不再给出解释,只给出最精炼的语言!
你的三连就是我创作的最大动力!
直接插入排序
直接插入,怎么想呢,就是从第2个数开始与前面的进行排序,然后改变位置。
void InsertSort(RecordList r[],int n){
for(int i=2;i<=n;i++){
//为什么i从2开始,因为i=1不用排序
count++;//计数每一趟
r[0]=r[i];//设置哨兵,将待排序放在r[0]
int j=i-1;//j表示前面已经排序过的位置
while(r[0].key<r[j].key){
//此处从小到大排序
//将j后移,因为前面已经排序了,所以遇到大于就停
r[j+1]=r[j];
j--;
}
r[j+1]=r[0];//此时将哨兵插入到j+1位置
//现在进行每一趟的输出
cout<<"第"<<count<<"趟排序:";
for(int k=1;k<=n;k++){
cout<<r[k].key<<" ";
}
cout<<endl;
}
}
冒泡排序
冒泡怎么小,很简单,就是每一趟选最小的在第i个位置(i=1,2…n)
void BubbleSort(RecordList r[],int n){
bool change=true;//设置flag,这样不用做无用的循环
for(int i=1;i<=n-1&&change;i++){
count++;
change=false;//如果已经排序完了,那么会变成false,下一次循环则跳出
//不过我觉得这样多次一举,直接让j从i+1开始比较不就可以了吗?
for(int j=1;j<=n-1;j++){
if(r[j].key>r[j+1].key){
swap(r[j],r[j+1]);
change=true;
}
}
//现在进行每一趟的输出
cout<<"第"<<count<<"趟排序:";
for(int k=1;k<=n;k++){
cout<<r[k].key<<" ";
}
cout<<endl;
}
}
快速排序
快速排序怎么想,先选出一个基准,一般为第一个数,然后进行从右和从左进行排序,注意先从右开始,然后遇到比基准小的,再从左开始,遇到比基准大的,然后对刚选出来的进行交换,这样直到中间,此时一个基准已经完成,后面分治递归即可。
//一趟快速排序过程
int QKPass(RecordList r[],int low,int high){
count++;
//对r[low]和r[high]进行划分,得出基准位置
RecordList x=r[low];//现在从low开始并且保存在x中,也就是说x作为基准,将会对数组进行划分,比他小的在左,大的在左边
//关键步骤,注意先从指针j开始,再从i开始
while(low<high){
while(low<high&&r[high].key>=x.key){
high--;
}
//为什么这里每次都要出现low<high,因为每一次调整都需要看是否基准到位了,如果没达到则进行调整
if(low<high){
r[low]=r[high];
low++;
}
while(low<high&&r[low].key<x.key){
low++;
}
if(low<high){
r[high]=r[low];
high--;
}
}
r[low]=x;
//现在进行每一趟的输出
cout<<"第"<<count<<"趟排序:";
for(int k=1;k<=N;k++){
cout<<r[k].key<<" ";
}
cout<<endl;
return low;
}
void QuickSort(RecordList r[],int low,int high){
if(low<high){
int pos=QKPass(r,low,high);
//采用分治法进行排序,其实也有类似于希尔排序的味道
QuickSort(r,low,pos-1);
QuickSort(r,pos+1,high);
}
}
选择排序
选择排序怎么想呢?简单来说,就是选出最小的在左边,思想类似于冒泡排序,不过冒泡是一个数一直到底,而选择排序呢就是相邻的数交换。
void SelectSort(RecordList r[],int n){
for(int i=1;i<=n-1;i++){
//为什么从1开始,不像插入排序从2开始,因为i=1也需要选出最小的在第一位
count++;
int k=i;//保存此时i的位置
for(int j=i+1;j<=n;j++){
//区别于冒泡排序,j从i+1开始,因为前面已经排序,那么只需要对后面的进行排序
if(r[j].key<r[k].key){
k=j;//后面的如果比此时第i位小那么保存此时j的位置
}
}
if(k!=i){
swap(r[i],r[k]);
}
//现在进行每一趟的输出
cout<<"第"<<count<<"趟排序:";
for(int k=1;k<=N;k++){
cout<<r[k].key<<" ";
}
cout<<endl;
}
}
堆排序
堆排序怎么想?首先你要有二叉树的基本知识,特别是层次遍历,对于二叉树的孩子双亲数组有认识才能理解算法的创建。注意本质就是从左到右进行选父亲结点,然后把根节点当在堆底。
//筛选法:改变一次堆
void sift(RecordList r[],int k,int m){
//这是层次遍历的二叉树数组,r[k]为根,r[2k]左,r[2k+1]右,为什么呢?参考二叉树孩子双亲存储
RecordList t=r[k];//保存根的位置
int x=r[k].key;
int i=k;//保存指针k的位置i
int j=2*i;//注意只是指i的左子树,先从左到右进行遍历
bool finished=false;//为什么要设置flag,类似于冒泡,避免无效多余的循环
while(j<m&&!finished){
//m是什么呢?m就是堆底
//首先比较左右子树,如果左右子树大小不一样,则指针改变,变成右子树
if(j<m&&r[j].key<r[j+1].key) j++;
//如果父亲结点大于孩子结点则true,不然就交换位置,注意这是一次改变
if(x>=r[j].key) finished=true;
else{
r[i]=r[j];
i=j;
j=2*i;
}
}
//将t插入到此时i位置,如果没有改变则还是原位置,如果改变则进行位置转换
r[i]=t;
}
//建立堆算法
void crt_heap(RecordList r[],int n){
for(int i=n/2;i>=1;--i){
//为什么从n/2开始呢?二叉树呗,教材以中序遍历为根,没进行一次都需要改变
sift(r,i,n);
}
}
//堆排序,前面两步都是前期工作,相当于每一次都要改变全部根节点
void HeapSort(RecordList r[],int n){
crt_heap(r,n);
for(int i=n;i>2;i--){
//为什么从n开始,这样比较好操作
count++;
RecordList b=r[1];//第一位置
r[1]=r[i];
r[i]=b;
sift(r,1,i-1);
//现在进行每一趟的输出
cout<<"第"<<count<<"趟排序:";
for(int k=1;k<=N;k++){
cout<<r[k].key<<" ";
}
cout<<endl;
}
}
归并排序
归并排序怎么想呢?有没有感觉希尔排序?有增量的感觉。从中间开始拆分,然后排序,最后一个个的合并,这就是其本质。
void Merge(RecordList r1[],int low,int mid,int high,RecordList r[]){
count++;
//从中间开始归并,将r1[low..mid]和r1[mid+1...high]排列保存在r[]
int i=low,j=mid+1,k=low;//k作为r的low位
while((i<=mid)&&(j<=high)){
//从中间分成两路,然后对两路进行排序
if(r1[i].key<=r1[j].key){
//如果小于中间的,那么放在r[low]中
r[k]=r1[i];
i++;
}
else{
r[k]=r1[j];
j++;
}
k++;//r[]遍历创建
}
while(i<=mid){
r[k]=r1[i];
k++;
i++;
}
while(j<=high){
r[k]=r1[j];
k++;
j++;
}
}
void MergeSort(RecordList r1[],int low,int high,RecordList r[]){
//排序后放在r[]中,r2作为辅助空间
count++;
RecordList *r2;
r2=new RecordList[high-low+1];
if(low==high) r[low]=r1[low];
else{
int mid=(low+high)/2;
MergeSort(r1,low,mid,r2);
MergeSort(r1,mid+1,high,r2);
Merge(r2,low,mid,high,r);
}
delete r2;
}
总结
以上几种排序是最基本的算法,当然排序算法还是可以进行进一步优化的,有时间出一个优化算法。这些算法都是数据结构的基本功,尽管理解起来比较容易,但是代码实现还是有一定的难度,特别体现在快速、归并、堆排序中。对于排序的理解,我感觉是注意抓住指针(位置)的特性,什么时候改变位置,什么时候交换都是要比较清楚的。