排序再学习 - 冒泡、快速、归并、堆排序
文章发布于博客园,转载请注明出处,http://www.cnblogs.com/lifehack/p/3978101.html
1. 冒泡排序
每次比较数组中的两个数,如果和你期望的顺序不一致,就交换这两个数,一次循环下来能将一个数摆在正确的位置上。外层循环共需要N-1次,因为N-1个数都已经摆在正确的位置上,那第N个数也已经是正确的了。内层循环也可以是N-1次,也可以每次都比上一次少循环一次,第一种情况会比较已经排好序的部分,第二种情况已经排序过的部分不需要再比较了。还是两层循环都是N-1次比较好写。
void bubble_sort(int array[], int n)
{
int i, j, temp;
for ( i = 0; i < n - 1; i++ )
{
for ( j = 0; j < n - 1; j++ )
{
if ( array[j] > array[j+1] )
{
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
return;
}
内层循环每次减少一次
void bubble_sort(int array[], int n)
{
int i, j, temp;
for ( i = 0; i < n - 1; i++ )
{
for ( j = 0; j < n - 1 - i; j++ )
{
if ( array[j] > array[j+1] )
{
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
return;
}
2. 快速排序
在网上搜了几篇介绍快速排序的文章,个人感觉这篇《坐在马桶上看算法:快速排序》很不错,有图好理解。以下大部分内容均摘自此文章。
网址:http://developer.51cto.com/art/201403/430986.htm
快速排序的思想比较简单,整个排序过程分为三步:
- 在数组中选择一个元素作为基准值(pivot)
- 所有小于基准的元素,都移到基准的左边,大于基准的元素都移到基准的右边
- 对于基准的左边和右边的两个子集, 不断重复第一步和第二步,直到所有子集只剩下一个元素为止
待排序数组:6 1 2 7 9 3 4 5 10 8
1. 第一次选择最左边的6作为基准值
2. 从右边开始选择第一个小于6的数,下标递减,即j--
3. 从左边开始选择第一个大于6的数,下标递增,即i++
4. 交换这两个值,即交换7和5
5. 此时i != j,继续寻找
6. 第二次交换9和4
7. 再次寻找的话 i 就等于 j 了,这时交换基准值和array[i]
8. 这时6左边的数全部小于6,6右边的数全部大于6,接下来对这两个子序列进行同样的操作。
在写代码时要注意几个条件,开始扫描时,i必须总是小于j,i与j相等时则与基准值交换。另外,在递归调用时,判断low是否小于high,否则需要在quick_sort函数中对low和hign进行比较。
void quick_sort(int array[], int low, int high)
{
int base = array[low];
int i = low;
int j = high;
int temp;
while ( i != j )
{
/* 从右侧寻找第一个小于基准值的数 */
while ( array[j] >= base && i < j ) j--;
/* 从左侧寻找第一个大于基准值的数 */
while ( array[i] <= base && i < j ) i++;
if ( i != j )
{
temp = array[j];
array[j] = array[i];
array[i] = temp;
}
}
/* 此时i==j,和基准值交换 */
array[low] = array[i];
array[i] = base;
/* 继续对两个子序列进行同样的操作 */
if ( low < i - 1 )
quick_sort(array, low, i-1);
if ( i + 1 < high )
quick_sort(array, i+1, high);
return;
}
3. 归并排序
归并排序的核心思想是,将两个已经排序的子序列合并到一个序列中,时间复杂度为O(N)。如何得到有序的子序列呢?将整个序列进行二分,直到每个元素都是一个子序列。然后依次对每个子序列归并。分解过程采用递归的方式实现,时间复杂度为O(logN),所以归并排序的时间复杂度为O(NlogN).
void merge_array(int array[], int low, int mid, int high)
{
int i = low, j = mid + 1;
int m = mid, n = high;
int k = 0;
int *temp = new int[high-low+1];
/* 将两个有序序列array[low...mid]和array[mid+1...high]合并*/
while ( i <= m && j <= n )
{
if ( array[i] <= array[j] )
{
temp[k++] = array[i++];
}
else
{
temp[k++] = array[j++];
}
}
/* 有一个序列会剩下,则将此序列的全部元素拷贝到临时数组中 */
while ( i <= m )
{
temp[k++] = array[i++];
}
while ( j <= n )
{
temp[k++] = array[j++];
}
/* 拷贝回原始数组中 */
for ( i = 0; i < k; i++ )
{
array[low+i] = temp[i];
}
delete [] temp;
return;
}
void merge_sort(int array[], int low, int high)
{
if ( low < high )
{
int mid = (low + high) / 2;
//printf("%d %d %d\r\n", low, mid, high);
merge_sort(array, low, mid);
merge_sort(array, mid+1,high);
merge_array(array, low, mid, high);
}
return;
}
4. 堆排序
本节内容来自http://www.cnblogs.com/zabery/archive/2011/07/26/2117103.html
堆分为大根堆和小根堆,是完全二叉树,大根堆的要求是每个节点的值都不大于其父节点的值,在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。首先需要把数组转换成堆,并调整堆符合大根堆的要求。请看下图,数组为A = {1, 3, 4, 5, 7, 2, 6, 8, 0}
操作步骤
1. 数组A转换成堆为图1.1的形式,父节点的左叶子节点为 2*i+1, 右叶子节点是 2*i+2,i从0开始
2. 调堆的过程从最后一个非叶子节点开始,从图1.1可以看出,最后一个非叶子节点是A[3]=5
如果数组有奇数个,则最后一个非叶子节点拥有两个孩子,即最后一个非叶子节点的右孩子为数组的最后一个值,则如下表达式成立 2*i+2 = n-1,得出i=(n-3)/2
如果数组有偶数个,则最后一个非叶子节点拥有左孩子,这个左孩子为数组的最后一个值,2*i+1 = n-1,得出i=(n-2)/2
由于除号只保留整数部分,所以这两个表达式计算得出的i是相等的,下面的代码中,我们把(n-2)/2作为最后一个非叶子节点
3. 图1.4与图1.3对比,3与8交换,此时3小于其左孩子5,所以3和5也要交换,得出图1.5. 图1.6到图1.7也有两次交换,所以调整堆时要注意大根堆的定义,即每个节点的值都不大于其父节点的值。要循环判断直到符合要求。
4. 每次调整堆结束后,堆的第一个元素都是当前堆的最大值,然后将此元素与堆的最后一个元素交换,堆元素的个数减一,继续调整堆,交换堆首元素与堆的最后一个元素,如此反复,直到堆的元素个数还剩1,排序完成
/*
n:元素个数
i:需要比较的子树的根节点
*/
void adjust_heap(int array[], int n, int i)
{
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;
int temp;
while ( left < n || right < n )
{
/* 找到根节点和两个叶子节点中最大的值 */
if ( left < n && array[largest] < array[left] )
{
largest = left;
}
if ( right < n && array[largest] < array[right] )
{
largest = right;
}
/* 如果根节点不是最大值,则与最大值交换 */
if ( largest != i )
{
temp = array[largest];
array[largest] = array[i];
array[i] = temp;
/* 操作步骤3的实现,循环比较整个子树是否符合要求 */
i = largest;
left = 2 * i + 1;
right = 2 * i + 2;
}
else
{
break;
}
}
return;
}
void build_heap(int array[], int n)
{
int i;
/* 操作步骤2的实现,从最后一个非叶子节点开始调整堆,直到根节点 */
for ( i = (n - 2)/2; i >= 0; i-- )
{
adjust_heap(array, n, i);
}
return;
}
void heap_sort(int array[], int n)
{
int i, temp;
build_heap(array, n);
/* 步骤4的实现,每次循环交换堆的第一个元素与最后一个元素,堆大小每次减1,
由于每次调整堆,堆的第一个元素都是当前堆的最大值,交换后就得到一个排
好序的数组 */
for ( i = n - 1; i > 0; i-- )
{
temp = array[0];
array[0] = array[i];
array[i] = temp;
adjust_heap(array, i, 0);
}
return;
}