排序
来源:互联网 发布:java jlabel 链接 编辑:程序博客网 时间:2024/04/28 22:05
排序算法总结
1. 概述
定义
排序是将一个数据元素记录的任意序列,重新排成一个按关键字有序的序列。
稳定性
标准:对于两个大小相同的元素,排序前位置领先的元素在排序后依然领先,则这种排序算法
是稳定的 ;反之,若可能使排序后两者的位置交换,则这种算法就是不稳定的。判断:说某种排序算法是稳定的,意思是说存在一种代码实现,使之满足以上标准;说某种排
序算法不是稳定的,意思是说,该算法的任何一种实现形式,都存在某组特殊的关键字,使之
不稳定。分类
按排序过程中的不同原则来分,可以分为 交换排序、选择排序、插入排序、归并排序和计数排序等五类。
按时间复杂度来分,可以分为简单排序(时间复杂度为O (n^2))、先进排序(时间复杂度为O (nlogn))和
基数排序(时间复杂度为O (dn))三类。
2. 交换排序
2.1 冒泡排序(Bubble Sort)
- 思想
首先将记录的第一个数据和第二个数据比较,若为逆序则将两个数据的位置交换,然后比较第二个和第三个数据。
以此类推,直到第n-1和第n个数据比较完,完成一趟冒泡排序,此时最大值位于最后一个位置处。进行完n-1趟排序
或者在一趟排序过程中没有发生数据交换,则排序完成。 - 伪代码
do swapped = false for i = 1 to indexOfLastUnsortedElement if leftElement > rightElement swap(leftElement, rightElement) swapped = true; swapCounter++while swapped
- 代码
void bubble_sort(int *a, int n){ swapped = 1; while(swapped) { int i,j,temp; swapped = 0; for(i=0;i<n-1-j;i++) { if(a[i]>a[i+1]) { temp = a[i]; a[i]=a[i+1]; a[i+1] = temp; swapped = 1; } } j++; }}
void bubble_sort(int *a, int n){ int i, j, temp,swapped; for(i = 0; i < n-1; i++) { swapped =0; for(j = 0; j < n-1-i; j++) { if(a[j] > a[j+1]) { temp = a[j]; a[j] = a[j+1]; a[j+1] = temp; swapped =1; } } if(swapped==0) break; }}
- 分析
交换次数较少,最少为0,比较次数多,为n(n-1)/2,时间复杂度为O (n^2)。
2.2 快速排序(Quick Sort)
思想
快速排序是对冒泡排序的一种改进,也是交换排序的一种。快速排序首先选择一个基数(通常选择序列的第一个数据),
然后以这个基数为标准将序列分成两个部分,其中一部分中的数据全部大于该基数,另一部分全部小于该基数。然后
将分成的两个部分再分别进行以上的排序处理,从而使整个序列有序。一般用递归来实现。代码
///////////////////////快速排序//查找位置int find_pos(int *a, int low, int high){ int val = a[low]; while(low < high) { while(low < high && a[high] >= val) {//大于移动,小于则赋值,降序则相反 high--; } a[low] = a[high]; while(low < high && a[low] <= val) {//小于移动,大于则赋值,降序则相反 low++; } a[high] = a[low]; }//终止while循环之后low和high一定是相等的 //high可以改为low a[low] = val; return low;}//low:第一个元素下标//high:最后一个元素下标void quick_sort(int *a, int low, int high){ if(low < high) //长度大于1 { int pos = find_pos(a, low, high); //将序列一分为二 quick_sort(a, low, pos-1); //对低位序列快速排序 quick_sort(a, pos+1, high); //对高位序列快速排序 }}
- 分析
快速排序平均时间复杂度为O (nlogn),最坏情况(逆序)的时间复杂度同冒泡法一样为O (n^2)。
快速排序是对冒泡排序的改进,冒泡排序是对每个相邻的元素进行比较,快速排序是对所有的数据同基准元素进行比较,空间跨度更大,比较次数和交换次数减少。同时,用到了二分的思想,降低的时间复杂度。
快速排序在同等数量级knlogn的排序算法中常数因子k最小,是平均时间最少的一种排序算法。
平均空间复杂度为O (log2n +1),最坏情况为O(n),此时栈的深度为n。
3. 选择排序
3.1 简单选择排序(Simple Selection Sort)
思想
令i从1到n-1,进行n-1趟选择排序,每一趟都从未进行排序的n-i+1个数据中找出最小的数据,然后和第i个数据进行交换,完成排序。
伪代码
repeat (numOfElements - 1) times{ set the first unsorted element as the minimum for each of the unsorted elements { if element < currentMinimum set element as new minimum } swap minimum with first unsorted position}
- 代码
void select_sort(int *a, int n){ int i, j, k, temp; for(i = 0; i < n-1; i++) { k = i; for(j = i+1; j < n; j++) { if(a[k] > a[j]) { k = j; } } if(i != k) { temp = a[i]; a[i] = a[k]; a[k] = temp; } }}
- 分析
交换次数较少,最少为0,最大为3(n-1),比较次数多,为n(n-1)/2,时间复杂度为O (n^2)。
3.2 堆排序(Heap Sort)
回顾补充
先定义一下二叉堆,二叉堆是完全二叉树或者是近似完全二叉树,且满足:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。下图展示一个最小堆:
堆的存储: 一般用数组来表示堆,i节点的父节点下标为(i-1)/2,它的左右子节点的下标为2*i+1和2*i+2。
思想
堆顶是序列的最小值(或最大值),输出堆顶后,将其余的元素重新排列成堆,可以得到序列的次小值,
然后将次小值输出,如此反复执行,便能得到一个有序序列。
所以,堆排序要做的就是输出堆顶和重新排列一个堆。- 代码
// 从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2 void RebuildMinHeap(int *a,int i, int n){ int j,temp; temp =a[i]; j=2*i+1; while(j<n) { if(j+1<n && a[j+1]<a[j]) //在左右孩子中找最小的 j++; if(a[j]<temp) //如果孩子节点比父节点小,则将孩子节点的值赋值给父节点,并继续往下走, { //如果上述赋值破坏了原有的堆结构,则重建。 a[i]=a[j]; i=j; j=2*i+1; } else break; } a[i]=temp; //最后,将开始的调整的顶节点的值放到合适的位置上}// 堆排序//1.建立堆化数组;//2.取出堆的顶点,把最后一位放到顶点位置;//3.重新建立堆化数组,重复步骤2,直到所有数据都被取出;void heap_sort (int *a,int n) { int i,j,temp; for(i=n/2 -1;i>=0;i--) //对于叶子节点来说,可以认为它已经是一个合法的堆了, { //所以从第一个非叶子节点n/2 -1开始重建堆。 RebuildMinHeap(a,i,n); //建立堆化数组 } for(i=n-1;i>=1;i--) //数组中第一个数据已经是最小值,先将该值取出,最后一个数据放到堆顶, { //然后,重新建立堆化数组。 temp= a[0]; a[0]=a[i]; a[i]=temp; RebuildMinHeap(a,0,i); }}//* 注意 使用最小堆排序后是递减数组,要得到递增数组,可以使用最大堆。
- 分析
由于每次重新恢复堆的时间复杂度为O(logn),共n- 1次重新恢复堆操作,再加上前面建立堆时n / 2次向>下调整,每次调整时间复杂度也为O(logn)。二次操作时间相加还是O(nlogn)。故堆排序的时间复杂度>为时间复杂度为O (nlogn)。
4. 插入排序
4.1 直接插入排序 (Straight Insertion Sort)
思想
最简单的排序方法,将一个元素插入到已经排好序的有序表中,得到一个新的、元素加1的有序表。
将表中的第一个元素看做是已经有序的表,然后将后面的元素依次插入到该表中。
具体做法是,将第一个元素看做是已经有序的,从第二个到第n个元素依次进行排序,排序的元素
先和已经排好序的表中的最后一个元素开始比较,若大于最后一个元素则插入到最后一个元素的后
一位,若小于最后一个元素,则最后一个元素后移一位,然后和倒数第二个元素比较,依次类推,
最终将排序的元素插入到有序列表中。然后再进行下一个元素的排序。伪代码
mark first element as sortedfor each unsorted element 'extract' the element for i = lastSortedIndex to 0 if currentSortedElement > extractedElement move sorted element to the right by 1 else: insert extracted element
- 代码
void insert_sort(int *a, int n){ int i, j, temp; for(i = 1; i < n; i++) { temp = a[i]; for(j = i-1; j >= 0 && a[j] > temp; j--) { a[j+1] = a[j];//将前面的值往后移一位 } a[j+1] = temp; //待排序元素放在a[j]的后面 }}
- 分析
需要的辅助空间为1,比较和交换次数约为(n^2)/4,时间复杂度为O (n^2)。
4.2 希尔排序 (Shells Sort)
思想
先将整个待排序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,
再对全体记录进行一次直接插入排序。子序列的分割是将相隔某个“增量”的记录组成一个序列。增量
的选取较复杂。若增量选取为:n,n/2,n/4,n/8···2,1 则称之为折半插入排序。所以说折半插入排序是希
尔排序的一种,下面以折半插入排序为例进行介绍。代码
void shell_sort(int *a, int n) //希尔排序(折半插入排序){ int i, j, flag, temp; int gap = n; while(gap > 1) { gap = gap/2; //增量缩小,每次减半(折半) do { flag = 0; //n-gap是控制上限不让越界 for(i = 0; i < n-gap; i++) { j = i + gap; //相邻间隔的前后值进行比较 if(a[i] > a[j]) { temp = a[i]; a[i] = a[j]; a[j] = temp; flag = 1; } } }while(flag != 0); }}
- 分析
需要的辅助空间和直接插入排序相同,比较次数较直接插入排序减少了,但交换次数不变,故约时间复杂度仍为O (n^2)。若增量选取的好,希尔排序的时间复杂度可以降为O (n^3/2)。
5. 归并排序 (Merge Sort)
思想
归并排序建立在归并操作上的一种有效的排序算法。该算法是采用分治法。
首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?
可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。代码
//合并两个有序数列//从first到mid为第一个序列,mid+1到last为第二个序列void mergearray(int *a,int first,int mid,int last,int *temp){ int i=first,j= mid +1; int n=mid, m= last; int k=0; while(i<=n && j<=m) { if(a[i]<=a[j]) temp[k++]=a[i++]; else temp[k++]=a[j++]; } while(i<=n) { temp[k++]=a[i++]; } while(j<=m) { temp[k++]=a[j++]; } for(i=0;i<k;i++) { a[first+i]=temp[i]; //将缓存到temp中的有序序列转移到a数组中 }}//归并排序void merge_sort(int *a,int first,int last,int *temp){ if(first < last) { int mid =(first + last)/2; merge_sort(a,first,mid,temp); //左侧序列有序 merge_sort(a,mid+1,last,temp); //右侧序列有序 mergearray(a,first,mid,last,temp); //两个有序序列合并 }}
- 分析
将数列分开成小数列一共要logn步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(n),故时间复杂度一共为O (nlogn)。
辅助空间为n。
是一种稳定的排序算法(快速排序和堆排序都是不稳定的)。
6. 计数排序 (Count sort)
思想
计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。
算法的步骤如下:- 找出待排序的数组中最大和最小的元素
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
- 对所有的计数累加(从C中的位置为1的元素开始,每一项和前一项相加)
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
代码
void count_sort(int *a,int n){ int i,j,num,max=-INF,min=INF; for(i=0;i<n;i++) //找最大值最小值 { if(a[i]>max) { max=a[i]; } if(a[i]<min) { min=a[i]; } } num=max-min+1; //计算C数组的大小 int c[num]; for(i=0;i<num;i++) //初始化C数组 { c[i]=0; } for(i=0;i<n;i++) //填充C数组 { c[a[i]-min]++; } j=0; i=0; while(i<n && j<num) //从C数组中取值出来 { while(i<n && c[j]!=0) { a[i]=j+min; c[j]--; i++; } j++; }}
- 分析
由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。时间复杂度为O (n)。 是一种稳定排序算法。
7. 总结
比较以上几种排序算法,有以下结果:
以上就是对排序算法的一个小的总结,还有一些算法没有涉及到,如果以后涉及到了,再做补充。
参考:
1. 《数据结构-C语言版》(严蔚敏,吴伟民版);
2. http://blog.csdn.net/jnu_simba/article/details/9705111?utm_source=tuicool&utm_medium=referral
3. http://blog.csdn.net/morewindows/article/details/6709644
4. http://blog.csdn.net/morewindows/article/details/6678165/
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- Android自定义View实战---圆盘温度计
- 多线程编程入门(7):线程范围内的共享变量
- 排序算法 之 希尔排序ShellSort
- 匈牙利算法(膜拜大神orz)
- XZ_iOS之View的生命周期
- 排序
- POJ Wormholes 3259(最短路)
- Android Cursor类的了解和使用
- Codeforces 14D 求树的直径(网上大多数是DFS于是自己写了个BFS)
- java之网络编程基础
- 排序算法之一 快速排序
- 采用libxml2解析xml资源
- vs2008进行移动开发WINCE时,编译慢的解决方法
- 对拍程序