![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/ec5c0ecf580d3f5520688c9e3ae5b5aa.png)
1. 冒泡排序
分析: 如果左边小于右边,就会执行一次交换。遍历n(数组长度)次数组,每次遍历n - -个数(第一次遍历 0~n-1,第二次遍历 0 ~ n-2 ,直到 遍历 0~0),每一次遍历数组造成的效果都会把遍历范围的最大值交换至范围的最后
最好情况 : O(n)
最坏情况 : O(n^2)
稳定性:稳定
示例代码:
void BubbleSort(int* arr, int n)
{
int end = n - 1;
while (end--)
{
int flag = 0;
for (int i = 1; i < end; i++)
{
if (arr[i] < arr[i - 1])
{
int tmp = arr[i];
arr[i] = arr[i - 1];
arr[i - 1] = tmp;
flag = 1;
}
}
if (flag) break;
end--;
}
}
2. 选择排序
分析: 以 i 下标为开始 ,选择其后数中最小的数,在 i 处 与选择的最小数坐标进行交换。
最好情况 : O(n ^2)
最坏情况 : O(n ^2)
稳定性:不稳定
例如 3 3 1 1 能保证 1 的稳定性,但是无法保证 3 的稳定性, 会跨数的交换会破坏稳定性
示例代码:
(与图示不同 , 是双指针法, 在 begin ~ end 的范围内,选择最大数下标 与end交换,最小数下标与 begin交换。)
void SelectSort(int* arr, int n)
{
int begin = 0;
int end = n-1;
while (begin < end)
{
int mini = begin;
int maxi = end;
for (int i = begin; i <= end; i++)
{
if (arr[i] > arr[maxi])
{
maxi = i;
}
if (arr[i] < arr[mini])
{
mini = i;
}
}
Swap(&arr[begin], &arr[mini]);
if (maxi != begin)
{
Swap(&arr[maxi], &arr[end]);
}
++begin;
--end;
}
}
3. 插入排序
分析: 在 0 ~ i-1 的范围内 , 设 end = i ,记录 key = arr [ i ] 并且向前遍历 , 如果 arr[end] > arr[ i ] 则令 arr[end+1] = arr[end] (即令其 end 下表的数覆盖至 end+1 下标的数),否则停止遍历,并且令此时的arr[end] = key
最好情况 : O(n) – 此时待排序列为逆序,或者说接近逆序
最坏情况 : O(n^2) – 此时待排序列为升序,或者说接近升序
稳定性:稳定
示例代码:
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = a[end + 1];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end+1] = a[end];
end--;
}
else
{
break;
}
}
a[end+1] = tmp;
}
}
4. 希尔排序
希尔排序是插入排序的进阶版,不会直接进行排序,而是会将数组如图所示分为几组,在组内先进行插入排序,这个过程被称作 预排序 ,目的在于更快的把较大的数放在后面,较小的数放在前面。每分一次组,gap/=2 ,当 gap = 1 ,就会对整个数组做一次正常的插入排序。
分析: 在 0 ~ i-1 的范围内 , 设 end = i ,记录 key = arr [ i ] 并且向前遍历 , 如果 arr[end] > arr[ i ] 则令 arr[end+1] = arr[end] (即令其 end 下表的数覆盖至 end+1 下标的数),否则停止遍历,并且令此时的arr[end] = key
最好情况 : O(n) – 此时待排序列为逆序,或者说接近逆序
最坏情况 : O(n^2) – 此时待排序列为升序,或者说接近升序
稳定性:不稳定 – 相同的数预排序可能会分到不同的组中 。
示例代码:
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap /= 2;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end+ gap] = a[end ];
end-= gap;
}
else
{
break;
}
}
a[end+ gap] = tmp;
}
}
}
5. 堆排序
通过建大小堆来实现排序, 升序建小堆, 降序建大堆。
示例代码:
//堆排序 向上调整
void Adjustdown(int* a, int n, int root)
{
int parent = root;
int kid = parent * 2 + 1;
while (kid < n)
{ //大堆
if (kid + 1 < n && a[kid] < a[kid + 1])
{
kid++;
}
// 孩大 大堆
if (a[kid] > a[parent])
{
Swap(&a[kid], &a[parent]);
parent = kid;
kid = parent * 2 + 1;
}
else
{
break;
}
}
}
//堆排序
void HeapSort(int* a, int n)
{
//建堆 i = (n - 1 - 1)/2 选取数组中的最后一个数的父节点
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
Adjustdown(a, n, i);
}
int end = n - 1;
//将堆建成排序结构 将最大的数放在最后,再
while (end > 0)
{
Swap(&a[0], &a[end]);
Adjustdown(a, end, 0);
end--;
}
}
6. 快速排序
6.1 hoare版本(左右指针法)
分析 : 1.最左边的下标设为 begin , 最右的下标设为 end ,设 key = arr[ begin ] 。
2. end 先走 , 向前找小于 key 的下标 , 停下。
3. begin 后走 , 向后找大于key 的下标。
4. 若 end 与 begin 未相遇 , 交换 begin 与end 的值。
5. 若 end 与 begin 相遇 , 交换 相遇位置 与 key 的值。
6. 相遇之后,在将相遇位置作为分割,分为左区间与右区间,分别再重复以上过程,重复的过程通过递归实现。
时间复杂度: O( n * log n)
示例代码:
//快排 hoare法
void QuickSort01(int* arr, int begin,int end)
{
if (begin >= end)
return;
int left = begin;
int right = end;
int keyi = begin;
while (begin < end)
{
while (begin < end && arr[end] >= arr[keyi])
{
end--;
}
while (begin < end && arr[begin] <= arr[keyi])
{
begin++;
}
Swap(&arr[begin], &arr[end]);
}
Swap(&arr[keyi], &arr[end]);
keyi = end;
QuickSort01(arr, left, keyi - 1);
QuickSort01(arr, keyi+1, right);
}
6.2 挖坑法
挖坑法的思路与hoare版本 大体相同,但思路更加简单
//快速排序法 挖坑法
void QuickSort02(int* arr, int begin, int end)
{
if (begin >= end) return;
int left = begin;
int right = end;
int keyi = arr[begin];
while (begin < end)
{
while (arr[end] >= keyi && begin < end)
{
end--;
}
arr[begin] = arr[end];
while (arr[begin] <= keyi && begin < end)
{
begin++;
}
arr[end] = arr[begin];
}
arr[begin] = keyi;
keyi = begin;
QuickSort02(arr, left, keyi - 1);
QuickSort02(arr, keyi + 1,right);
}
6.3 非递归法
递归实现快排会不断建立栈帧,可能会导致栈的空间不足,非递归法可以将内存开在堆上,大体上就可以避免这个问题。
//快排 非递归
int PartSort(int* arr, int begin, int end)
{
int key =arr[begin] ;
while (begin < end)
{
while (begin < end && arr[end] >= key)
{
end--;
}
arr[begin] = arr[end];
while (begin < end && arr[begin] <= key)
{
begin++;
}
arr[end] = arr[begin];
}
arr[end] = key;
return end;
}
//快排 非递归
void QuickSortNonR(int* arr, int begin, int end)
{
Stack st;
StackInit(&st);
StackPush(&st, end);
StackPush(&st, begin);
while (!StackEmpty(&st))
{
int left = StackTop(&st);
StackPop(&st);
int right = StackTop(&st);
StackPop(&st);
int mid = PartSort(arr, left, right);
if (left < mid - 1)
{
StackPush(&st, mid - 1);
StackPush(&st, left);
}
if (right > mid + 1)
{
StackPush(&st, right);
StackPush(&st, mid + 1);
}
}
}
7.归并排序
时间复杂度: O(n*logn)
稳定性:稳定
示例代码
void _MergeSort(int* arr, int begin,int end,int *tmp)
{
if (begin == end) return;
int mid = (begin + end) / 2;
_MergeSort(arr, begin, mid, tmp);
_MergeSort(arr, mid+1, end, tmp);
int begin1 = begin;
int end1 = mid;
int begin2 = mid + 1;
int end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (arr[begin1] < arr[begin2])
{
tmp[i++] = arr[begin1++];
}
else
{
tmp[i++] = arr[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = arr[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = arr[begin2++];
}
memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* arr, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
_MergeSort(arr, 0, n - 1, tmp);
free(tmp);
}
8.计数排序
计数排序 基于哈希表实现非比较排序 ,将每个数出现的数的次数记录下来 , 在从小到大一个个放回原数组中。
示例代码:
void CountSort(int* arr, int n)
{
int max = arr[0];
int min = arr[0];
for (int i = 1; i < n; i++)
{
if (arr[i] > max) max = arr[i];
if(arr[i] <min) min = arr[i];
}
int* count = (int*) malloc(sizeof(int) * (max - min + 1));
memset(count, 0, sizeof(int)*(max - min + 1));
for (int i = 0; i < n; i++)
{
count[arr[i] - min]++;
}
int j = 0;
for (int i = 0; i < max - min+1; i++)
{
while (count[i]-- != 0)
{
arr[j++] = i + min;
}
}
}
时间复杂度 : O(n)
计数排序适合于数据大小较集中的数据