一、排序的概念
排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
应用:
淘宝的搜索栏,大学的排名等等。
二、常见排序算法的实现
1. 插入排序
基本思想:
直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
void InsertSort(int *a, int n)
{
//把tmp插入到数组的[0,end]有序区间中
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;
}
}
}
2.希尔排序
希尔排序法又称缩小增量法。
基本思想:
先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
增量越大,大的和小的数可以更快的挪到对应的方向取,增量越大,越不接近有序。
增量越小,大的和小的数可以更慢的挪到对应的方向去,增量越小,就越接近有序。
增量==1其实就是插入排序。
void shellsort(int* a, int n)
{
int gap;
int end=0;
int tmp = a[end + gap];
while (gap > 1)
{
gap = (gap / 3 + 1);
for (int i = 0; i < n - gap; ++i)
{
while (end >= 0)
{
if (tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
a[end + gap] = tmp;
}
}
}
}
3.选择排序
基本思想:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
直接选择排序:
在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素
直接选择排序的特性总结:
- 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:不稳定
void Swap(int *p1, int *p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void SelectSort(int *a, int n)
{
int left = 0, right = n - 1;
int minIndex = left, maxIndex = left;
for (int i = left; i <= right; i++)
{
if (a[i] < a[minIndex])
{
minIndex = i;
}
if (a[i>a[maxIndex]])
{
maxIndex = i;
}
}
}
4.堆排序
具体的请看上一篇链接: https://blog.csdn.net/weixin_53831496/article/details/119803381.
三、交换排序
基本思想:
所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
1.冒泡排序
void BubbleSort(int *a, int n)
{
for (int j = 1; j < n;j++)
{
for (int i = 1; i < n-j; ++i)
{
if (a[i - 1] < a[i])
{
Swap(a[i - 1], a[i]);
}
}
}
}
2.快速排序
基本思想:
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
单趟排序
选出一个pivot,一般是最左边的或者是最右边的pivot放到他正确的位置上去,左边的比pivot要小,右边的比pivot要大。
左右指针法
假设我们选6做pivot选左边的值做pivot,right先走,right找小,left找大,交换left和right位置的值直到相遇。
int PartSort(int *a, int left, int right)
{
int left = begin, right = end;
int keyi = left;
while (left < right)
{
//找小
while (left<right && a[right] >= a[keyi])
{
--right;
}
//找大
while (left<right && a[left] <= a[keyi])
{
++left;
}
Swap(&a[left], &a[right]);
}
int meeti = left;
Swap(&a[keyi], &a[left]);
}
挖坑法
先定一个数,把这个数定为空,然后right出发去找比坑小的数,将right所指向的值赋值给left所指向的值,然后left出发去找比基准数大的数,将left所指向的值赋给right所指向的值。
int hole(int* a, int left, int right)
{
int tmp = a[left];
while (left < right)
{
//找小
while (left < right && a[right] >= tmp)
right--;
//放到左边的坑位中,右边就形成新的坑
a[left] = a[right];
//找大
while (left < right && a[left] <= tmp)
left++;
//放到右边的坑位中,左边就形成新的坑
a[right] = a[left];
}
a[left] = tmp;
return left;
}
前后指针法
cur跟prev开始一前一后,cur去找比keyi位置小的值,找到小以后,++prev,再交换prev直到数组尾.
int partsort(int* a,int left,int right)
{
int keyi = left,prev = left,cur = left + 1;
for(cur = left + 1;cur <= right;cur++)
{
if(a[cur] < a[keyi])
{
++prev;
Swap(&a[cur],&a[prev]);
}
}
Swap(&a[prev],&a[keyi]);
return prev;
}
上面是单趟排序,下面使用递归和非递归算法进行实现。
递归方法
void QuickSort(int* a,int left,int right)
{
// 区间只有一个值或区间不存在
if(left >= right)
return;
int keyi = partsort(a,left,right);
// 递归左区间
QuickSort(a,left,keyi - 1);
// 递归右区间
QuickSort(a,keyi + 1,right);
}
非递归方法
void QuickNonR(int* a,int begin,int end)
{
Stack st;
StackInit(&st);
// 将初始的begin和end入栈
StackPush(&st, begin);
StackPush(&st, end);
while (!StackEmpty(&st))
{
// 得到区间的右端点
int right = StackTop(&st);
StackPop(&st);
// 得到区间的左端点
int left = StackTop(&st);
StackPop(&st);
// 对该段区间进行单趟快排
int keyi = partsort(a, left, right);
// 将左区间的左右端点入栈
if (left < keyi - 1)
{
StackPush(&st, left);
StackPush(&st,keyi - 1);
}
// 将右区间的左右端点入栈
if (right > keyi + 1)
{
StackPush(&st, keyi + 1);
StackPush(&st, right);
}
}
// 销毁栈
StackDestroy(&st);
}
3.归并排序
基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
void _MergeSort(int *a, int left, int right,int *tmp)
{
if (left >= right)
{
return;
}
int mid = (left + right) >> 1;
//[left,mid][mid+1,right]
_MergeSort(a, left, mid, tmp);
_MergeSort(a, mid + 1, right, tmp);
//两段有序子区间归并到tmp,并拷贝回去
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int i = 0;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
tmp[i++] = a[begin2++];
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
for (int j = left; j <= right; j++)
{
a[j] = tmp[j];
}
}
内排序:数据量少,可以放到内存中进行排序。
外排序:数据量大,内存中放不下,数据只能放到磁盘文件中,需要排序
4.计数排序
基本思想:
计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。
操作步骤:
- 统计相同元素出现次数
- 根据统计的结果将序列回收到原来的序列中
void CountSort(int* a,int n)
{
// 选择出最大值,最小值
int i = 0,min = a[0],max = a[0];
for(i = 0;i < n;i++)
{
if(a[i] > max)
max = a[i];
if(a[i] < min)
min = a[i];
}
int range = max - min + 1;
int* count = (int*)malloc(sizeof(int) * range);
memset(count,0,sizeof(int) * range);
for(i = 0;i < n;i++)
{
count[a[i] -min]++;
}
int j = 0;
for(i = 0;i < range;i++)
{
while(count[i]--)
{
a[j++] = i + min;
}
}
}
总结:计数排序在数据范围集中时,效率很高,但是适用范围及场景有限