各种排序算法的实现和总结(部分原创)
直接上代码:
主程序:
#include <iostream> using namespace std; int main() { int a[6]; cout<<"请输入要排序的六个数字:"; //偷个懒,只输入6个数 for(int k=0;k<6;k++) { cin>>a[k]; } cout<<"排序前:"; for(int m=0;m<6;m++) cout<<a[m]<<' '; cout<<endl; //此处插入各种排序算法核心代码....... cout<<"排序后:"; for(int m=0;m<6;m++) cout<<a[m]<<' ';
之后就是各种算法的代码了,在调用相应的算法函数前记得先在main函数中声明哦。
首先冒泡排序,这个比较简单,就像冒泡一样(可以从小到大,也可以从大到小),大的升上去,小的降下来。我这里用的是小的降下来,如下:
void popsort(int a[],int n) { int j=0; for(int i=0;i<n-1;i++) { for(j=n-1;j>i;j--) { if(a[j]<a[j-1]) { swap(a[j],a[j-1]]); } } }
下面是选择排序,思路很简单,核心是从无序区(i+1~n-1)选择出最小的值与无序区的第一个数值交换。
1 //选择排序: 2 void selectsort(int a[],int length) 3 { 4 for(int i=0;i<length-1;i++) 5 { 6 int min_index=i; 7 for(j=i+1;j<length;j++) 8 { 9 if(a[j]<a[min_index]) 10 { 11 min_index=j; 12 } 13 } 14 if(min_index!=i) 15 swap(a[min_index],a[i]); 16 } 17 }
下面是插入法,跟打扑克牌的思路是一样的,从无序区按顺序拿出一个与有序区进行比较放到合适的位置。
1 void Insert_sort(int a[],int n)
2 {
3 for(int i=1;i<n;i++)
4 {
5 for(int j=i;j>0;j--)
6 {
7 if(a[j]<a[j-1])
8 swap(a[j],a[j-1]);
9
10 }
11 }
12
13 }
选择排序,冒泡法和插入法的共同点都是分为有序区和无序区,通过一定的方法将无序区的数据迁移到有序区,冒泡法和选择排序都是从无序区找出最大或最小放到有序区,而插入法则是直接从无序区随便选一个数据(从头开始)放到有序区,在放的过程中进行排序。简单说就是冒泡和选择是在无序区进行的排序操作,插入法是在有序区进行的排序操作。
然后是快速排序法,这个比较重要,采用的递归分治的思想,但是写起来稍微有点麻烦,得经常练练。我这里采用了csdn上的MoreWindows的白话经典上的写法,感觉这个比较清晰,理解起来不难。
void quick_sort(int a[],int l,int r) { if(l<r){ int i=l,j=r; int x=a[l]; //初始化三个变量,其中l和r对应数组的两个边界(0和n-1),关键值x所在的位置相当于一个坑,需要别人来补,最初的坑的值已经保存到了x中。 while(i<j) { while(j>i&&a[j]>x)//从右边开始找小于x的值 j--; if(i<j) a[i++]=a[j]; //这里i++主要是因为下一步中a[i]不需要跟x比较,而是从a[i+1]开始比较。 while(i<j&&a[i]<x)//从左边开始找第一个大于x的值 i++; if(i<j) a[j--]=a[i]; } a[i] = x; quick_sort(a, l, i - 1); // 对左边递归调用 quick_sort(a, i + 1, r); //对右边递归调用 }
我最早的思路是这样的,不是采用的赋值,而是用的交换,这样感觉好记忆一些,但是貌似效率不如直接赋值
void quick_sort(int a[],int l,int r) { if(l<r){ int i=l,j=r; int x=a[l]; while(i<j) { while(j>i&&a[j]>x) j--; if(i<j) swap(a[i++],a[j]); //这里采用的swap函数代替的上面的方法1 while(i<j&&a[i]<x) i++; if(i<j) swap(a[j--],a[i]); } quick_sort(a, l, i - 1); //此处为跟方法一的另一个不一样的地方,这里不需要另a[i]=x;了,因为之前交换以后a[i]已经是x了 quick_sort(a, i + 1, r); } }
其实上面两个方法的对比正是快排的优化方法之一,另外几个优化方法包括
1,初始点的选择优化,即任意挑选m(比如3)个,选出最中间的数据作为初始点。
2,优化小数组排序时的排序方法,即因为快排适合于对大量数据进行排序,对小数量是体现不出优势,所以当数组数量(j-i)小于某个值(一般是7)时采用插入排序代替快排,这样能提高速度。
3,优化递归,即改成尾递归的形式,也就是把原来的两个递归调用改成一个,这样编译的速度会加快。
最后是归并排序:
归并排序写起来也比较复杂,主要分为两步,归和并,归是递归将原始数组分成小的数组,如下图示,最小的一层每个子数组有两个元素。
然后是并,并的过程并不难,难理解的是最后一步,每一步并完以后要把list1变成合并后的序列。
//归并排序
//首先给出合并函数
void Merge(int *list1,int list1_size,int *list2,int list2_size) { int i,j,k; i=j=k=0; int const maxsize=6; int temp[maxsize]; //按照大小将两个小序列(list1,list2)合并为大序列temp while(i<list1_size&&j<list2_size) { if(list1[i]<list2[j]) { temp[k++]=list1[i++]; } else { temp[k++]=list2[j++]; } } //下面是把list1或list2中还没有排到合并序列temp的数据放到temp里。 while(i<list1_size) { { temp[k++]=list1[i++]; } while(j<list2_size) { temp[k++]=list1[j++]; } } //最后把temp数组的数据传给list1,这样list1在递归往回退的时候就变成了list1和list2合并后的数组,而n在递归时每层已经确定,从而完成每一层的合并。该步骤很关键! for(int m=0;m<(list1_size+list2_size);m++) temp[m]=list1[m]; } void mergesort(int a[],int n) { //通过递归将原来的数组划分成很短的片段 int *list1=a; //这里最关键的就是a,list2可以由a和n所确定。 int list1_size=n/2; int *list2=a+n/2; int list2_size=n-list1_size; if(n>1) { mergesort(list1,list1_size); mergesort(list2,list2_size); Merge(list1,list1_size,list2,list2_size); }
常用的排序还有堆排序(基于完全二叉树的,思路比较简单,只需将顶的根节点元素拿出来放到最后面,然后重新更新堆,不断重复,,,有点像选择排序)
希尔排序(基于插入法,将数组进行分组,然后对分的组分别进行插入排序)。
有时间再写上面两个。
最后给出各个算法的时间复杂度
平均时间复杂度
插入排序 O(n2)
冒泡排序 O(n2)
选择排序 O(n2)
快速排序O(n log n)
堆排序 O(n log n)
归并排序 O(n log n)
希尔排序 O(n1.25)
over~