排序算法是我们学习算法的基础,虽然在我们日常编程中用到的不多,可能我们在使用时直接就有sort函数帮助,但是我们也不应该懈怠排序算法的思想。下面我就简单分享一下我学到的八大排序算法的思想以及代码的实现。
八大排序算法
直接插入
1.直接插入排序:
大多数人在玩扑克时,都会使用到插入排序的思想。因此这个算法理解起来并不难。
排序思想:默认前n-1个数是有序数列,我们将第n个元素插入到正确的位置即可。由于我们不知道待排元素中哪个子序列是已有序的,所以我们默认第一个元素有序,然后依次将后面的每个元素经过比较插入正确的位置上,直至所有的元素都插入即可。
代码实现:
//插入排序
void InsertSort(int* a, int n)
{
int i = 0;
for (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;
//代码执行到此位置有两种情况:
//1.待插入元素找到应插入位置(break跳出循环到此)。
//2.待插入元素比当前有序序列中的所有元素都小(while循环结束后到此)。
}
}
2.希尔排序:
希尔对上述直接插入排序方法进行分析得出,若数组原本接近升序,那么插入排序的时间复杂度就可以达到O(N),于是他提出了“预排序”的思路。
排序思想:先将待排数据每隔gap值的元素分为一组进行排序(位置不变),再缩小gap值重复上步方法进行排序,直至gap为1时,排序完成。
注:一般情况下,取序列的一半作为增量,然后依次减半,直到增量为1(也可自己设置)。
代码实现:
//希尔排序
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 2;//gap折半
int i = 0;
//进行一趟排序
for (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;
}
}
}
选择排序
3.直接选择排序:
顾名思义,即先选择再排序。
排序思想:每次从待排数据中选择最小的数,若它不是第一个位置的值则与第一个位置的值进行交换,将剩下n-1个值继续先选择再排序操作,直到待排数据全部排完。
代码实现:
//选择排序(一次选一个数)
void SelectSort(int* a, int n)
{
int i = 0;
for (i = 0; i < n; i++)//i代表参与该趟选择排序的第一个元素的下标
{
int start = i;
int min = start;//记录最小元素的下标
while (start < n)
{
if (a[start] < a[min])
min = start;//最小值的下标更新
start++;
}
Swap(&a[i], &a[min]);//最小值与参与该趟选择排序的第一个元素交换位置
}
}
实际上还有更快的方法:我们用两个指针begin和end,分别从待排数据两边开始找到最大值和最小值,将最大值放在最后一个位置,将最小值放在第一个位置,再将剩下n-2个值继续操作,直到待排数据全部排完。
代码实现:
//选择排序(一次选两个数)
void SelectSort(int* a, int n)
{
int left = 0;//记录参与该趟选择排序的第一个元素的下标
int right = n - 1;//记录参与该趟选择排序的最后一个元素的下标
while (left < right)
{
int minIndex = left;//记录最小元素的下标
int maxIndex = left;//记录最大元素的下标
int i = 0;
//找出最大值及最小值的下标
for (i = left; i <= right; i++)
{
if (a[i] < a[minIndex])
minIndex = i;
if (a[i]>a[maxIndex])
maxIndex = i;
}
//将最大值和最小值放在序列开头和末尾
Swap(&a[minIndex], &a[left]);
if (left == maxIndex)
{
maxIndex = minIndex;//防止最大值位于序列开头,被最小值交换
}
Swap(&a[maxIndex], &a[right]);
left++;
right--;
}
}
4.堆排序:
将数组本身看作一个堆,且升序建大堆,降序建小堆。堆排序的难点主要在于堆的向下调整算法,若我们想要排升序,那么就要建大堆,即每个根节点的值都是最大的;若我们想要排降序,那么就要建小堆,即每个根节点的值都是最小的。
排序思想:大堆建好之后,将堆顶元素与堆最后一个元素交换,然后对根位置进行一次堆的向下调整,但是调整时被交换到最后的那个最大的数不参与向下调整。重新调整后,这时的堆又恢复大顶堆(将最大的元素排序到最后面的位置)。继续将剩下n-1个元素进行调整,直到堆中只有一个数据时便结束。此时便得到了一个升序数组。
代码实现:
//堆的向下调整算法
void AdjustDown(int* a, int n, int root)
{
int parent = root;
int child = 2 * parent + 1;//假设左孩子较大
while (child < n)
{
if (child + 1 < n&&a[child + 1] > a[child])//右孩子存在,并且比左孩子大
{
child++;//左右孩子的较大值
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = 2 * parent + 1;
}
else//已成堆
{
break;
}
}
}
交换思想
5.冒泡排序:
排序思想:将相邻的两个元素依次比较,较大的元素向下沉(较小的元素向上升),每次都会得到一个最大。最小的值,一共需要排n-1趟。
代码实现:
//冒泡排序
void BubbleSort(int* a, int n)
{
int end = 0;
for (end = n - 1; end >= 0; end--)
{
int exchange = 0;//记录该趟冒泡排序是否进行过交换
int i = 0;
for (i = 0; i < end; i++)
{
if (a[i]>a[i + 1])
{
Swap(&a[i], &a[i + 1]);
exchange = 1;
}
}
if (exchange == 0)//该趟冒泡排序没有进行过交换,已有序
break;
}
}
6.快速排序:
排序思想:任取待排序元素序列中的某元素作为基准值,按照该基准值将待排序列分为两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后左右序列重复该过程,直到所有元素都排列在相应位置上为止。
快速排序常见的方法有:直接快排、挖坑法、前后指针法
因快排内容较多,也比较重要,我在【 快速排序】文中有快排思想和代码的详解,这里就不赘述了。
代码实现:
有序归并
7.归并排序:
归并排序是采用分治法的一个非常典型的应用。
排序思想:将已有序的子序合并,从而得到完全有序的序列,即先使每个子序列有序,再使子序列段间有序,将两个有序表二路归并成一个有序表。
具体实现就是先将数组元素拆分,然后再依次有序的合并。下面这张图很形象~
代码实现-递归实现:
从排序思想看,归并排序很适合递归实现。我们需要先申请一个与待排序列大小相同的数组用于合并过程两个有序的子序列,合并完毕后再将数据拷贝回原数组。
//归并排序(子函数)
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left >= right)//归并结束条件:当只有一个数据或是序列不存在时,不需要再分解
{
return;
}
int mid = left + (right - left) / 2;//中间下标
_MergeSort(a, left, mid, tmp);//对左序列进行归并
_MergeSort(a, mid + 1, right, tmp);//对右序列进行归并
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
//将两段子区间进行归并,归并结果放在tmp中
int i = left;
while (begin1 <= end1&&begin2 <= end2)
{
//将较小的数据优先放入tmp
if (a[begin1] < a[begin2])
tmp[i++] = a[begin1++];
else
tmp[i++] = a[begin2++];
}
//当遍历完其中一个区间,将另一个区间剩余的数据直接放到tmp的后面
while (begin1 <= end1)
tmp[i++] = a[begin1++];
while (begin2 <= end2)
tmp[i++] = a[begin2++];
//归并完后,拷贝回原数组
int j = 0;
for (j = left; j <= right; j++)
a[j] = tmp[j];
}
//归并排序(主体函数)
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);//申请一个与原数组大小相同的空间
if (tmp == NULL)
{
printf("malloc fail\n");
exit(-1);
}
_MergeSort(a, 0, n - 1, tmp);//归并排序
free(tmp);//释放空间
}
代码实现-非递归实现:
这里非递归实现无需栈来辅助。我们只需要控制每次参与合并的元素个数即可,最终便能使序列变为有序,但是不能保证每次合并的小组都完整,所以我们要注意控制边界,小心越界访问。
//归并排序(子函数)
void _MergeSortNonR(int* a, int* tmp, int begin1, int end1, int begin2, int end2)
{
int j = begin1;
//将两段子区间进行归并,归并结果放在tmp中
int i = begin1;
while (begin1 <= end1&&begin2 <= end2)
{
//将较小的数据优先放入tmp
if (a[begin1] < a[begin2])
tmp[i++] = a[begin1++];
else
tmp[i++] = a[begin2++];
}
//当遍历完其中一个区间,将另一个区间剩余的数据直接放到tmp的后面
while (begin1 <= end1)
tmp[i++] = a[begin1++];
while (begin2 <= end2)
tmp[i++] = a[begin2++];
//归并完后,拷贝回原数组
for (; j <= end2; j++)
a[j] = tmp[j];
}
//归并排序(主体函数)
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);//申请一个与待排序列大小相同的空间,用于辅助合并序列
if (tmp == NULL)
{
printf("malloc fail\n");
exit(-1);
}
int gap = 1;//需合并的子序列中元素的个数
while (gap < n)
{
int i = 0;
for (i = 0; i < n; i += 2 * gap)
{
int begin1 = i, end1 = i + gap - 1;
int begin2 = i + gap, end2 = i + 2 * gap - 1;
if (begin2 >= n)//最后一组的第二个小区间不存在或是第一个小区间不够gap个,此时不需要对该小组进行合并
break;
if (end2 >= n)//最后一组的第二个小区间不够gap个,则第二个小区间的后界变为数组的后界
end2 = n - 1;
_MergeSortNonR(a, tmp, begin1, end1, begin2, end2);//合并两个有序序列
}
gap = 2 * gap;//下一趟需合并的子序列中元素的个数翻倍
}
free(tmp);//释放空间
}
统计计数
8.计数排序:
计数排序,又叫非比较排序。顾名思义,该算法不是通过比较数据的大小来进行排序的,而是通过计数后再展开。
注:这种排序一般用于重复元素较多时,数据范围较集中的元素进行排序;若数据太分散,则会造成空间浪费。并且计数排序只适用于整型排序,不适用与浮点型排序。
排序思想:统计数组中相同元素出现的次数,再根据统计的结果将序列回收到原来的序列中。
实现方式:用两个数组,一个存原数组一个存统计后数组。将统计后的数组排序后,再将原数组的元素按个数展开。
代码实现:
//计数排序
void CountSort(int* a, int n)
{
int min = a[0];//记录数组中的最小值
int max = a[0];//记录数组中的最大值
for (int i = 0; i < n; i++)
{
if (a[i] < min)
min = a[i];
if (a[i] > max)
max = a[i];
}
int range = max - min + 1;//min和max之间的自然数个数(包括min和max本身)
int* count = (int*)calloc(range, sizeof(int));//开辟可储存range个整型的内存空间,并将内存空间置0
if (count == NULL)
{
printf("malloc fail\n");
exit(-1);
}
//统计相同元素出现次数(相对映射)
for (int i = 0; i < n; i++)
{
count[a[i] - min]++;
}
int i = 0;
//根据统计结果将序列回收到原来的序列中
for (int j = 0; j < range; j++)
{
while (count[j]--)
{
a[i++] = j + min;
}
}
free(count);//释放空间
}
完整代码
sort.c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top;
int _capacity;
}Stack;
void StackInit(Stack* ps);
void StackDestory(Stack* ps);
void StackPush(Stack *ps, STDataType x);
void StackPop(Stack* ps);
STDataType StackTop(Stack* ps);
int StackEmpty(Stack* ps);
void StackInit(Stack* ps)
{
assert(ps);
ps->_a = NULL;
ps->_capacity = 0;
ps->_top = 0;
}
void StackDestory(Stack* ps)
{
assert(ps);
ps->_capacity = 0;
ps->_top = 0;
free(ps->_a);
ps->_a = NULL;
}
void StackPush(Stack *ps, STDataType x)
{
assert(ps);
if (ps->_top == ps->_capacity)
{
size_t newcapacity = (ps->_capacity == 0 ? 4 : ps->_capacity * 2);
ps->_a = (STDataType*)realloc(ps->_a, sizeof(STDataType)*newcapacity);
ps->_capacity = newcapacity;
}
ps->_a[ps->_top] = x;
ps->_top++;
}
void StackPop(Stack* ps)
{
assert(ps&&ps->_top>0);
ps->_top--;
}
STDataType StackTop(Stack* ps)
{
assert(ps&&ps->_top>0);
return ps->_a[ps->_top - 1];
}
int StackEmpty(Stack* ps)
{
assert(ps);
if (ps->_top == 0)
return 0;
else return 1;
}
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void sortPrint(int* a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
//插入排序
void InsertSort(int* a, int n)
{
for (int i = 0; i < n - 1; i++)
{
int end = i;
int tmp = a[end + 1];
while (end>=0)
{
if (a[end]>tmp)
{
a[end + 1] = a[end];
end -= 1;
}
else break;
}
a[end + 1] = tmp;
}
}
//希尔排序
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap - 1; i++)
{
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;
}
}
}
//选择排序
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while (begin<end)
{
int min = begin;
int max = end;
for (int i = begin; i <= end; i++)
{
if (a[min] >a[i])
min = i;
if (a[max]<a[i])
max = i;
}
Swap(&a[begin], &a[min]);
if (begin == max)
max = min;
Swap(&a[end], &a[max]);
begin++;
end--;
}
}
//向下调整算法--堆排序
void AdjustDown(int* a, int n, int parent)
{
int son = 2 * parent + 1;
while (son<n)
{
if (son + 1 < n&&a[son + 1] > a[son])
son++;
if (a[parent] < a[son])
{
Swap(&a[parent], &a[son]);
parent = son;
son = 2 * parent + 1;
}
else break;
}
}
void HeapSort(int* a, int n)
{
for (int i = (n - 2) / 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--;
}
}
//冒泡排序
void BubbleSort(int* a, int n)
{
int end = n - 1;
while (end > 0)
{
int exchange = 0;
for (int i = 1; i < n; i++)
{
if (a[i - 1]>a[i])
Swap(&a[i - 1], &a[i]);
exchange = 1;
}
if (exchange == 0)
{
break;
}
end--;
}
}
//直接快速排序
int PartSort1(int* a, int begin, int end)
{
int key = begin;
while (begin < end)
{
while (begin<end&&a[end] >= a[key])
end--;
while (begin<end&&a[begin] <= a[key])
begin++;
Swap(&a[end], &a[begin]);
}
Swap(&a[end], &a[key]);
return end;
}
//三数取中法
int GetMidIndex(int* a, int begin, int end)
{
int mid = begin + (end - begin)/2;
if (a[begin] < a[mid])
{
if (a[mid] < a[end]) return mid;
else if (a[begin]>a[end]) return begin;
else return end;
}
//a[begin]>a[mid]
else
{
if (a[mid]>a[end]) return mid;
else if (a[end] > a[begin]) return begin;
else return end;
}
}
int PartSort2(int* a, int begin, int end)
{
int key = begin;
int mid = GetMidIndex(a,begin,end);
Swap(&a[begin], &a[mid]);
while (begin < end)
{
while (begin<end&&a[end] >= a[key])
end--;
while (begin<end&&a[begin] <= a[key])
begin++;
Swap(&a[end], &a[begin]);
}
Swap(&a[end], &a[key]);
return end;
}
//挖坑法
int PartSort3(int* a, int begin, int end)
{
int k = a[begin];
while (begin < end)
{
while (begin<end&&a[end]>=k)
end--;
a[begin] = a[end];
while (begin < end&&a[begin] <= k)
begin++;
a[end] = a[begin];
}
a[end] = k;
return begin;
}
//前后指针法
int PartSort4(int* a, int begin,int end)
{
int cur = begin + 1;
int prev = begin;
while (cur<=end)
{
if (a[cur] < a[begin]&&prev++!=cur)
Swap(&a[cur], &a[prev]);
cur++;
}
Swap(&a[prev], &a[begin]);
return prev;
}
//快速排序
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
else
{
//int div = PartSort1(a, left, right);
//int div = PartSort2(a, left, right);
int div = PartSort3(a, left, right);
//int div = PartSort4(a, left, right);
QuickSort(a, left, div - 1);
QuickSort(a, div + 1, right);
}
}
void QuickSort1(int* a, int left, int right)
{
if (left - right < 1)
{
int div = PartSort3(a, left, right);
QuickSort(a, left, div - 1);
QuickSort(a, div + 1, right);
}
else
{
InsertSort(a + left, right - left + 1);
}
}
//非递归的快速排序
void QSort(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, right);
StackPush(&st, left);
while (StackEmpty(&st) != 0)
{
int begin = StackTop(&st);
StackPop(&st);
int end = StackTop(&st);
StackPop(&st);
int div = PartSort4(a, begin, end);
//划分区间
if (begin < (div-1))
{
StackPush(&st, div-1);
StackPush(&st, begin);
}
if ((div + 1) < end)
{
StackPush(&st, end);
StackPush(&st, div+1);
}
}
}
//归并排序
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end) return;
int mid = begin + ((end - begin) >> 1);
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid+1,end, tmp);
int b1 = begin, e1 = mid;
int b2 = mid + 1, e2 = end;
int index = begin;
while (b1<=e1&&b2<=e2)
{
if (a[b1] < a[b2]) tmp[index++] = a[b1++];
else tmp[index++] = a[b2++];
}
while (b1 <= e1)
tmp[index++] = a[b1++];
while (b2 <= e2)
tmp[index++] = a[b2++];
index = begin;
while (begin<=end)
a[begin++] = tmp[index++];
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
_MergeSort(a, 0, n - 1, tmp);
free(tmp);
}
//计数排序
void CountSort(int* a, int n)
{
int max = a[0], min = a[0];
for (int 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 (int i = 0; i < n; i++)
{
count[a[i] - min]++;
}
int j = 0;
for (int i = 0; i < range; i++)
{
while (count[i]--)
a[j++] = i + min;
}
}
void sortTest()
{
int a[] = {9,4,5, 3, 4, 5, 1, 2, 7, 6, 9, 8 };
int size = sizeof(a) / sizeof(a[0]);
printf("插入排序的结果是: ");
InsertSort(a, size);
sortPrint(a, size);
int b[] = { 9, 4, 5, 3, 4, 5, 1, 8 };
int size1 = sizeof(b) / sizeof(b[0]);
printf("希尔排序的结果是: ");
ShellSort(b, size1);
sortPrint(b, size1);
int c[] = { 1, 2, 5,6,9,3, 4 };
int size2 = sizeof(c) / sizeof(c[0]);
printf("选择排序的结果是: ");
SelectSort(c, size2);
sortPrint(c, size2);
int d[] = { 1, 2,3,7, 6, 9, 3, 4 };
int size3 = sizeof(c) / sizeof(c[0]);
printf("堆排序的结果是: ");
HeapSort(d, size3);
sortPrint(d , size3);
int e[] = { 6,5,7,9,2,1,0 };
int size4 = sizeof(e) / sizeof(e[0]);
printf("冒泡排序的结果是: ");
BubbleSort(e , size4);
sortPrint(e , size4);
int f[] = { 6, 5,3,5, 2, 1, 0 };
int size5 = sizeof(f) / sizeof(f[0]);
printf("快速排序的结果是: ");
QuickSort(f , 0, size5 - 1);
//QuickSort1(f, 0, size5 - 1);
//QSort(f, 0, size5 - 1);
sortPrint(f , size5);
int g[] = { 6, 5, 3, 5, 2, 1, 0 };
int size6 = sizeof(g) / sizeof(g[0]);
printf("计数排序的结果是: ");
CountSort(g, size6);
sortPrint(g, size6);
int h[] = { 6, 5, 3, 5, 2,7,8, 1, 0 };
int size7 = sizeof(h) / sizeof(h[0]);
printf("归并排序的结果是: ");
MergeSort(h, size7);
sortPrint(h, size7);
}
main.c
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdlib.h>
#include <stdio.h>
int main()
{
sortTest();
system("pause");
return 0;
}