“ 快速排序、归并排序、堆排序”
01
—
快速排序
算法思想
快排(quicksort)过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在简单排序基础上的递归分治法。
算法步骤
从数列中挑出一个元素,称为 “主元”(pivot)。
重新排序数列,所有元素比主元值小的摆放在主元前面,所有元素比主元值大的摆在主元的后面(相同的数可以到任一边)。在这个分区退出之后,该主元就处于数列的中间位置。这个称为分区(partition)操作。
递归地把小于主元值元素的子数列和大于主元值元素的子数列排序。
过程图示
算法复杂度
最优情况
分区每次都划分得很均匀,如果排序n个关键字,其递归树的深度就为[log2n]+1 ([x] 表示不大于 x 的最大整数),即仅需递归[log2n]次。第一次分区应该是需要对整个数组扫描一遍,做n次比较。然后,获得的枢轴将数组一分为二,那么各自还需要T(n/2)的时间(注意是最好情况,所以平分两半)。
时间复杂度为:O(nlog2n)
最差情况
当待排序的序列为正序或逆序排列时为最糟糕情况下的快排。此时需要执行n‐1次递归调用,且第i次划分需要经过n‐i次关键字的比较才能找到第i个记录,也就是枢轴的位置,因此比较次数为:
时间复杂度为:O(n²)
代码实现
//分区
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 pivot;//主元 if (low < high) { pivot = partition(arr, low, high); Quick_Sort(arr, low, pivot - 1); Quick_Sort(arr, pivot + 1, high); }}
//快速排序(非递归)
void Quick_Sort(int arr[], int low, int high)
{
int pivotpos;//主元
std::stack<int> 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);
}
}
}
02
—
堆排序
算法思想
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。本文采用大顶堆。
算法步骤
创建一个堆 H[0……n-1];
各个根节点与其叶子节点比较,最大者换为根节点
把堆首(最大值)和堆尾互换;
把堆的尺寸缩小1,并重新进行步骤2,目的是把新的数组顶端数据调整到相应位置;
重复步骤 3,直到堆的尺寸为 1。
算法复杂度
堆排序的主要阶段为:初始化建立堆和重建堆。因此堆排序的时间复杂度由这两部分组成。
初始化建立堆
假如有N个节点,那么高度为h=logN,,最后一层每个父节点最多只需要下调1次,倒数第二层最多只需要下调2次,顶点最多需要下调h次,而最后一层父节点共有2^(h−1)个,倒数第二层公有2^(h-2) ,顶点只有一个所以总共的时间复杂度为:
将h代入后s=2N−2−log2N,近似的时间复杂度就是O(N)。
重建堆
重建的过程,需要循环 n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 logn,总时间:logn(n-1) = nlogn - logn。
故综合以上可以得出堆排序时间复杂度: O(nlog2n)
代码实现
/* * 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); // 数组长度范围减一 }}
03
—
归并排序
算法思想
将序列每相邻两个数字进行归并操作(merge),形成floor(n/2+n%2)(floor向下取整函数)个序列,排序后每个序列包含两个元素将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素。重复步骤2,直到所有元素排序完毕。
算法步骤
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
设定两个指针,最初位置分别为两个已经排序序列的起始位置;
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
重复步骤 3 直到某一指针达到序列尾
将另一序列剩下的所有元素直接复制到合并序列尾。
算法复杂度
归并排序需要不仅时时间还有空间上的辅助,因此从时间复杂度和空间复杂度进行分析。
时间复杂度
总时间=分解时间+解决问题时间+合并时间。分解时间就是把一个待排序序列分解成两序列,时间为一常数,时间复杂度o(1).解决问题时间是两个递归式,把一个规模为n的问题分成两个规模分别为n/2的子问题,时间为2T(n/2)。合并时间复杂度为O(n)。总时间T(n)=2T(n/2)+O(n) 这个递归式可以用递归树来解,其解是 O(nlogn) 此外在最坏、最佳、平均情况下归并排序时间复杂度均为 O(nlogn)。
空间复杂度
如之前的算法步骤第一步,需要申请空间,该空间的作用时用于存放合并后的序列。因此需要初始序列规模n的空间,故空间复杂度为 O(n)。
代码实现
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);}
归并排序和堆排序GIF图片示意帧数太大无法上传,可在公众号内回复堆排序领取动态图片。