【算法引出】:是简单选择排序算法的改进算法,简单选择排序中每一趟比较选出一个最小值,但是后一趟的比较中会重复前面的比较结果,存在重复。堆排序对其的改进体现在—— 每次选择最小值的同时,根据结果对其他的值进行调整。
【堆的概念】:堆是具有以下性质的完全二叉树,每个节点都大于或等于左右孩子节点的值,称为大顶堆;每个节点都小于等于左右孩子节点的值,称为小顶堆。
【算法思想】:以大顶端堆为例,(1)首先构造一个大顶堆,即堆顶为所有元素的最大值;(2)其次,将该堆顶元素与堆末尾元素互换,此时堆末尾便存储了最大元素;(3)除去堆末尾元素,对于剩余的N-i个元素反复执行(1)、(2)操作即可完成排序。
//堆排序,i表示当前节点编号,N表示全部节点编号,N从0开始 void heapAdjust(int arr[], int i, int N) { int j; j = i*2+1; //左孩子节点 while(j< N) { if( j+1 < N && arr[j+1] > arr[j]) { j++; //取左右孩子中的较大值,若只有左儿子没有右儿子则不用 } if(arr[j] <= arr[i]) { break; //大顶堆,如果出现儿子节点值小,则终止循环. } //否则交换子节点和父节点 swap(arr[j],arr[i]); //循环变量增加 i= j; j= 2*i + 1; } } void heapSort(int arr[], int N) { //构建大顶堆,初始是凌乱的,调整后成一个大顶堆,对每个有子节点的分支节点都需要进行比较才能完成一个大顶堆。 //时间复杂度为NlogN for(int i = N-1; i >= 0; i--) { heapAdjust(arr,i,N-1);//比较的位置从最后开始. } //把顶取出,放入最末元素进行调整,总共进行N次,时间复杂度NlogN for(int i = N-1; i >= 0; i--) { swap(arr[i],arr[0]); heapAdjust(arr,0,i); //终止的大小为i,每次只调整根节点即可。 } }
归并排序
参考:http://blog.csdn.net/morewindows/article/details/6678165
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。
可以看出合并有序数列的效率是比较高的,可以达到O(n)。
解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?
可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。
归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。
//将有序数组//将有二个有序数列a[first...mid]和a[mid...last]合并。 void mergearray(int a[], int first, int mid, int last, int c[]) { int i, j, n, m, k ; i = first; j = mid + 1; n = mid; m = last; k = 0; while( i <= n && j <= m) { if(a[i] <= a[j]) { c[k] = a[i]; i++; k++; }else { c[k] = a[j]; j++; k++; } } while(i <= n) { c[k] = a[i]; i++; k++; } while(j <= m) { c[k] = a[j]; j++; k++; } for (i = 0; i < k; i++) { a[first + i] = c[i]; } } void mergesort(int a[], int first, int last, int temp[]) { //若first<last,即分组中元素大于一个时,继续划分,否则停止. if (first < last) { int mid = (first + last) / 2; mergesort(a, first, mid, temp); //递归左边有序 mergesort(a, mid + 1, last, temp); //递归右边有序 mergearray(a, first, mid, last, temp); //再将二个有序数列合并 } } //归并排序 bool MergeSort(int a[], int n) { //申请临时转存空间,空间复杂度为O(N) //用于转存用,不必每次都重新申请存储空间 int *p = new int[n]; if (p == NULL) return false; //进行归并排序 mergesort(a, 0, n - 1, p); //删除临时转存空间 delete[] p; return true; }
参考:http://blog.csdn.net/morewindows/article/details/6684558
快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。
该方法的基本思想是:
1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。
虽然快速排序称为分治法,但分治法这三个字显然无法很好的概括快速排序的全部步骤。因此我的对快速排序作了进一步的说明:挖坑填数+分治法:
先来看实例吧,定义下面再给出(最好能用自己的话来总结定义,这样对实现代码会有帮助)。
以一个数组作为示例,取区间第一个数为基准数。
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
72 |
6 |
57 |
88 |
60 |
42 |
83 |
73 |
48 |
85 |
初始时,i = 0; j = 9; X = a[i] = 72
由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。
从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++; 这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j--;
数组变为:
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
48 |
6 |
57 |
88 |
60 |
42 |
83 |
73 |
88 |
85 |
i = 3; j = 7; X=72
再重复上面的步骤,先从后向前找,再从前向后找。
从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
从i开始向后找,当i=5时,由于i==j退出。
此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。
数组变为:
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
48 |
6 |
57 |
42 |
60 |
72 |
83 |
73 |
88 |
85 |
可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。
对挖坑填数进行总结
1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。
照着这个总结很容易实现挖坑填数的代码:
int AdjustArray(int s[], int l, int r) //返回调整后基准数的位置 { int i = l; int j = r; int x = s[l]; //s[l]即s[i]就是第一个坑 while(i < j) { //从右向左找小于x的数来填s[i] while(i < j && s[j] >= x) { j--; } //将s[j]填到s[i]中,s[j]就形成了一个新的坑 if(i < j) { s[i] = s[j]; i++; //i值右移 } //从左向右找大于或等于x的数来填s[j] while(i < j && s[i] < x) { i++; } //将s[i]填到s[j]中,s[i]就形成了一个新的坑 if(i < j) { s[j] = s[i]; j--;//j值左移 } } //退出时,i等于j。将x填到这个坑中。 s[i] = x; return i; } void quick_sort1(int s[], int l, int r) { if (l < r) { int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[] quick_sort1(s, l, i - 1); // 递归调用 quick_sort1(s, i + 1, r); } }
排序算法归类
插入排序类 |
选择排序类 |
交换排序类 |
归并排序类 |
|||
直接插入排序 |
希尔排序 |
直接选择排序 |
堆排序 |
冒泡排序 |
快速排序 |
归并排序 |
排序算法汇总:
|
平均时间复杂度 |
最好情况 |
最差情况 |
空间复杂度 |
稳定性 |
直接插入排序 |
O(n2) |
O(n) |
O(n2) |
O(1) |
稳定 |
冒泡排序 |
O(n2) |
O(n) |
O(n2) |
O(1) |
稳定 |
直接选择排序 |
O(n2) |
O(n2) |
O(n2) |
O(1) |
不稳定 |
希尔排序 |
O(nlogn)~O(n2) |
O(n1.3) |
O(n2) |
O(1) |
不稳定 |
快速排序 |
O(nlogn) |
O(nlogn) |
O(n2) |
O(logn) |
不稳定 |
堆排序 |
O(nlogn) |
O(nlogn) |
O(nlogn) |
O(1) |
不稳定 |
归并排序 |
O(nlogn) |
O(nlogn) |
O(nlogn) |
O(n) |
稳定 |