在介绍排序算法之前我们需要了解一些基础算法
1.Swap函数的实现(实现元素的交换)
注意需要用&转换成地址作为传参进行传址调用,此处也是元素交换类算法常考的问题。如果直接进行值交换只是交换了形参的数值,形参存在于内存中的其他区域与实参无关,最终结果不会实现实参的交换。关于交换函数的方法可以参考下边的博客。
C/C++中关于交换(Swap)函数的三种方法_c++ swap_立志学好编程的小白的博客-CSDN博客
void Swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
下面开始介绍各类排序方式的实现方法
一、交换排序
1.冒泡排序【稳定】
冒泡排序是一种简单的排序算法。一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。例如升序排序:首位1号数据与2号数据比较,若1号数据比2号数据大则调换两数据位置,否则位置不变。再比较2号数据与3号数据的大小,若2号数据比3号数据大则调换两数据位置,否则位置不变。这个算法的名字由来是因为越小(大)的元素会经由交换慢慢“浮”到数列的顶端。
//冒泡排序
void BubbleSort(int* arr, int n)
{
//使用end记录交换次数
int end = n;
//使用flag作为标志位,当flag为0时代表再无元素交换
int flag = 0;
do
{
for (int i = 0; i < end - 1; ++i)
{
if (arr[i] > arr[i + 1])
{
//Swap实现了元素的交换,这里需要用&转换成地址作为传参。
Swap(&arr[i], &a[i+1]);
flag = 1;
}
}
--end;
}while(flag); //当flag为0时代表再无元素交换,退出循环
}
二、插入排序
1.简单插入【稳定】
思路:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。实际中我们玩扑克牌时,就用了插入排序的思想。
实现方法:若数组(arr)除最后一个元素外其余全部有序,设最后一个元素的下标为i,将arr[i]与前面的元素比较,前面的元素比他大则前面的元素向右移动,比他小则在该元素的后面插入。
void InsertSort(int n, int *a) {
int i, j;
for(i = 1; i < n; ++i) {
int x = a[i]; //a[i]前面的i-1个数都认为是排好序的,令x = a[i]
for(j = i-1; j >= 0; --j) { //逆序的枚举所有的已经排好序的数
if(x <= a[j]) { //若枚举到的a[j]比要插入的数x大,则当前数往后挪一位
a[j+1] = a[j];
}else
break;
}
a[j+1] = x; //将x插入到合适位置;
}
}
2.希尔排序(缩小增量法)【不稳定】
思路:先选定一个整数gap,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一组内的元素进行排序。然后将gap逐渐减小重复上述分组和排序的工作。当到达gap=1时,所有元素在统一组内排好序。
// 希尔排序
void ShellSort(int* a, int n)
{
assert(a);
int gap = n;
while (gap > 1)
{
//gap /= 2;
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int x = a[end + gap];
while (end >= 0)
{
if (a[end] > x)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = x;
}
}
}
三、选择排序
1.选择排序【不稳定】
是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
// 选择排序
void SelectSort(int* a, int n)
{
assert(a);
int begin = 0;//保存数组的起始位置
int end = n - 1;//保存换数组的末尾位置
while (begin < end)
{
int maxi = begin;//保存最大元素下标
int mini = begin;//保存最小元素下标
//遍历数组寻找最小和最大元素
for (int i = begin; 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);
//移动数组起始和末尾位置
begin++;
end--;
}
}
2.堆排序【不稳定】
四、快速排序【不稳定】
1.hoare版本(左右指针法)
思路:
1、选出一个key,一般是最左边或是最右边的。
2、定义一个begin和一个end,begin从左向右走,end从右向左走。(需要注意的是:若选择最左边的数据作为key,则需要end先走;若选择最右边的数据作为key,则需要bengin先走)。
3、在走的过程中,若end遇到小于key的数,则停下,begin开始走,直到begin遇到一个大于key的数时,将begin和right的内容交换,end再次开始走,如此进行下去,直到begin和end最终相遇,此时将相遇点的内容与key交换即可。(选取最左边的值作为key)
4.此时key的左边都是小于key的数,key的右边都是大于key的数
5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序
五、归并排序【稳定】
六、非比较排序
1.计数排序
2.桶排序
3.基数排序
七、排序的时间复杂度和稳定性
除了算法实现的基本原理,我们还应该记住各算法的时间复杂度以及稳定性,这也是我在面试的选择题中常见的一种类型。
稳定排序有:简单插入排序、冒泡排序、归并排序
不稳定排序:希尔排序、快速排序、选择排序、堆排序
口诀,不稳定的排序:快(快排)些(希尔)选(选择)一堆(堆排)