🍁前言
本篇博客有两大部分:
①附上八个排序的详解版链接,方便大家跳转,查看各排序详细的算法思路和实现过程;
②八大排序的源码,复制后可直接进行排序(升序),为了汇总知识点和方便日后查阅。
目录
一、 排序详解链接
下面是各种排序的详解版,大家可以直接链接跳转。
源码在下半部分,可以配合目录进行浏览。
二、 源代码
1. 插入排序
插入排序算法实现:
- 设置初始值end为0,即从下标为0处开始,假设0处数据是已经排好序;用temp记录下end+1处的数据,进入循环。
- 在循环中,如果end处的值大于temp中的值,则将end+1处覆盖,即arr[end+1]=end;再将end -- ,使end向前移动。
- 如果end处的值 <= temp的值,则跳出循环,无需进行覆盖;直接break跳出循环。
- 跳出循环后,将temp的值赋给end+1,则是将值插入到数组中。
- 设置循环条件为 end >= 0,则 end 减到 -1 时就会跳出循环,表示着遍历完了整个数组。
//插入排序
void InsertSort(int* arr, int n)
{
for (int i = 1; i < n; i++)
{
int end = i;
int temp = arr[end];
while (end >= 1)
{
if (temp < arr[end - 1])
{
arr[end] = arr[end - 1];
}
else
{
break;
}
end--;
}
arr[end] = temp;
}
}
2. 希尔排序
希尔排序是插入排序的一种,它是针对直接插入排序算法的改进。
希尔排序又称缩小增量排序,因 DL.Shell 于 1959 年提出而得名。
它通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。
//希尔排序
void ShellSort(int* arr, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; ++i)
{
int end = i;
int temp = arr[end + gap];
while (end >= 0)
{
if (temp < arr[end])
{
arr[end + gap] = arr[end];
}
else
{
break;
}
end -= gap;
}
arr[end + gap] = temp;
}
}
}
3. 选择排序
选择排序算法原理如下:
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
void swap(int* r1, int* r2)
{
int temp = *r1;
*r1 = *r2;
*r2 = temp;
}
//选择排序
void SelectSort(int* a, int n)
{
for (int i = 0; i < n-1; i++)
{
int temp = i;
for (int j = i+1; j < n; j++)
{
if (a[j] < a[temp])
{
temp = j;
}
}
swap(&a[temp], &a[i]);
}
}
4. 堆排序
void swap(int* r1, int* r2)
{
int temp = *r1;
*r1 = *r2;
*r2 = temp;
}
//堆排序
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;
}
}
5. 冒泡排序
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
void swap(int* r1, int* r2)
{
int temp = *r1;
*r1 = *r2;
*r2 = temp;
}
//冒泡排序
void BubbleSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int exchange = 1;
for (int j = 0; j < n - 1 - i; j++)
{
if ( a[j + 1]< a[j])
{
swap(&a[j], &a[j + 1]);
exchange = 0;
}
}
if (exchange)
break;
}
}
6. 快速排序
快速排序原理如下:
从数列中挑出一个元素,称为 "基准"(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
三个版本都可以直接进行排序,并附上了三数取中的优化方式。
本博客没有快排和归并的非递归写法噢。
6.1 hoare 版本
void swap(int* r1, int* r2)
{
int temp = *r1;
*r1 = *r2;
*r2 = temp;
}
//三数取中
int GetMid(int* a, int begin, int end)
{
int mid = (begin + end) / 2;
if (a[begin] > a[end])
{
if (a[end] > a[mid])
return end;
else if (a[mid] > a[begin])
return begin;
else
return mid;
}
else
{
if (a[end] < a[mid])
return end;
else if (a[end] < a[begin])
return begin;
else
return mid;
}
}
//hoare 的方法
void QuickSort_hoare(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
int left = begin;
int right = end;
int keyi = left;
int mid = GetMid(a, begin, end);
swap(&a[mid], &a[keyi]);
while (left < right)
{
while (left < right && a[keyi] <= a[right])
{
right--;
}
while (left < right && a[left] <= a[keyi])
{
left++;
}
swap(&a[left], &a[right]);
}
swap(&(a[keyi]), &(a[right]));
keyi = right;
QuickSort_hoare(a, begin, keyi - 1);
QuickSort_hoare(a, keyi + 1, end);
}
6.2 挖坑法
void swap(int* r1, int* r2)
{
int temp = *r1;
*r1 = *r2;
*r2 = temp;
}
//三数取中
int GetMid(int* a, int begin, int end)
{
int mid = (begin + end) / 2;
if (a[begin] > a[end])
{
if (a[end] > a[mid])
return end;
else if (a[mid] > a[begin])
return begin;
else
return mid;
}
else
{
if (a[end] < a[mid])
return end;
else if (a[end] < a[begin])
return begin;
else
return mid;
}
}
//挖坑法
void QuickSort_dig(int* a, int begin, int end)
{
if (begin >= end)
return;
int key = a[begin];
int piti = begin;
int left = begin;
int right = end;
while (left < right)
{
while (left < right && a[right] >= key)
{
right--;
}
a[piti] = a[right];
piti = right;
while (left < right && a[left] <= key)
{
left++;
}
a[piti] = a[left];
piti = left;
}
a[piti] = key;
QuickSort_dig(a, begin, piti - 1);
QuickSort_dig(a, piti + 1, end);
}
6.3 前后指针法
void swap(int* r1, int* r2)
{
int temp = *r1;
*r1 = *r2;
*r2 = temp;
}
//三数取中
int GetMid(int* a, int begin, int end)
{
int mid = (begin + end) / 2;
if (a[begin] > a[end])
{
if (a[end] > a[mid])
return end;
else if (a[mid] > a[begin])
return begin;
else
return mid;
}
else
{
if (a[end] < a[mid])
return end;
else if (a[end] < a[begin])
return begin;
else
return mid;
}
}
//前后指针法
void QuickSort_Pointer1(int* a, int begin, int end)
{
if (begin >= end)
{
return;
}
int prev = begin;
int cur = begin + 1;
int keyi = begin;
int mid = GetMid(a, begin, end);
swap(&a[mid], &a[keyi]);
while (cur <= end)
{
if (a[cur] < a[keyi] && ((++prev) != cur))
{
swap(&a[cur], &a[prev]);
}
cur++;
}
swap(&a[prev], &a[keyi]);
keyi = prev;
QuickSort_Pointer1(a, begin, keyi - 1);
QuickSort_Pointer1(a, keyi + 1, end);
}
6.4 非递归版本
需要借助栈来实现,感兴趣可以点击链接去浏览。
int PartSort3(int * a, int begin, int end)
{
int prev = begin;
int cur = begin + 1;
int keyi = begin;
int mid = GetMid(a, begin, end);
swap(&a[mid], &a[keyi]);
while (cur <= end)
{
if (a[cur] < a[keyi] && ((++prev) != cur))
{
swap(&a[cur], &a[prev]);
}
cur++;
}
swap(&a[prev], &a[keyi]);
return prev;
}
void QuickSortNonR(int* a, int begin, int end)
{
ST st;
StackInit(&st);
StackPush(&st, end);
StackPush(&st, begin);
while (!StackEmpty(&st))
{
int left = StackTop(&st);
StackPop(&st);
int right = StackTop(&st);
StackPop(&st);
int keyi = PartSort3(a, left, right);
if (keyi + 1 < right)
{
StackPush(&st, right);
StackPush(&st, keyi + 1);
}
if (left < keyi - 1)
{
StackPush(&st, keyi - 1);
StackPush(&st, left);
}
}
StackDestory(&st);
}
7. 归并排序
归并排序算法原理如下:
- 把数组从中间划分成两个子数组。
- 一直递归地把子数组划分成更小的数组,直到子数组里面只有一个元素。
- 依次按照递归的返回顺序,不断合并排好序的子数组,直到最后把整个数组顺序排好。
7.1 递归版本
//归并排序
void _MergeSort(int* a, int begin, int end, int* temp)
{
if (begin >= end)
return;
int mid = (begin + end) / 2;
_MergeSort(a, begin, mid, temp);
_MergeSort(a, mid + 1, end, temp);
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
temp[i++] = a[begin1++];
}
else
{
temp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
temp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
temp[i++] = a[begin2++];
}
memcpy(a + begin, temp + begin, (end - begin + 1) * sizeof(int));
}
void MergeSort(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
_MergeSort(a, 0, n - 1, temp);
free(temp);
}
7.2 非递归版本
修改边界方法一:
void MergeSortNonR1(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
if (end1 >= n)
{
end1 = n - 1;
begin2 = n;
end2 = n - 1;
}
else if (begin2 >= n)
{
begin2 = n;
end2 = n - 1;
}
else if (end2 >= n)
{
end2 = n - 1;
}
int j = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
temp[j++] = a[begin1++];
}
else
{
temp[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
temp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
temp[j++] = a[begin2++];
}
}
memcpy(a, temp, n * sizeof(int));
gap *= 2;
}
}
修改边界方法二:
void MergeSortNonR2(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int) * n);
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
if (end1 >= n || begin2 >= n)
{
break;
}
else if (end2 >= n)
{
end2 = n - 1;
}
int m = end2 - begin1 + 1;
int j = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
temp[j++] = a[begin1++];
}
else
{
temp[j++] = a[begin2++];
}
}
while (begin1 <= end1)
{
temp[j++] = a[begin1++];
}
while (begin2 <= end2)
{
temp[j++] = a[begin2++];
}
memcpy(a + i, temp + i, m * sizeof(int));
}
gap *= 2;
}
free(temp);
}
8. 计数排序
计数排序算法原理如下:
- 计算出当前数据集合的范围区间range,即最大值 - 最小值。
- 开辟range大小的计数表(使用calloc),记录每个数据出现的次数。
- 统计每个数出现的次数,放到减去最小值后其在计数表中的相对位置。
- 回写数据。遍历计数表,如果表中位置不为零,则直接回写到元素组中,其值为下标 + 最小值,则可将相对位置转回为绝对位置。
void CountSort(int* a, int n)
{
int max = a[0];
int min = a[0];
for (int i = 1; i < n; i++)
{
if (max < a[i])
max = a[i];
if (min > a[i])
min = a[i];
}
int range = max - min+1;
int* temp = (int*)calloc(range, sizeof(int));
for (int i = 0; i < n; i++)
{
temp[a[i] - min]++;
}
int j = 0;
for (int i=0;i<range;i++)
{
while (temp[i]--)
{
a[j++] = i+min;
}
}
}