数据结构中常见排序方法主要有以下几种:
插入排序: ① 直接插入排序 ② 希尔排序
选择排序: ① 选择排序 ② 堆排序
交换排序: ① 冒泡排序 ② 快速排序
归并排序
1. 直接插入排序
假设第一个数据有序, 将待插入数据从后向前依次比较.
代码如下:
void InsertSort(int* array, int n) {
for (int i = 0; i < n - 1; i++) {
//新数据插入
//end表示有序序列的最后一个位置
int end = i;
//key表示待插入数据
int key = array[end + 1];
//找到第一个小于等于key的位置
while (end >= 0 && array[end] > key) {
//当前数据向后移动
array[end + 1] = array[end];
end--;
}
array[end + 1] = key;
}
}
插入排序特性:
时间复杂度: 最坏O(n^2) 最好O(n) 平均O(n^2)
空间复杂度: O(1)
稳定性: 稳定
数据敏感度: 敏感
2. 希尔排序
先进行多轮预排序, 再进行普通插入排序
代码如下:
void ShellSort(int* array, int n) {
//gap表示步长
int gap = n;
//多轮插入排序
while (gap > 1) {
gap = gap / 2; //或者 gap = gap / 3 + 1;
//一轮插入排序
for (int i = 0; i < n - gap; i++) {
//通过gap进行逻辑分组
//组内各自进行插入排序
//不同族元素交替排序
int end = i;
int key = array[end + gap];
while (end >= 0 && array[end] > key) {
array[end + gap] = array[end];
end -= gap;
}
array[end + gap] = key;
}
}
}
希尔排序特性:
时间复杂度: 最坏O(n^1.3) 最好O(n^1.3) 平均O(n)
空间复杂度: O(1)
稳定性: 不稳定---->预排序可能导致相对位置发生变化
数据敏感度: 敏感
3. 选择排序
每一次从待排序的数据中选取最大或最小值, 放在序列的起始位置, 直到全部元素排完.
代码如下:
void SelectSort(int* array, int n) {
for (int i = 0; i < n; i++) {
//start表示未排序数据的最左边
int start = i;
//最小值的位置
int min = start;
//从未排序数据中找最小值
for (int j = start + 1; j < n; j++) {
if (array[j] < array[min]) {
min = j;
}
}
Swap(array, start, min);
}
}
选择排序特性:
时间复杂度: 最坏O(n^2) 最好O(n^2) 平均O(n^2)
空间复杂度: O(1)
稳定性: 稳定
数据敏感度: 不敏感
选择排序还有第二种优化方案: 同时选取最大值和最小值进行排序, 可以提高时间效率
代码如下:
void SelectSort2(int* array, int n) {
int begin = 0;
int end = n - 1;
while (begin < end) {
//每一次选择最大值和最小值
int min = begin;
int max = begin;
for (int i = begin + 1; i <= end; i++) {
if (array[i] > array[max]) {
max = i;
}
if (array[i] < array[min]) {
min = i;
}
}
//最小值放在begin
Swap(array, begin, min);
//如果最大值位置发生变化, 需要更新
if (max == begin) {
max = min;
}
//最大值放在end
Swap(array, end, max);
begin++;
end--;
}
}
4. 堆排序
是选择排序的一种, 排升序需要建大堆, 排降序需要建小堆.
代码如下:
void AdjustDwon(int* array, int n, int parent) {
int child = 2 * parent + 1;
while (child < n) {
if (child + 1 < n && array[child + 1] > array[child]) {
child++;
}
if (array[child] > array[parent]) {
Swap(array, child, parent);
parent = child;
child = 2 * parent + 1;
}
else {
break;
}
}
}
void HeapSort(int* array, int n) {
//建堆
for (int i = (n - 2) / 2; i >= 0; i--) {
AdjustDwon(array, n, i);
}
//循环删除
while (n) {
Swap(array, 0, n - 1);
n--;
AdjustDwon(array, n, 0);
}
}
堆排序特性:
时间复杂度: 最坏O(nlogn) 最好O(nlogn) 平均O(nlogn)
空间复杂度: O(1)
稳定性: 不稳定
数据敏感度: 不敏感
5. 冒泡排序
相邻元素进行比较, 大的向后移动
代码如下:
void BubbleSort(int* array, int n) {
while (n) {
//一轮冒泡排序
for (int i = 0; i < n - 1; i++) {
//相邻元素进行比较, 大的向后移动
if (array[i] > array[i + 1]) {
Swap(array, i, i + 1);
}
}
n--;
}
}
冒泡排序特性:
时间复杂度: 最坏O(n^2) 最好O(n) 平均O(n^2)
空间复杂度: O(1)
稳定性: 稳定
数据敏感度: 敏感
6. 快速排序
三种常见的快速排序方法为: hoare法, 挖坑法, 前后指针法
(1) hoare法
步骤: ① 从待划分区间选取一个基准值;
② 从待划分区间从后向前找第一个小于基准值的数据;
③ 从前向后找第一个大于基准值的数据;
④ 将②③找到的数据交换, 循环执行②③④;
⑤ 当查找到相遇位置时, 把基准值和相遇位置进行交换.
tps. ②③顺序不能换, 否则会导致第⑤步进行交换时出问题.
代码如下:
int PartSort1(int* array, int left, int right) {
//选择基准值
int key = array[left];
int start = left;
//划分
while (left < right) {
//从后往前找第一个小于key的位置
while (left < right && array[right] >= key) {
right--;
}
//从前向后找第一个大于key的位置
while (left < right && array[left] <= key) {
left++;
}
//交换left, right
Swap(array, left, right);
}
//key和相遇位置数据进行交换
Swap(array, start, left);
//返回基准值位置
return left;
}
(2) 挖坑法
步骤: ① 选取基准值, 第一个坑为基准值位置;
② 从后向前找第一个小于基准值的数据, 去填上一次的坑;
③ 从前向后找第一个大于基准值的数据, 去填上一次的坑, 循环执行②③;
④ 相遇位置, 将基准值填入坑内
代码如下:
int PartSort2(int* array, int left, int right) {
//选取基准值
int key = array[left];
while (left < right) {
//从后往前找第一个小于key的位置
while (left < right && array[right] >= key) {
right--;
}
//填坑:left位置的坑
array[left] = array[right];
//从前向后找第一个大于key的位置
while (left < right && array[left] <= key) {
left++;
}
//填坑:right位置的坑
array[right] = array[left];
}
//相遇位置, 最后一个坑, 填入key
array[left] = key;
//返回基准值的位置
return left;
}
(3) 前后指针法
设定两个指针: 前指针和后指针
cur表示下一个小于基准值的位置
prev表示最后一个小于基准值的位置
判断两个指针之间是否有大于基准值的数据--------------如果前后指针连续: 表示中间没有大于基准值的数据
如果前后指针不连续: 表示中间有大于基准值得数据
如果不连续, 则将大数据和新发现的小数据进行交换, 最终实现基准值左边都是小于基准值的, 右边都是大于基准值的
代码如下:
int PartSort3(int* array, int left, int right) {
//选取基准值
int key = array[left];
//prev: 最后一个小于基准值的位置
int prev = left;
//cur: 新发现的下一个小于基准值的位置
int cur = left + 1;
while (cur <= right) {
//找到下一个小于基准值的位置并且判断前后指针是否连续
if (array[cur] < key && prev + 1 != cur) {
//下一个大于基准值的位置
prev++;
//小于的和大于的交换
//大数据向后移动, 小数据向前移动
Swap(array, prev, cur);
}
cur++;
}
Swap(array, left, prev);
return prev;
}
快速排序的特性:
时间复杂度: 最坏O(n^2)-->基本不会出现 最好O(nlogn) 平均O(nlogn)
空间复杂度: O(logn)
稳定性: 不稳定
数据敏感度: 敏感
快速排序的优化:
三数取中法确定基准值: 可以把最坏情况变成最理想情况----> 均衡划分
代码如下:
int getMid(int* array, int begin, int end) {
int mid = begin + (end - begin) / 2;
//begin, mid, end中选取中间值位置
if (array[begin] < array[mid]) {
//begin < mid;
if (array[mid] < array[end]) {
return mid;
}
else {
// begin < mid, end <= mid
if (array[begin] > array[end]) {
return begin;
}
else {
return end;
}
}
}
else {
//begin >= mid;
if (array[mid] > array[end]) {
return mid;
}
else {
//begin >= mid, end >= mid;
if (array[begin] < array[end]) {
return begin;
}
else {
return end;
}
}
}
}
快速排序的非递归实现:
栈实现非递归: 区间入栈----每次获取栈顶区间, 进行划分----划分之后的小区间继续入栈----直到栈为空结束
代码如下:
//栈实现非递归: 保存待划分区间
void QuickSortNonR(int* array, int n) {
Stack st;
stackInit(&st, 10);
//起始区间入栈: 先右后左
if (n > 1) {
stackPush(&st, n - 1);
stackPush(&st, 0);
}
//遍历栈, 划分栈中的每个区间
while (stackEmpty(&st) != 1) {
//获取栈顶区间
int begin = stackTop(&st);
stackPop(&st);
int end = stackTop(&st);
stackPop(&st);
//划分
int keyPos = PartSort3(array, begin, end);
//子区间入栈: 先入右区间
//右: keyPos + 1, end
if (keyPos + 1 < end) {
stackPush(&st, end);
stackPush(&st, keyPos + 1);
}
//左: begin, keyPos - 1
if (begin < keyPos - 1) {
stackPush(&st, keyPos - 1);
stackPush(&st, begin);
}
}
}
队列实现快排非递归:
//队列实现: 快排非递归
void QuickSortNonR(int* array, int n) {
Queue q;
queueInit(&q);
if (n > 1) {
//先左后右
queuePush(&q, 0);
queuePush(&q, n - 1);
}
while (queueEmpty(&q) != 1) {
//获取队头区间
int begin = queueFront(&q);
queuePop(&q);
int end = queueFront(&q);
queuePop(&q);
//划分
int keyPos = PartSort3(array, begin, end);
//子区间入队
if (begin < keyPos - 1) {
queuePush(&q, begin);
queuePush(&q, keyPos - 1);
}
if (keyPos + 1 < end) {
queuePush(&q, keyPos + 1);
queuePush(&q, end);
}
}
}
7. 归并排序
合并有序的子序列, 使其变成一个更大的有序子序列.
合并的前提是: 子序列本身有序
开始的子序列: 只包含一个元素的子序列本身有序
合并过程: 相邻的有序子序列进行合并
tips. 不能进行原地合并, 会覆盖元素 ---------> 要借助辅助空间
代码如下:
//合并: 需要知道两个有序子序列的空间: [begin, mid] [mid + 1, end]
void Merge(int* array, int begin, int mid, int end, int* tmp) {
int begin1 = begin, end1 = mid, begin2 = mid + 1, end2 = end;
int idx = begin;
//合并
while (begin1 <= end1 && begin2 <= end2) {
if (array[begin1] <= array[begin2]) {
tmp[idx++] = array[begin++];
}
else {
tmp[idx++] = array[begin2++];
}
}
//查看是否有剩余元素
if (begin1 <= end1) {
memcpy(tmp + idx, array + begin1, sizeof(int) * (end1 - begin1 + 1));
}
if (begin2 <= end2) {
memcpy(tmp + idx, array + begin2, sizeof(int) * (end2 - begin2 + 1));
}
//拷贝到原始空间
memcpy(array + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSortR(int* array, int begin, int end, int* tmp) {
if (begin >= end) {
return;
}
int mid = begin + (end - begin) / 2;
//首先保证子区间有序, 子区间先进行排序
MergeSortR(array, begin, mid, tmp);
MergeSortR(array, mid + 1, end, tmp);
//合并有序子区间
Merge(array, begin, mid, end, tmp);
}
void MergeSort(int* array, int n) {
int* tmp = (int*)malloc(sizeof(int) * n);
MergeSortR(array, 0, n - 1, tmp);
free(tmp);
}
归并排序非递归实现, 代码如下:
void MergeSortNonR(int* array, int n) {
int* tmp = (int*)malloc(sizeof(int) * n);
//待合并区间的元素个数
int k = 1;
//多轮的归并
while (k < n) {
k *= 2;
//一轮归并
for (int i = 0; i < n; i += 2 * k) {
//[begin, mid] [mid + 1, end]
int begin = i;
int mid = i + k - 1;
//判断mid是否越界
if (mid >= n - 1) {
continue;
}
int end = i + 2 * k - 1;
//判断end是否越界
if (end >= n) {
end = n - 1;
}
Merge(array, begin, mid, end, tmp);
}
}
}
归并排序特性:
时间复杂度: 最坏O(nlogn) 最好O(nlogn) 平均O(nlogn)
空间复杂度: O(n)
稳定性: 稳定
数据敏感度: 不敏感
8. 计数排序
代码如下:
void CountSort(int* array, int n) {
//统计范围
int min = array[0], max = array[0];
for (int i = 1; i < n; i++) {
if (array[i] > max) {
max = array[i];
}
if (array[i] < min) {
min = array[i];
}
}
int range = max - min + 1;
//开辅助空间, 进行计数
int* countArr = (int*)malloc(sizeof(int) * range);
//初始化为0
memset(countArr, 0, sizeof(int) * range);
//统计次数
for (int i = 0; i < n; i++) {
countArr[array[i] - min]++;
}
//恢复数据, 遍历计数数组
int idx = 0;
for (int i = 0; i < range; i++) {
while (countArr[i]--) {
array[idx++] = i + min;
}
}
free(countArr);
}