堆排序
堆:堆是一棵完全二叉树,它具有以下的性质,每个结点的值大于等于其左右孩子结点的值,称为大顶堆;或每个结点的值小于等于其左右孩子的值,称为小顶堆。
基本原理:首先需创建一个大顶堆(升序),将堆顶与数组最后一个元素交换,此时堆不再满足堆的性质,因此我们再对剩下的n-1个元素进行调整,使其重新成为一个大顶堆。再将堆顶元素与倒数第二个元素交换,交换之后又不满足堆的性质,此时再对剩余的n-2个元素进行调整,使其重新成为一个大顶堆。依次类推......
那么如何创建大顶堆呢?我们借助一个调整函数,该函数的功能是在某个结点的左右子树均为大顶堆时,对以该结点为根的这棵树进行调整,使其成为大顶堆。在创建时,实际上就是从完全二叉树的最后一个非叶子结点开始向前调整,直到根节点调整完毕,此时大顶堆便创立起来了。
因为堆是一颗完全二叉树,因此可用一维数组来存储。
调整函数具体调整过程如下图,以0号元素(18)为例,先判断左子树是否存在,若不存在则不需要调整,若存在则找出左孩子和右孩子(若右孩子不存在,则左孩子为较大者)中的较大者,比较结点与孩子中的较大者,若结点不小于较大者,则调整结束;否则交换结点与左右孩子中的较大者,又因为交换之后可能使子树又不满足堆的性质,则需继续调整。
调整函数代码:
void HeapAdjust(int arr[], int size, int parent)
{
int child = 2 * parent + 1;
while (child < size)
{
//找左右孩子中的较大者
if (child + 1 < size && arr[child + 1] > arr[child])
child += 1;
//判断是否需要交换
if (arr[parent] < arr[child])
{
Swap(&arr[parent], &arr[child]);
parent = child;
child = 2 * parent + 1;
}
else
return;
}
}
排序代码:
void HeapSort(int arr[], int size)
{
//end为最后一个非叶子节点
int end = (size - 2) / 2;
//1创建堆
for (; end >= 0; end--)
HeapAdjust(arr, size, end);
//2排序
end = size - 1; //end为最后一个结点
for (; end > 0; end--)
{
Swap(&arr[0], &arr[end]);
HeapAdjust(arr, end, 0);
}
}
快速排序
基本思想:通过一次排序将待排记录分割成独立的两部分,其中一部分的记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。至于分割的方法则有多种。
方法1:
假设有序列 9, 4, 2, 6, 3, 1, 8, 7, 0, 5
我们选取最后一个元素作为基准值(5),设有begin和end两个指针分别指向第一个元素和最后一个元素,begin从左往右找第一个大于5的元素,此时begin指向9,end从右往左找第一个小于5的元素,end指向0,交换9和0此时变成
0, 4, 2, 6, 3, 1, 8, 7, 9, 5
继续查找,第二次begin指向6,end指向1,交换之后
0, 4, 2, 1, 3, 6, 8, 7, 9, 5
第三次begin向后移动指向6,此时begin==end循环断开,再将基准值与arr[begin]交换得到
0, 4, 2, 1, 3, 5, 8, 7, 9, 6
此时已将序列分成两个序列,且左序列小于右序列。此时继续对左右序列进行分割即可。
代码如下:
//int Partion(int arr[], int left, int right)
//{
// int key = arr[right];
// int begin = left;
// int end = right;
// while (begin < end)
// {
// //从左往右找第一个大于key的数组下标
// while (begin < end && arr[begin] <= key)
// begin++;
// //从右往左找第一个小于key的数组下标
// while (begin < end && arr[end] >= key)
// end--;
// if (begin != end)
// {
// Swap(&arr[begin], &arr[end]);
// }
// }
// Swap(&arr[begin], &arr[right]);
// return begin;
//}
方法2:挖坑法
和方法1不同的是当从begin位置往右找到第一个大于基准值的元素时,直接用当前元素覆盖end位置元素(当然需先记录基准值),然后再从end位置往左找第一个小于基准值的元素来覆盖begin位置元素。然后再从begin位置继续按上述方法执行,直到begin==end,再用基准值覆盖begin位置元素。至此,完成分割操作。
分割过程如下:
→
9, 4, 2, 6, 3, 1, 8, 7, 0, 5
←
9, 4, 2, 6, 3, 1, 8, 7, 0, 9
→
0, 4, 2, 6, 3, 1, 8, 7, 0, 9
←
0, 4, 2, 6, 3, 1, 8, 7, 6, 9
→
0, 4, 2, 1, 3, 1, 8, 7, 6, 9
0, 4, 2, 1, 3, 5, 8, 7, 6, 9
代码如下:
//int Partion(int arr[], int left, int right)
//{
// int key = arr[right];//用最后一个元素做基准值
// int begin = left;
// int end = right;
// while (begin < end)
// {
// //找第一个比基准值大的元素
// while (begin < end && arr[begin] <= key)
// begin++;
// //将比基准值大的移到高端
// arr[end] = arr[begin];
// //找第一个比基准值小的的元素
// while (begin < end && arr[end] >= key)
// end--;
// //将比基准值小的移到低端
// arr[begin] = arr[end];
// }
// arr[begin] = key;
// return begin;
//}
整体代码:
void QuickSort(int arr[], int left, int right)
{
if (left < right)
{
int div = Partion(arr, left, right);
QuickSort(arr, left, div - 1);
QuickSort(arr, div + 1, right);
}
}