以下以无序的整形数组调整至非降序为例
直接插入排序
只存在一个元素的时候是有序的,所以直接调整数组的第二个元素,此时符合上面图示
让需要插入元素从后往前依次与前面的已经有序部分进行比较,如果插入元素大于比较的元素,表明已经找到需要插入的位置,否则让前面直接覆盖后面一个元素,从而为插入元素腾出空间,覆盖前已经用临时变量保存了插入元素
最后找到合适位置,将临时空间保存的元素赋给需要插入的位置
上面是一次调整的过程,依次将剩下无序的部分都重复上述过程,最终整个序列将有序
void InsertSort(int array[], int size)
{
int key, i, j;
for (i=1; i<size; i++)
{
key = array[i];
for (j=i-1; j>=0; j--)
{
if (key > array[j])
{
break;
}
array[j+1] = array[j];
}
array[j+1] = key;
}
}
希尔排序
希尔排序是对直接插入排序的优化,在插入排序中如果遇到极端情况:最后一个元素比第一个元素小,则需要从后往前将前面所有的元素往后覆盖,以空出第一个位置,以便最后一个元素的插入
希尔排序则是在整体排序之前,做多次预排序,预排序的目的是保证序列尽可能有序,尽量避免不再出现上面的极端情况
如何实现预排序,则是通过分组的方式,在组内先进性小范围排序。分组的方法是通过保留固定间隙来完成,当间隙为0,则表示进行的是最后一次的整体排序,此时上面的极端情况已经通过前面的预排序避免掉了
void _InsertSort(int array[], int size, int gap)
{
int key, i, j, g;
for (g=0; g<gap; g++)
{
for (i=gap+g; i<size; i+=gap)
{
key = array[i];
for (j=i-gap; j>=0; j-=gap)
{
if (key >= array[j])//如果只有大于则算法将不是稳定的
{
break;
}
array[j+gap] = array[j];
}
array[j+gap] = key;
}
}
}
void ShellSort(int array[], int size)
{
int gap = size;
while (1)
{
gap = gap / 3 + 1;
_InsertSort(array, size, gap);
if (gap == 1)
{
break;
}
}
}
当然上面的排序方式是先将一个组通过插入排序排好后,再去排第二组,也可以先统一调整每组的第一个需要调整的元素,调整完后再调整后面的元素,最终的目的是相同的
void _InsertSort(int array[], int size, int gap)
{
int key, i, j;
for (i=gap; i<size; i++)
{
key = array[i];
for (j=i-gap; j>=0; j-=gap)
{
if (key >= array[j])//如果只有大于则算法将不是稳定的
{
break;
}
array[j+gap] = array[j];
}
array[j+gap] = key;
}
}
选择排序
每次找无序序列中最大/最小的,将该元素放到合适的位置,接下来需要调整的序列个数就比原来的序列个数少一个,直到需要调整的序列元素个数为1,此时有序不需要调整,这个过程就是减治的思想
void SelectSort(int array[], int size)
{
int i, j, max;
for (i=0; i<size-1; i++)
{
max = 0;//假设第一个元素最大
for (j=1; j<size-i; j++)
{
if (array[j] > array[max])
{
max = j;//不断更新最大元素
}
}
Swap(array+size-1-i, array+max);
}
}
当然也可以对选择排序做出优化,就是在一次查找过程中找出最大和最小两个元素,分别将两个元素放到序列的尾和首,这样接下来最调整的序列个数就比原序列元素个数少2个,假设需要调整的序列起始位置为left,结束位置为right,当序列元素为奇数个时,left=right表示需要调整序列元素个数为1,不再需要调整;当序列元素个数为偶数个时left>right表示调整结束,统一起来便是当left<right时持续做调整
void SelectSortOP(int array[], int size)
{
int max, min, i;
int left = 0;
int right = size - 1;
while (left < right)
{
min = max = left;
for (i=left+1; i<=right; i++)
{
if (array[i] < array[min])
{
min = i;
}
if (array[i] > array[max])
{
max = i;
}
}
Swap(array+left, array+min);
if (left == max)
{
max = min;//跟踪最大元素
}
Swap(array+right, array+max);
left++;
right--;
}
}
代码中由于归置最小元素,再归置最大元素
如果做元素交换的时候,如果确立的大元素在最小元素应有的位置处,第一次交换会把最大元素交换走,所以需要对最大元素做出“跟踪”
堆排序
调整成升序,需要建大堆;调整成降序,需要建小堆
当在调整的过程中,将大堆堆顶的元素放到数组的最后一个位置上时,此时作为非降序序列的最后一个位置的元素已经确定,只需要在逻辑上“删除”掉堆尾的元素,做一次向下调整,堆中剩余元素的最大元素,也就是序列的次大元素将会浮到堆顶,重复上述操作,当堆中仅有一个元素时调整结束
void AdjustDown(int array[], int size, int root)
{
int cLeft, cRight, max;
while (1)
{
cLeft = root * 2 + 1;
cRight = root * 2 + 2;
if (cLeft >= size)
{
break;
}
max = cLeft;
if (cRight < size && array[cRight] > array[cLeft])
{
max = cRight;
}
if (array[root] < array[max])
{
Swap(array+root, array+max);
root = max;
}
else
{
break;
}
}
}
void CreateHeap(int array[], int size)
{
int i = (size - 2) / 2;//最后一个非叶子结点
for (; i>=0; i--)
{
AdjustDown(array, size, i);
}
}
void HeapSort(int array[], int size)
{
int i = 0;
CreateHeap(array, size);
for (; i<size-1; i++)
{
Swap(array, array+size-1-i);
AdjustDown(array, size-1-i, 0);
}
}
冒泡排序
冒泡排序和选择排序十分相似,结果都是比较一遍后将最值确定到正确位置,接着对剩余的元素再做同样的调整。不同的是,选择排序在查找过程中不做交换,只在最后确定最值位置后做一次交换,而冒泡排序每一趟确定最值位置,都需要从前往后作比较,只要前后位置需要调整就做交换
void BubbleSort(int array[], int size)
{
int i, j, flag;
for (i=0; i<size-1; i++)
{
flag = 0;
for (j=0; j<size-1-i; j++)
{
if (array[j] > array[j+1])
{
Swap(array+j, array+j+1);
flag = 1;
}
}
if (flag == 0)
{
break;
}
}
}
快速排序
快速排序是利用分治的思想来完成的,先确定一个基准值,将比基准值小的放到基准值的左边;比基准值大的放到基准值的右边,如次一来,基准值的位置就归置到了正确的位置,接下来递归的对左右两部分进行调整,递归终止条件是只有一个元素或者没有元素
调整基准值的函数返回调整结束后基准值的位置,后面的递归调整左右两部分,该位置将不再移动
void _QuickSort(int array[], int left, int right)
{
int div;
if (left >= right)
{
return;
}
div = AdjuestDiv_1(array, left, right);
_QuickSort(array, left, div-1);
_QuickSort(array, div+1, right);
}
void QuickSort(int array[], int size)
{
//统一接口,方便递归调用
_QuickSort(array, 0, size-1);
}
基准值的选取如果能尽量居中,基准值两边的元素就会尽可能均匀,如果以取两边元素的方式选取基准值不太理想,可以随机选取或者取前中后三元素,取这三元素中间元素
归置基准值的方法有三种,分别是:
hoare版本
确定基准值后从需要调整的前后未知依次向中间找,如果前面找到一个比基准值大的元素停下来,后面找到一个比基准值小的元素停下来,交换两个元素。直到前后指针调整完中间未知的元素,由于基准值是在最右边,一开始先移动的左指针,所以当最终调整结束停下来的时候,左指针指向的元素是比基准值大的元素,此时将该元素与最右的基准值再做一次交换,基准值便归位,后面递归的对左右两部分进行调整
int AdjuestDiv_1(int array[], int left, int right)
{
int begin = left;
int end = right;
while (begin < end)
{
//移动“指针”之前要先判断左右“指针”的位置关系
while (begin < end && array[begin] <= array[right])
{
begin++;
}
while (begin < end && array[end] >= array[right])
{
end--;
}
Swap(array+begin, array+end);
}
Swap(array+begin, array+right);
return begin;
}
挖坑法
将基准值存入临时变量,左“指针”先走,找到一个比基准值大的元素,覆盖基准值,然后右“指针”找到一个比基准值小的元素填进左指针刚挖的坑里面
int AdjuestDiv_2(int array[], int left, int right)
{
int begin = left;
int end = right;
int tmp = array[right];
while (begin < end)
{
//移动“指针”之前要先判断左右“指针”的位置关系
while (begin < end && array[begin] <= tmp)
{
begin++;
}
array[end] = array[begin];
while (begin < end && array[end] >= tmp)
{
end--;
}
array[begin] = array[end];
}
array[begin] = tmp;
return begin;
}
前后指针法
div指针的目的是确定基准值的位置,cur指针一直向后遍历,找到了比基准值小的元素,跟div指向的元素交换,div指向下一个元素(指向的下一个元素是cur已经判断过的大于基准值的元素),直到cur遍历完了所有未知元素,此时交换div指向的元素和基准值
int AdjustDiv_3(int array[], int left, int right)
{
int cur = left;
int div = left;
while (cur < right)
{
if (array[cur] < array[right])
{
Swap(array+cur, array+div);
div++;
}
cur++;
}
Swap(array+div, array+right);
return div;
}
归并排序
将排序元素“均等”划分,直到仅有一个元素时即有序,然后向上合并,其实排序是在合并时完成的,合并的方法是借助另一块空间,分别从头开始遍历需要合并的两个序列,为了保证算法的稳定性,左序列元素不大于右序列元素时,在合并的空间存放左序列元素,否则存放右序列元素
划分的过程:
合并的时候两个序列的大小可能不完全相等,最后要再把剩余元素添加到合并数组的后面去
合并的过程:
合并过程中是将有序序列存放到临时空间上去,所以在合并完成之后再把有序序列copy回array数组中去
临时空间是在堆上创建的,所以整个递归调整的过程都借助这一块空间,使用完后释放
void Merge(int a[], int b[], int left, int mid, int right)
{
int left_i = left;
int right_i = mid + 1;
int b_i = left;
while (left_i <= mid && right_i <= right)
{
if (a[left_i] <= a[right_i])
{
b[b_i++] = a[left_i++];
}
else
{
b[b_i++] = a[right_i++];
}
}
while (left_i <= mid)
{
b[b_i++] = a[left_i++];
}
while (right_i <= right)
{
b[b_i++] = a[right_i++];
}
}
void Copy(int a[], int b[], int left, int right)
{
int i = left;
for (; i<=right; i++)
{
a[i] = b[i];
}
}
void _MergeSort(int array[], int left, int right, int merge[])
{
int mid = left + (right - left) / 2;
if (left >= right)
{
return;
}
_MergeSort(array, left, mid, merge);
_MergeSort(array, mid+1, right, merge);
Merge(array, merge, left, mid, right);
Copy(array, merge, left, right);
}
void MergeSort(int array[], int size)
{
//在堆上创建空间
int *merge = (int *)malloc(sizeof(int) * size);
assert(merge);
//统一接口,方便递归调用
_MergeSort(array, 0, size-1, merge);
free(merge);
}
各种排序算法比较
稳定的排序算法的是指,在排序之前两个相等元素的位置在排序之后不发生变化