之前两篇关于排序算法的综述以及平方阶复杂度的3种具体类型的排序算法,这一篇将具体介绍其中平均时间复杂度在平方阶 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)的三个排序算法,以及各种算法的代码实现(亲测正确)。
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 O ( n l o g n ) O(nlogn) O(nlogn)次比较。在最坏状况下则需要 O ( n 2 ) O(n^2) O(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 O ( n l o g n ) O(nlogn) O(nlogn)算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在简单排序基础上的递归分治法。
因为快排是根据基准pivotpos来进行的分区操作,当存在元素与基准相同时,由于分区的操作,最后会将基准值放在与之相同元素的后面,因此快速排序时一种不稳定的排序算法。
// 分区操作
int partition(int arr[], int low, int high)
{
int pivot = arr[low]; //基准
while (low < high)
{
while (arr[high] >= pivot && low < high)
--high; // 找到排在后面但是小于基准的最先元素
arr[low] = arr[high];
while (arr[low] <= pivot && low < high)
++low;
arr[high] = arr[low];
}
arr[low] = pivot;
return low;
}
//快速排序
void Quick_Sort(int arr[], int low, int high)
{
int pivotpos;
if (low < high)
{
pivotpos = partition(arr, low, high);
Quick_Sort(arr, low, pivotpos - 1);
Quick_Sort(arr, pivotpos + 1, high);
}
}
//非递归方法
void Quick_Sort_NonRecursive(int arr[], int low, int high)
{
int pivotpos;
std::stack pos_stack;
pos_stack.push(low);
pos_stack.push(high);
while (!pos_stack.empty())
{
high = pos_stack.top(); // 注意出栈顺序
pos_stack.pop();
low = pos_stack.top();
pos_stack.pop();
if (low < high)
{
pivotpos = partition(arr, low, high);
//左边序列起始、终止位置入栈
pos_stack.push(low);
pos_stack.push(pivotpos - 1);
//右边
pos_stack.push(pivotpos + 1);
pos_stack.push(high);
}
}
}
根据上面时间复杂度的分析,可以看出快速排序的时间复杂度最优、最坏的关键在于基准的选择上。因此对于基准的选择的优化便是对于快速排序的算法优化。
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
将序列每相邻两个数字进行归并操作(merge),形成floor(n/2+n%2)个序列,排序后每个序列包含两个元素将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素。重复步骤2,直到所有元素排序完毕。
归并排序需要不仅时时间还有空间上的辅助,因此从时间复杂度和空间复杂度进行分析。
元素的移动完全在合并操作上,对于合并的过程,我们完全可以添加条件限制相同的元素是否移动,所以合并排序是具有稳定性的排序。
int * temp = new int[len];
// 合并操作
void merge(int arr[], int low, int mid, int high)
{
int i, j, index;
for (int i = low; i <= high; ++i) //复制数组,空间复杂度为O(n)
temp[i] = arr[i];
for (i = low, j = mid + 1, index = low; i <= mid && j <= high; ++index)
{
if (temp[i] > temp[j])
{
arr[index] = temp[j];
++j;
}
else
{
arr[index] = temp[i];
++i;
}
}
while (i <= mid) arr[index++] = temp[i++];
while (j <= high) arr[index++] = temp[j++];
memset(temp, 0, sizeof(temp));
}
void Merge_Sort(int arr[], int low, int high)
{
int mid;
if (low < high)
{
mid = (high + low) / 2;
Merge_Sort(arr, low, mid);
Merge_Sort(arr, mid + 1, high);
merge(arr, low, mid, high); // 归并
}
}
//非递归
void Merge_Sort_NonRecursive(int arr[], int n)
{
int step = 2, low, high, mid; //二路归并步长
while (step <= n)
{
int curpos = 0;
while (curpos + step <= n)
{
high = curpos + step - 1;
low = curpos;
mid = curpos + step / 2 - 1;
merge(arr, low, mid, high);
curpos += step;
}
if (curpos < n - step / 2) // 如过剩余个数比一个step长度还多,那么就在进行一次合并
{
mid = curpos + step / 2 - 1;
merge(arr, curpos, mid, n - 1);
}
step *= 2;
}
mid = step / 2 - 1;
merge(arr, 0, mid, n - 1);
}
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。
补充:
堆:是一种特殊的数据结构。满足:
堆排序的主要阶段为:初始化建立堆和重建堆。因此堆排序的时间复杂度由这两部分组成。
故综合以上可以得出堆排序时间复杂度: O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。因为堆排序是就地排序,空间复杂度为常数 O ( 1 ) O(1) O(1)。
堆排序是不稳定的算法,它不满足稳定算法的定义。它在交换数据的时候,是比较父结点和子节点之间的数据,所以,即便是存在两个数值相等的兄弟节点,它们的相对顺序在排序也可能发生变化。
/* 最大堆向下调整算法
* param: index 调整开始位置
* length 数组长度范围
*/
void MaxHeap(int arr[], int index, int length)
{
int node = index;
int child_index = node * 2 + 1;
int current = arr[node];
for (; child_index <= length; node = child_index, child_index = node * 2 + 1)
{
if (child_index < length && arr[child_index] < arr[child_index + 1])
++child_index; // 子节点中的最大值
if (current > arr[child_index]) break;
else
{
arr[node] = arr[child_index];
arr[child_index] = current;
}
}
}
void Heap_Sort(int arr[], int n)
{
for (int i = n / 2 - 1; i >= 0; --i)
{
MaxHeap(arr, i, n - 1); // 建立最大堆
}
for (int i = n - 1; i > 0; --i) // 从最后开始调整
{
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
MaxHeap(arr, 0, i - 1); // 数组长度范围减一
}
}
当数据量,数据规模较大时,应该采用此3类排序算法,这样效率相比于之前的时间复杂度为 O ( n 2 ) O(n^2) O(n2)的三种排序算法来说更高、更好些。
这三类排序算法的结论:
https://github.com/hustcc/JS-Sorting-Algorithm