一、希尔排序:
(可以看做插入排序的升级,属于插入排序类)
基本思想:
将待排序列划分为若干组,在每一组内进行插入排序,以使整个序列基本有序,然后再对整个序列进行插入排序。
基本有序的概念:就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间。
基本有序举例:{ 2,1,3, 6,4,7, 5,8,9 }
划分为若干组的目的:减少待排序记录的个数,并使整个序列向基本有序发展。
如何划分:一般采用跳跃分割策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
注:对于增量序列的增量值的选取值,迭代的最后一个增量值必须等于 1,因为需要对基本有序的序列进行插入排序。
还有由于跳跃式的移动,希尔排序并不是一种稳定的排序算法。
实现代码:
void shell_sort(int p[], int len) { int i = 0, j = 0; int gap = len; do { gap = gap/3 + 1; for(i=gap; i<len; i++) { int tmp = p[i]; /*可对比插入排序,插入排序即是 gap = 1的希尔排序*/ for(j=i-gap; (j>=0) && (p[j] > tmp); j -= gap) { p[j+gap] = p[j]; } p[j+gap] = tmp; } }while(gap > 1);
二、快速排序
(可以看做冒泡排序的升级,属于交换排序类)
基本思想:
1》任取待排序序列中的某个数据元素(例如:第一个元素)作为基准,按照该元素的关键字代销将整个序列划分为左右两个子序列:
?左侧子序列中所有元素都小于或等于基准元素
?右侧子序列中所有元素都大于基准元素
?基准元素排在这两个子序列中间
2》分别对这两个子序列重复施行上述方法,直到所有的对象都排在相应位置上为止。
从上面的基本思想可以得出,快速排序也利用的递归分治的思想:首先对无序的记录序列进行“一次划分”,之后分别对分割所得两个子序列”递归“进行快速排序。
还有由于跳跃式的移动,快速排序并不是一种稳定的排序算法。
学习博客:http://blog.csdn.net/morewindows/article/details/6684558#reply
实现代码:
/*采用交换的方式,返回枢轴位置*/ int partition(int p[], int low, int high) { /直接采用第一元素做枢轴,有待优化。详见下文*/ int pv = p[low]; while(low < high) { while(low < high && p[high] >= pv) { high--; } swap(p, low, high); while(low < high && p[low] <= pv) { low++; } swap(p, low, high); } return low; } void q_sort(int p[], int low, int high) { int pv = 0; if(low < high) { /*将传进来的序列一分为二,选出枢轴*/ pv = partition(p, low, high); /*对低值序列进行递归排序*/ q_sort(p, low, pv-1); /*对高值序列进行递归排序*/ q_sort(p, pv+1, high); } } void quick_sort(int p[], int len) { q_sort(p, 0, len-1); }
快速排序的优化:
优化1:关于快速排序程序中枢轴的选取(int
pv = p[low];),若此值恰在整个序列的中间,则我们可以将整个序列分成小数集合和大数集合。但是如果此值偏大,或者偏小,将影响算法的性能。因此有必要使用一定的方法对枢轴进行选取
a> 随机获取low与high之间的数 rnd,但是这种选取方法却有些运气成分。。
b>三数取中法:取三个关键字先进行排序,将中间数作为枢轴,一般是取左端、右端、中间三个数。
对于非常大的待排序的序列来说为了选取合适的枢轴,还可以采用九数取中法:先从数组中分三次取样,每次取三 个数,三个样品各取出中数,然后在这三个中数中再取出一个中数作为枢轴。
优化2:可以优化不必要的交换(以取第一个元素做枢轴值)
采用”挖数填坑“的方式,从右边(high)遍历第一个比枢轴值小的元素,填到选取 枢轴位置 的”坑“里(此也为”挖数过程",为下一元素挖的”坑“)。然后从左边( low )遍历第一个比枢轴大的元素,填到刚才的坑里。如此往复。直到( low = = high).将备份的枢轴值填进去。省去了不必要的交换过程.
优化3:优化小序列的排序方案
如果数组非常小,使用直接插入排序算法的性能反而更好,也就是说快速排序更适合非常大的序列排序。这样我们最好在程序里设置合适的阈值以选择合适的排序算法。譬如下述算法:
if( (high - low) > max_length_insert_sort) { /*将传进来的序列一分为二,选出枢轴*/ pv = partition(p, low, high); /*对低值序列进行递归排序*/ q_sort(p, low, pv-1); /*对高值序列进行递归排序*/ q_sort(p, pv+1, high); } else insert_sort(p, len)
三、归并排序
(属于归并排序类)
基本思想:
将两个或两个以上的有序序列合并成一个新的有序序列:
有序序列V[1]...V[M] 和 V[m+1]...V[n] → V[1]...V[n]
上述的归并方法称为2路归并。
由此还可以延伸出3路归并、多路归并。。
归并排序是一种稳定的排序算法。
归并步骤:
即:将两个有序序列A和B 归并到 C后 新的有序序列过程
? i 和 j都在两个序列内变化,根据 i 和 j下标指向关键字大小将较小的数据元素排放到新的序列C中,放到 k 所指的位置
? 当 i 和 j中有一个已经超出序列时,将另一个序列中剩余部分照搬到新序列中。
实现代码:
void merge(int src[], int des[], int low, int mid, int high) { int i = low; int j = mid + 1; int k = low; while( (i <= mid) && (j <= high) ) { if(src[i] <= src[j]) { des[k++] = src[i++]; } else { des[k++] = src[j++]; } } /*若 i 指向的序列还有剩余,则把剩余的搬过去*/ while(i <= mid) { des[k++] = src[i++]; } /*若 j 指向的序列还有剩余,则把剩余的搬过去*/ while(j <= high) { des[k++] = src[j++]; } }
划分步骤:
将初始无序序列划分为有序队列
显然归并的为有序序列,但是提供的一般为无序的序列,那么怎么将无序的序列转化为有序的序列呢?
我们知道若在初始队列中含有 n 个记录,则可以看成是 n 个有序的子序列,每个子序列的长度为1.
则我们可以依照上述情景对初始队列进行划分。。
实现代码:
void m_sort(int src[], int des[], int low, int high, int max) { /*划分到只有一个数据元素的情况 递归划分、归并的终止条件*/ if(low == high) { des[low] = src[low]; } else { int mid = (low + high) / 2; /*辅助空间,每次递归都申请空间的话,消耗有点多*/ int* space = (int*)malloc(sizeof(int) * max); if(space != NULL) { /*容易想到的在第一次划分后,一直是先对划分的左表进行划分、归并、排序*/ m_sort(src, space, low, mid, max); /*之后对第一次划分后的右表进行划分、归并*/ m_sort(src, space, mid+1, high, max); merge(space, des, low, mid, high); } free(space); } }
归并排序函数:
void merge_sort(int p[], int len) { /*将p数组中的无序序列 归并排序 到p数组中, 中间过程需要使用辅助空间*/ m_sort(p, p, 0, len-1, len); }
注意思考每种排序使用的场合,归并排序是以上三种排序算法中唯一一个可以直接作为外排序的排序算法。
对各个算法的各个指标的对比: