视频学习网址:点击打开链接
参考书籍《算法导论》
Pre比较:
理论时间:
堆排序 归并排序 快速排序
最坏时间 O(nlogn) O(nlogn) O(n^2)
最好时间 O(nlogn) O(nlogn) O(nlogn)
平均时间 O(nlogn) O(nlogn) O(nlogn)
空间复杂度 O(1) O(n) ???这里不对啊,我觉得是O(1)啊(ps:这里的空间复杂度还包括了递归的过程,由于快排是递归实现的,所以有log(n)到n的空间复杂度,堆排序如果用递归实现也是logn的空间复杂度,不过堆排序可以改成非递归实现)
实际测量:
4种非平方级的排序:
希尔排序,堆排序,归并排序,快速排序
我测试的平均排序时间:数据是随机整数,时间单位是秒
数据规模 快速排序 归并排序 希尔排序 堆排序
1000万 0.75 1.22 1.77 3.57
5000万 3.78 6.29 9.48 26.54
1亿 7.65 13.06 18.79 61.31
堆排序是最差的。
这是算法硬伤,没办法的。因为每次取一个最大值和堆底部的数据(记为X)交换,重新筛选堆,把堆顶的X调整到位,有很大可能是依旧调整到堆的底部(堆的底部X显然是比较小的数,才会在底部),然后再次和堆顶最大值交换,再调整下来。
从上面看出,堆排序做了许多无用功。
排序算法稳定性定义:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。
堆排序、快速排序、希尔排序、直接选择排序 不是稳定的排序算法,(快排和直接选择排序都是在发生交换的时候破坏的,希尔排序由于步长大于1时开头就会跳过一些元素,堆排序在维护堆的时候会破坏稳定性)
而基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序、计数排序 是稳定的排序算法。
参考:百度百科
参考视频:点击打开链接
1 冒泡排序:两两比较,大的放后;关注for循环中的两个变量的变化范围;时间复杂度O(n^2)
#include <iostream>
class BubbleSort
{
public:
BubbleSort(){};//默认构造函数
BubbleSort(BubbleSort &other){};//拷贝构造函数
//冒泡排序函数
int *bubbleSort(int *pa, int n);
private:
};
//冒泡排序函数
int *BubbleSort::bubbleSort(int *pa, int n)
{
int temp = 0;
//for循环,两两比较排序
for (int j = 0; j < n - 1; j++)
{
for (int k = 0; k < n - 1 - j; k++)
{
if (pa[k]>pa[k + 1])
{
temp = pa[k];
pa[k] = pa[k + 1];
pa[k + 1] = temp;
}
}
}
//返回
return pa;
}
2 插入排序:逐个选取数组中元素,与前面元素比较插入;时间复杂度O(n^2)
class InsertionSort {
public:
int* insertionSort(int* A, int n)
{
// write code here
int key = 0;
//遍历数组中每一个元素
for (int i = 1; i < n; i++)
{
key = A[i];
int j = i - 1;
//与前面的元素逐个比较,大于则插入
while (j>=0 && A[j]>key)
{
A[j + 1] = A[j];
j--;
}
A[j + 1] = key;
}
return A;
};
private:
};
3 选择排序:选择最大值,放到数组的后面;时间复杂度O(n^2)
class SelectionSort {
public:
int* selectionSort(int* A, int n) {
// write code here
int maxKey = 0;
int maxInd = 0;
int temp = 0;
//每次选择最大的,放在数组的后面
for (int i = n - 1; i >= 0; i--)
{
maxKey = A[0];
maxInd = 0;
//找出最大的
for (int j = 0; j <= i; j++)
{
if (A[j] > maxKey)
{
maxKey = A[j];
maxInd = j;
}
}
//交换 最大值 和 查找范围末尾位置的值
temp = A[i];
A[i] = A[maxInd];
A[maxInd] = temp;
}
return A;
}
};
4 归并排序:递归分解为子问题,将子问题合并。时间复杂度O(nlogn),空间上需要动态分配内存辅助,额外的空间需求
class MergeSort
{
public:
int *mergeSort(int *A,int n)
{
mergeSortAC(A, 0, n - 1);
return A;
}
private:
//将pa指向的数组,划分成两个小数组,排序后再合并
int mergeSortAC(int *pa, int a, int c);
//合并函数
int merge(int *pa, int a, int b, int c);
};
//将pa指向的数组,划分成两个小数组,排序后再合并
int MergeSort::mergeSortAC(int *pa, int a, int c)
{
//边界条件:只有一个元素,返回
if (a >= c) return 0;
int b = (a + c) >> 1;
//对[a,b],[b+1,c]的两个数组分别排序,递归调用
mergeSortAC(pa, a, b);
mergeSortAC(pa, b + 1, c);
//合并两个已经排好序的数组
merge(pa, a, b, c);
return 0;
}
//合并函数
int MergeSort::merge(int *pa, int a, int b, int c)
{
//分配两个动态数组
int *pb = new int[b - a + 2];
assert(pb != NULL);
int *pc = new int[c - b + 1];
assert(pc != NULL);
//将要排序的部分复制到动态数组中
int i = a;
int j = b + 1;
while (i <= b)
{
pb[i - a] = pa[i];
i++;
}
while (j <= c)
{
pc[j - b - 1] = pa[j];
j++;
}
//为动态数组添加末尾无穷大值
pb[b - a + 1] = INT_MAX;
pc[c - b] = INT_MAX;
//逐个比较复制到pa中
i = j = 0;
for (int k = a; k <= c; k++)
{
if (pb[i] < pc[j])
{
pa[k] = pb[i];
i++;
}
else
{
pa[k] = pc[j];
j++;
}
}
//释放动态内存
delete[] pb;
delete[] pc;
//返回
return 0;
}
5 快速排序:随机抽取一个数,小的放左边,大的放右边。利用小于等于区间,可以原址排序。使用随机函数,引入随机选择。理论最差情况,时间复杂度O(n^2),期望复杂度是O(nlogn),实际使用中,快排的速度是最快的,而且由于是原址排序,所以空间需求少,不像归并排序需要O(n)的辅助空间。
#include <stdlib.h>
#include <time.h>
class QuickSort
{
public:
int *quickSort(int *A, int n)
{
quickSortBC(A,0,n-1);
return A;
}
int *quickSortBC(int *A, int b, int c)
{
//边界条件
if (b >= c) return 0;
int i = 0;
i = partition(A, b, c);//划分
quickSortBC(A, b, i-1);//对划分的子集,快速排序
quickSortBC(A, i+1, c);
return 0;
}
//划分函数
int partition(int *A, int b, int c)
{
//边界条件
if (b >= c) return 0;
//为快排加入随机选择
srand((unsigned)time(0));
int ind = b + rand() % (c - b + 1);//ind取值范围[b,c]
int key = A[ind];
//将ind对应的元素,换到数组末尾
A[ind] = A[c];
A[c] = key;
int temp = key;
int i = b - 1;
int j = b - 1;
while (j < c)
{
j++;
if (A[j] <= key)
{
i++;
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
//返回key值的索引
return i;
}
private:
};
6 堆排序:利用最大堆,每次取堆根,然后维护堆;时间复杂度上界是O(nlogn),堆排在实际中并不快,做了很多无用的比较;
class HeapSort
{
public:
int *heapSort(int *A, int n);
private:
int buildHeap(int *A, int size);//建堆
int maxHeap(int *A, int ind, int size);//维护最大堆
inline int exchange(int *A, int a, int b);//交换两个元素
};
int *HeapSort::heapSort(int *A, int n)
{
//将最小元素换到A[0],因为A[0]是不参与建堆的
int minEle = A[0];
int ind = 0;
for (int i = 0; i < n; i++)
{
if (minEle>A[i])
{
minEle = A[i];
ind = i;
}
}
A[ind] = A[0];
A[0] = minEle;
//建堆
buildHeap(A, n - 1);
//循环取堆根放到数组A末尾,维护堆
for (int size = n - 1; size > 1;)
{
exchange(A, 1, size);
size--;
maxHeap(A, 1, size);
}
return A;
}
int HeapSort::buildHeap(int *A, int size)//建堆
{
//from size/2 to 1
for (int i = size >> 1; i > 0; i--)
{
maxHeap(A, i, size);
}
return 0;
}
int HeapSort::maxHeap(int *A, int ind, int size)//维护最大堆
{
int largest = ind;
int left = (ind << 1);
int right = (ind << 1) + 1;
if (left <= size && A[left] > A[ind])//与左孩子比较
{
largest = left;
}
if (right <= size && A[right] > A[largest])//与右孩子比较
{
largest = right;
}
if (largest != ind)
{
exchange(A, largest, ind);
maxHeap(A, largest, size);
}
return 0;
}
inline int HeapSort::exchange(int *A, int a, int b)
{
int temp = A[a];
A[a] = A[b];
A[b] = temp;
return 0;
}
7 希尔排序