本篇文章主要讲解直接插入排序、希尔排序、选择排序、堆排序、冒泡排序算法实现以及时间复杂度,稳定性分析。将在接下来的文章讲解快速排序、归并排序和计数排序。
本文全部代码在文章最后
目录
一、常见的排序算法
a.排序实现的接口
1.1插入排序
1.1.1基本思想:
1.2直接插入排序:
1.2.1直接插入排序的特性总结:
1.2.2代码讲解与实现:
1.3希尔排序( 缩小增量排序 )
1.3.1基本思想:
1.3.2希尔排序的特性总结:
1.3.3代码讲解与实现
2.1选择排序
2.2基本思想:
2.3.1直接选择排序:
2.3.2直接选择排序的特性总结:
2.2.4代码讲解以及实现
2.3.1堆排序
3.1交换排序
3.1.1冒泡排序
3.1.2代码
二、完整代码
Sort.h
Sort.c
Test.c
这是用于测试排序性能对比的函数,便于我们更加直观观察每个排序的性能差异
void TestOP()
{
srand(time(0));
const int N = 100000;
int* a1 = (int*)malloc(sizeof(int)*N);
int* a2 = (int*)malloc(sizeof(int)*N);
int* a3 = (int*)malloc(sizeof(int)*N);
int* a4 = (int*)malloc(sizeof(int)*N);
int* a5 = (int*)malloc(sizeof(int)*N);
int* a6 = (int*)malloc(sizeof(int)*N);
for (int i = 0; i < N; ++i)
{
a1[i] = rand();
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
a5[i] = a1[i];
a6[i] = a1[i];
}
int begin1 = clock();
InsertSort(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort(a4, N);
int end4 = clock();
int begin5 = clock();
QuickSort(a5, 0, N-1);
int end5 = clock();
int begin6 = clock();
MergeSort(a6, N);
int end6 = clock();
printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
printf("QuickSort:%d\n", end5 - begin5);
printf("MergeSort:%d\n", end6 - begin6);
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
free(a6);
}
一、常见的排序算法
a.排序实现的接口
// 排序实现的接口
// 插入排序
void InsertSort(int* a, int n);
// 希尔排序
void ShellSort(int* a, int n);
// 选择排序
void SelectSort(int* a, int n);
// 堆排序
void AdjustDwon(int* a, int n, int root);
void HeapSort(int* a, int n);
// 冒泡排序
void BubbleSort(int* a, int n)
1.1插入排序
1.1.1基本思想:
直接插入排序是一种简单的插入排序法,其基本思想是:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。(例如,玩扑克牌)
1.2直接插入排序:
当插入第i(i>=1)个元素时,前面的array[0],array[1],…,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],…的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移。
1.2.1直接插入排序的特性总结:
1. 元素集合越接近有序,直接插入排序算法的时间效率越高
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1),它是一种稳定的排序算法
4. 稳定性:稳定
1.2.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 (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
1.3希尔排序( 缩小增量排序 )
1.3.1基本思想:
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
1.3.2希尔排序的特性总结:
1. 希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定:
《数据结构(C语言版)》--- 严蔚敏:《数据结构-用面相对象方法与C++描述》--- 殷人昆:
我们一般参照Knuth所提出的方式取值。
1.3.3代码讲解与实现
预排序:
gap越大,大的值可以更快调到后面,小的值可以更快调在前面,越不接近有序
gap越小,跳的越慢,但是越接近有序,如果gap==1就是直接插入排序
如图:
但是我们更喜欢多组并排:
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
其实我们可以明显观察到,希尔排序的底层思想是在直接插入排序上加入了gap,让每次的排序先排一部分,再排一部分,直到gap减小为1时,就与直接排序一样,但此时数组中的顺序已经几乎有序。
2.1选择排序
2.2基本思想:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
2.3直接选择排序:
在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素
若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素
2.3.2直接选择排序的特性总结:
1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:不稳定
2.3.3代码讲解以及实现
我们使用两个变量一个作为小值下标,一个作为大值下标,在每一次找到最小值后就将小值与a[begin]交换将该值防御数组开头,找到最大值后就将改值与数组末尾值交换,将最大值存入数组最后,再++begin,--end,新的较小较大值依次进行排序。但需要注意当maxi 的值与begin相同时,因Swap(&a[begin], &a[mini]);导致maxi值被覆盖,要进行还原maxi = mini;
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while(begin < end)
{
int mini = begin, maxi = begin;
for (int i = begin + 1; i <= end; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
Swap(&a[begin], &a[mini]);
if (maxi == begin)
{
maxi = mini;
}
Swap(&a[end], &a[maxi]);
--end;
begin++;
}
}
2.4堆排序
详细讲解与实现见博客堆排序的详解
2.4.1堆排序的特性总结
1. 堆排序使用堆来选数,效率就高了很多。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(1)
4. 稳定性:不稳定
3.1交换排序
3.1.1基本思想:
所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
3.2冒泡排序
3.2.1冒泡排序的特性总结:
1. 冒泡排序是一种非常容易理解的排序
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:稳定
3.2.2代码
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n; j++)
{
for (int i = 1; i < n-j; i++)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
}
}
}
}
交换排序的快速排序以及剩下的排序将在下篇文章详细讲解
二、完整代码
Sort.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
#include<time.h>
void PrintArray(int* a, int n);
void InsertSort(int* a, int n);
void BubbleSort(int* a, int n);
void ShellSort(int* a, int n);
void SelectSort(int* a, int n);
void HeapSort(int* a, int n);
Sort.c
#include"Stact.h"
void PrintArray(int* a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
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 (tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
else
{
break;
}
}
a[end + 1] = tmp;
}
}
void Swap(int* t1,int* t2)
{
int tmp = *t1;
*t1 = *t2;
*t2 = tmp;
}
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n; j++)
{
for (int i = 1; i < n-j; i++)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
}
}
}
}
void ShellSort0(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int j = 0; j < gap; ++j)
{
for (int i = j; i < n - gap; i += gap)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
}
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while(begin < end)
{
int mini = begin, maxi = begin;
for (int i = begin + 1; i <= end; i++)
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
Swap(&a[begin], &a[mini]);
if (maxi == begin)
{
maxi = mini;
}
Swap(&a[end], &a[maxi]);
--end;
begin++;
}
}
void AdjustDown(int* a, int size, int parent)
{
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size && a[child + 1] > a[child])
{
++child;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
// 升序
void HeapSort(int* a, int n)
{
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;
}
}
int Getmid(int* a, int begin, int end)
{
int midi = (begin + end) / 2;
if (a[begin] > a[end])
{
if (a[end] > a[midi])
{
return end;
}
else if(a[begin]<a[midi])
{
return begin;
}
else
{
return midi;
}
}
else
{
if (a[begin] < a[midi])
{
return begin;
}
else if (a[end] > a[midi])
{
return end;
}
else
{
return midi;
}
}
}
Test.c
#include"Sort.h"
void TestInsertSort()
{
int a[] = { 3, 2, 6, 8, 4, 6, 0, 9, 5, 7, 1 };
InsertSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
//
void TestBubbleSort()
{
int a[] = { 3, 2, 6, 8, 4, 6, 0, 9, 5, 7, 1 };
BubbleSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestShellSort()
{
int a[] = { 3, 2, 6, 8, 4, 6, 0, 9, 5, 7, 1 };
ShellSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestSelectSort()
{
//int a[] = { 3, 2, 6, 8, 4, 6, 0, 9, 5, 7, 1 };
int a[] = { 13, 2, 6, 8, 4, 6, 0, 9, 5, 7, 1 };
SelectSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestHeapSort()
{
//int a[] = { 3, 2, 6, 8, 4, 6, 0, 9, 5, 7, 1 };
int a[] = { 13, 2, 6, 8, 4, 6, 0, 9, 5, 7, 1 };
HeapSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestOP()
{
srand(time(0));
const int N = 100000;
int* a1 = (int*)malloc(sizeof(int) * N);
int* a2 = (int*)malloc(sizeof(int) * N);
int* a3 = (int*)malloc(sizeof(int) * N);
int* a4 = (int*)malloc(sizeof(int) * N);
for (int i = 0; i < N; ++i)
{
a1[i] = rand();
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
}
int begin1 = clock();
InsertSort(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort(a4, N);
int end4 = clock();
printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
free(a1);
free(a2);
free(a3);
free(a4);
}
int main()
{
TestInsertSort();
TestBubbleSort();
TestShellSort();
TestSelectSort();
TestHeapSort();
//TestOP();
return 0;
}