排序
基于插入的排序:
- 直接插入排序算法
- shell(希尔)排序
基于交换的排序: - 冒泡排序
- 快速排序
基于选择的排序: - 简单选择排序
- 堆排序
其他的排序: - 归并排序
- 基于统计的排序
直接插入排序
直接插入排序优化出两个算法
- 折半插入排序
- 2路插入排序
直接插入排序:
- 在添加新的数据时,我们使用顺序查找的方式找到其要插入的位置,将其插入
- 性质
- 时间复杂度: O(n2)O(n^2)O(n2)
- 稳定排序
- 就地排序
- 内部排序
折半插入排序
- 在直接插入排序中的顺序查找换成折半查找(二分查找)
- 优化后和没优化基本没有多少分别
二路插入排序
- 新建一个和原数组相同大小的循环数组,然后再把元素依然插进去,可以节省一点交换元素的时间
- 但优化后和没优化基本没有多少分别
cpp
void straight_insert_sort()//直接插入排序
{
int i, j, x;
for (i = 0; i < n; i++)
{
x = arr[i];
for (j = 0; j < i; j++)
if (arr[j] > arr[i])
break;
for (int z = i; z > j; z--)
arr[z] = arr[z - 1];
arr[j] = x;
}
}
shell(希尔)排序
希尔排序又叫缩小增量排序
理论基础
- 插入排序在处理基本有序 的数组时,效率非常高,接近 O(n)O(n)O(n)。
- 通过将数组按大增量分组进行插入排序,可以快速地让距离较远的元素到达其大致正确的位置,从而使整个数组宏观上变得"基本有序"。
- 随着增量逐渐缩小,数组的有序程度越来越高,直到最后增量为1时,进行一次标准的直接插入排序,此时由于数组已基本有序,排序效率很高
定义: 在直接插入排序算法的基础上,对待排序的数组进行分组,先对每一组进行排序,然后不断缩小组数,不断排序,直到缩小到为1组
如何分组
- 比如有9个数可以分成三组(增量), 分成
[1,4,7],[2,5,8],[3,6,9] - 增量的选择: 不同的增量的选择, 会使得希尔排序有不同的时间复杂度, 以下给出一个可能的增量方法
- 对n个数据进行排序: n/2->n/4->n/8->...->1
性质
- 时间复杂度
- 不同的增量的选择有不同的时间复杂度
- 不稳定排序
- 就地排序
- 内部排序
cpp
void shell_sort()
{
int x,j;
for (int k = n/2; k >= 1; k /= 2)
{
for (int i = k ; i < n; i++)
{
x = arr[i];
for (j = i; j >= k; j -= k)
{
if (arr[j-k] < x)
break;
arr[j] = arr[j - k];
}
arr[j] = x;
}
}
}
冒泡排序
冒泡排序: 通过不断的比较两个相邻的元素,若这两个元素是乱序的,则交换位置,从而实现每趟都把最大的数据交换到最后面
性质
- 时间复杂度: O(n2)O(n^2)O(n2)
- 稳定排序
- 就地排序
- 内部排序
cpp
void bubble_sort()
{
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - 1 - i; j++)
if (arr[j] > arr[j + 1])
swap(arr[j], arr[j + 1]);
}
快速排序
快速排序: 分治法, 把一个相对无序的数组, 分为两个相对有序的数组, 那么分出来的两个相对有序的数组再分别运行相同的操作(即把它看做一个相对无序的数组进行处理)
- 首先选定一个基准数x(比较的标准),把比基准数x小的数据放在x前面,把比基准数x大的数据放在后面, 排好一趟之后,x把序列分成了两部分,这两部分可还都是乱序,再分别对这两部分进行快速排序
性质:
- 时间复杂度: O(nlogn)O(n\log n)O(nlogn)
- 就地排序
- 不稳定排序
- 内部排序
cpp
void quick_sort(int arr[],int l, int r)
{
if (l >= r)
return;
int x = arr[l+r>>1], i = l - 1, j = r + 1;
while (i < j)
{
do i++; while (arr[i] < x);
do j--; while (arr[j] > x);
if (i < j) swap(arr[i], arr[j]);
}
quick_sort(arr,l, j), quick_sort(arr,j + 1, r);
}
简单选择排序
简单选择排序: 每次从待排序区中,选择一个最小的数,放在待排序区的第一个位置,从而实现升序排列
性质
- 时间复杂度: O(n2)O(n^2)O(n2)
- 不稳定排序
- 就地排序
- 内部排序
cpp
void select_sort()
{
for (int i = 0; i < n - 1; i++)
{
int pos = i;
for (int j = i; j < n; j++)
if (a[pos] > a[j])
pos = j;
swap(a[i], a[pos]);
}
}
堆排序
堆排序只是对简单选择排序的一个优化,找待排序区的最小一个数的时间复杂度从O(n)到O(log n) , 重点是堆的概念(上一篇有说)
cpp
#include <iostream>
using namespace std;
int a[105];
void down_adjust(int a[], int i, int n)
{
int father = i, child = 2 * i;
while (child <= n)
{
if (child + 1 <= n && a[child] > a[child + 1])
child = child + 1;
if (a[father] <= a[child])
return;
else
{
swap(a[father], a[child]);
father = child;
child = father * 2;
}
}
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = n / 2; i >= 1; i--)
down_adjust(a, i, n);
for (int i = 1,j=n; i <= n; i++,j--)
{
cout << a[1] << " ";
a[1] = a[j];
down_adjust(a, 1, j-1);
}
return 0;
}
归并排序
思路: 分治法, 把两个有序的数组合并为一个有序的数组
- 先将所有记录完全分开,然后两两合并,在合并的过程中将其排好序,最终能够得到一个完整的有序表
性质:
- 时间复杂度: O(nlogn)O(n\log n )O(nlogn)
- 非就地排序
- 稳定排序
cpp
void merge_sort(int a[], int l, int r)
{
if (l >= r)return;
int middle = l + (r - l) / 2;
merge_sort(a, l, middle), merge_sort(a, middle + 1, r);
int i = l, j = middle + 1, k = 0;
while (i <= middle && j <= r)
{
if (a[i] <= a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
while (i <= middle)temp[k++] = a[i++];
while (j <= r)temp[k++] = a[j++];
for (int i = l, j = 0; i <= r; i++, j++)
a[i] = temp[j];
}
基于统计的排序
- 计数排序: 统计每个数出现的次数,然后直接按次数输出
- 桶排序: 把数放在桶中,然后再对每一个桶进行排序
- 基数排序: 和桶排序差不多,但对每一个位数进行一次排序,排序一定要稳定