八大排序算法之堆排序

时间复杂度:O(Nlog(N))
额外空间复杂度:O(1)
是否可实现稳定性:否

思路:

把数组逻辑上看成一个二叉树,数组下标和二叉树中的位置逻辑对应。
在二叉树中的父节点p和孩子l,r有对应关系(在数组中的下标的对应关系)
l = 2*index +1 r = 2 * index +2 这是已知父节点index的下标,求孩子节点的下标
index = (index-1)/2 这是已知孩子节点index,求父节点的下标
堆排序主要是通过大根堆实现的,何为大根堆呢?大根堆是一个完全二叉树,根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,找到大根堆后,把数组对应的逻辑二叉树最右下角的节点和根节点交换位置,这样最大的数就放到数组的最后了,然后数组的下标-1,再把此时的根节点做沉底操作,沉底操作就是如果换位置之后的根节点的值依然是最大值就不用换位置,如果比自己的孩子节点小,就和孩子节点中的较大的一个交换位置,直到沉底,然后循环这个过程。
如果还不懂结合代码注释理解

代码:

 public static void heapSort(int[] arr){

        if (arr==null||arr.length<2){
            return;
        }
        //建立一个size=数组长度的大根堆,这个大根堆的size就是数组长度
        int size = arr.length;
        //建立大根堆的过程
        for (int i = 0;i<arr.length;i++){
            heapInsert(arr,i);
        }
        //交换0位置和最后的数,因为在建立大根堆以后0位置的数已经是所有数最大
        swap(arr,0,--size);
        //循环,做0和--size(最后位置,就是每次size缩小后的最后位置)的交换
        while (size>0){
            //沉底操作
            heapify(arr,0,size);
            //交换
            swap(arr,0,--size);
        }
    }

    public static void heapify(int[] arr,int index,int size){
        //当前结点的左孩子
        int left = 2*index+1;
        //判断左孩子是否小宇size,小于就说明数组没有越界,如果左孩子越界有孩子肯定越界
        while (left<size){
            //设置一个最大的变量,他的初始值是左孩子和右孩子中的最大值
            int largest = left+1<size&&arr[left+1]>arr[left] ? left+1 : left;
            //判断当前结点和孩子节点最大值的大小,更新largest的值
            largest = arr[largest] > arr[index] ? largest:index;
            //如果largest还是index,说明当前结点的值就是此时大根堆的最大值
            if (largest == index ){
                break;
            }
            //否则 交换
            swap(arr,largest,index);
            //然后index变成largest在大根堆中的位置,继续下沉
            index = largest;
            left = 2*index+1;
        }
    }

    

    public static void heapInsert(int[] arr, int index){
        //这个判断条件两个作用,
        //第一个作用如果当前结点比父节点的值大,就交换
        //直到index=(index-1)/2说明到了根节点跳出循环
        while (arr[index]>arr[(index-1)/2]){
            swap(arr,index,(index-1)/2);
            index = (index-1)/2;
        }
    }
    


    public static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

扩展(堆的相关应用):

题目1:

求实时中位数.png

思路:

建立一个大根堆,一个小根堆,将数组的数加入到这两个堆里,加入的规则是,选择第一个数字进入的堆,任意选,大根堆小根堆都行,比如加入了大根堆,然后再加一个数,每次加数前,都查看大根堆和小根堆的大小差距,如果差距大于一,就把当前堆的堆顶加入到另一个堆。
在这里注意,优先级队列,默认小根堆,如果想把它变成大根堆,需要定义比较器,传入创建的优先级队列。比较器大于零升序,小根堆;小于零,降序,大根堆。

