C语言数据结构与算法(排序)
大家好,欢迎来到“干货”小仓库!!很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同劳绩更好的自己!!无人扶我青云志,我自踏雪至山巅!!!
https://i-blog.csdnimg.cn/direct/6dc06468e9614fd18b1999ef86bbdf58.png
1.插入排序
1.1基本思想
直接插入排序是一种简朴的插入排序法,其基本思想是:把待排序的记载按其关键码值的巨细逐个插入到一个已经排好序的有序序列中,直到所有的记载插入完为止,得到一个新的有序序列 。
生存实例:我们玩扑克牌时,就用了插入排序思想。
https://i-blog.csdnimg.cn/direct/ecedc8610c7843bc9c03a949a76e8617.png
1.2直接插入排序
就是将一组已经有序的数组中插入一个新的数据,将其放在数组的精确位置,最终使数组变成有序。
单趟图例:
https://i-blog.csdnimg.cn/direct/493a2365271e41eb99869794a0e04052.png
代码实现及解析:
https://i-blog.csdnimg.cn/direct/8e3e01e5db9d4c05b668402adbe638e4.png
//插入排序
void InsertSort(int* a, int n)
{
for (int i = 1; i < n; i++)
{
int end=i-1;
int tmp=a;
while (end >= 0)
{
if (a > tmp)
{
a = a;
--end;
}
else
break;
}
a = tmp;
}
} 特性总结:
① 元素聚集越靠近有序,直接插入排序算法的时间效率越高
② 时间复杂度:O(N^2)
③ 空间复杂度:O(1),它是一种稳定的排序算法
④ 稳定性:稳定
2.希尔排序
2.1基本思想
希尔排序法又称缩小增量法。 基本思想: ①将数据分组(分的组越多,每组数据越少)
②对每组中的数据排好序
③再对整体数据分组,比上次分的组数要少,然后再对每组举行排序,依次循坏举行。
④直到数据分成一组,数据就全部有序了。
2.2实现
代码实现及图解:
https://i-blog.csdnimg.cn/direct/4916c0e2840c4e628f4f6de7525a6ec6.png
//希尔排序
void ShellSort(int* a, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int tmp = a;
while (end >= 0)
{
if (a > tmp)
{
a = a;
end -= gap;
}
else
break;
}
a = tmp;
}
}
} 特性总结:
①希尔排序是对直接插入排序的优化。
② 当gap > 1时都是预排序,目的是让数组更靠近于有序。当gap == 1时,数组已经靠近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以举行性能测试的对比。
③稳定性:不稳定。
3.选择排序
3.1基本思想
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
3.2实现
单趟原理:
①挑选出最大值和最小值
②将挑选出的最大值和最小值分别与末了一个数据和起始数据交换
代码实现及图解:
https://i-blog.csdnimg.cn/direct/c7f9dbfe52764727827bad9bb6f4ee57.png
//交换
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//选择排序
void SelectSort(int* a, int left, int right)
{
while (left < right)
{
int min=left, max=left;
for (int i = left+1; i <= right; i++)
{
if (a < a)
min=i;
if (a > a)
max=i;
}
Swap(&a, &a);
if (max == left)
max = min;
Swap(&a, &a);
++left;
--right;
}
} 特性总结:
①直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
②时间复杂度:O(N^2)
③ 空间复杂度:O(1)
④稳定性:不稳定
4.堆排序
4.1基本思想
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所计划的一种排序算法,它是选择排序的一种。它是通过堆来举行选择数据。须要留意的是排升序要建大堆,排降序建小堆。
4.2实现
代码实现及解析:
https://i-blog.csdnimg.cn/direct/bfe0d5e2390c44d283e1563891327018.png
//交换
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//向下调整
void AdjustDown(int* a, int n, int parent)
{
int child = parent *2 + 1;
while (child < n)
{
if (child+1<n && a < a)
++child;
if (a > a)
{
Swap(&a, &a);
parent = child;
child = parent * 2 + 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, &a);
AdjustDown(a, end, 0);
--end;
}
} 特性总结:
①堆排序使用堆来选数,效率就高了很多。(上一篇文章《C语言数据结构与算法(二叉树)》有讲TOPK问题)
②时间复杂度:O(N*logN)
③空间复杂度:O(1)
④稳定性:不稳定
5.冒泡排序
5.1基本思想
所谓交换,就是根据序列中两个记载键值的比力结果来对换这两个记载在序列中的位置,交换排序的特点是:将键值较大的记载向序列的尾部移动,键值较小的记载向序列的前部移动。
5.2实现
代码实现及图解:
https://i-blog.csdnimg.cn/direct/73de399f97f8433bbe7079d5cfac31cd.png
//交换
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//冒泡排序
void BubbleSort(int* a, int n)
{
for (int j = 0; j < n; j++)
{
for (int i = 1; i < n-j; i++)
{
if (a > a)
Swap(&a, &a);
}
}
} 特性总结:
①冒泡排序是一种非常轻易理解的排序
②时间复杂度:O(N^2)
③空间复杂度:O(1)
④稳定性:稳定
6.快速排序
基本思想:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序聚集分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都分列在相应位置上为止。
6.1hoare版本
①选出一个基准值,一样平常选最左边或者最右边的那个数据,也可以选中间的数据,然后交换到最左边或者最右边即可。
②左边做基准值,右边先开始移动,找到比基准值小就停下来,然后左边找比基准值大的。
③将左右两边找的值举行交换,然后继续移动右边,左边,直到左边大于或大于右边则停下来,将基准值和停下来的那个位置举行交换,到此单趟就完成了。
④利用递归继续实行上面步骤。
代码解析图解:
https://i-blog.csdnimg.cn/direct/30915f3b14ed462ba8e18772164634f9.png
//交换
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//horea版本
intPart1(int* a, int left, int right)
{
int keyi = left;
while (left < right)
{
while (left < right && a >= a)
--right;
while (left < right && a <= a)
++left;
Swap(&a, &a);
}
Swap(&a, &a);
keyi = left;
return keyi;
}
//快排
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int keyi = Part1(a, left, right);
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
6.2挖坑法
代码解析图解:
https://i-blog.csdnimg.cn/direct/f528e8a344fe4956be4a8fcdd7d54186.png
intPart2(int* a, int left, int right)
{
int key = a;
int hole = left;
while (left < right)
{
while (left < right && a >= key)
--right;
a = a;
hole = right;
while (left < right && a <= key)
++left;
a = a;
hole = left;
}
a = key;
return hole;
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int keyi = Part1(a, left, right);
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
6.3前后指针法
代码解析图解:
https://i-blog.csdnimg.cn/direct/5c20c3ce6e354002821522d1e5647e18.png
//交换
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//前后指针法
intPart3(int* a, int left, int right)
{
int keyi = left;
int prev = left;
int cur = left+1;
while (cur<=right)
{
if (a >= a)
++cur;
else
{
++prev;
if (prev != cur)
{
Swap(&a, &a);
++cur;
}
else
++cur;
}
}
Swap(&a, &a);
keyi = prev;
return keyi;
}
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
int keyi = Part1(a, left, right);
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
} 6.4快速排序优化
当上面的三种快速排序方法遇到靠近有序的数据的时间,效率会大大低沉,可做如下优化:
①三数取中(选基准值)。上面三种方法都可以加上三数取中进步代码效率。
②小区间优化(小区间用插入排序)。
代码解析及图解:
https://i-blog.csdnimg.cn/direct/b8a2ecd4590d4ba1aa18f1804cebac3a.png
//三数取中
int GetMidNum(int* a, int left, int right)
{
int mid = left + rand() % (right - left);
/*int mid = (left + right) / 2;*/
if (a > a)
{
if (a > a)
return left;
else if (a < a)
return right;
else
return mid;
}
else
{
if (a > a)
return right;
else if (a < a)
return left;
else
return mid;
}
}
//交换
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//前后指针法
intPart3(int* a, int left, int right)
{
int tmp = GetMidNum(a, left, right);
if (tmp != left)
Swap(&a, &a);
int keyi = left;
int prev = left;
int cur = left+1;
while (cur<=right)
{
if (a >= a)
++cur;
else
{
++prev;
if (prev != cur)
{
Swap(&a, &a);
++cur;
}
else
++cur;
}
}
Swap(&a, &a);
keyi = prev;
return keyi;
}
//快排
void QuickSort(int* a, int left, int right)
{
if (left >= right)
return;
//小区间优化--小区间直接使用插入排序
if ((right - left + 1) > 10)
{
//int keyi = Part1(a, left, right);//hoare版本
//int keyi = Part2(a, left, right);//挖坑法
int keyi = Part3(a, left, right); //前后指针法
// keyi
QuickSort(a, left, keyi - 1);
QuickSort(a, keyi + 1, right);
}
else
{
InsertSort(a + left, right - left + 1);//插入排序
}
} 6.5三路划分法
结合了三数取中、小区间优化。
特殊用途:用于解决大量数据类似的情况。
原理:
①比基准值小的数据往左边放。
②和基准值相称的数据往中间放。
③比基准值大的数据往右边放。
代码解析及图解:
https://i-blog.csdnimg.cn/direct/c4dffc6000e94c35b767b4adbd4329cb.png
voidQuickSortPart4(int* a, int left, int right)
{
if (left >= right)
return;
if ((right - left + 1) < 10)
{
InsertSort(a + left, right - left + 1);//插入排序
}
else
{
int begin = left;
int end = right;
int tmp = GetMidNum(a, left, right);
if (tmp != left)
Swap(&a, &a);
int keyi = left;
int cur = left + 1;
while (cur <= right)
{
if (a < a)
{
Swap(&a, &a);
++left;
++keyi;
}
else if (a > a)
{
Swap(&a, &a);
--right;
}
else
++cur;
}
QuickSortPart4(a, begin, left - 1);
QuickSortPart4(a, right + 1, end);
}
} 6.6总结
①快速排序整体的综合性能和使用场景都是比力好的,所以才敢叫快速排序
② 时间复杂度:O(N*logN)
③空间复杂度:O(logN)
④稳定性:不稳定
7.快速排序的非递归
递归的问题:
①效率。(影响不是很大)
②深度太深,会导致栈溢出。
递归改非递归有两种方式:
①直接改成循环。类似斐波那契等情况。
②使用栈辅助改循环
通过画出快排的递归展开图,可以看出,本质就是区间在不断的变革。
代码解析:
https://i-blog.csdnimg.cn/direct/9f126be3ce32438fb04afa3941bf7861.png
void QuickSortNonR(int* a, int left, int right)
{
//利用之前栈的实现接口函数
Stack st;
StackInit(&st);
StackPush(&st, right);
StackPush(&st, left);
while (!StackEmpty(&st))
{
int begin = StackTop(&st);
StackPop(&st);
int end = StackTop(&st);
StackPop(&st);
int keyi = Part3(a, begin, end);//前后指针法
// keyi
if (keyi + 1 < end)
{
StackPush(&st, end);
StackPush(&st, keyi + 1);
}
if (begin < keyi - 1)
{
StackPush(&st, keyi - 1);
StackPush(&st, begin);
}
}
StackDestroy(&st);
}
8.归并排序
8.1基本思想
归并排序( MERGE-SORT )是建立在归并操作上的一种有用的排序算法 , 该算法是采用分治法( Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤: https://i-blog.csdnimg.cn/direct/b12d7350776c4c8fa37ef18f8d9fb0ac.png
8.2实现
①归并排序先递归到区间只有一个数据的时间(分解),才开始举行往回排序(合并)。
②在合并的时间,须要改变数据的位置,而且又不能对其他数据造成影响,故须要别的的一个数组,临时存储排好序的数据,然后拷贝回原数组。
递归展开图:
https://i-blog.csdnimg.cn/direct/8172c220c3414f65aa2ee0e164f792e4.png
void _MergeSort(int* a, int left, int right, int* tmp)
{
if (left >= right)
return;
int mid = (left + right) / 2;
//
_MergeSort(a, left, mid,tmp);
_MergeSort(a, mid + 1, right, tmp);
int begin1 = left; int end1 = mid;
int begin2 = mid + 1; int end2 = right;
int i = left;
while (begin1 <= end1 && begin2 <= end2)
{
if (a <= a)
{
tmp = a;
}
else
tmp = a;
}
while (begin1 <= end1)
tmp = a;
while (begin2 <= end2)
tmp = a;
memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}
//归并排序
void MergeSort(int* a, int left, int right)
{
int* tmp = (int*)malloc(sizeof(int) * (right - left + 1));
if (tmp==NULL)
exit(2);
_MergeSort(a, left, right, tmp);
free(tmp);
} 归并排序的特性总结:
1. 归并的缺点在于须要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(N)
4. 稳定性:稳定
9.归并排序的非递归
归并排序的递归,本质上就是在改变要排序的数据的个数,可以直接改成循环。
图解:
https://i-blog.csdnimg.cn/direct/2b48e45179d5478cbf8699f544b38024.png
https://i-blog.csdnimg.cn/direct/99abad7818ff45f5945682cccac5fd77.png
//归并非递归
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * (n));
if (tmp == NULL)
exit(2);
int gap = 1;
while (gap < n)
{
for (int i = 0; i < n; i += 2 * gap)
{
int begin1 = i; int end1 = i + gap - 1;
int begin2 = i + gap; int end2 = i + 2 * gap - 1;
if (end1 >= n || begin2 >= n)
break;
else if (end2 >= n)
end2 = n - 1;
int j = i;
while (begin1 <= end1 && begin2 <= end2)
{
if (a <= a)
{
tmp = a;
}
else
tmp = a;
}
while (begin1 <= end1)
tmp = a;
while (begin2 <= end2)
tmp = a;
//归并一部分拷贝一部分(拷贝回原组)
memcpy(a+i, tmp+i, sizeof(int) * (end2-i+1));
}
gap *= 2;
}
} 10.计数排序
10.1基本思想
① 统计类似元素出现次数 ② 根据统计的结果将序列接纳到原来的 10.2实现
代码分析:
https://i-blog.csdnimg.cn/direct/ef81f36585824dc192cf65f3d11a22b3.png、
//计数排序
void CountSort(int* a, int n)
{
int min = a;
int max = a;
for (int i = 1; i < n; i++)
{
if (min > a)
min = a;
if (max < a)
max = a;
}
int range = max - min + 1;
int* CountA = (int*)malloc(sizeof(int) * range);//计数数组
if (CountA == NULL)
{
perror("malloc fail\n");
exit(2);
}
memset(CountA, 0, sizeof(int) * range);
for (int i = 0; i < n; i++)
{
CountA - min]++;
}
int j = 0;
for (int i = 0; i < range; i++)
{
while (CountA--)
{
a = i + min;
}
}
free(CountA);//释放
} 11.排序稳定性分析
稳定性 :假定在待排序的记载序列中,存在多个具有类似的关键字的记载,若经过排序,这些记载的相对序次保持稳定,即在原序列中,r=r ,且 r 在 r 之前,而在排序后的序列中, r 仍在 r 之前,则称这种排序算法是稳定的;否则称为不稳定的。 (简朴来说就是排好序后,类似数据的相对位置保持稳定则是稳定的,否则不稳定)。 排序总结: https://i-blog.csdnimg.cn/direct/6fe481e836b5494ba82887724e9b23e2.png 快乐的时光总是短暂,咱们下篇博客再见啦!!!觉得不错的,不要忘了给岑寂努力的自己点个赞和收藏咯,感谢支持,谢谢大家!!!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]