码迷,mamicode.com
首页 > 编程语言 > 详细

Chapter 6 堆排序

时间:2015-01-28 06:09:16      阅读:278      评论:0      收藏:0      [点我收藏+]

标签:

-------------------注明----------------

以下内容来自于《算法导论》           lz新手,存在各种错误以及各种不合理的地方望大家指出


 

堆排序时间复杂度为O(nlgn),并且就有空间原址性:任何时候都只需常数个额外的元素空间存储临时数据

6.1 堆

堆是一个数组,但可以被看做近似的完全二叉树,树上的每个结点对应数组中的一个元素。除最底层外,该树是完全充满的!

实例(原书上的图):技术分享

可分为最大堆和最小堆,最大堆性质:A[parent(i)]≥A[i]

---------练习--------

6.1-1 在高度为h的堆中,元素个数最多和最少分别是多少?(注:先说明高度这个概念,如图(a)高度就为3)

             max=2h+1-1            min=2h

6.1-2 证明含n个元素的堆的高度为小于等于lgn的整数

对于具有k层的完全二叉树有 1+2+22+...2k=(1-2k+1)/(1-2)=2k+1-1

从而易得含有n个元素的堆落在哪一层,即2k-1<n<2k+1-1

结合k要取整数,从而得证

----------------------

6.2 使堆“恢复”最大堆的性质(Maintaining the heap property)

为解决在后续操作中出现第i位置的子结点不满足最大堆性质(i的各子结点满足最大堆性质),建立"恢复"i为根时最大堆性质的函数(注:以下维护只能保证i和他的儿子满足最大堆性质,而不能保证他的儿子和他的儿子的儿子满足最大堆性质)

        static void MaxHeapify(List<int> array,int i,int length) //此处加个length是为了堆排序时取出最大下标
        {
            int largest = i;         
            int left = 2 * i + 1, right = 2 * i + 2;
            if (left < length && array[left] > array[i]) //找出i,2i+1,2i+2中最大那个值的下标
                largest = left;
            if (right < length && array[right] > array[largest])
                largest = right;
            if(largest!=i)
            {
                int temp = array[i];       //将最大值放入i中
                array[i] = array[largest];
                array[largest] = temp;
                MaxHeapify(array, largest,length);   //将被改动那个下标再次递归进行维护
            }
        }

------练习------

6.2-2 维护相应最小堆的Code:

技术分享
        static void MinHeapify(List<int> array, int i)
        {
            int min = i, length = array.Count;
            int left = 2 * i + 1, right = 2 * i + 2;
            if (left < length && array[left] < array[i]) //找出i,2i+1,2i+2中最小那个值的下标
                min = left;
            if (right < length && array[right] < array[min])
                min = right;
            if (min != i)
            {
                int temp = array[i];       //将最小值放入i中
                array[i] = array[min];
                array[min] = temp;
                MinHeapify(array, min);   //将被改动那个下标再次递归进行维护
            }
        }
View Code

6.2-5 使用循环控制结构取代递归,重写MaxHeapify,Code如下:

技术分享
        static void MaxHeapify2(List<int> array,int i)
        {
            int largest = i, left = 0, right = 0;
            while(true)      //用来取代递归
            {
                left = 2 * i + 1; right = 2 * i + 2;
                if (left < array.Count && array[left] > array[i])
                    largest = left;
                if (right < array.Count && array[right] > array[largest])
                    largest = right;
                if (i != largest)
                {
                    int temp = array[i];
                    array[i] = array[largest];
                    array[largest] = temp;
                    i = largest;
                }
                else
                    break;
            }
        }
View Code

-----------------

6.3 建堆(Building a heap)

为了将整个"杂乱无章"的数组转换为具有最大堆的性质,我们需要对每个含有子结点的结点进行"MaxHeapify",并且需要从底往上(即从(n-1)/2 to 0)进行

        static void BuildMaxHeap(List<int> array)
        {
            for (int i = (array.Count - 2) / 2; i >= 0; i--) //此处i从(n-2)/2开始,因为没有子节点的节点无需进行"维护"
                MaxHeapify(array, i,array.Count);
        }

6.4 堆排序算法

根据最大堆的特点,根结点的值最大,因此可以采取每次将根结点取出(用数组末尾的数顶上),取出并替换后的堆除根结点外均满足最大堆性质,因此满足Maxheapify的要求!---最大堆化一次又恢复到最大堆性质,从而不断进行便ok

        static void HeapSort(List<int> array)
        {
            BuildMaxHeap(array);
            for(int i=array.Count-1;i>0;i--) //每次将堆得根与最大下标的值互换,并将最大下标"剔除"
            {
                int temp = array[i];
                array[i] = array[0];
                array[0] = temp;
                MaxHeapify(array, 0,i);
            }
        } 

6.5 优先队列

应用堆的优先序列其实本质上只是在最大或最小堆的形式上,加入一些功能,如:

  • Insert(S,x):把元素x插入集合S中,这一操作等价于 S=S∪{x}
  • Maximum(S):返回S中具有最大键字的元素
  • ExtractMax(S):去掉并返回S中的具有最大键字的元素
  • IncreaseKey(S,x,k):将元素x的关键字值增加到k,这里假设k的值不小于x的关键字值

Maximum操作(其中的array已经是经过最大堆处理):

        static int HeapMaximum(List<int> array)
        {
            return array[0];
        }