代码:

    public static class MedianHolder {
        private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new MaxHeapComparator());
        private PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>(new MinHeapComparator());

        private void modifyTwoHeapsSize() {
            if (this.maxHeap.size() == this.minHeap.size() + 2) {
                this.minHeap.add(this.maxHeap.poll());
            }
            if (this.minHeap.size() == this.maxHeap.size() + 2) {
                this.maxHeap.add(this.minHeap.poll());
            }
        }

        public void addNumber(int num) {
            if (this.maxHeap.isEmpty()) {
                this.maxHeap.add(num);
                return;
            }
            if (this.maxHeap.peek() >= num) {
                this.maxHeap.add(num);
            } else {
                if (this.minHeap.isEmpty()) {
                    this.minHeap.add(num);
                    return;
                }
                if (this.minHeap.peek() > num) {
                    this.maxHeap.add(num);
                } else {
                    this.minHeap.add(num);
                }
            }
            modifyTwoHeapsSize();
        }

        public Integer getMedian() {
            int maxHeapSize = this.maxHeap.size();
            int minHeapSize = this.minHeap.size();
            if (maxHeapSize + minHeapSize == 0) {
                return null;
            }
            Integer maxHeapHead = this.maxHeap.peek();
            Integer minHeapHead = this.minHeap.peek();
            if (((maxHeapSize + minHeapSize) & 1) == 0) {
                return (maxHeapHead + minHeapHead) / 2;
            }
            return maxHeapSize > minHeapSize ? maxHeapHead : minHeapHead;
        }

    }

    public static class MaxHeapComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            if (o2 > o1) {
                return 1;
            } else {
                return -1;
            }
        }
    }

    public static class MinHeapComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            if (o2 < o1) {
                return 1;
            } else {
                return -1;
            }
        }
    }

    // for test
    public static int[] getRandomArray(int maxLen, int maxValue) {
        int[] res = new int[(int) (Math.random() * maxLen) + 1];
        for (int i = 0; i != res.length; i++) {
            res[i] = (int) (Math.random() * maxValue);
        }
        return res;
    }

    // for test, this method is ineffective but absolutely right
    public static int getMedianOfArray(int[] arr) {
        int[] newArr = Arrays.copyOf(arr, arr.length);
        Arrays.sort(newArr);
        int mid = (newArr.length - 1) / 2;
        if ((newArr.length & 1) == 0) {
            return (newArr[mid] + newArr[mid + 1]) / 2;
        } else {
            return newArr[mid];
        }
    }

    public static void printArray(int[] arr) {
        for (int i = 0; i != arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        boolean err = false;
        int testTimes = 200000;
        for (int i = 0; i != testTimes; i++) {
            int len = 30;
            int maxValue = 1000;
            int[] arr = getRandomArray(len, maxValue);
            MedianHolder medianHold = new MedianHolder();
            for (int j = 0; j != arr.length; j++) {
                medianHold.addNumber(arr[j]);
            }
            if (medianHold.getMedian() != getMedianOfArray(arr)) {
                err = true;
                printArray(arr);
                break;
            }
        }
        System.out.println(err ? "Oops..what a fuck!" : "today is a beautiful day^_^");

    }

题目二:

金条问题.png

思路:

相当于把传入的数组建立一个小根堆,返回叶子结点的和。为什么这么做对,贪心思想,把建立的小根堆如果元素大于两个,就弹出两个(每次弹出后结构自动调整,优先级队列实现),取和cur,num+=cur,然后再把cur装入小根堆,依次下去,即可求出结果。

代码:

public static int lessMoney(int[] arr) {
        PriorityQueue<Integer> pQ = new PriorityQueue<>();
        for (int i = 0; i < arr.length; i++) {
            pQ.add(arr[i]);
        }
        int sum = 0;
        int cur = 0;
        while (pQ.size() > 1) {
            cur = pQ.poll() + pQ.poll();
            sum += cur;
            pQ.add(cur);
        }
        return sum;
    }

  public static void main(String[] args) {
        // solution
        int[] arr = {6, 7, 8, 9};
        System.out.println(lessMoney(arr));
    }

问题3:

获利最大.png

思路:

建立一个大根堆,用来存利润,建立一个小根堆,用来记录最小花费,把所有的项目加入到小根堆中,然后把当前启动资金能满足的项目加入到大根堆中,弹出大根堆的堆顶,加到本金中,即第二次的本金=原来的本金+利润,注意在这里利润就是扣除当前项目花费后能赚的前,比如启动资金是100,当前项目花费是50,利润是100,做完这个项目后身上的钱=100 +(150-50) = 200;依次执行下去,就可得到最大利润

代码:

public static class Node {
        public int p;
        public int c;

        public Node(int p, int c) {
            this.p = p;
            this.c = c;
        }
    }

    public static class MinCostComparator implements Comparator<Node> {

        @Override
        public int compare(Node o1, Node o2) {
            return o1.c - o2.c;
        }

    }

    public static class MaxProfitComparator implements Comparator<Node> {

        @Override
        public int compare(Node o1, Node o2) {
            return o2.p - o1.p;
        }

    }

    public static int findMaximizedCapital(int k, int W, int[] Profits, int[] Capital) {
        Node[] nodes = new Node[Profits.length];
        for (int i = 0; i < Profits.length; i++) {
            nodes[i] = new Node(Profits[i], Capital[i]);
        }

        PriorityQueue<Node> minCostQ = new PriorityQueue<>(new MinCostComparator());
        PriorityQueue<Node> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
        for (int i = 0; i < nodes.length; i++) {
            minCostQ.add(nodes[i]);
        }
        for (int i = 0; i < k; i++) {
            while (!minCostQ.isEmpty() && minCostQ.peek().c <= W) {
                maxProfitQ.add(minCostQ.poll());
            }
            if (maxProfitQ.isEmpty()) {
                return W;
            }
            W += maxProfitQ.poll().p;
        }
        return W;
    }

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,932评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,554评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,894评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,442评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,347评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,899评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,325评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,980评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,196评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,163评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,085评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,826评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,389评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,501评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,753评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,171评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,616评论 2 339

推荐阅读更多精彩内容

  • B树的定义 一棵m阶的B树满足下列条件: 树中每个结点至多有m个孩子。 除根结点和叶子结点外,其它每个结点至少有m...
    文档随手记阅读 13,126评论 0 25
  • 1 序 2016年6月25日夜,帝都,天下着大雨,拖着行李箱和同学在校门口照了最后一张合照,搬离寝室打车去了提前租...
    RichardJieChen阅读 5,068评论 0 12
  • 二叉树 满二叉树 国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,...
    简_爱SimpleLove阅读 4,235评论 0 3
  • 树的概述 树是一种非常常用的数据结构,树与前面介绍的线性表,栈,队列等线性结构不同,树是一种非线性结构 1.树的定...
    Jack921阅读 4,414评论 1 31
  • 上一篇讲了如何搭建一个中型的电商网站以及背后的架构是什么样的,今天我们一起来聊下大型电商网站的架构是什么样子的。 ...
    DearNicole阅读 1,963评论 3 8