一.直接选择排序
直接选择排序是所有排序方法中,思想最简单,最直观的排序方法,每次选待排序列中选择最大或者最小的数,放在整个序列的起始位置,话不多说,直接上code,
void SlectSort(int* arr, int size)
{
int j = 0;
for(j; j < size - 1; j++)
{
//将初始的最小值设置为待排序列的第一个元素
int min = j;
//从第二个元素开始遍历,找最小的值
int i = min + 1;
for (i; i < size; i++)
{
if (arr[i] < arr[min])
{
min = i;
}
}
int tmp = arr[min];
//向后移动,插入
BackMove(arr + j, min - j);
arr[j] = tmp;
}
}
void BackMove(int* pa, int size)
{
memmove(pa + 1, pa, size * sizeof(int));
}
二.堆排序
堆排序是对简单选择排序的改进,我们发现在简单选择排序中,每次找最大或者最小值时,重复比较的次数太多,有没有一种办法,可以将之前比较过的记录进行一次痕迹存储?因此堆排序应运而生。
关于堆的概念即性质,可以在二叉树章节补。在这里只做简单总结,堆可以分为两种,小根堆和大根堆,小根堆即父节点的value小于等子节点value的完全二叉树,与之对应,大根堆是父节点的value大于等于子节点value的完全二叉树。
堆排序的具体步骤:
1.将待排序的序列构造成一个堆(升序构造大根堆,降序构造小根堆)。
2.将堆顶的元素(最大或者最小值)与堆底的元素交换,然后--size,再将剩余的元素调整成堆,即又找的次大的元素,直到堆中只剩一个元素。
3.该排序的核心点在于调整,本文采用的是,向下调整。
所谓向下调整,就是从最后一个节点的父亲节点开始(因为叶子节点度为0,不需要调整),向下调整,没调整一次后,--parent。直到parent成根节点。每一次交换事实上是,把堆顶最大或者最小的元素交换到最后,这也是为何升序采用大根堆,降序采用小根堆的本质原因。
code(以升序为例):
void HeapSort(int* arr, int size)
{
//构造大根堆
int parent = (size - 2) / 2;
while (parent >= 0)
{
AdjustDown(arr, size, parent--);
}
//反复交换堆顶堆底然后从堆顶向下调整,只剩一个元素时结束
while (size > 2)
{
Swap(arr, arr + size - 1);
AdjustDown(arr, --size, 0);
}
}
void AdjustDown(int* arr, int size, int parent)
{
assert(parent < size);
int left_child = parent * 2 + 1;
int right_child = (parent + 1) * 2;
int max = left_child;
if (arr[left_child] < arr[right_child])
{
++max;
}
while (max < size)
{
if (arr[max] > arr[parent])
{
Swap(arr + max, arr + parent);
parent = max;
max = parent * 2 + 1;
if (arr[max] < arr[max + 1])
{
++max;
}
}
else
{
return;
}
}
}
void Swap(int* pa, int* pb)
{
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
三.基础的快速排序
以升序为例,基本思路如下:
1.选取一个key值(基准),一般是序列中第一个数or最后一个数
2.定义两个指针,left和right,分别指向序列的首尾
3.right先走,right找比key小的数,left找比key大的数,找到以后将left和right交换。
4.当left和right相遇后,则将key与相遇的值交换,最终完成一次排序,将整个序列分成了以key值为基准的左右两部分(左边比key小,右边比key大)
5.完成一次排序以后,则以同样的思路去递归排序左边和右边的序列。
CODE:
void QuickSort(int* arr, int size)
{
if (size <= 10)
{
InsertSort(arr, size);
return;
}
if (size == 0 || size == 1)
{
return;
}
int key = *arr;
int left = 0;
int right = size - 1;
while (left < right)
{
if (arr[right] < key)
{
if (arr[left] > key)
{
Swap(arr + left, arr + right);
--right;
}
else
{
++left;
}
}
else
{
--right;
}
}
Swap(arr, arr + left);
int line = left;
QuickSort(arr, line);
QuickSort(arr + line + 1, size - line - 1);
}
四.快速排序的“挖坑”优化
right先走,遇到比key小的值,则直接与key交换;然后left再开始动,遇到比key大的数则直接与key交换,当left和right相遇时,key值已经自己到它该去的位置上了。
CODE:
void HoleOptim(int* arr, int size)
{
int key = 0;
int left = 0;
int right = size;
while (left < right)
{
do
{
--right;
} while (left < right && arr[right] >= arr[key]);
if (left < right)
{
Swap(arr + right, arr + key);
key = right;
}
do
{
++left;
} while (left < right && arr[left] <= arr[key]);
if (left < right)
{
Swap(arr + left, arr + key);
key = left;
}
}
}
五.快速排序的三数取中优化
最好的情况时,选到的key值恰好是整个序列的中位数;所以三数取中优化的目的,是为了选择一个相对中间的值作为key值。在这里,我们取序列中的首 尾 以及中间位置的值,然后从他们三个中选出中间大小的值作为Key,这里需要注意的是,快速排序牵扯到交换元素的位置,所以我们这里的优化最终要返回key的下标。
CODE:
int GetMidIndex(int* arr, int size)
{
int mid = 0;
if (arr[0] > arr[size - 1])
{
mid = (arr[size - 1] >= arr[size / 2] ? size - 1 : size / 2);
}
else if(arr[0] < arr[size - 1])
{
mid = (arr[0] <= arr[size / 2] ? size / 2 : 0);
}
else
{
mid = 0;
}
return mid;
}