既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
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(NlogN) 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;
}
}
### 交换排序
所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排 序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
#### 冒泡排序
重复地走访过要排序的元素列,依次比较两个相邻的[元素](https://gitee.com/vip204888),如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。
**动态演示:**
****
**代码实现:**
// 冒泡排序 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 . 冒泡排序是一种非常容易理解的排序
>
>
> 2. 时间复杂度: O(N^2)
>
>
> 3. 空间复杂度: O(1 )
>
>
> 4. 稳定性:稳定
>
>
>
#### **快速排序**
快速排序是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
}
}
#### 快速排序非递归实现
这里非递归就将递归转化成非递归,通过栈(先进后出)的特征实现递归。因为递归就是操作系统的[函数栈帧](https://gitee.com/vip204888),我们可以模拟栈帧,在堆上开辟空间(malloc),赋予[栈](https://gitee.com/vip204888)的功能。一个是操作系统的栈,一个是数据结构的栈。

首先我们知道,在实现非递归之前我会用到数据结构中的栈,我们通过运用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 . 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
>
>
> 2. 时间复杂度: O(N\*logN)
>
>
> 3. 空间复杂度: O(logN)
>
>
> 4. 稳定性:不稳定
>
>
>
### 归并排序
归并排序(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)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
>
>
> 2. 时间复杂度: O(N\*logN)
>
>
> 3. 空间复杂度: O(N)
>
>
> 4. 稳定性:稳定
>
>
>
### 排序算法复杂度及稳定性分析


#### 时间性能分析
**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(NlogN) 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) { // 三数取中 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;
}
// 快速排序前后指针法 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;
}
// 快速排序递归实现 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
}
}
// 快速排序 非递归实现 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)
{
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新