目录
介绍几种常见的排序算法思想,以及他们的实现代码。
什么是排序?
看到这个问题,很多读者一定会觉得很可笑,排序还不简单,就是将一串无序数列中的数据按照一定顺序进行排列呗。我们日常比较常见的就是排升序和排降序,不管哪种排序,笔者这里希望提醒大家一下,一定要牢牢记住排序的本质(这里以排升序为例):较小数一定在较大数的前面。不管是那种算法,本质上都遵循这种思想。好了,话不多说,咱们进入正题。
一、冒泡排序
算法思想:将数组中前一个数和后一个数进行比较,如果前一个数大于后一个数,那么交换这两个数的位置,直到将最大数交换到数组的最后一个位置。当数组本身就是有序数组或者在不断循环的过程中变为有序时,冒泡排序还会不断的重复,导致效率变低。因此可以引入一个flag,让flag初始为1,当出现交换的时候让flag值为0。对flag值进行判定,为1时说明数组不存在交换,跳出循环,提高了效率。
算法实现:
void Swap(int* left, int* right)
{
int tmp = *left;
*left = *right;
*right = tmp;
}
void Bubllue(int* a, int n)
{
//n个数,需要循环n-1次
for (int j = 0; j < n - 1; j++)
{
//判断是否出现了交换
int flag = 1;
//一趟冒泡排序
for (int i = 1; i < n - j; i++)
{
//前一个数比后一个数大,交换
if (a[i - 1] > a[i])
{
//当flag为0说明出现了交换,为1说明不存在交换,数组有序
flag = 0;
Swap(&a[i], &a[i - 1]);
}
}
if (flag)
return;
}
}
二、选择排序
算法思想:选择排序是遍历数组,找到数组中最小的数,将最小的数和数组首元素交换。从首元素的下一个位置开始,找数组中最小的数放在数组首元素。不断循环,直到整个数组有序。
算法实现:
void Swap(int* left, int* right)
{
int tmp = *left;
*left = *right;
*right = tmp;
}
void SelectSort(int* a, int n)
{
int begin = 0, 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[mini], &a[begin]);
//当数组首元素为最大值时,mini和begin中的数据已经交换。
if (maxi == begin)
{
maxi = mini;
}
Swap(&a[maxi], &a[end]);
begin++;
end--;
}
}
三、插入排序
算法思想:从数组首元素开始,不断构建有序数组。将有序数组下一个元素作为新的值,不断插入到有序数组中,直到数组有序。
算法实现:
void InsertSort(int* a, int n)
{
for (int end = 0; end < n - 1; end++)
{
//保存end下一位数
int tmp = a[end + 1];
while (end >= 0)
{
//如果tmp比end小,end往后挪动一位;反之跳出循环
if (tmp < a[end])
{
a[end + 1] = a[end--];
}
else
{
break;
}
}
//end指向的是空位前一位的数,所以要往后挪动一位
a[end + 1] = tmp;
}
}
四、希尔排序
算法思想:是插入算法的优化。将数组每隔gap个元素分为一组,在对每组的数据进行插入排序,当每组都完成一次插入排序,数组中较大数越靠后。提升了效率。
算法实现:
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
//gap越大,大的数需要移动次数越少,数组相对混乱;gap越小,大的数需要移动次数越多,数组相对有序。
gap = gap / 3 + 1;//gap的值会不断缩小,直到缩小到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 Swap(int* pchild, int* pparent)
{
int tmp = *pchild;
*pchild = *pparent;
*pparent = tmp;
}
//向下调整算法
void AdjustDwon(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)
{
//向下调整建堆,O(N)必须满足左半边是个堆,右半边也是个堆,直接传过去最后一个叶子结点的父节点
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
AdjustDwon(a, n, i);
for (int i = n - 1; i > 0; i--)
{
//最后一个叶子结点与根交换
Swap(&a[0], &a[i]);
AdjustDwon(a, --n, 0);
}
}
六、快速排序
算法思想:选取一个基准数key,遍历数组,将所有比key小的数放在key的左边,比key大的数都放在key的右边。在对[begin, key-1]区间和[key + 1, end]区间中选取新的key,不断递归直到所有相对较小数都在key左边,相对较大数都在key右边,也就实现了升序。
算法实现:
void Swap(int* left, int* right)
{
int tmp = *left;
*left = *right;
*right = tmp;
}
int PartSort1(int* a, int begin, int end)
{
//让key的值为数组首元素,先从后往前找比key小的数,在从前往后找比key大的数,再将相遇的位置的数和key交换可以保证key左边的数都比key小,右边的数都比key大
int keyi = begin;
while (begin < end)
{
//从右往左找,比下标为keyi的值小的数
while (begin < end && a[end] >= a[keyi])
{
end--;
}
//从左往右找,比小标为keyi的值大的数
while (begin < end && a[begin] <= a[keyi])
{
begin++;
}
//交换这两个数
Swap(&a[begin], &a[end]);
}
//交换相遇数和下标为keyi的数
Swap(&a[begin], &a[keyi]);
return begin;
}
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
int keyi = PartSort1(a, begin, end);
//递归[begin, keyi - 1]keyi[keyi + 1, right]
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi + 1, end);
}
七、归并排序
算法思想:将数组分成两段,让左半段、右半段实现有序,创建一块和原来数组等长的空间tmp。用两个指针分别指向左半段和右半段,将两个指针中较小数依次放入新的数组tmp中,当两个数组中都为空时,新数组tmp中就是有序数组,再将其拷贝回原数组。
算法实现:
void _MergeSort(int* a, int begin, int end, int* tmp)
{
//当数组区间不存在时,返回
if (begin >= end)
return;
//将数组从中间分成左右两个子数组
int mid = begin + (end - begin) / 2;
//两个数组的区间分别为[begin, mid][mid+1, end]
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid + 1, end, tmp);
//归并[begin, mid][mid+1, end]
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
//选两个数组中小的数,拷贝到新数组tmp中
if (a[begin1] < a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
//因为tmp里面的值要拷贝回a。begin2有剩余的时候,直接将tmp拷贝回去就可以。
//[0 1 2 3 4 5 6 ]
memcpy(a + begin, tmp + begin, sizeof(int) * (i - begin));
return;
}
void MergeSort(int* a, int n)
{
//大小为n的新空间tmp
int* tmp = (int*)malloc(sizeof(int) * n);
assert(tmp);
//[0, n-1]
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
tmp = NULL;
}