排序算法大总结
来源:互联网 发布:js a.length 编辑:程序博客网 时间:2024/04/29 19:14
前面在看STL源码剖析时,发现自己对排序算法掌握得不好,遂花时间彻底的学习了一番,并做个全面的总结如下。
一.直接插入排序
插入排序的基本思想是:每步将一个待排序的纪录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。
算法的伪代码:
for j ←2 to n
do key ← A[ j]
i ← j – 1
while i > 0 and A[i] > key
do A[i+1] ← A[i]
i ← i – 1
A[i+1] = key
分析:每次选择一个元素A[j]插入到之前已排好序的部分A[1…j-1]中,先将A[j]赋值到key变量,在插入过程中,key依次由后向前与A[1…j-1]中的元素进行比较,即从i=j-1、j-2,直到第一个元素0,若比较中发现key<A[i],则将A[i]往后移动,当key<A[i]不成立时,将key值放到i+1位置,即key应该插入的位置。
注意:此处是从后往前比较然后进行插入的,这样在数据基本有序的情况下算法的执行时间会很快。此算法是稳定的。
具体的复杂度分析如下:
最好情况:数据为完全正序,比较次数为n次,不需要移动元素,复杂度为O(n)。
最坏情况:数据为完全逆序,比较次数为1+2+..+n-1,复杂度为O(n2)。
平均情况:O(n2)。
下面是C++实现代码:
void insertsort(int a[],int n){int key;int i,j;for(i=1;i<n;++i){key=a[i];j=i-1;while(j>=0&&key<a[j]){a[j+1]=a[j];j--;}a[j+1]=key;}}
二、希尔排序(插入排序)
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。
最好情况:由于希尔排序的好坏和步长d的选择有很多关系,不能确切求出最好的情况下的算法时间复杂度。
最坏情况:O(N*logN)
平均情况:O(N*logN)
下面是希尔排序的c++代码:
void shellsort(int *a,int len){int i,j;int d=len/2;while(d>0){for(i=d;i<len;++i){j=i-d;while(j>=0&&a[j+d]<a[j]){swap(a[j+d],a[j]);j=j-d;} }d=d/2;}}
三、冒泡排序(交换排序)
基本思想:通过无序区中相邻记录关键字间的比较和位置的交换,使关键字最小的记录如气泡一般逐渐往上“漂浮”直至“水面”。
算法实现步骤:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。结束后,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
void bubblesort(int *a,int len){int i,j;for(i=0;i<len-1;++i){for(j=1;j<len-i;++j){if(a[j]<a[j-1]) swap(a[j-1],a[j]);}}}
实现代码二:
void bubblesort(int *a,int len){int i,j;bool didswap=false;for(i=0;i<len-1;++i){didswap=false;for(j=1;j<len-i;++j){if(a[j]<a[j-1]){swap(a[j-1],a[j]);didswap=true;}}if(didswap==false) return;}}代码二可以实现最佳情况下复杂度为O(n),代码中加入了一个判断,当内层循环中没有进行交换操作,则说明序列已经有序了,因此,算法结束,返回结果。
四、快速排序(交换排序)
基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
算法实现步骤:设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。
最坏情况:基本有序时,退化为冒泡排序,几乎要比较N*N次,故为O(N*N)。
快速排序算法实现有很多种,下面对各种实现一一进行分析。
实现一:递归实现,key为数组第一个元素
void quicksort(int *a,int first,int last){if(first>=last)return;int low=first,high=last;int key=a[first];while(low<high){while(low<high&&key<=a[high]) --high;a[low]=a[high];while(low<high&&key>=a[low]) ++low;a[high]=a[low];}a[low]=key;quicksort(a,first,low-1);quicksort(a,high+1,last);}实现一是用第一个元素作为关键元素(枢纽),当第一个数据为极值时,会出现最坏的情况,算法的复杂度没有得到改进。在算法导论上,看到快速排序的实现和此方式有点不同,虽然也是递归算法,但二分区间的方式不同,于是也实现了一下。有点不太直观,借用了这个博客中的图方便理解:http://blog.sina.com.cn/s/blog_73428e9a01017f9x.html
实现代码为:
int partition(int *a,int first,int last){int key=a[last];int i=first-1;int j;for(j=first;j<last;j++){if(a[j]<=key){++i;swap(a[i],a[j]);}}swap(a[i+1],a[j]);return i+1;}void quicksort(int *a,int first,int last){if(first>=last)return;int mid=partition(a,first,last);quicksort(a,first,mid-1);quicksort(a,mid+1,last);}上面都是递归实现的快速排序,但是基于非递归算法比对应的递归算法速度快的思想,因此尝试实现了非递归的快速排序。
实现二.非递归:
void quicksort(int *a,int size){stack<int> st;st.push(0);st.push(size-1);while(!st.empty()){int end=st.top;st.pop();int start=st.top;st.pop();int key=a[end];int i=start-1;int j;for(j=start;j<end;j++){if(a[j]<x){++i;swap(a[i],a[j];}}swap(a[i+1],a[end]);if(start<1){st.push(start);st.push(i);}if(i+2<end){st.push(i+2);st.push(end);}}}
但测试结果却是此非递归算法更慢,原因是STL容器是stack效率太低,因此,使用时最好用递归算法实现的代码。
实现三:随机化版本
快速排序大多时选择第一个元素或者最后一个作为主元,此处使用随机化选取主元的方式,这种情况下虽然最坏情况仍然是O(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。
int rand(int low,int high){int size=high-low+1;return low+rand()%size;}void quicksort(int *a,int first,int last){if(first>=last)return;swap(a[rand(first,last)],a[first]);int low=first,high=last;int key=a[first];while(low<high){while(low<high&&key<=a[high]) --high;a[low]=a[high];while(low<high&&key>=a[low]) ++low;a[high]=a[low];}a[low]=key;quicksort(a,first,low-1);quicksort(a,high+1,last);}
实现四:三平均分区法
是选择待排数组的第一个数作为中轴,而是选用待排数组最左边、最右边和最中间的三个元素的中间值作为中轴。这一改进对于原来的快速排序算法来说,主要有两点优势:
(1) 首先,它使得最坏情况发生的几率减小了。
(2) 其次,未改进的快速排序算法为了防止比较时数组越界,在最后要设置一个哨点。
五、直接选择排序
对于每一趟,选择未排序序列中的最小的数放到排序序列的最后面。即第一趟,选择数组所有元素中的最小值放到第一个位置,第二趟,选择第二个到数组尾中最小值(即次小值)放到第二个位置...直到最后一个。算法实现如下:
void selectsort(int a[],int first,int last){for(int i=0;i<last;++i){int min=i;for(int j=i+1;j<=last;++j){if(a[j]<a[min])min=j;}if(min!=i)swap(a[i],a[min]);}}
六、堆排序(选择排序)
直接看算法实现,分为三个子程序.
1、调整堆,对于一个存在子节点的节点,调整此节点的位置,使此节点所在的树满足大顶堆或者小顶堆的定义。下面程序时按升序排列,因此建立大顶堆。
void heapadjust(int a[],int first,int last){if(first>=last)return;int child=2*first+1;while(child<=last){if(child+1<=last&&a[child+1]>a[child])//选择子节点中较大的一个++child;if(a[first]<a[child])//父节点小于子节点{swap(a[first],a[child]);//交换first=child;child=2*child+1;}elsebreak;}for(int i=0;i<8;++i)cout<<a[i]<<" "; //方便查看结果cout<<endl;}对于上述程序,可以有一点改进,如果父节点值较小,需要多次和子节点、子节点的子节点...进行交换,此处将父节点的值保存下来,等到最后其插入位置确定后才进行赋值操作,减少赋值操作次数。改进的调整堆程序如下:
void heapadjust(int a[],int first,int last){if(first>=last)return;int child=2*first+1;int temp=a[first];while(child<=last){if(child+1<=last&&a[child+1]>a[child])++child;if(temp<a[child]) //此处和上述程序不同,需注意{a[first]=a[child];first=child;child=2*child+1;}elsebreak;}if(a[first]!=temp)a[first]=temp; //最后才进行赋值。for(int i=0;i<8;++i)cout<<a[i]<<" ";cout<<endl;}2、建立堆
在堆排序中,建堆是第一步,但是建堆也需要使用调整堆程序,原因是建堆过程是从最后一个非叶子节点开始进行调整的,直到根节点。
void buildheap(int a[],int first,int last){for(int i=(last-1)/2;i>=0;--i)heapadjust(a,i,last);}3、排序过程
先建立堆,然后将堆顶元素与最后一个未排序元素交换,然后对堆顶元素进行调整。如下:
void heapsort(int a[],int first,int last){buildheap(a,first,last);for(int i=last;i>0;--i){swap(a[i],a[0]);heapadjust(a,0,i-1);}}
- 排序算法大总结
- 排序算法大总结
- 排序算法大总结
- 排序算法大总结
- 【排序算法】:九大排序算法总结
- 九大排序算法总结
- 十大排序算法总结
- 九大排序算法总结
- 九大排序算法总结
- 九大排序算法总结
- 各大排序算法总结
- 九大排序算法总结
- 九大排序算法总结
- 8大排序算法总结
- 九大排序算法总结
- 九大排序算法总结
- 九大排序算法总结
- 10大排序算法总结
- JSP四种注释方法及相关注意事项
- Mysql的Root密码忘记
- hadoop知识之fsimage和editlog
- 操作系统概念学习笔记 14 死锁(二)
- 发送post或get请求
- 排序算法大总结
- 阅读一款3D引擎的方法备忘
- MIB OIDs for IIS 6.0
- Redis常用命令
- hadoop知识之fsimage和editlog
- maven---Cannot change version of project facet Dynamic web
- Python Xpath与Regex的区别
- CocoaPods 使用
- Fatal NI connect error 12170