基本排序算法 之七 ——堆排序

 


基本排序算法总结与对比 之七  ——堆排序 


1、堆排序  递归版本

       首先放递归的版本,因为递归版本更容易理解过程。

       堆排序实际是把数列看成一颗完全二叉树,而不是真的去用 指针结构体 构造一颗二叉树。数列在堆中从上到下,从左至右依次排成一棵树。

       下面是递归版本的代码

//用来调整节点,保证节点大于左右孩子
template<typename T>
void adjustNode(T arr[], int node, int len) {	//node为节点的下标

	int lchNode = 1 + (node << 1);	//node节点 左孩子 的下标
	int rchNode = 2 + (node << 1);	//node节点 右孩子 的下标

	if (rchNode < len) {	//说明该节点一定存在右孩子
		if (arr[node] < arr[lchNode] || arr[node] < arr[rchNode])	//如果左右孩子有一个大于节点
		{
			if (arr[lchNode] > arr[rchNode]) {		//如果左孩子大于右孩子
				std::swap(arr[node], arr[lchNode]);	//交换左孩子 与 节点
				if (lchNode <= (len >> 1) - 1)		//如果这个左孩子还有孩子
					adjustNode(arr, lchNode, len);	//调整这个左孩子作为节点的分支
			}
			else {	    //如果右孩子大于等于左孩子,必须留一个等于的口子,要不然左右相等时无作为
				std::swap(arr[node], arr[rchNode]);	//交换右孩子 与 节点
				if (rchNode <= (len >> 1) - 1)		//如果这个右孩子还有孩子
					adjustNode(arr, rchNode, len);	//调整这个右孩子作为节点的分支
			}
		}
	}
	else {					//如果该节点只有左孩子
		if (arr[node] < arr[lchNode]) {				//如果左孩子大于右孩子
			std::swap(arr[node], arr[lchNode]);
			if(lchNode <= (len >> 1) - 1)
				adjustNode(arr, lchNode, len);
		}			
	}
}

//调整树中所有的节点
template<typename T>
void adjustHeep(T arr[], int len) {
    for (int node = (len >> 1) - 1; node >= 0; node--) {	// (len >> 1) - 1 为最后一个 有孩子的节点 的下标
        adjustNode(arr, node, len);			        //从最后一个节点,一直调整到堆顶
    }
}

template<typename T>
void heapSort(T arr[], int lo, int hi) {
    adjustHeep(arr + lo, hi - lo);			//首先从尾到头调整一遍
    while (hi - lo > 2) {					
        std::swap(arr[lo], arr[hi - 1]);	//每次将堆顶元素与最后元素交换
        adjustNode(arr + lo, 0, --hi - lo);	//重新调整堆顶节点
    }
    std::swap(arr[lo], arr[hi - 1]);		//最后一次调整了,还没有交换; 交换了就不需要再调整了,所以放到循环外
}

2、堆排序 迭代版本 

       上述递归版本的adjustNode()函数中逻辑判断虽然明显易读,但是判断多余冗余;另外,迭代版本 效率 一般低于迭代版本。下面给出迭代版本的代码。

//用来调整节点,保证节点大于左右孩子
template<typename T>
void adjustNode(T arr[], int node, int len) {	//node为节点的下标

    int lchNode = 1 + (node << 1);	//node节点 左孩子 的下标

    while (lchNode < len) {    //至少得有个左孩子 才能往下走吧
        if (lchNode != len - 1) {	//即该节点有 左右 孩子,只有左孩子则不能进来
            if (arr[lchNode + 1] > arr[lchNode]) lchNode++;    //如果右孩子大于左孩子,下标记录为右孩子
        }
        if (arr[lchNode] > arr[node]) {   //不管有没有右孩子,现在lchNode记录的是 最大的 或者 唯一的 孩子的下标,并且与节点node比较
            std::swap(arr[lchNode], arr[node]);
            node = lchNode;    //节点得改成刚刚交换完的孩子
            lchNode = 1 + (node << 1);    //同时更新左孩子下标
        }
        else break;    //没有交换了就跳出
    }
}

//调整树中所有的节点
template<typename T>
void adjustHeep(T arr[], int len) {
    for (int node = (len >> 1) - 1; node >= 0; node--) {	// (len >> 1) - 1 为最后一个 有孩子的节点 的下标
        adjustNode(arr, node, len);			        //从最后一个节点,一直调整到堆顶
    }
}

template<typename T>
void heapSort(T arr[], int lo, int hi) {
    adjustHeep(arr + lo, hi - lo);			//首先从尾到头调整一遍
    while (hi - lo > 2) {					
        std::swap(arr[lo], arr[hi - 1]);	//每次将堆顶元素与最后元素交换
        adjustNode(arr + lo, 0, --hi - lo);	//重新调整堆顶节点
    }
    std::swap(arr[lo], arr[hi - 1]);		//最后一次调整了,还没有交换; 交换了就不需要再调整了,所以放到循环外
}

        堆排序的时间复杂度:初始的n/2次调整节点 + (n - 1)次 从堆顶开始的 从上往下的log2n次调整 = n/2 + (n-1)log2n = nlog2n;所以时间复杂度为O(nlogn)。显然,堆排序的常数时间较大,所以堆排序速度比快排要慢。另外,堆排序为不稳定的排序方法。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值