堆排序(C#)
逻辑结构:完全二叉树结构
实际结构实现:把数组从0出发连续一段可对应成完全二叉树
例如数组{0,1,2,3,4,5}
变成堆为
对于堆中一个节点的位置序号
大根堆:每一个节点为头的子树,子树上整体最大值为头节点
小根堆:每一个节点为头的子树,子树上整体最小值为头节点
堆的重要操作
【HeapInsert】
用户输入一组数字,怎样确保用户输入最后是一个大根堆
例:用户输入5,3,6,7…
思路:定义heapSize=0;定义一个数组arr[]存放大根堆
(1)输入5,heapSize++;
(2)输入3,heapSize++,让3与父节点【(i-1)/2】进行比较
若小于等于父节点,不变
若大于父节点,交换。
(3)输入6,heapSize++,让6父节点【(i-1)/2】进行比较
(4)输入7,heapSize++,让7父节点【(i-1)/2】进行比较,
若小于等于父节点,不变
若大于父节点,交换。然后再计算交换位置后是否有父节点,若有则继续比较,直到小于等于父节点或没有父节点为止。
代码实现:
public void HeapInsert(int[] arr)
{
int heapSize = 1;
int index = 1;
for (int i = 1; i < arr.Length; i++)
{
index = i;
while (arr[index] > arr[(index - 1) / 2])
{
Swap(arr,index,(index - 1) / 2);
index=(index - 1) / 2;
}
heapSize++;
}
}
private void Swap(int[] arr, int i, int j)
{
int index = arr[i];
arr[i] = arr[j];
arr[j] = index;
}
【Heapify】
用户输入结束(输入已经为大根堆),要求返回最大数并去掉,同时保证剩下的仍然是一个大根堆
(1)返回数组0位置上的数即可
(2)去掉最大数后,确保剩下的数仍然是大根堆做法
将已形成的堆结构最后一个数字放到0位置上
heapSize-1
从头节点开始,现在左右孩子中找最大值,再与头节点进行比较。
若大于头节点,交换头节点与最大值孩子的位置。交换后原头节点位置下若仍有子孩子,继续像之前一样比较,直到没有子孩子或大于子孩子。
若小于头节点,则结束。
代码实现:
public void Heapify(int[] arr)
{
Swap(arr,0,arr.Length-1);
int heapSize = arr.Length - 1;
int i = 0;
int max = 0;
int left = 2 * i + 1;
while (heapSize > left)//下方有孩子的时候
{
//left + 1 < heapSize 有右孩子
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;//如果有右孩子,先左右孩子比较,若没有largest为左孩子
largest = arr[largest] > arr[i] ? largest : i;//找到的最大的孩子与该结点相比较
if (largest == i)//如果最大的为该节点,停止比较
break;
Swap(arr,largest,i);
i = largest;//继续顺着改变的结点向下比较
left = 2 * i + 1;
}
}
private void Swap(int[] arr, int i, int j)
{
int index = arr[i];
arr[i] = arr[j];
arr[j] = index;
}
}
拓展:
对于一个堆区域为0~heapSize-1
用户要求改变i位置的值a,此时怎么保证改变后仍然是大根堆?
思路:判断改变后的a值时变大还是变小
若是变大,向上进行HeapInsert操作
若是变小,向下进行Heapify操作
堆排序:(将HeapInsert与Heapify结合操作)
时间复杂度O(N*logN),空间复杂度O(1)
思路:
(1)先将数组排成大根堆,纪录此时heapSize=数组长度
将数组排成大根堆的方法除了用HeapInsert,还可以利用Heapify来进行
for (int i = arr.Length - 1; i >= 0; i--)
{
Heapify(arr,i,arr.Length);
}
该方法是先从最后一个节点看,从下往上进行大根堆的制作。(确保每个子树是大根堆)
注:
利用该方法会比使用HeapInsert快一点点,但堆排序时间复杂度还是O(N*logN)
(2)将大根堆头节点与最后一个数交换(此时最后一个数位置排好),heapSize–;
(3)对除最后一个数进行Heapify,变成大根堆重复(2)(3)操作,知道heapSzie=0;
代码实现:
public void HeapSorts(int[] arr)
{
if (arr == null || arr.Length < 2)
return;
/*for (int i = 0; i < arr.Length; i++)//O(N)
{
HeapInsert(arr,i);//O(logN)
}*/
//还可以使用heapify来进行大根堆制作
for (int i = arr.Length - 1; i >= 0; i--)//O(N)
{
Heapify(arr,i,arr.Length);//O(logN)
}
int heapSize = arr.Length;
Swap(arr,0,--heapSize);
while (heapSize > 0)
{
Heapify(arr, 0,heapSize);
Swap(arr,0,--heapSize);
}
}
//先把数组搞成大根堆
public void HeapInsert(int[] arr,int index)
{
while (arr[index] > arr[(index - 1) / 2])
{
Swap(arr,index,(index-1)/2);
index = (index - 1) / 2;
}
}
public void Heapify(int[] arr, int index,int heapSize)
{
int left = 2 * index + 1;
while (heapSize > left) //还有孩子时
{
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index)
break;
Swap(arr,largest,index);
index = largest;
left = 2 * index + 1;
}
}
public void Swap(int[] arr, int i, int j)
{
int index = arr[i];
arr[i] = arr[j];
arr[j]=index;
}
}
堆排序例题:
已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过K,并且K相对于数组来说比较小。请选择合适排序算法对这个数据进行排序。
解题思路:使用堆排序
设K=4,数组arr={0,3 ,5, 7,1,2,4,5,7}
(1)准备一个小根堆,遍历数组,先将数组位置为0~4的5个数放到小根堆,排好。然后将小根堆的头节点放到数组的0位置。
(解释:因为由于每个数移动位置不超过K,所以对于数组中排序后位于第一个位置的数,它的活动返为应该就在0到4位置间,所以在未排序的数组中0~4位置一定有数组最小值[即位置在第0位置的数])
(2)将第5位放入小根堆,排好,将头节点放到数组的1位置。
(3)将第6位放入小根堆,排好,将头节点放到数组的2位置
…
直到将最后一个数纳入小根堆,然后依次把小根堆中最小值弹出,放到数组中即可。
代码实现:
public void Extern(int[] arr,int k)
{
int[] help = new int[k+1];
int index = 0;
int end = k;
//让help变成小根堆
for (int i = 0; i < k+1; i++)
{
help[i] = arr[i];
SmallHeapInsert(help,i);
}
while (end != arr.Length-1)
{
arr[index++] = help[0];
help[0] = arr[++end];//help后移,加入新数到头结点覆盖掉之前的最小值
SmallHeapify(help,0);
}
//将最后一个数纳入help中,编程小根堆后,对help中的位置1~k数字排序,然后一次放到arr空余的位置上
BubbleSort(help,1,k);
for (int w = 0; w < k + 1; w++)
{
arr[index++] = help[w];
}
Console.WriteLine(string.Join(",",arr));
}
public void SmallHeapInsert(int[] arr, int heapSize)
{
while (arr[heapSize] < arr[(heapSize - 1) / 2])
{
Swap(arr,heapSize,(heapSize - 1) / 2);
heapSize = (heapSize - 1) / 2;
}
}
public void SmallHeapify(int[] help, int i)
{
int left = 2 * i + 1;
while (left < help.Length)
{
int smallest = left + 1 < help.Length && help[left] < help[left + 1] ? left : left + 1;
smallest = help[smallest] < help[i] ? smallest : i;
if (smallest == i)
{
break;
}
Swap(help,i,smallest);
i = smallest;
left = 2 * i + 1;
}
}
public void BubbleSort(int[] help,int start,int end)
{
int length = end - start + 1;
for (int i = 0; i < length; i++)
{
for (int j = start; j < end; j++)
{
if(help[j]>help[j+1])
Swap(help,j,j+1);
}
}
}
public void Swap(int[] arr, int i, int j)
{
int index = arr[i];
arr[i] = arr[j];
arr[j] = index;
}
}