8.1 插入排序
8.1.1 直接插入排序
void InsertSort() {
for(int i = 2; i <= n; i++) {
if(A[i] < A[i - 1]) {
A[0] = A[i];
int j = i - 1;
for(; A[0] < A[j]; j--)
A[j + 1] = A[j];
A[j + 1] = A[0];
}
}
}
8.1.2 二分插入排序
因为前面是有序的所以可以用二分查找找到要插入的位置
void InsertSort2() {
for(int i = 2; i <= n; i++) {
A[0] = A[i];
int low = 1;
int high = i - 1;
while(low <= high) {
int mid = (low + high) / 2;
if(A[mid] > A[0])
high = mid - 1;
else
low = mid + 1;
}
for(int j = i - 1; j >= high + 1; j--)
A[j + 1] = A[j];
A[high + 1] = A[0];
}
}
8.1.3 希尔排序(缩小增量排序)
先追求部分有序,再逐渐逼近全局有序
利用插入排序“越有序效率越高”的特点。
空间复杂度 O(1),平均时间复杂度O(n^1.3),最坏O(n ^2);不稳定
void ShellSort() {
for(int d = n / 2; d >= 1; d /= 2) { //增量变化
for(int i = d + 1; i <= n; i++) { //对所以组采用直接插入排序
if(A[i] < A[i - d]) { //要注意每组中相邻元素距离为d,其余和直接插入排序一样
A[0] = A[i];
int j = i - d;
for(; j > 0 && A[0] < A[j]; j -= d)
A[j + d] = A[j];
A[j + d] = A[0];
}
}
}
}
8.2 交换排序
8.2.1冒泡排序
void BubbleSort() {
for(int i = 1; i <= n; i++) {
bool flag = false;
for(int j = n; j > i; j--) {
if(A[j - 1] > A[j]) {
swap(A[j - 1], A[j]);
flag = true;
}
}
if(!flag) //剪枝
return;
}
}
8.2.2 快速排序
分治思想,每次选一个尽量为中值的元素为枢轴,将比枢轴大的放它右边,小的放左边(方法是用2个指针分别从两端交替扫描),这样就分解成了2个性质相同且独立的子问题,分而治之。
所以可以看出来每趟排序之后会将枢轴元素放到最终的位置,且不稳定
最好时间复杂度 O(nlogn),最坏时间复杂度O(n^2),平均时间复杂度O(nlogn)
最坏空间复杂度O(n)【栈】,空间复杂度O(logn)。
int Partition(int low, int high) {
int pivot = A[low]; //选择一个元素为枢轴,一般就是第一个元素
while(low < high) { //从两端交替向中间扫描,low==high终止
while(low < high && A[high] >= pivot)
--high; //从右往左找到第一个小于pivot
A[low] = A[high];//pivot放到左边,保证可以分为2个独立的子问题
while(low < high && A[low] <= pivot)
++low;
A[high] = A[low];
}
A[low] = pivot;
return low;
}
void QuickSort(int low, int high) {
if(low < high) {
int pivotpos = Partition(low, high); //分治,将问题分成2个相同的且独立的子问题;
QuickSort(low, pivotpos - 1);
QuickSort(pivotpos + 1, high);
}
}
8.3 选择排序
8.3.1 简单选择排序
void SelectSort() {
for(int i = 1; i < n; i++) {
int Min = i;
for(int j = i + 1; j <= n; j++) {
if(A[j] < A[Min])
Min = j;
}
swap(A[i], A[Min]);
}
}
8.3.2 堆排序
将数组看成一颗完全二叉树,利用堆的性质不断建堆,每趟排序建立一个堆,然后将根节点(也就是最大值)和堆的最后一个节点交换(之后那个最大值就永远固定了,看成离开了堆),这个时候堆就被破坏,继续调整成为新堆(下坠),如此循环。
其实就是利用堆来“选择排序”,每次选择最大值放到最后。
时间复杂度 O(nlogn),其中初始建堆O(n),然后n-1趟建堆排序,每趟O(logn)
空间复杂度为O(1),不稳定。
//调整以元素k为根的子树为堆
void HeadAdjust(int k, int len) {
A[0] = A[k]; //暂存根节点
for(int i = 2 * k; i <= len; i *= 2) { //沿着较大的儿子下坠
if(i < len && A[i] < A[i + 1])
i++; //A[i]为较大的儿子
if(A[0] >= A[i]) //这个时候满足堆的性质了
break;
else { //否则
A[k] = A[i]; //下坠,将A[i]这个大的儿子往上移
k = i; //修改k值,继续
}
}
A[k] = A[0]; //放入最终位置
}
//建初始堆
void BuildMaxHeap(int len) {
for(int i = len / 2; i > 0; i--) //将所有的非叶子节点按照顺序调整
HeadAdjust(i, len);
}
void HeapSort(int len) {
BuildMaxHeap(len); //初始建大根堆 O(n)的时间复杂度
for(int i = len; i > 1; i--) { //n-1趟交换和下坠,每次O(logn),所以一共O(nlogn)
swap(A[i], A[1]); //这个时候的A[1]就是堆的最大元素,就是选择排序里面“选择”的那个数字,将他放到最后固定
HeadAdjust(1, i - 1); //大根堆被破坏,将根节点下坠调整成新的大根堆
}
}
8.4 归并排序
分治思想
空间复杂度:O(n)
时间复杂度:每趟都是O(n),一共是logn趟,所以为O(nlogn),且最好最坏都是O(nlogn)
二路归并算法是稳定的。
int B[maxn];
void Merge(int low, int mid, int high) {
for(int k = low; k <= high; k++)
B[k] = A[k];
int i = low, j = mid + 1, k = low;
while(i <= mid && j <= high) {
if(B[i] <= B[j])
A[k++] = B[i++];
else
A[k++] = B[j++];
}
while(i <= mid)
A[k++] = B[i++];
while(j <= high)
A[k++] = B[j++];
}
void MergeSort(int low, int high) {
if(low < high) {
int mid = (low + high) / 2;
MergeSort(low, mid);
MergeSort(mid + 1, high);
Merge(low, mid, high);
}
}