既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
动态演示:
代码实现:
// 冒泡排序
void BubbleSort(int* a, int n)
{
for (int i = 0; i < n; i++)
{
int flag = 0;
for (int j = 1; j < n-i; j++)
{
if (a[j - 1] > a[j])
Swap(&a[j -1], &a[j]);
flag = 1;
}
if (flag == 0)
break;
}
}
冒泡排序的特性总结:
1 . 冒泡排序是一种非常容易理解的排序
时间复杂度: O(N^2)
空间复杂度: O(1 )
稳定性:稳定
快速排序
快速排序是Hoare于1 962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
hoare版本
确定key值,right先向左找比key小的值,找到停止。然后left向右找比key大值,找到与right交换。再次循环相同操作,直到他们相遇,将相遇值与key值交换,返回相遇值。
动态演示:
代码实现:
// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int keyi = left;
while (left < right)
{
// R找小
while (left < right && a[right] >= a[keyi])
{
--right;
}
// L找大
while (left < right && a[left] <= a[keyi])
{
++left;
}
if (left < right)
Swap(&a[left], &a[right]);
}
int meeti = left;
Swap(&a[meeti], &a[keyi]);
return meeti;
}
挖坑法
设置一个坑位,将坑位的值赋值为key,right向左移找比key小值,找到将值放入坑位。left向右移找比key大值,找到将值放入坑位。相继操作到相遇为止,当相遇时key值放入相遇坑位,最后返回该值。
动态演示:
代码实现:
int PartSort2(int* a, int left, int right)
{
// 三数取中
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int key = a[left];
int hole = left; //坑位
while (left<right)
{
//右边先找小,填左坑位
while (left < right&&a[right] >= a[key])
{
right--;
}
a[hole] = a[right];
hole = right;
//左边找大,填右坑位
while (left < right&&a[left] <= a[key])
{
left++;
}
a[hole] = a[left];
hole = left;
}
a[hole] = a[key];
return hole;
}
前后指针
在开始之前,prev与key位置相同,cur则在prev前面。这里主要分为两种情况:1.当cur的值小于key和prev的值也小于key时,prev先向前移动,cur再向前移动。2.当cur的值大于key值时,cur继续向后移,直到cur的值小于key值为止,然后prev再向前移动,直到prev的值大于key时,他们交换。直到cur大于right时,prve的值与key的值交换,最后跳出循环。
动态演示:
代码实现:
// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{
// 三数取中
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int key = left;
int prev = left;
int cur = left + 1;
while (cur<=right)
{
//找小 prev先++ 相等跳出判断cur++,cur的值大于key值跳出判断cur++
if (a[cur] < a[key] && ++prev != cur)
Swap(&a[prev], &a[cur]);
cur++;
}
Swap(&a[prev], &a[key]);
return prev;
}
三个版本是都是单趟排序,我通过它的单趟排序观察出都是先确定key值,那么优选key的逻辑:1.随机选一个位置做key,2.针对有序,选正中间值做key,3.三数取中:通过比较第一个位置, 中间位置和最后一个位置,选取中间的位置即可。
为什么需要三数取中呢?因为当一组数据是接近逆序时,我们发现right找小会将一组数据全部找完。这里我们hoare方法来举例:
三数取中
代码实现:
//三数取中
int GetMidIndex(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] < a[mid])
{
if (a[right] > a[mid])
return mid;
else if (a[left] < a[right])
return left;
else
return right;
}
else
{
if (a[right]>a[left])
return left;
else if (a[right] < a[mid])
return mid;
else
return right;
}
}
快速排序递归实现
上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,我们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。
在实现递归的时候,我们发现最后三层递归,占了全部递归次数的85%左右,当递归在最后三层的时候已经非常接近有序了,所以我们选择用直接插入。
代码实现:
// 快速排序递归实现
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
else if (right - left <= 8) //2^3 倒数三层(每次递归/2,递归三次 ),递归次数占接近85%
{
InsertSort(a + left, right - left + 1); //直接插入
}
else
{
int key = PartSort1(a, left, right);//key的作用:划分范围
QuickSort(a, left, key - 1);//递归向左减少key
QuickSort(a, key + 1, right);//递归向右增加key
}
}
快速排序非递归实现
这里非递归就将递归转化成非递归,通过栈(先进后出)的特征实现递归。因为递归就是操作系统的函数栈帧,我们可以模拟栈帧,在堆上开辟空间(malloc),赋予栈的功能。一个是操作系统的栈,一个是数据结构的栈。
首先我们知道,在实现非递归之前我会用到数据结构中的栈,我们通过运用Stack.h,Stack.c构建的栈来实现快排的非递归。在这个过程中,我们需要联想到出栈入栈,寻找key值,始终记住这个过程只是通过栈将数据处理,排序的操作在单次排序进行。
代码实现:
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st,left);
StackPush(&st,right);
while (!StackEmpty(&st))
{
left = StackTop(&st);
StackPop(&st);
right = StackTop(&st);
StackPop(&st);
int key = PartSort1(a, left, right);
if (right > key+1)
{
StackPush(&st, key+1);
StackPush(&st, right);
}
if (left < key-1)
{
StackPush(&st, left);
StackPush(&st, key - 1);
}
}
StackDestroy(&st);
}
快速排序的特性总结:
1 . 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
时间复杂度: O(N*logN)
空间复杂度: O(logN)
稳定性:不稳定
归并排序
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
动态演示:
递归
代码实现:
void _MergeSort(int* a, int begin, int end, int* tmp)
{
if (begin >= end)
return;
int mid = (end + begin) / 2;
_MergeSort(a, begin, mid, tmp);
_MergeSort(a, mid + 1, end, tmp);
// 归并 取小的尾插
int begin1 = begin, end1 = mid;
int begin2 = mid + 1, end2 = end;
int i = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
// 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
memcpy(a + begin, tmp + begin, (end - begin + 1)*sizeof(int));
}
// 归并排序递归实现
void MergeSort(int* a, int n)
{
int* temp = (int*)malloc(sizeof(int)*n);
if (temp == NULL)
{
perror("malloc fail");
exit(-1);
}
_MergeSort(a, 0, n-1, temp);
free(temp);
temp = NULL;
}
动态演示:
非递归
代码实现:
// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int)*n);
if (tmp == NULL)
{
perror("malloc fail");
exit(-1);
}
int gap = 1;
while (gap < n)
{
for (int j = 0; j < n; j += 2 * gap)
{
int begin1 = j, end1 = j + gap - 1;
int begin2 = j + gap, end2 = j + 2 * gap - 1;
//第一组end1越界
if (end1>=n)
{
break;
}
//第二组全部越界
if (begin2 >= n)
{
break;
}
//第二组部分越界
if (end2 >= n)
{
end2 = n - 1;
}
int i = j;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] <= a[begin2])
{
tmp[i++] = a[begin1++];
}
else
{
tmp[i++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[i++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[i++] = a[begin2++];
}
// 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
memcpy(a + j, tmp + j, (end2 - j + 1)*sizeof(int));
}
gap *= 2;
}
free(tmp);
tmp = NULL;
}
归并排序的特性总结:
1 . 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
时间复杂度: O(N*logN)
空间复杂度: O(N)
稳定性:稳定
排序算法复杂度及稳定性分析
时间性能分析
Test.c文件
我们在测试文件中,我们可以改变N,用于改变数据的多少,数据量越大则时间越久,我们可以通过不同的数据来观察各个排序所需时间。也能更直观的观察到时间复杂度,
#define _CRT_SECURE_NO_WARNINGS
#include "Sort.h"
#include "Stack.h"
// 测试排序的性能对比
void TestOP()
{
srand(time(0));
const int N = 100000;
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);
int* a6 = (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];
a6[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();
QuickSort(a5, 0, N - 1);
int end5 = clock();
//int begin6 = clock();
//MergeSort(a6, N);
//int end6 = 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("QuickSort:%d\n", end5 - begin5);
//printf("MergeSort:%d\n", end6 - begin6);
free(a1);
free(a2);
free(a3);
free(a4);
free(a5);
//free(a6);
}
void TestInsertSort()
{
int a[] = { 34, 56, 25, 65, 86, 99, 72, 66 };
InsertSort(a, sizeof(a) / sizeof(int));
}
void TestShellSort()
{
//int a[] = { 9,8,7,6,5,4,3,2,1,0 };
int a[] = { 34, 56, 25, 65, 86, 99, 72, 66 };
ShellSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestSelectSort()
{
int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
SelectSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestHeapSort()
{
int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
HeapSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestBubbleSort()
{
int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
BubbleSort(a, sizeof(a) / sizeof(int));
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestQuickSort()
{
int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
QuickSort(a, 0, sizeof(a) / sizeof(int)-1);
PrintArray(a, sizeof(a) / sizeof(int));
}
void TestQuickSortNonR()
{
int a[] = { 100, 56, 25, 65, 86, 99, 72, 66 };
QuickSort(a, 0, sizeof(a) / sizeof(int)-1);
PrintArray(a, sizeof(a) / sizeof(int));
}
int main()
{
//TestOP();
//TestBubbleSort();
//TestQuickSort();
TestQuickSortNonR();
return 0;
}
Sort.c文件
#include "Sort.h"
#include "Stack.h"
//交换
void Swap(int *p1, int *p2)
{
int tem=*p1;
*p1 = *p2;
*p2 = tem;
}
void PrintArray(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 tem = a[end + 1];
while (end >= 0)
{
if (a[end] > tem)
{
a[end + 1] = a[end];
end--;
}
else
break;
}
a[end + 1] = tem;
}
}
希尔排序
//void ShellSort(int* a, int n)
//{
// int gap = n;
// while (gap>1)
// {
// gap = gap / 3 + 1;
// for (int i = 0; i < gap; i++)
// {
// for (int j = i; j< n-gap; j += gap)
// {
// int end = j;
// int tem = a[end + gap];
// while (end <= 0)
// {
// if (a[end] > tem)
// {
// a[end + gap] = a[end];
// end -= gap;
// }
// else
// break;
// }
// a[end + gap] = tem;
// }
// }
//
// }
//
//}
//
// 希尔排序(化简)
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 tem = a[end + gap];
while (end <= 0)
{
if (a[end] > tem)
{
a[end + gap] = a[end];
end -= gap;
}
else
break;
}
a[end + gap] = tem;
}
}
}
// 选择排序
void SelectSort(int* a, int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
int min = begin, max = begin;
for (int i = begin; i < end; i++)
{
if (a[min] < a[i])
min = i;
if (a[max]>a[i])
max = i;
}
Swap(&a[min], &a[begin]);
if (max == begin)
max = min;
Swap(&a[max], &a[end]);
begin++;
end--;
}
}
void AdjustDown(int* a, int n, int parent)
{
int minChild = parent * 2 + 1;
while (minChild < n)
{
// 找出小的那个孩子
if (minChild + 1 < n && a[minChild + 1] > a[minChild])
{
minChild++;
}
if (a[minChild] > a[parent])
{
Swap(&a[minChild], &a[parent]);
parent = minChild;
minChild = parent * 2 + 1;
}
else
{
break;
}
}
}
// O(N*logN)
void HeapSort(int* a, int n)
{
// 大思路:选择排序,依次选数,从后往前排
// 升序 -- 大堆
// 降序 -- 小堆
// 建堆 -- 向下调整建堆 - O(N)
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
// 选数 N*logN
int i = 1;
while (i < n)
{
Swap(&a[0], &a[n - i]);
AdjustDown(a, n - i, 0);
++i;
}
}
// 冒泡排序
void BubbleSort(int* a, int n)
{
for (int i = 0; i < n; i++)
{
int flag = 0;
for (int j = 1; j < n-i; j++)
{
if (a[j - 1] > a[j])
Swap(&a[j -1], &a[j]);
flag = 1;
}
if (flag == 0)
break;
}
}
//三数取中
int GetMidIndex(int* a, int left, int right)
{
int mid = (left + right) / 2;
if (a[left] < a[mid])
{
if (a[right] > a[mid])
return mid;
else if (a[left] < a[right])
return left;
else
return right;
}
else
{
if (a[right]>a[left])
return left;
else if (a[right] < a[mid])
return mid;
else
return right;
}
}
// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int keyi = left;
while (left < right)
{
// R找小
while (left < right && a[right] >= a[keyi])
{
--right;
}
// L找大
while (left < right && a[left] <= a[keyi])
{
++left;
}
if (left < right)
Swap(&a[left], &a[right]);
}
int meeti = left;
Swap(&a[meeti], &a[keyi]);
return meeti;
}
// 挖坑法
int PartSort2(int* a, int left, int right)
{
![img](https://img-blog.csdnimg.cn/img_convert/5c03c8d8968479b3bf74f5a6d5f4a28f.png)
![img](https://img-blog.csdnimg.cn/img_convert/6fbb25cd1fe694f9e14c3d18a8576e2c.png)
![img](https://img-blog.csdnimg.cn/img_convert/f1484ede13e11e31ac595599019f9957.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
lse if (a[right] < a[mid])
return mid;
else
return right;
}
}
// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{
int mid = GetMidIndex(a, left, right);
Swap(&a[left], &a[mid]);
int keyi = left;
while (left < right)
{
// R找小
while (left < right && a[right] >= a[keyi])
{
--right;
}
// L找大
while (left < right && a[left] <= a[keyi])
{
++left;
}
if (left < right)
Swap(&a[left], &a[right]);
}
int meeti = left;
Swap(&a[meeti], &a[keyi]);
return meeti;
}
// 挖坑法
int PartSort2(int* a, int left, int right)
{
[外链图片转存中...(img-qqLcWzY0-1715622959570)]
[外链图片转存中...(img-976qcKiE-1715622959570)]
[外链图片转存中...(img-weEVsMvq-1715622959570)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**