堆排序
所謂堆排序,就是將數組元素組成一個包含左右子節點的樹,與之前使用的結構(如下所示相同)
public class TreeNode
{
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int x) { val = x; }
}
但我們並不是生成一個實體的TreeNode,而是根據數組中元素的位置去判斷該元素是葉子節點,還是非葉子節點。
假如數組爲 [4,1,2,6,5,8,7] 對應數組下標分別爲**[0,1,2,3,4,5,6]**
我們先來找規律,假設我們的任意位置i,那麼如何找到i元素對應的左右節點的數組對應下標位置呢?
這裏注意,組成的樹我們是按照廣度遍歷去組成樹的。可以組成如下圖所示的樹形結構。
參考途中所示,你會發現一個規律,所有的一橫排的子樹數量,比他所有的父節點的數量和都多一。
第一排爲1個,第二排爲2個。 2-1
第二排爲2個+第一排爲1個,第三排爲4個。4-3
以此類推…
由上面的規律,我們可以算出,最大的非葉子節點爲arr.length/2-1;
還有一個規律,就是一個父節點下標爲i,那麼該元素左子節點爲arr[2i+1],右子節點爲 arr[2i+2]
之所以在這裏寫下這兩個規律,是因爲我們的堆排序中需要使用這兩個規律。爲了避免看到代碼不理解,先將規律寫在上方。
接下里就是代碼,我怕各位不好直接理解,代碼我會從最後被調用的代碼開始寫。
首先交換數組中的兩個元素的代碼
這個應該很好理解,交換數組中的兩個節點值。
/// <summary>
/// 交換數組中的兩個元素
/// </summary>
/// <param name="nums"></param>
/// <param name="a"></param>
/// <param name="b"></param>
public void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
大頂推的調整
不要考慮的太複雜,比方一個樹形結構,組成數組爲[2,3,1]。父節點爲2,左子節點爲3,右子節點爲1。我們需要做的就是找到這三個元素組成結構中最大的位置。顯然數組中的第二個元素最大。我們交換用前面的Swap方法去交換父節點和左節點值爲3的元素。就構成了一次大頂堆的調整。
這裏還要考慮一下,我們是從最後一個父節點向前調整的,當交換數組結構後,有可能會對我們之前已經調整的結構打亂。因此這裏加一個遞歸,判斷被移動到下面的節點和所有的子節點大小。
代碼如下
/// <summary>
/// 大頂推的調整 針對根節點以及根節點下所有被改變的節點
/// </summary>
/// <param name="nums">數組</param>
/// <param name="currentIndex">要調整的根節點位置</param>
/// <param name="Numslength">要調整的數組的長度</param>
public void heapify(int[] nums,int currentIndex,int Numslength)
{
//左節點位置
int left = currentIndex * 2 + 1;
//右節點位置
int right = currentIndex * 2 + 2;
//記錄父節點,左子節點,右子節點中最大的節點位置 我們默認先給父節點
int largePosition = currentIndex;
//判斷左節點和根節點
if (left<Numslength&&nums[left]>nums[largePosition])
{
largePosition = left;
}
//判斷右節點和根節點
if (right<Numslength&& nums[right] > nums[largePosition])
{
largePosition = right;
}
if (largePosition!=currentIndex)
{
Swap(ref nums[currentIndex],ref nums[largePosition]);
//我們是從最後一個父節點向前調整的,當交換數組結構後,有可能會對我們之前已經
//調整的結構打亂。因此這裏加一個遞歸,此時largePosition位置爲被移動的父節點。
//判斷父節點和所有的子節點大小
heapify(nums, largePosition, Numslength);
}
}
循環所有的非葉子節點 構建一個大頂堆
如何將一整棵樹形結構都構建成大頂堆呢,當然是遍歷所有的非葉子節點,非葉子節點的數量我們已經介紹完了nums.Length / 2 - 1。代碼如下
/// <summary>
/// 循環所有的非葉子節點 構建一個大頂堆
/// </summary>
/// <param name="nums"></param>
public void buildMaxHeap(int[] nums)
{
//最大的非葉子節點爲nums.Length / 2 - 1,我們從最後一個向前遍歷
for (int i = nums.Length / 2 - 1; i >= 0; i--)
{
heapify(nums,i,nums.Length); //調整大頂堆
}
}
堆排序
到了對獲取排序的時候了,當我們構建了大頂堆的時候,頂層元素nums[0]一定爲最大的,我們將最大元素與數組末尾依次交換,再調用heapify,此時我們需要排序的根節點是nums[0]的位置,因爲我們將子節點上移至最上面,但我們要調整的數組的長度,爲我們將數組長度減去最大元素移動末尾的次數。
/// <summary>
/// 堆排序
/// </summary>
/// <param name="nums"></param>
public void HeapSort(int[] nums)
{
//我們先創建一個大頂堆
buildMaxHeap(nums);
//堆頂元素nums[0]爲最大元素 ,所以我們將堆頂的元素與數組最後的元素交換
//調用heapify,對交換後的最頂層元素進行重新的排序
//但此時我們傳入的要調整的數組的長度就減去一位。
for (int i = nums.Length-1; i > 0; i--)
{
Swap(ref nums[0],ref nums[i]);
heapify(nums, 0, i);
}
}
堆排序整體代碼如下
/// <summary>
/// 堆排序
/// </summary>
/// <param name="nums"></param>
public void HeapSort(int[] nums)
{
//我們先創建一個大頂堆
buildMaxHeap(nums);
//堆頂元素nums[0]爲最大元素 ,所以我們將堆頂的元素與數組最後的元素交換
//調用heapify,對交換後的最頂層元素進行重新的排序
//但此時我們傳入的要調整的數組的長度就減去一位。
for (int i = nums.Length-1; i > 0; i--)
{
Swap(ref nums[0],ref nums[i]);
heapify(nums, 0, i);
}
}
/// <summary>
/// 循環所有的非葉子節點 構建一個大頂堆
/// </summary>
/// <param name="nums"></param>
public void buildMaxHeap(int[] nums)
{
//最大的非葉子節點爲nums.Length / 2 - 1,我們從最後一個向前遍歷
for (int i = nums.Length / 2 - 1; i >= 0; i--)
{
heapify(nums,i,nums.Length); //調整大頂堆
}
}
/// <summary>
/// 大頂推的調整 針對根節點以及根節點下所有被改變的節點
/// </summary>
/// <param name="nums">數組</param>
/// <param name="currentIndex">要調整的根節點位置</param>
/// <param name="Numslength">要調整的數組的長度</param>
public void heapify(int[] nums,int currentIndex,int Numslength)
{
//左節點位置
int left = currentIndex * 2 + 1;
//右節點位置
int right = currentIndex * 2 + 2;
//記錄父節點,左子節點,右子節點中最大的節點位置 我們默認先給父節點
int largePosition = currentIndex;
//判斷左節點和根節點
if (left<Numslength&&nums[left]>nums[largePosition])
{
largePosition = left;
}
//判斷右節點和根節點
if (right<Numslength&& nums[right] > nums[largePosition])
{
largePosition = right;
}
if (largePosition!=currentIndex)
{
Swap(ref nums[currentIndex],ref nums[largePosition]);
//我們是從最後一個父節點向前調整的,當交換數組結構後,有可能會對我們之前已經
//調整的結構打亂。因此這裏加一個遞歸,此時largePosition位置爲被移動的父節點。
//判斷父節點和所有的子節點大小
heapify(nums, largePosition, Numslength);
}
}
/// <summary>
/// 交換數組中的兩個元素
/// </summary>
/// <param name="nums"></param>
/// <param name="a"></param>
/// <param name="b"></param>
public void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}