C#数据结构与算法学习笔记 堆排序

什么是优先队列和堆?



    class MaxHeap<E> where E : IComparable<E>
    {
        private E[] heap;
        private int N;

        public MaxHeap(int capacity)
        {
            heap = new E[capacity + 1];
            N = 0;
        }

        public MaxHeap() : this(10) { }

        public int Count { get { return N; } }

        public bool IsEmpty { get { return N == 0; } }
    }

元素上游

在11的位置添加68。虽然是一个完全二叉树,但是不满足最大堆的性质,因为68比它的父节点还要大,所以需要执行元素的上游操作,让68往上游,和它的父节点完成位置的交换。

    class MaxHeap<E> where E : IComparable<E>
    {
        private E[] heap;
        private int N;

        public MaxHeap(int capacity)
        {
            heap = new E[capacity + 1];
            N = 0;
        }

        public MaxHeap() : this(10) { }

        public int Count { get { return N; } }

        public bool IsEmpty { get { return N == 0; } }

        public void Insert(E e)
        {
            heap[N + 1] = e;
            N++;
            Swim(N);
        }

        private void Swim(int k)
        {
            while (k > 1 && heap[k].CompareTo(heap[k / 2]) > 0)
            {
                Swap(k, k / 2);
                k = k / 2;
            }
        }

        private void Swap(int i, int j)
        {
            E e = heap[i];
            heap[i] = heap[j];
            heap[j] = e;
        }
    }

元素下沉

目的:删除堆顶元素70。
将堆顶元素70和最后一个元素28交换位置。

交换完后将70删除,此时堆中只有元素28是不满足最大堆的性质,此时就要对28进行元素下沉操作。

选择大的元素进行位置的交换(将28和65进行位置进行交换)。

此时还是不满足最大堆的性质(28比40小),继续下沉,完成操作。

        //删除堆顶元素
        public E RemoveMax()
        {
            if (IsEmpty)
                throw new ArgumentException("堆为空!");

            Swap(1, N);
            E max = heap[N];
            heap[N] = default(E);
            N--;

            Sink(1);
            return max;
        }

        //查看最大值
        public E Max()
        {
            if (IsEmpty)
                throw new ArgumentException("堆为空!");

            return heap[1];
        }

        //元素下沉
        private void Sink(int k)
        {
            //只有当k这个位置至少还有一个左孩子的时候,才有交换的可能,N是我们最后元素的索引
            while (2 * k <= N)
            {
                int j = 2 * k; //j合适的交换位置,先看左孩子

                //如果有右孩子,并且右孩子比左孩子大,将j更新为右孩子
                if (j + 1 <= N && heap[j + 1].CompareTo(heap[j]) > 0) 
                	j++;

                //如果父亲比它们两个孩子都大,不需要交换,直接跳出while
                if (heap[k].CompareTo(heap[j]) >= 0) 
                	break;

                //和较大的孩子进行交换
                Swap(k, j);

                //交换完毕后,更新k的位置。接着判断比较,是否需要接着下沉
                k = j;
            }
        }

堆构造轨迹

扩容、缩容、调整数组容量的大小,输出的信息:

        //扩容
        public void Insert(E e)
        {
            if (N == (heap.Length - 1))
                ResetCapacity(heap.Length * 2);
			...
        }
        
        //缩容
        public E RemoveMax()
        {
        	...
            if (N == (heap.Length - 1) / 4)
                ResetCapacity(heap.Length / 2);
            ....
        }
        
        //调整数组容量的大小
        private void ResetCapacity(int newCapacity)
        {
            E[] newHeap = new E[newCapacity];
            for (int i = 1; i <= N; i++)
                newHeap[i] = heap[i];

            heap = newHeap;
        }
        
        //输出数组类的信息
        public override string ToString()
        {
            StringBuilder res = new StringBuilder();
            res.Append("[");
            for (int i = 1; i <= N; i++)
            {
                res.Append(heap[i]);
                if (i != N)
                    res.Append(", ");
            }
            res.Append("]");
            return res.ToString();
        }

测试:

    class Program
    {
        static void Main(string[] args)
        {
            /
            // 3      3          3              5               5            4     //
            //      /          /   \          /   \           /   \        /   \   //
            //     2          2     1        3     1         4     1      3     1  //
            //                              /               / \          /         // 
            //                             2               2   3        2          // 
            /
            
            MaxHeap<int> maxHeap = new MaxHeap<int>();
            int[] a = { 3, 2, 1, 5, 4 };
            for (int i = 0; i < a.Length; i++)
            {
                maxHeap.Insert(a[i]);
                Console.WriteLine(maxHeap);
            }

            maxHeap.RemoveMax();
            Console.WriteLine(maxHeap);

            Console.Read();
        }
    }

