排序算法之堆排序(C#)

堆排序

所謂堆排序,就是將數組元素組成一個包含左右子節點的樹,與之前使用的結構(如下所示相同)

	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;
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章