目录
一.排序
1.插入排序
插入排序基本思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列
实现过程单趟排序:将插入值与前一个数进行比较,如果大就直接插入到后面,如果小,将前一个数后移,继续与之前的比较,小的话插入空的位置,否则继续向前比较
我们可以将每一个排序分为单趟和整体排序,了解单趟排序,对于整体排序,我们可以将它看作将数据一个一个的向里面插入
cpp
void InsertSort(int* a, int n)
{
assert(a);
for (int i = 0; i < n - 1; ++i)
{
//单趟排序,[0,end]有序,将end + 1向其中插入
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.时间复杂度:O(N^2),顺序时最好,逆序时最坏
2.空间复杂度:O(1)
3.稳定性:稳定
4.当排序的数组越接近有序,算法的时间效率越高
2.希尔排序
我们将希尔排序分为预排序(使数组接近有序),然后直接插入排序
预排序:把间距为gap的值分为一组,进行插入排序
以gap为3为例的图
当gap的值越大时,前面大的值可以更快的到后面,后面小的值可以更快的到前面,但是gap越大,数组内数据越不接近有序,而当gap为1的,就相当于插入排序
cpp
// 希尔排序
void ShellSort(int* a, int n)
{
//gap>1相当于预排序,让数组接近有序
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;//+1保证了gap最后一次一定为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;
}
}
}
希尔排序特性总结:
1.希尔排序是对插入排序的优化
2.当gap>1时为预排序,是为了让数组更接近有序,gap==1时数组已经基本有序,插入时就会较快.对于整个过程可以起到优化
3.稳定性:不稳定
4.gap的时间复杂度不好计算,因为gap的值不同
在下图中我们可以看到对于一组数据插入排序和希尔排序的处理时间
我们可以看到希尔排序的处理速度相比于插入排序提升很大
3.选择排序
选择排序:我们在begin和end的区间内寻找到最小值和最大值的下标,将最小值和最大值找到分别置于首和尾后,++begin,--end,缩小区间,继续寻找,直到搜索完整个区间
选择排序特性总结:
1.直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
2.时间复杂度:O(N^2)
3.空间复杂度:O(1)
4.稳定性:不稳定
我们可以看到选择排序的处理效率与直接插入排序差不多
4.堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆
堆排序在前面已经讲解过,在此不多解释
cpp
//向下调整
//小堆向下调整前提:左右子树都为小堆
//大堆向下调整前提:左右子树都为大堆
void AdjustDown(int* a, int n, int root)
{
int parent = root;//将根节点位置给父节点
int child = 2 * parent + 1;//子节点下标为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;
}
}
}
//堆排序
//1.升序建大堆
//2.降序建小堆
void HeapSort(int* a, int n)
{
//建堆
//假设树有N个节点,树高度:logN.时间复杂度:O(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;
}
}
堆排序特性总结:
-
堆排序使用堆来选数,效率就高了很多
-
时间复杂度:O(N*logN)
-
空间复杂度:O(1)
-
稳定性:不稳定
5.冒泡排序
冒泡排序:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动,前后比较,大的就交换
cpp
// 冒泡排序
void BubbleSort(int* a, int n)
{
assert(a);
int end = n;
while (end > 0)
{
int exchange = 0;
for (int i = 1; i < end; ++i)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
exchange = 1;
}
}
//如果一趟冒泡中没有发生交换,则说明已经有序,不需要冒泡
if (exchange == 0)
{
break;
}
}
}
冒泡排序特性总结:
-
冒泡排序是一种非常容易理解的排序
-
时间复杂度:O(N^2)
-
空间复杂度:O(1)
-
稳定性:稳定
二.整体代码
1.Sort.h
cpp
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <time.h>
void PrintArray(int* a, int n);
void Swap(int* p1, int* p2);
void AdjustDown(int* a, int n, int root);
// 插入排序
void InsertSort(int* a, int n);
// 希尔排序
void ShellSort(int* a, int n);
// 选择排序
void SelectSort(int* a, int n);
// 堆排序
void AdjustDown(int* a, int n, int root);
void HeapSort(int* a, int n);
// 冒泡排序
void BubbleSort(int* a, int n);
2.Sort.c
cpp
#include "Sort.h"
void PrintArray(int* a, int n)
{
for (int i = 0; i < n; ++i)
{
printf("%d ", a[i]);
}
printf("\n");
}
// 插入排序
// 时间复杂度O(N^2),顺序有序时最好,逆序时最坏
//空间复杂度O(1)
void InsertSort(int* a, int n)
{
assert(a);
for (int i = 0; i < n - 1; ++i)
{
//单趟排序,[0,end]有序,将end + 1向其中插入
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;
}
}
// 希尔排序
void ShellSort(int* a, int n)
{
//gap>1相当于预排序,让数组接近有序
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;//+1保证了gap最后一次一定为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* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
// 选择排序
void SelectSort(int* a, int n)
{
assert(a);
int begin = 0, end = n - 1;
while (begin < end)
{
//在[bagin,end]之间找出最小数和最大数的下标
int mini, maxi;
mini = maxi = begin;
for (int i = begin + 1; i <= end; ++i)
{
if (a[i] > a[maxi])
{
maxi = i;
}
if (a[i] < a[mini])
{
mini = i;
}
}
Swap(&a[begin], &a[mini]);
//如果maxi和begin位置重叠,maxi需要修正
if (begin == maxi)
{
maxi = mini;
}
Swap(&a[end], &a[maxi]);
++begin;
--end;
}
}
//向下调整
//小堆向下调整前提:左右子树都为小堆
//大堆向下调整前提:左右子树都为大堆
void AdjustDown(int* a, int n, int root)
{
int parent = root;//将根节点位置给父节点
int child = 2 * parent + 1;//子节点下标为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;
}
}
}
//堆排序
//1.升序建大堆
//2.降序建小堆
void HeapSort(int* a, int n)
{
//建堆
//假设树有N个节点,树高度:logN.时间复杂度:O(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;
}
}
// 冒泡排序
void BubbleSort(int* a, int n)
{
assert(a);
int end = n;
while (end > 0)
{
int exchange = 0;
for (int i = 1; i < end; ++i)
{
if (a[i - 1] > a[i])
{
Swap(&a[i - 1], &a[i]);
exchange = 1;
}
}
//如果一趟冒泡中没有发生交换,则说明已经有序,不需要冒泡
if (exchange == 0)
{
break;
}
}
}
3.test.c
cpp
#include "Sort.h"
// 测试排序的性能对比
void TestOP()
{
srand(time(0));
const int N = 10000;
int* a1 = (int*)malloc(sizeof(int) * N);
int* a2 = (int*)malloc(sizeof(int) * N);
int* a3 = (int*)malloc(sizeof(int) * N);
int* a4 = (int*)malloc(sizeof(int) * N);
int* a5 = (int*)malloc(sizeof(int) * N);
for (int i = 0; i < N; ++i)
{
a1[i] = rand();
a2[i] = a1[i];
a3[i] = a1[i];
a4[i] = a1[i];
a5[i] = a1[i];
}
int begin1 = clock();
InsertSort(a1, N);
int end1 = clock();
int begin2 = clock();
ShellSort(a2, N);
int end2 = clock();
int begin3 = clock();
SelectSort(a3, N);
int end3 = clock();
int begin4 = clock();
HeapSort(a4, N);
int end4 = clock();
int begin5 = clock();
BubbleSort(a5, N);
int end5 = clock();
printf("InsertSort:%d\n", end1 - begin1);
printf("ShellSort:%d\n", end2 - begin2);
printf("SelectSort:%d\n", end3 - begin3);
printf("HeapSort:%d\n", end4 - begin4);
printf("BubbleSort:%d\n", end5 - begin5);
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
}
void TestInsertSort()
{
int a[] = { 3,2,4,6,7,9,11,1,0 };
PrintArray(a, sizeof(a) / sizeof(int));
InsertSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestShellSort()
{
int a[] = { 9,8,7,6,5,4,3,2,1};
PrintArray(a, sizeof(a) / sizeof(int));
ShellSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestSelectSort()
{
int a[] = {5,4,7,6,4,2,9,7,8 };
PrintArray(a, sizeof(a) / sizeof(int));
SelectSort (a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestHeapSort()
{
int a[] = { 1,8,4,7,6,3,9,12,6 };
PrintArray(a, sizeof(a) / sizeof(int));
HeapSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestBubbleSort()
{
int a[] = { 7,8,1,14,6,10,2,12,9 };
PrintArray(a, sizeof(a) / sizeof(int));
BubbleSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
int main()
{
TestInsertSort();
TestShellSort();
TestSelectSort();
TestHeapSort();
TestBubbleSort();
TestOP();
}