常见的排序算法:
1、插入排序
基本思想:直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
直接插入排序的特性总结:
- 元素集合越接近有序,直接插入排序算法的时间效率越高
- 时间复杂度:O(N^2)
- 空间复杂度:O(1),它是一种稳定的排序算法
- 稳定性:稳定
代码实现:
void Insertsort(int* a, int n) //插入排序
{
for (int i = 0; i < n; ++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;
}
}
}
2、希尔排序
基本思想:希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
希尔排序的特性总结:
- 希尔排序是对直接插入排序的优化。
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
- 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N1.3–N2)
- 稳定性:不稳定
代码实现:
void ShellSort(int* a,int n) //希尔排序
{
//单趟排序 间距为gap的插入排序
//当gap > 1时 预排序
//当gap = 1时 插入排序
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (size_t i = 0; i < n - gap; ++i)//同时对gap组进行排序
{
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;
}
}
}
}
3、选择排序
基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
直接选择排序的特性总结:
- 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:不稳定
代码实现:
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; i <= end; ++i)
{
if (a[i] > a[maxi])
{
maxi = i;
}
if (a[i] < a[mini])
{
mini = i;
}
}
swap(&a[begin], &a[mini]);
if (begin == maxi)
maxi = mini;
swap(&a[end], &a[maxi]);
--end;
++begin;
}
}
4、堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
堆排序的特性总结:
- 堆排序使用堆来选数,效率就高了很多。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(1)
- 稳定性:不稳定
代码实现
//堆排序
void AdjustDwon(int* a, int n, int root)
{
int parent = root;
int child = parent * 2 + 1;
while (child < n)
{
if (a[child + 1] > a[child] && child+1<n)
{
++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)
{
AdjustDwon(a, n, i);
}
int end = n - 1;
while (end > 0)
{
swap(&a[0], &a[end]);
AdjustDwon(a, end, 0);
--end;
}
}
5、交换排序–冒泡排序
基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
冒泡排序的特性总结:
- 冒泡排序是一种非常容易理解的排序
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:稳定
代码实现:
void BubbleSort(int* a, int n) //冒泡排序
{
int end = n;
while (end > 0)
{
int exchange = 0;
for (int i = 1; i < end; ++i)
{
if (a[i - 1]>a[i])
{
swap(&a[i - 1], &a[i]);
exchange = 1;
}
}
if (exchange == 0)
{
break;
}
--end;
}
}
6、交换排序–快速排序
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。将区间按照基准值划分为左右两半部分的常见方式有:
- hoare版本
- 挖坑法
- 前后指针版本
快速排序的特性总结:
- 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
- 时间复杂度:O(N*logN)
- 空间复杂度:O(logN)
- 稳定性:不稳定
代码实现:
//快速排序
//有序情况下最坏 O(N*N) 每次选的key都是中位数最好 O(N*logN)
//使用三数取中法优化,避免有序情况导致最坏
//小区间优化
//关键字法
//选第一个值为关键字,end先走
//选最后一个值为关键字,begin先走
int PartSrot1(int* a, int begin, int end)//单趟
{
int mid = GetMidIndex(a, begin, end);
swap(&a[mid], a[begin]);
int key = begin;//左边为key
while (begin < end)
{
//end先走,找小
while (begin < end && a[end] >= a[key])
{
--end;
}
//begin走,找大
while (begin < end && a[begin] <= a[key])
{
++begin;
}
swap(&a[begin], &a[end]);
}
swap(&a[key], &a[begin]);
return begin;
}
//int PartSort2(int* a, int begin, int end)
//{
// int mid = GetMidIndex(a, begin, end);
// swap(&a[mid], a[end]);
// int key = end;//右边为key
// while (begin < end)
// {
// //begin先走,找大
// while (begin < end && a[begin] <= a[key])
// {
// ++begin;
// }
// //end后走,找小
// while (begin < end && a[end] >= a[key])
// {
// --end;
// }
// swap(&a[begin], &a[end]);
//
// }
// swap(&a[key], &a[begin]);
// return begin;
//}
//挖坑法
int PartSrot3(int* a, int begin, int end)//单趟
{
int mid = GetMidIndex(a, begin, end);
swap(&a[mid], a[begin]);
int key = a[begin];
while (begin < end)
{
while (begin < end && a[end] >= key)
{
--end;
}
a[begin] = a[end];
while (begin < end && a[begin] <= key)
{
++begin;
}
a[end] = a[begin];
}
a[begin] = key;
return begin;
}
//前后指针版本
int PartSort4(int* a, int begin, int end)
{
int prev = begin - 1;
int cur = begin;
int key = end;
while (cur < end)
{
/*if (a[cur] < a[key])
{
++prev;
swap(&a[cur], &a[prev]);
++cur;
}
else
{
++cur;
}*/
if (a[cur] < a[key] && ++prev != cur)
swap(&a[cur], &a[prev]);
++cur;
}
swap(&a[++prev], &a[key]);
return prev;
}
int GetMidIndex(int* a, int begin, int end) //三数取中
{
//int mid = (begin+end)
int mid = begin + ((end - begin) >> 1);
if (a[begin] < a[mid])
{
if (a[mid] < a[end])
{
return mid;
}
else if (a[begin]>a[end])
{
return begin;
}
else
{
return end;
}
}
else //a[begin] > a[mid]
{
if (a[mid] > a[end])
{
return mid;
}
else if (a[begin] < a[end])
{
return begin;
}
else
{
return end;
}
}
}
void QuickSort(int*a, int left,int right)//递归
{
if (left >= right)
{
return;
}
if (right - left + 1 < 10) //小区间优化
{
Insertsort(a + left, right - left + 1);
}
else
{
int keyindex = PartSrot1(a, left, right);//keyindex--》key最后的位置
//[left,keyindex-1] keyindex [keyindex+1,right]
QuickSort(a, left, keyindex - 1);
QuickSort(a, keyindex + 1, right);
}
}
7、归并排序
基本思想:归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并排序的特性总结:
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 稳定性:稳定
代码实现:
//归并排序
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
void _MergeSort(int* a, int begin, int end, int* tmp)
{
int mid = (begin + end) / 2;
//对两段子区间[begin,mid] [mid+1,end]递归排序
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid + 1, end, tmp);
//归并
int begin1 = begin, begin2 = mid + 1;
int end1 = mid, end2 = end;
int index = begin;
}
排序算法复杂度及稳定性分析: