排序总结
这里介绍了插入排序(直接、二分法),希尔排序, 直接选择排序,堆排序,冒泡排序,快速排序, 归并排序 排序算法。
问题描述:
假设元素为整数,按照从小到大的顺序排序。
一 直接插入排序
1. 描述
上图就是对插入排序的直观解释,来自维基百科。
2. 复杂性分析
最好情况:已排好序的时候,只需要 n-1 次比较
时间复杂度为 O(n)
最坏情况:逆序排序,需要 n(n-1)/2 次比较
时间复杂度为 O(n2)
平均情况:
时间复杂度为 O(n2)
3. 稳定性
直接插入排序是稳定的,不会改变相同元素的相对位置。
代码实现(C++):
void sort_insert(vector<int> array, int size)
{
if(size <= 1) return;
int i, j;
for(i = 1; i < size; ++i)
if(array[i] < array[i - 1])
{
int temp = array[i];
for(j = i - 1; array[j] > temp; --j) array[j + 1] = array[j];
array[j + 1] = temp;
}
}
二 二分插入排序
1. 描述
在直接插入排序中,为了找到合适的插入位置,我们采用的是从后到前的顺序查找进行比较,为了减少比较的次数,采用二分查找。
2. 复杂度分析
时间复杂度:
最好情况:O(log2n)
最坏情况:O(n2)
平均情况:O(n2)
3. 稳定性
稳定排序。
代码实现(C++)
void BinarySearch(int array[], int start, int end, int k)
{
while(start <= end)
{
int middle = (start + end) / 2;
if(array[middle] > k)
end = middle - 1;
else
start = middle + 1;
}
return start;
}
void sort_insert(int array[], int size)
{
if(size <= 1) return ;
int i, j;
for(i = 1; i < size; ++i)
{
if(array[i] < array[i - 1])
{
int temp = array[i];
int insert_index = BinarySearch(array, 0, i, array[i]);
for(j = i - 1; j >= insert_index, --j) array[j + 1] = array[j];
array[insert_index] = temp;
}
}
}
三 希尔排序
1. 描述
希尔排序是插入排序的一种,是直接插入排序的一种改进版本,是DL. Shell 于1959年提出的。
将待排序序列划分为若干组,在每一组内进行插入排序,以使整个序列基本有序,直至序列的间隔为1。
2. 复杂性分析
希尔排序的时间复杂度与增量序列的选取有关。
希尔增量的时间复杂度为O(n2) {N/2, (N/2)/2, ..., 1}
Hibbard增量的 时间复杂度为 O(n3/2) {2k-1, ..., 3, 1}
3. 稳定性
不稳定排序。
代码实现(C++)
void sort_shell(int array[], int size)
{
int gap = size;
while(gap > 1)
{
gap = gap / 3 + 1;
for(int i = gap; i < size; ++i)
{
if(array[i] < array[i - gap])
{
int temp = array[i];
int j = i - gap;
while(j >= 0 && array[j] > temp)
{
array[j + gap] = array[j];
j = j - gap;
}
array[j + gap] = temp;
}
}
}
}
四 直接选择排序
1. 描述
直接选择排序指每次选择所要排序的数组中的最小值的数组元素,将这个数组元素与最前面没有进行排序的数组元素交换。
2. 复杂度分析
在一个长度为N的数组中,第一趟遍历N个数,找出最小的数与第一个元素互换,
...
第N-1趟遍历 2 个数,找出最小的数与第N-1 个元素互换。
时间复杂度
比较:O(n2)
交换:O(n) 数据移动是最少的。
3. 稳定性
不稳定排序
代码实现(C++)
void sort_selection(vector<int> &array, int size)
{
for(int i = 0; i < size - 1; ++i)
{
int index = i;
for(int j = i + 1; j < size; ++j)
if(array[j] < array[index]) index = j;
if(index != i)
{
int temp = array[index];
array[index] = array[i];
array[i] = temp;
}
}
}
五 堆排序
参考页面:http://www.cnblogs.com/0zcl/p/6737944.html
堆排序也属于选择排序的一种。
给定一个列表array=[16, 7, 3, 20, 17, 8],对其进行堆排序。
1. 过程描述
首先根据该数组元素构建一个完全二叉树,得到
然后需要构造初始堆,则从最后一个非叶节点开始调整,调整过程如下:
第一步: 初始化大顶堆(从最后一个有子节点开始往上调整最大堆)
20和16交换后导致16不满足堆的性质,因此需重新调整
得到了初始堆。
第二步: 堆顶元素R[1]与最后一个元素R[n]交换,交换后堆长度减一
即每次调整都是从父节点、左孩子节点、右孩子节点三者中选择最大者跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,因此每次交换之后要重新对被交换的孩子节点进行调整)。有了初始堆之后就可以进行排序了。
第三步: 重新调整堆。此时3位于堆顶不满堆的性质,则需调整继续调整(从顶点开始往下调整)
重复上面的步骤。
2. 复杂性分析
最好,最坏,平均 :O(nlog2(n))
3. 稳定性
不稳定排序
使用情况:
选择最小(大)的k个数, 可以使用堆排序,复杂情况变为O(nlog2(k))
代码实现(C++)
void adjust_heap(vector<int> &data, int parent, int end)
{
int child = 2 * parent + 1;
while(child < end)
{
// if the parent node have right child and the value of the right child is bigger than the value of the left child.
if(child + 1 < end && data[child + 1] > data[child]) ++child;
// the value of parent node is bigger than its child nodes
if(data[parent] >= data[child]) break;
swap(data[parent], data[child]);
parent = child;
child = 2 * child + 1;
}
}
void sort_heap(vector<int> &data)
{
// the initial heap
for(int i = data.size() / 2; i >= 0; --i)
adjust_heap(data, i, data.size() - 1);
for(int i = data.size() - 1; i >= 0; --i)
{
swap(data[i], data[0]);
adjust_heap(data, 0, i);
}
}
六 冒泡排序
1. 描述
2. 复杂性分析
最好情况:
数据正序,只需要走一趟就可完成。 O(n)
最坏情况:
数据是反序的,需要进行n-1趟排序。没趟排序需要进行 n-i 次比较, 比较次数为 (n(n-1)/2) = O(n2)。
平均情况:
O(n2)
3. 稳定性
稳定排序
数组中逆序对的个数,即冒泡排序的交换次数。
代码实现(C++)
void sort_bubble(vector<int> &array, int size)
{
for(int i = 0; i < size - 1; ++i)
for(int j = 0; j < size - 1 - i; ++j)
if(array[j] > array[j + 1]) swap(array[j], array[j + 1]);
}
七 快速排序
1. 描述
快速排序是对冒泡排序的一种改进,基本思想是选取一个记录作为枢轴,经过一趟排序,将整段序列分为两个部分,一部分的值小于枢轴,另一部分的值大于枢轴。循环进行此过程,直至有序。
挖坑法
初始数据: 72, 6, 57, 88, 60, 42, 83, 73, 48, 85
[i = 0, j = 9]
第一趟排序的过程为:
① 先挖坑,将i = 0 挖出来,记为 temp = 72
② 从右边开始寻找比 temp小的数,将其赋值给 i 指向的元素。
48, 6, 57, 88, 60, 42, 83, 73, 48, 85
[i = 0, j = 8]
③ 从左边开始寻找比 temp大 的数, 将其赋值给 j 指向的元素。
48, 6, 57, 88, 60, 42, 83, 73, 88, 85
[i = 3, j = 8]
④ 重复②③, 直到 i == j
⑤ 将 temp填入 i 指向的坑中。
(48, 6, 57, 42, 60) 72 (83, 73, 88, 85)
[i = j = 5 ]
左右指针法
左右交换,最后一步要注意,交换 array[i], array[left]。
2. 复杂性分析
最坏情况 O(n2)
最好情况 O(nlogn)
3. 稳定性
不稳定排序。
代码实现(C++)
// 挖坑法
void sort_quick(vector<int> &array, int left, int right)
{
if (left >= right) return;
int i = left;
int j = right;
int temp = array[left];
while (i < j)
{
while (j > i && array[j] >= temp) --j;
if (j > i) array[i] = array[j];
while (i < j && array[i] <= temp) ++i;
if (i < j) array[j] = array[i];
}
array[i] = temp;
sort_quick(array, left, i - 1);
sort_quick(array, i + 1, right);
}
void sort_quick2(int array[], int left, int right)
{
if (left >= right)
{
return;
}
int i = left;
int j = right;
int temp = array[left];
while (i < j)
{
while (j > i && array[j] >= temp) --j;
while (i < j && array[i] <= temp) ++i;
swap(array[i], array[j]);
}
array[left] = array[i];
array[i] = temp;
sort_quick(array, left, i - 1);
sort_quick(array, i + 1, right);
}
八 归并排序
参考页面:http://www.cnblogs.com/chengxiao/
1. 描述
分而治之。
归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
动画演示:
2. 复杂性分析
最好、最坏、平均情况都是 O(nlogn)
3. 稳定性
稳定排序
代码实现(C++)
#inlcude <iostrem>
#include <vector>
using namespace std;
void merge(vector<int> &array, vector<int> &temp, int begin, int end)
{
if(begin == end) return;
// divide
int middle = begin + ((end - begin) >> 1);
merge(array, temp, begin, middle);
merge(array, temp, middle + 1, end);
// conquer
int i = begin;
int j = middle + 1;
int index = begin;
while(i <= middle && j <= end)
{
if(array[i] < array[j])
temp[index++] = array[i++];
else
temp[index++] = array[j++];
}
while(i <= middle) temp[index++] = array[i++];
while(j <= end) temp[index++] = array[j++];
for(int i = begin; i <= end; ++i) array[i] = temp[i];
}
void sort_merge(vector<int> &array)
{
// 额外空间: O(n)
vector<int> temp(array);
merge(array, temp, 0, array.size() - 1);
}
注:有些图片未找见出处,如侵权,请告知。