堆排序及优化

堆排序(HeapSort): 移除位在第一个数据的根节点,并做最大堆调整的递归运算。

    class HeapSort1
    {
        //时间复杂度:建堆O(NlogN)+ 出堆(NlogN)=O(2NlogN)
        //空间复杂度:O(N)
        public static void Sort(int[] arr)
        {
            int n = arr.Length;

            MaxHeap<int> maxHeap = new MaxHeap<int>(n);
            for (int i = 0; i < arr.Length; i++)
                maxHeap.Insert(arr[i]);

            for (int i = n-1; i >= 0; i--)
                arr[i] = maxHeap.RemoveMax();
        }
    }

原地堆排序:
从最后一个非叶子节点开始考察,(11-1-1)/ 2 = 4,也就是从35位置开始,一直考察到节点80。

35应该和它的右孩子65交换,交换完毕就满足了堆的性质(父节点比两个子节点都大)。

同理29和32交换,20和30交换,40和65交换。最后80已经比两个子节点都大了,所以80不用下沉。就这样将数组整理成了最大堆。

排序这个数组:首先排序最大值,把堆顶元素80放到最后一个位置,交换80和35。交换完毕后,80已经排好序,就不再需要考虑80这个位置了。

现在堆顶元素是35,比65小,不满足最大堆的性质,需要整理成最大堆。

让35下沉,和65交换,再和40交换。这样就又是最大堆了。

现在排序65,让65放着数组倒数第二个位置,和15交换位置,完成65的排序。

同样需要将元素15下沉,将它整理成最大堆。

然后再把40放到数组倒数第三个位置,就排序好了3个元素。按照这个思路,每次排序完,将剩余的元素整理成一个最大堆,再进行下一次的排序。如此往复,直到完成这个数组的原地堆排序。

    class HeapSort2
    {
        public static void Sort(int[] arr)
        {
            int n = arr.Length;

            //先将原数组整理成最大堆
            for (int i = (n-1-1)/2; i >= 0; i--)
                Sink(arr,i,n-1);

            //原地的堆排序
            for (int i = n-1; i >= 0; i--)
            {
                Swap(arr, 0, i);
                Sink(arr, 0, i - 1);
            }
        }

        //元素下沉
        private static void Sink(int[] arr,int k,int N)
        {
            while (2 * k + 1<= N)
            {
                int j = 2 * k + 1;

                if (j + 1 <= N && arr[j + 1].CompareTo(arr[j]) > 0) j++;

                if (arr[k].CompareTo(arr[j]) >= 0) break;

                Swap(arr,k, j);

                k = j;
            }
        }

        //交换数组中i和j对应位置的元素
        private static void Swap(int[] arr,int i,int j)
        {
            int e = arr[i];
            arr[i] = arr[j];
            arr[j] = e;
        }
    }

测试:

    class Program
    {
        static void Main(string[] args)
        {
            int N = 1000000;

            Console.WriteLine("测试随机数组: ");
            int[] a = TestHelper.RandomArray(N, N);
            int[] b = TestHelper.CopyArray(a);
            int[] c = TestHelper.CopyArray(a);
            int[] d = TestHelper.CopyArray(a);
            TestHelper.TestSort("QuickSort3", a);
            TestHelper.TestSort("MergeSort2", b);
            TestHelper.TestSort("HeapSort1", c);
            TestHelper.TestSort("HeapSort2", d);

            Console.WriteLine();

            Console.WriteLine("测试近乎有序数组: ");
            a = TestHelper.NearlyOrderedArray(N, 100);
            b = TestHelper.CopyArray(a);
            c = TestHelper.CopyArray(a);
            d = TestHelper.CopyArray(a);
            TestHelper.TestSort("QuickSort3", a);
            TestHelper.TestSort("MergeSort2", b);
            TestHelper.TestSort("HeapSort1", c);
            TestHelper.TestSort("HeapSort2", d);
            Console.WriteLine();

            Console.WriteLine("测试大量重复元素数组: ");
            a = TestHelper.RandomArray(N, 10);
            b = TestHelper.CopyArray(a);
            c = TestHelper.CopyArray(a);
            d = TestHelper.CopyArray(a);
            TestHelper.TestSort("QuickSort3", a);
            TestHelper.TestSort("MergeSort2", b);
            TestHelper.TestSort("HeapSort1", c);
            TestHelper.TestSort("HeapSort2", d);

            Console.Read();
        }
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值