第六章堆排序之“保持堆的性质MAX-HEAPIFY”(递归版)

本文深入解析堆调整算法MaxHeapIfy,针对最大堆的优化过程,通过具体代码实例,详细阐述左右子堆的判断与交换逻辑,确保堆的有序性。了解MaxHeapIfy对于数据结构和算法学习者至关重要。


MAX-HEAPIFY用于最大堆的堆调整,i的左右子堆已经满足最大堆要求。

数组的第0个位置未用,这样计算左右子女的索引简单点。

void MaxHeapIfy(int *a,int i,int heapSize)
{
	int left=i<<1;//左子索引
	int right=i<<1+1;//(移位运算比算术运算优先级低,切记!)右子索引
	int tmp;
	int largest=i;
	
	if(left<=heapSize&&a[i]<a[left])
	{
		largest=left;
	}
	if(right<=heapSize&&a[largest]<a[right])
	{
		largest=right;
	}
	
	if(i!=largest)//不加这个判断可能会产生死循环,递归一个劲的在i这打转 
	{
		tmp=a[i];
		a[i]=a[largest];
		a[largest]=tmp;
		
		MaxHeapIfy(a,largest,heapSize);
	}
}


<think>我们被要求实现堆排序算法,并给出JavaScript代码。堆排序是一种基于二叉(通常使用最大或最小)的排序算法。其基本思想是: 1. 将待排序的序列构造成一个最大(或最小),此时整个序列的最大值(或最小值)就是顶的根节点。 2. 将根节点与末尾节点进行交换,此时末尾就为最大值(或最小值)。 3. 然后将剩余n-1个序列重新构造成一个,这样会得到n个元素中的次大值(或次小值)。如此反复执行,便能得到一个有序序列了。 在JavaScript中,我们通常使用数组来表示,因为是一个完全二叉树。对于数组中的第i个元素(索引从0开始): - 它的父节点索引为:`Math.floor((i-1)/2)` - 它的左子节点索引为:`2*i+1` - 它的右子节点索引为:`2*i+2` 堆排序的步骤: 1. 构建最大(Build Max Heap):将无序数组构建成最大。 2. 调整Heapify):从最后一个非叶子节点开始(即索引为`Math.floor(arr.length/2)-1`)向前调整,使得每个节点都满足最大性质(父节点大于等于子节点)。 3. 堆排序:将顶元素(最大值)与当前的最后一个元素交换,然后将的大小减一(即排除最后一个元素),再对顶元素进行向下调整(Heapify)以维持最大性质。重复此过程直到中只剩下一个元素。 下面我们根据引用中提供的代码示例,结合堆排序的标准实现,给出一个清晰、正确且高效的JavaScript堆排序实现。 注意:我们使用最大来实现升序排序。 代码实现: ```javascript function heapSort(arr) { // 1. 构建最大(从最后一个非叶子节点开始调整) let n = arr.length; // 最后一个非叶子节点的索引:Math.floor(n/2)-1 for (let i = Math.floor(n / 2) - 1; i >= 0; i--) { heapify(arr, n, i); } // 2. 堆排序:将顶元素(最大值)与当前的最后一个元素交换,然后调整 for (let i = n - 1; i > 0; i--) { // 将顶元素(最大值)与当前的最后一个元素交换 [arr[0], arr[i]] = [arr[i], arr[0]]; // 调整(此时的大小为i,即排除已经排序好的元素) heapify(arr, i, 0); } return arr; } // 调整,使其满足最大性质 // n: 的大小(即数组中需要调整的部分长度) // i: 当前需要调整的节点索引 function heapify(arr, n, i) { let largest = i; // 初始化当前节点为最大值 let left = 2 * i + 1; // 左子节点 let right = 2 * i + 2; // 右子节点 // 如果左子节点存在且大于当前节点,则更新最大值索引 if (left < n && arr[left] > arr[largest]) { largest = left; } // 如果右子节点存在且大于当前最大值节点,则更新最大值索引 if (right < n && arr[right] > arr[largest]) { largest = right; } // 如果最大值不是当前节点,则交换并递归调整 if (largest !== i) { [arr[i], arr[largest]] = [arr[largest], arr[i]]; // 递归调整受影响的子树 heapify(arr, n, largest); } } // 测试 let arr = [12, 11, 13, 5, 6, 7]; console.log(heapSort(arr)); // 输出: [5, 6, 7, 11, 12, 13] ``` 代码说明: 1. `heapSort`函数:主函数,负责构建初始最大和进行堆排序- 第一步:从最后一个非叶子节点开始,向前遍历,对每个节点调用`heapify`函数,构建最大- 第二步:将顶元素(最大值)与当前的最后一个元素交换,然后将的大小减一(即排除最后一个元素),再对顶元素进行`heapify`调整。重复此过程直到中只剩下一个元素。 2. `heapify`函数:调整,使其满足最大性质。它接收三个参数:数组`arr`、的大小`n`、当前要调整的节点索引`i`。 - 首先,假设当前节点`i`是最大值,然后比较其左右子节点,如果子节点大于当前节点,则更新最大值索引。 - 如果最大值索引不是当前节点,则交换当前节点和最大值节点,并递归调整受影响的子树。 时间复杂度:构建的时间复杂度为$O(n)$,每次调整的时间复杂度为$O(\log n)$,总共进行n-1次调整,所以堆排序的时间复杂度为$O(n \log n)$。 空间复杂度:由于是原地排序,除了递归调用栈(可优化为迭代)外,不需要额外空间,所以空间复杂度为$O(1)$。但递归本的堆排序在最坏情况下递归深度为$O(\log n)$,所以递归栈空间为$O(\log n)$。但通常我们将其视为原地排序算法。 注意:上面的代码使用了递归,在实际应用中,如果数据量很大,递归可能导致栈溢出。我们可以将递归的`heapify`改为迭代本以避免递归深度过大。下面是迭代本的`heapify`: ```javascript function heapify(arr, n, i) { let largest = i; while (true) { let left = 2 * i + 1; let right = 2 * i + 2; if (left < n && arr[left] > arr[largest]) { largest = left; } if (right < n && arr[right] > arr[largest]) { largest = right; } if (largest === i) break; [arr[i], arr[largest]] = [arr[largest], arr[i]]; i = largest; // 继续调整受影响的子树 } } ``` 使用迭代本的`heapify`可以避免递归调用栈过深的问题,更加健壮。 因此,完整的堆排序代码(使用迭代调整)如下: ```javascript function heapSort(arr) { let n = arr.length; // 构建最大 for (let i = Math.floor(n / 2) - 1; i >= 0; i--) { heapify(arr, n, i); } // 堆排序 for (let i = n - 1; i > 0; i--) { [arr[0], arr[i]] = [arr[i], arr[0]]; heapify(arr, i, 0); } return arr; } function heapify(arr, n, i) { let largest = i; while (true) { let left = 2 * i + 1; let right = 2 * i + 2; if (left < n && arr[left] > arr[largest]) { largest = left; } if (right < n && arr[right] > arr[largest]) { largest = right; } if (largest === i) break; [arr[i], arr[largest]] = [arr[largest], arr[i]]; i = largest; } } ``` 这样,我们就实现了一个健壮的堆排序算法。 §§相关问题§§ 1. 堆排序的时间复杂度是多少?为什么是$O(n \log n)$? 2. 堆排序在什么情况下表现最好? 3. 堆排序和快速排序相比有什么优缺点? 4. 除了堆排序,还有哪些常见的排序算法?它们的时间复杂度和空间复杂度分别是多少?</think>### JavaScript 堆排序算法实现 堆排序是一种基于二叉(完全二叉树)的高效排序算法,通过构建最大/最小实现升序/降序排序。其核心思想是将数组视为完全二叉树,通过调整节点位置实现化。以下是完整的 JavaScript 实现: #### 算法步骤 1. **构建最大**:将无序数组转化为最大(父节点 ≥ 子节点) 2. **堆排序**: - 交换顶(最大值)与末尾元素 - 缩小范围并重新调整 - 重复直到大小为 1 #### JavaScript 代码实现 ```javascript function heapSort(arr) { // 构建最大(从最后一个非叶子节点开始) for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) { heapify(arr, arr.length, i); } // 堆排序:依次将顶元素移到末尾 for (let i = arr.length - 1; i > 0; i--) { [arr[0], arr[i]] = [arr[i], arr[0]]; // 交换顶与末尾元素 heapify(arr, i, 0); // 调整剩余 } return arr; } // 调整函数(最大) function heapify(arr, n, i) { let largest = i; // 初始化最大值为当前节点 const left = 2 * i + 1; // 左子节点索引 const right = 2 * i + 2; // 右子节点索引 // 比较左子节点 if (left < n && arr[left] > arr[largest]) { largest = left; } // 比较右子节点 if (right < n && arr[right] > arr[largest]) { largest = right; } // 若最大值不是当前节点,交换并递归调整 if (largest !== i) { [arr[i], arr[largest]] = [arr[largest], arr[i]]; heapify(arr, n, largest); // 递归调整子树 } } // 示例用法 const arr = [12, 11, 13, 5, 6, 7]; console.log(heapSort(arr)); // 输出: [5, 6, 7, 11, 12, 13] ``` #### 关键点解析 1. **构建起点**:从最后一个非叶子节点(索引 `Math.floor(n/2)-1`)开始调整,因为叶子节点无需调整[^1]。 2. **调整**:`heapify` 函数确保以 `i` 为根的子树满足最大性质,时间复杂度 $O(\log n)$。 3. **原地排序**:直接在原数组操作,空间复杂度 $O(1)$(递归栈空间不计入)。 4. **时间复杂度**: -阶段:$O(n)$ - 排序阶段:$O(n \log n)$ - 总复杂度:$O(n \log n)$ #### 算法特点 - **稳定性**:非稳定排序(交换可能破坏相同元素顺序) - **适用场景**:大数据量且对最坏时间复杂度有要求时(如实时系统) - **优势**:时间复杂度稳定为 $O(n \log n)$,无需额外空间 > 注:此实现使用递归调整,大数据量时可改用迭代避免栈溢出风险[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值