八大排序算法
插入排序
直接插入排序
基本思想: 把待排序的按其数值的大小,逐个插入到已有序序列,直到插入完为止。
直接插入排序动图:
逻辑步骤:
实现:
void InsertSort(int* a, int n)
{
for (int i = 1; i < n; i++)
{
//定义end和tmp,一前一后的关系
int end = i - 1;
int tmp = a[i];
//循环的两个结束条件,end<0或者tmp>=a[end]
while (end >= 0)
{
if (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
//如果最初tmp>a[end],直接把tmp放到数组的最后即可
a[end + 1] = tmp;
}
}
希尔排序(缩小增量排序)
相当于插入排序的优化
基本思想: 把数据进行分组,把位置差异较大的值,快速靠近该值正确的位置
逻辑步骤:
实现:
思考:gap等于多少就是被分成多少组。
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
//这样写的目的,为了提高效率,也可以写成gap = gap / 2;只要最后gap为1即可,为1的时候,插入排序过一遍
gap = gap / 3 + 1;
//注意循环的结束条件判断
for (int i = 0; i < n - gap; i++)
{
//end和tmp的关系,变成每组的前后关系
int end = i;
int tmp = a[i + gap];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
选择排序
简单选择排序
基本思想: 每次从待排序的数组中选择最小的(最大的)一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
简单选择排序动图:
逻辑步骤:
实现:
void SelectSort(int* a, int n)
{
int left = 0;
int right = n - 1;
while (left < right)
{
//一次寻找最大最小两个值
int mini = left;
int maxi = left;
//注意:该循环的起始、结束位置,不是0和n,因为已经排好的有序序列不需要再排
for (int i = left + 1; i <= right; i++)
{
if (a[mini] > a[i])
{
mini = i;
}
if (a[maxi] < a[i])
{
maxi = i;
}
}
//注意交换时候的特殊情况
Swap(&a[mini], &a[left]);
if (left == maxi)
{
maxi = mini;
}
Swap(&a[maxi], &a[right]);
left++;
right--;
}
}
堆排序
之前写的堆排序的链接:堆排序
交换排序
冒泡排序
基本思想: 根据序列中两个数值的比较结果来对换这两个数值在序列中的位置。
冒泡排序动图:
实现:
//交换
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
void BubbleSort(int* a, int n)
{
int i = 0;
int j = 0;
//趟数
for (i = 0; i < n - 1; i++)
{
int flag = 0;
//比较的次数
for (j = 0; j < n - i - 1; j++)
{
//两两比较
if (a[j] > a[j + 1])
{
Swap(&a[j], &a[j + 1]);
flag = 1;
}
}
//如果一趟循环过后一次没比较,也就代表该序列是有序的,直接结束循环-->属于冒泡循环的一种优化
if (flag == 0)
break;
}
}
快速排序
快速排序的基本思想: 任取待排序序列中的某个元素作为基准值,按照该排序码将待排序集合分割成两个子序列,左子序列中所有元素均小于基准值,右子序列中的所有元素都大于基准值,然后递归该过程,直到所有元素都排列在相应的位置上。
在介绍快速排序的实现方法之前先介绍一下三数取中法。 (对快速排序的优化)
思想:在一个序列中取序列左右中三个数,比较三个数的大小,取中间值。
使用三数取中法,为了预防序列中的数据出现升序和降序的特殊情况,因为一个序列是升序和降序序列,使用快速排序效率低下。
三数取中法实现:
//三数取中法
int GetMidNum(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[mid] > a[left])
{
if (a[left] > a[right])
{
return left;
}
else if (a[mid] > a[right])
{
return right;
}
else
{
return mid;
}
}
else //a[mid] < a[left]
{
if (a[left] < a[right])
{
return left;
}
else if (a[right] > a[mid])
{
return right;
}
else
{
return mid;
}
}
}
快速排序的方法(递归)
hoare法
hoare法排序动图:
逻辑步骤:
实现:
void HoareQuickSort(int* a, int left, int right)
{
//判断递归结束的条件
if (left >= right)
{
return;
}
//保存序列左右侧的值
int end = right;
int begin = left;
//三数取中法
int midi = GetMidNum(a, left, right);
//把所取得数和最左侧的数交换,把交换后最左侧的数当作关键值
if (midi != left)
Swap(&a[midi], &a[left]);
int keyi = left;
while (begin < end)
{
while (begin < end && a[end] >= a[keyi])
{
end--;
}
while (begin < end && a[begin] <= a[keyi])
{
begin++;
}
Swap(&a[end], &a[begin]);
}
Swap(&a[keyi], &a[begin]);
keyi = begin;
//递归确定别的关键值的位置
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
挖坑法
挖坑法排序动图:
逻辑步骤:
实现:
void HoleQuickSort(int* a, int left, int right)
{
if (left >= right)
return;
//三数取中法
int midi = GetMidNum(a, left, right);
if (midi != left)
Swap(&a[midi], &a[left]);
int key = a[left];
int hole = left;
while (left < right)
{
while (left < right && a[right] >= key)
{
right--;
}
a[hole] = a[right];
hole = right;
while (left < right && a[left] <= key)
{
left++;
}
a[hole] = a[left];
hole = left;
}
a[hole] = key;
HoleQuickSort(a, left, hole - 1);
HoleQuickSort(a, hole + 1, right);
}
前后指针法
前后指针法排序动图:
逻辑步骤:
实现:
void PointerQuickSort(int* a, int left, int right)
{
if (left >= right)
return;
//三数取中法
int midi = GetMidNum(a, left, right);
if (midi != left)
Swap(&a[midi], &a[left]);
int keyi = left;
int prev = left;
int cur = left + 1;
while (cur <= right)
{
//这个if语句完成了逻辑步骤中第一点的要求
if (a[cur] < a[keyi] && ++prev != cur)
{
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
快速排序(非递归)
逻辑步骤:
实现:
注意:这里需要用到栈。栈的相关知识链接:栈的实现
int PointerQuickSort(int* a, int left, int right)
{
//三数取中法
int midi = GetMidNum(a, left, right);
if (midi != left)
Swap(&a[midi], &a[left]);
int keyi = left;
int prev = left;
int cur = left + 1;
while (cur <= right)
{
if (a[cur] < a[keyi] && ++prev != cur)
{
Swap(&a[prev], &a[cur]);
}
cur++;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
return keyi;
}
void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, left);
StackPush(&st, right);
while (!StackEmpty(&st))
{
int end = StackTop(&st);
StackPop(&st);
int begin = StackTop(&st);
StackPop(&st);
int keyi = PointerQuickSort(a, begin, end);
//两个子序列的判断 + 压栈
if (keyi + 1 < end)
{
StackPush(&st, keyi + 1);
StackPush(&st, end);
}
if (keyi - 1 > begin)
{
StackPush(&st, begin);
StackPush(&st, keyi - 1);
}
}
StackDestroy(&st);
}
归并排序
基本思想: 采用的分治思想,将已经有序的子序列合并,得到完全有序的序列。将两个有序的序列合成一个有序的–>二路归并。
归并排序动图:
逻辑步骤:
归并排序递归实现
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left >= right)
return;
int mid = (left + right) / 2;
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int i = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] > a[begin2])
{
tmp[i++] = a[begin2++];
}
else
{
tmp[i++] = a[begin1++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
//注意拷贝的位置,左右之间的关系
memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (NULL == tmp)
{
perror("malloc::fail");
return;
}
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
tmp = NULL;
}
归并排序非递归实现
逻辑实现步骤:
void MergeSortNonR2(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
if (NULL == tmp)
{
perror("malloc::fail");
return;
}
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
//printf("front:[%d,%d][%d,%d]\n", begin1, end1, begin2, end2);
if (end1 >= n || begin2 >= n) //不用归并的直接结束即可
{
break;
}
else if (end2 >= n) //修正 + 归并
{
end2 = n - 1;
}
//printf("back:[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
int j = i;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] > a[begin2])
{
tmp[j++] = a[begin2++];
}
else
{
tmp[j++] = a[begin1++];
}
}
while (begin1 <= end1)
{
tmp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[j++] = a[begin2++];
}
memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
}
gap *= 2;
}
free(tmp);
}
非比较排序
计数排序
基本思想:
- 统计相同元素出现次数
- 根据统计的结果将序列回收到原来的序列中
逻辑步骤:
实现:
void CountSort(int* a, int n)
{
int min = a[0];
int max = a[0];
for (int i = 1; i < n; i++)
{
if (min > a[i])
{
min = a[i];
}
if (max < a[i])
{
max = a[i];
}
}
//寻找数据的范围
int range = max - min + 1;
//开辟一个数组计数
int* count = (int*)calloc(range, sizeof(int));
if (NULL == count)
{
perror("calloc::fail");
return;
}
//计数
for (int i = 0; i < n; i++)
{
count[a[i] - min]++;
}
//排序
int j = 0;
for (int i = 0; i < range; i++)
{
while (count[i]--)
a[j++] = i + min;
}
free(count);
count = NULL;
}
总结
八大排序的算法复杂度和稳定性分析
稳定性的概念: 在一串数值中会有相同的值,在完成排序后,相同的值之间的相对位置未发生改变
时间复杂度 | 空间复杂度 | 稳定性 | |
---|---|---|---|
直接插入排序 | O(N^2) | O(1) | 稳定 |
希尔排序 | O(N^1.3)约O(N*logN) | O(1) | 不稳定 |
简单选择排序 | O(N^2) | O(1) | 不稳定 |
堆排序 | O(N*logN) | O(1) | 不稳定 |
冒泡排序 | O(N^2) | O(1) | 稳定 |
快速排序 | O(N*logN) | O(logN) ~ O(N) | 稳定 |
归并排序 | O(N*logN) | O(N) | 稳定 |
八大排序的性能的比较
注意: 当输出为0的情况是该排序的性能不满足数据量较大的时候,所以去除了该排序的比较
- 当数据量为10000时
- 当数据量为50000时
- 当数据量为5000000时
- 当数据量为10000000时
小结
八大排序各有特色。
有些适用于教学,有些适用于数据量较大,有些适用于数据量不大不小的时候,还有些适用于数据量较大时选择前十个或者后十个。