下篇:《高阶排序算法:分治思想与性能突破》

发布于:2025-04-17 ⋅ 阅读:(36) ⋅ 点赞:(0)

个人主页: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问题)。
- **确保基准值正确归位**(快排)。

下期预告;

额外篇  非递归之美:归并排序与快速排序的创新实现