下篇:《高阶排序算法:分治思想与性能突破》
个人主页:strive-debug
1. 堆排序(Heap Sort)
- **核心思想**:利用 **大根堆(升序)** 或 **小根堆(降序)** 进行选择排序。
- **关键步骤**:
1. **建堆**(Heapify):从最后一个非叶子节点开始调整(`i=(n-2)/2`),使用 `AdjustDown`(下沉调整)。
2. **排序**:
- **交换**:将堆顶(最大值/最小值)与当前未排序部分的最后一个元素交换。
- **调整**:对剩余部分重新调整成堆结构。
- **时间复杂度**:
- **建堆**:`O(n)`(因为 `AdjustDown` 的时间复杂度是 `O(log n)`)。
- **排序**:`O(n log n)`(每次调整 `O(log n)`,共 `n`次)。
- **空间复杂度**:`O(1)`(原地排序)。
代码如下:
void HPAdjustDown(int* arr, int parent, int n)
{int child = parent * 2 + 1;while (child < n){//child+1是为了防止只有单个孩子的时候,就不需要比了if (child + 1 < n && arr[child] < arr[child + 1]){child++;}if (arr[child] > arr[parent]){Swap(&arr[child], &arr[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
//堆的排序
void HeapSort(int* arr, int n)
{//创建堆(向上)//for (int i = 0; i < n ; i++)//{// HPAdjustUp(arr, i);//}// 创建堆(向下)for (int i = (n - 1 - 1) / 2; i >= 0; i--){HPAdjustDown(arr, i, n);}//堆的排序int end = n - 1;while (end > 0){Swap(&arr[0], &arr[end]);HPAdjustDown(arr, 0, end);end--;}
}
---
2. TOP-K问题
- **核心思想**:
1. **建小根堆**(求前 K **最大**的元素)。
2. **遍历剩余数据**:
- **若当前数 > 堆顶**(最小值),则替换并调整。
- **最终保留的是最大的 K个数**。
- **时间复杂度**:
- `O(k)`(建小根堆)+ `O((n-k) log k)`(遍历调整)= `O(n log k)`。为了解决空间不够的问题:
代码如下:
void CreateNDate()
{// 造数据int n = 100000;srand(time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (int i = 0; i < n; ++i){int x = (rand() + i) % 1000000;fprintf(fin, "%d\n", x);}fclose(fin);
}
void topk()
{printf("请输⼊k:>");int k = 0;scanf("%d", &k);//从文件中读取const char* file = "data.txt";FILE* fout = fopen(file, "r");if (fout == NULL){perror("fopen error");return;}int val = 0;int* minheap = (int*)malloc(sizeof(int) * k);if (minheap == NULL){perror("malloc error");return;}//读取前k个数for (int i = 0; i < k; i++){fscanf(fout, "%d", &minheap[i]);}// 建k个数据的⼩堆for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(minheap, i, k);}int x = 0;while (fscanf(fout, "%d", &x) != EOF){// 读取剩余数据,⽐堆顶的值⼤,就替换他进堆if (x > minheap[0]){minheap[0] = x;AdjustDown(minheap, 0, k);}}for (int i = 0; i < k; i++){printf("%d ", minheap[i]);}fclose(fout);
}
---
3. 快速排序
快速排序是Hoare于1962年提出的⼀种⼆叉树结构的交换排序⽅法,其基本思想为:任取待排序元素 序列中的某元素作为基准值,按照该排序码将待排序集合分割成两⼦序列,左⼦序列中所有元素均⼩ 于基准值,右⼦序列中所有元素均⼤于基准值,然后最左右⼦序列重复该过程,直到所有元素都排列 在相应位置上为⽌。
快速排序实现主框架:
//快速排序
void QuickSort(int* a, int left, int right)
{
if (left >= right) {
return;
}
//_QuickSort⽤于按照基准值将区间[left,right)中的元素进⾏划分
int meet = _QuickSort(a, left, right);
QuickSort(a, left, meet - 1);
QuickSort(a, meet + 1, right);
}
hoare版本
算法思路 :
1)创建左右指针,确定基准值
2)从右向左找出⽐基准值⼩的数据,从左向右找出⽐基准值⼤的数据,左右指针数据交换,进⼊下次循环
代码如下:
int _QuickSort(int* arr, int left, int right)
{int keyi = left;left++;while (left < right){//找小while (left<=right && arr[right] > arr[keyi]){//要确保有意义,不要被开头的结束条件所迷惑//在符合开头条件进入之后,如果right一直--//或者left一直++呢,同样会发生left>right的情况//而且不会停止直到越界right--;}//找大while (left<=right && arr[left] < arr[keyi]){left++;}//同样,如果在循环内,left>right的话,把right和left互换的话就导致排序错误if (left <= right)// 比如3 1 2 4{Swap(&arr[right--], &arr[left++]);//不要忘记把right和left往前或往后++ --}}//最后不要忘记把keyi位置的给基本点,以为是一keyi来比较的Swap(&arr[keyi], &arr[right]);return right;
}//传过来的right是n-1,不是n,要想好,是下标
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}int keyi = _QuickSort(arr,left,right);//左区间[left,mid-1]QuickSort(arr, left, keyi - 1);//右区间[mid+1,right]QuickSort(arr, keyi + 1, right);
}//mid就是基本点
挖坑法
思路:
创建左右指针。⾸先从右向左找出⽐基准⼩的数据,找到后⽴即放⼊左边坑中,当前位置变为新的"坑",然后从左向右找出⽐基准⼤的数据,找到后⽴即放⼊右边坑中,当前位置变为新的"坑",结 束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标)
代码如下:
//填坑快排
int _QuickSort(int* arr, int left, int right)
{int hole = left;int keyi = arr[hole];while (left < right){//这里的left<right根据情况来写while (left < right && arr[right]>=keyi){right--;}arr[hole] = arr[right];hole = right;while (left < right && arr[left] <= keyi){left++;}arr[hole] = arr[left];hole = left;}arr[hole] = keyi;return hole;
}
//传过来的right是n-1,不是n,要想好,是下标
void QuickSort(int* arr, int left, int right)
{if (left >= right){return;}int keyi = _QuickSort(arr,left,right);//左区间[left,mid-1]QuickSort(arr, left, keyi - 1);//右区间[mid+1,right]QuickSort(arr, keyi + 1, right);
}//mid就是基本点
lomuto前后指针
创建前后指针,从左往右找⽐基准值⼩的进⾏交换,使得⼩的都排在基准值的左边。
代码如下:
int _QuickSort(int* arr, int left, int right)
{int prev = left, cur = left + 1;int key = left;while (cur <= right){//如果prev和cur位置相同就不需要进入,直接让cur++if (arr[cur] < arr[key] && ++prev != cur){Swap(&arr[cur], &arr[prev]);}++cur;}Swap(&arr[key], &arr[prev]);return prev;
}
---
**关键优化点**:
- **避免死循环和越界检查**(快排)。
- **减少不必要的调整次数**(TOP-K问题)。
- **确保基准值正确归位**(快排)。
下期预告;
额外篇 非递归之美:归并排序与快速排序的创新实现