ExtractMax操作(此操作过后,array的元素少一个):

        static int HeapExtractMax(List<int> array)
        {
            if (array.Count < 1)
                return -1;
            int max = array[0];
            array[0] = array[array.Count - 1];
            array.RemoveAt(array.Count - 1);   //移除array.count-1这个下标
            MaxHeapify(array, 0,array.Count);  //维护堆,本身被破坏的也只array[0]
            return max;
        }

IncreaseKey:

        static void HeapIncreaseKey(List<int> array,int i,int key)
        {
            if (key < array[i])  //这种"抛出异常写法不规范,偷懒而已"
            {
                Console.WriteLine("出错,key 必须大于array[i]!");
                return;
            }
            array[i] = key;
            while(i>0&&array[(i-1)/2]<array[i]) //此while为了维护最大堆的性质
            {
                int temp = array[i];            
                array[i] = array[(i - 1) / 2];
                array[(i - 1) / 2] = temp;
                i = (i - 1) / 2;
            }
        }

Insert:

        static void MaxHeapInsert(List<int> array,int key)
        {
            array.Add(key);
            HeapIncreaseKey(array, array.Count - 1, key);
        }

--------练习--------

6.5-6 利用插入排序内循环部分的思想,只用一次赋值来完成IncreaseKey中的 array[i]与array[parent[i]]互换(原本需要三次赋值,即引入了temp)

技术分享
        static void HeapIncreaseKey(List<int> array,int i,int key)
        {
            if (key < array[i])  //这种"抛出异常写法不规范,偷懒而已"
            {
                Console.WriteLine("出错,key 必须大于array[i]!");
                return;
            }
            array[i] = key;
            while(i>0&&array[(i-1)/2]<key) //此while为了维护最大堆的性质
            {
                array[i] = array[(i - 1) / 2];
                i = (i - 1) / 2;
            }
            array[i] = key;  
        }
View Code

 

6.5-8 HeapDelete(S,i):将结点i从堆S中删除:

技术分享
        static void HeapDelete(List<int> array, int i)
        {
            array[i] = array[array.Count - 1]; //将最后一个值取代要删除位置的值
            array.RemoveAt(array.Count - 1);     
            MaxHeapify(array, i, array.Count); //i位置值变化了,因此对此处进行"维护"
        }
View Code

6.5-9 将k个有序链表合并为一个有序链表,并且时间复杂度为O(nlgk),此处n是所有输入链表包含的总的元素个数

思路: 1. 从k个链表中取出每个链表的第一个元素,组成一个大小为k的数组arr,然后将数组arr转换为最小堆,那么arr[0]就为最小元素了

         2.取出arr[0],将其放到新的链表中,然后将arr[0]元素在原链表中的下一个元素补到arr[0]处,即arr[0].next,如果 arr[0].next为空,即它所在的链表          的元素已经取完了,那么将堆的最后一个元素补到arr[0]处,堆的大小自动减一,循环n-k次即可。

(注:以上思路参考于http://www.cnblogs.com/shuaiwhu/archive/2011/03/20/2065077.html)

Code分为以下几个模块:

①最小堆维护模块(输入数组为LinkedListNode<int>类型)

技术分享
        static void MinHeapify(List<LinkedListNode<int>> array, int i)
        {
            int min = i, length = array.Count;
            int left = 2 * i + 1, right = 2 * i + 2;
            if (left < length && array[left].Value< array[i].Value) //找出i,2i+1,2i+2中最小那个值的下标
                min = left;
            if (right < length && array[right].Value< array[min].Value)
                min = right;
            if (min != i)
            {
                LinkedListNode<int> temp = array[i];       //将最小值放入i中
                array[i] = array[min];
                array[min] = temp;
                MinHeapify(array, min);   //将被改动那个下标再次递归进行维护
            }
        }
View Code

②最小堆建堆模块

技术分享
        static void BuildMinHeap(List<LinkedListNode<int>> array)
        {
            for (int i = (array.Count - 2) / 2; i >= 0; i--) //此处i从(n-2)/2开始,因为没有子节点的节点无需进行"维护"
                MinHeapify(array, i);
        }
View Code

③将k个链表结合成一个链表

技术分享
        static LinkedList<int> CombineLink(List<LinkedList<int>> link)
        {
            LinkedList<int> result=new LinkedList<int>();
            List<LinkedListNode<int>> array = new List<LinkedListNode<int>>();
            foreach (LinkedList<int> l in link)  //将每个链表的第一个Node取出,存入数组array
                array.Add(l.First);
            BuildMinHeap(array);               //建立最小堆
            while (array.Count!=0)         
            {
                MinHeapify(array,0);           //由于每次array[0]均变化,所以需要进行维护
                result.AddLast(array[0].Value);
                if (array[0].Next != null)    //元素未取完时,将原链表中的下一个元素补到arr[0]处
                    array[0] = array[0].Next; 
                else                          //一个链表元素取完情况
                {
                    array[0] = array[array.Count - 1]; 
                    array.RemoveAt(array.Count - 1);
                }
            }
            return result;
        }
View Code

一次测试结果:技术分享

------------------

 

Chapter 6 堆排序

标签:

原文地址:http://www.cnblogs.com/Paul-chen/p/4254805.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!