精华内容
下载资源
问答
  • 下面小编就为大家分享一篇Java 堆排序实例(大顶堆、小顶堆),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • 大顶堆类(C++封装)

    2018-01-18 15:53:01
    利用C++实现了大顶堆,并封装了常用操作,包括:插入、删除、堆排序等。通过自己实现堆的常用操作,可以对堆的原理有更深的理解。
  • 大顶堆、小顶堆》 一、二叉堆的定义 二叉堆:首先是一棵二叉树,其次这棵二叉树要满足结构性质和堆序性质 结构性质:是一颗完全二叉树 堆序性质:对于树中的任意节点,要求key大于它的两个孩子,两个孩子...

    本文目录

    • 一、二叉堆的定义
      • 结构性质
      • 堆序性质
    • 二、二叉堆的底层存储结构
    • 三、二叉堆的插入
    • 四、二叉堆的删除
    • 五、源码和测试

    系列目录


    一、二叉堆的定义

    二叉堆:首先是一棵二叉树,其次这棵二叉树要满足结构性质和堆序性质

    • 结构性质:是一颗完全二叉树
    • 堆序性质:
      • 大顶堆:对于树中的任意节点,要求key大于等于它的两个孩子,两个孩子之间没有排序要求,所以根节点拥有最大值
      • 小顶堆:对于树中的任意节点,要求key小于等于它的两个孩子,两个孩子之间没有排序要求,所以根节点拥有最小值

    二叉堆常常用来实现优先级队列,在JDK的定时任务java.util.Timer中,就通过一个小顶堆TaskQueue来存放定时任务TimerTask。

    1.结构性质

    二叉堆是一颗完全二叉树。

    完全二叉树:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。

    这个定义看了就像没看一样,我们讲点通俗易懂的。

    满二叉树每一层的节点都是满的:

    • 第零层只有根节点,所以有2^0=1个节点
    • 第一层有2^1=2个节点
    • 第二层有2^2=4个节点
    • 第三层有2^3=8个节点
    • ......

    如果一棵二叉树每一层的节点都是满的,这棵树就是满二叉树。下图就是一颗满二叉树。

    满二叉树一定是一颗完全二叉树,但完全二叉树不一定是一颗满二叉树。

    完全二叉树结构上很接近满二叉树,只是允许最后一层不满,并且中间不允许空洞。

    下图就是一颗完全二叉树,中间没有空洞,虽然最后一层不满,但你从上到下逐层、每层从左往右的看,除了最后一层缺最后几个节点,中间一个不缺,没有一个漏洞。

    像下面两图,在最后一层从左往右看的时候,你会发现中间出现了一个空洞,所以既不是满二叉树,也不是完全二叉树。

    2.堆序性质

    如果是大顶堆,那么对于这颗完全二叉树中的任意节点,都有该节点的key大于等于它的两个孩子,而两个孩子之间没有排序的要求。

    如果是小顶堆,那么对于这颗完全二叉树中的任意节点,都有该节点的key小于等于它的两个孩子,而两个孩子之间没有排序的要求。

    下图就是一个小顶堆:

    这个结构虽然看起来杂乱无章,但切切实实的是一个小顶堆,稍后你看过二叉堆的源码实现你就能理解了。


    二、二叉堆的底层存储结构

    二叉堆的底层存储结构通常是数组,用顺序存储的数组来存放节点,并且数组的第0个下标是不用的。

    所以在二叉堆中,定义一个数组用来存储插入的节点:

    private Node<K, V>[] queue = new Node[size];

    二叉堆的底层存储结构形如下图:

    第0个下标不用,这样就很容易拿到某一个节点的父节点和孩子节点。

    找到某一个节点的父节点:

    	/**
    	 * 输入当前节点的index,获取父节点的index
    	 * @param index
    	 * @return
    	 */
    	private int parent(int index) {
    		return index >> 1;
    	}

    找到某一个节点的左孩子:

    	/**
    	 * 输入当前节点的index,获取其左孩子的index
    	 * @param index
    	 * @return
    	 */
    	private int left(int index) {
    		return 1 << index;
    	}

    找到某一个节点的右孩子:

    left(index) + 1;

    因为要满足完全二叉树的结构性质,所以如果某一个节点只有一个孩子,那一定是左孩子,如果是右孩子那相当于左孩子处出现空洞,也就意味着二叉堆底层存放节点的数组中间出现了值为null的下标。


    三、二叉堆的插入

    插入操作既不能破坏二叉堆的结构性质(完全二叉树)也不能破坏二叉堆的堆序性质。

    为了不破坏二叉堆完全二叉树的结构性质,我们在二叉堆中定义一个size,有两个用处:

    • 一则是用来记录堆中存放节点的数量;
    • 二则当插入节点的时候queue[++size]整好定位到新节点存放的位置,二叉堆中存放节点的数组queue[]中不会出现空洞;

    为了满足二叉堆的堆序性质,新节点要不断的向上回溯:

    • 如果是大顶堆,向上回溯直至根节点或其父节点大于等于自己;
    • 如果是小顶堆,向上回溯直至根节点或其父节点小于等于自己;

    大顶堆插入示例代码:

    	/**
    	 * 向二叉堆中插入数据
    	 * @param key
    	 * @param value
    	 */
    	public void insert(K key, V value) {
    		if ((size + 1) == this.queue.length)
    			this.queue = Arrays.copyOf(queue, 2 * queue.length);
    		queue[++size] = new Node<K, V>(key, value);
    		fixup(size);
    	}
    
    	/**
    	 * 新插入的节点放入数组index=size,这样不会破坏二叉堆的结构性质(完全二叉树)
    	 * 然后拿到其父节,比较两者的key,如果新插入节点的key大于父节点的key,则两者交换位置
    	 * 这样不断向上回溯,直至跟节点或小于其父节点的key
    	 */
    	private void fixup(int index) {
    		while (index > 1) {
    			int parentIndex = parent(index);
    			// 如果父亲的key大于等于自己的key,则停止向上回溯
    			if (queue[parentIndex].key.compareTo(queue[index].key) >= 0)
    				break;
    			// 如果父亲的key小于自己的key,则与父亲交换位置,然后继续向上回溯
    			Node<K, V> tmp = queue[parentIndex];
    			queue[parentIndex] = queue[index];
    			queue[index] = tmp;
    			index = parentIndex;
    		}
    	}


    四、二叉堆的删除

    二叉堆的删除操作是摘掉根节点,即把queue[1]摘掉。

    同样的,删除操作同样不能破坏二叉堆的结构性质和堆序性质。

    为了保证删除操作时二叉堆完全二叉树的结构性质,当把根节点queue[1]摘掉的时候会出现空洞,于是我们把最后一个节点queue[size]挪过来填补空洞。

    为了保证删除操作时二叉堆的堆序性质,当把最后一个节点queue[size]挪到根节点的时候,我们需要不断的向下回溯:

    • 如果是大顶堆,向下回溯直至两个孩子中最大的一个小于等于自己或已经没有孩子可以比较;
    • 如果是小顶堆,向下回溯直至两个孩子中最小的一个小于等于自己或已经没有孩子可以比较;

    大顶堆删除示例代码:

    	/**
    	 * 大顶堆的根节点拥有最大值,所以deleteMax会摘掉根节点
    	 * 这样会导致空洞,为了不破坏二叉堆完全二叉树的结构性质,将最后一个节点移动到根节点
    	 * 然后与它的两个孩子中较大的那一个作比较,如果小于则往下移动
    	 * 直至最后一层或大于其孩子中较大的那一个
    	 * @return
    	 */
    	public V deleteMax() {
    		Node<K, V> root = queue[1];
    		queue[1] = queue[size];
    		queue[size--] = null;
    		fixDown(1);
    		return root.value;
    	}
    
    	private void fixDown(int index) {
    		// 完全二叉树:如果一个节点有孩子,那一定先有左孩子
    		int childIndex;
    		/*
    		 * 如果节点至少有一个孩子(因为是完全二叉树,所以如果有一个孩子那一定先是左孩子)
    		 * 那么进入while循环,开始向下回溯
    		 */
    		while ((childIndex = left(index)) <= this.size) {
    			// 要找两个孩子中较大的那一个作比较,所以再判断一下有没有右孩子
    			int rightIndex = childIndex + 1;
    			if (rightIndex <= size && queue[rightIndex].key.compareTo(queue[childIndex].key) > 0)
    				childIndex = rightIndex;
    			// 比较较大的孩子和当前节点是否需要交换
    			if (queue[index].key.compareTo(queue[childIndex].key) >= 0)
    				break;
    			Node<K, V> tmp = queue[index];
    			queue[index] = queue[childIndex];
    			queue[childIndex] = tmp;
    			index = childIndex;
    		}
    	}


    五、源码和测试

    大顶堆源码:

    package cn.wxy.blog2;
    
    import java.util.Arrays;
    
    /**
     * 大顶堆
     * @author 王大锤
     * @date 2021年6月26日
     */
    public class BinaryHeap<K extends Comparable<K>, V> {
    	static class Node<K extends Comparable<K>, V> {
    		private K key;
    		private V value;
    
    		public Node(K key, V value) {
    			this.key = key;
    			this.value = value;
    		}
    
    		public K getKey() {
    			return key;
    		}
    
    		public void setKey(K key) {
    			this.key = key;
    		}
    
    		@Override
    		public String toString() {
    			return this.key + ":" + value + " ";
    		}
    	}
    
    	@SuppressWarnings("unchecked")
    	private Node<K, V>[] queue = new Node[16];
    	private int size = 0;
    
    	public BinaryHeap() {
    	}
    
    	/**
    	 * 输入当前节点的index,获取父节点的index
    	 * @param index
    	 * @return
    	 */
    	private int parent(int index) {
    		return index >> 1;
    	}
    
    	/**
    	 * 输入当前节点的index,获取其左孩子的index
    	 * @param index
    	 * @return
    	 */
    	private int left(int index) {
    		return 1 << index;
    	}
    
    	/**
    	 * 向二叉堆中插入数据
    	 * @param key
    	 * @param value
    	 */
    	public void insert(K key, V value) {
    		if ((size + 1) == this.queue.length)
    			this.queue = Arrays.copyOf(queue, 2 * queue.length);
    		queue[++size] = new Node<K, V>(key, value);
    		fixup(size);
    	}
    
    	/**
    	 * 新插入的节点放入数组index=size,这样不会破坏二叉堆的结构性质(完全二叉树)
    	 * 然后拿到其父节,比较两者的key,如果新插入节点的key大于父节点的key,则两者交换位置
    	 * 这样不断向上回溯,直至跟节点或小于其父节点的key
    	 */
    	private void fixup(int index) {
    		while (index > 1) {
    			int parentIndex = parent(index);
    			// 如果父亲的key大于等于自己的key,则停止向上回溯
    			if (queue[parentIndex].key.compareTo(queue[index].key) >= 0)
    				break;
    			// 如果父亲的key小于自己的key,则与父亲交换位置,然后继续向上回溯
    			Node<K, V> tmp = queue[parentIndex];
    			queue[parentIndex] = queue[index];
    			queue[index] = tmp;
    			index = parentIndex;
    		}
    	}
    
    	/**
    	 * 大顶堆的根节点拥有最大值,所以deleteMax会摘掉根节点
    	 * 这样会导致空洞,为了不破坏二叉堆完全二叉树的结构性质,将最后一个节点移动到根节点
    	 * 然后与它的两个孩子中较大的那一个作比较,如果小于则往下移动
    	 * 直至最后一层或大于其孩子中较大的那一个
    	 * @return
    	 */
    	public V deleteMax() {
    		Node<K, V> root = queue[1];
    		queue[1] = queue[size];
    		queue[size--] = null;
    		fixDown(1);
    		return root.value;
    	}
    
    	private void fixDown(int index) {
    		// 完全二叉树:如果一个节点有孩子,那一定先有左孩子
    		int childIndex;
    		/*
    		 * 如果节点至少有一个孩子(因为是完全二叉树,所以如果有一个孩子那一定先是左孩子)
    		 * 那么进入while循环,开始向下回溯
    		 */
    		while ((childIndex = left(index)) <= this.size) {
    			// 要找两个孩子中较大的那一个作比较,所以再判断一下有没有右孩子
    			int rightIndex = childIndex + 1;
    			if (rightIndex <= size && queue[rightIndex].key.compareTo(queue[childIndex].key) > 0)
    				childIndex = rightIndex;
    			// 比较较大的孩子和当前节点是否需要交换
    			if (queue[index].key.compareTo(queue[childIndex].key) >= 0)
    				break;
    			Node<K, V> tmp = queue[index];
    			queue[index] = queue[childIndex];
    			queue[childIndex] = tmp;
    			index = childIndex;
    		}
    	}
    
    	/**
    	 * 层序遍历二叉堆,实际上就是顺序打印二叉堆的底层数组
    	 */
    	public void traversalHeapByLevel() {
    		System.out.print("打印堆queue[]:");
    		for (Node<K, V> node : queue)
    			System.out.print(node + " ");
    		System.out.println();
    	}
    	
    	/**
    	 * 通过删除根节点来遍历大顶堆
    	 */
    	public void traverssalHeapByDelete() {
    		System.out.print("循环deleteMax:");
    		while(size > 0)
    			System.out.print(deleteMax() +" ");
    		System.out.println();
    	}
    }

    在大顶堆中,定义了两个方法来做测试,一个是traversalHeapByLevel方法打印大顶堆底层存储结构queue[],一个是traverssalHeapByDelete不断的deleteMax摘掉大顶堆的根节点,其中traverssalHeapByDelete的输出应该满足从大到小的顺序。

    测试代码:

    	public static void main(String[] args) {
    		int[] array = {8, 57, 11, 34, 53, 71, 87, 21, 98, 81};
    		printArray(array);
    		BinaryHeap<Integer, String> heap = new BinaryHeap<Integer, String>();
    		for (int i : array)
    			heap.insert(i, i + "");
    		heap.traversalHeapByLevel();
    		heap.traverssalHeapByDelete();
    	}
    
    	public static void printArray(int[] array) {
    		System.out.print("测试数据:");
    		Arrays.stream(array).forEach(value -> System.out.print(value + " "));
    		System.out.println();
    	}

    输出结果:

    测试数据:8 57 11 34 53 71 87 21 98 81 
    打印堆queue[]:null 98:98  87:87  71:71  53:53  81:81  11:11  57:57  8:8  21:21  34:34  null null null null null 
    循环deleteMax:98 87 81 71 57 53 34 21 11 8 


    参考资料:

    • 《算法导论》
    • 《数据结构与算法:Java语言描述》
    • 《大话数据结构》
    • jdk java.util.TaskQueue源码
    展开全文
  • 构建大顶堆 leetcode 刷题数据结构工具 方便js,ts写代码,调用时直接有提示补进。 :warning:ps:ListNode,TreeNode,RunScript均参考这个库。将代码精简并改成typescript。 如果有错误,欢迎开。 使用方法 ts:...
  • 这道题我们可以将比作一个薯片桶,我们将大顶堆放到左侧,小顶堆放到右侧,也就是说我们新添加的数要根据添加的数大小判断放到大顶堆还是小顶堆,还要根据大顶堆和小顶堆的长度判断放到大顶堆还是小顶堆。...

    如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

    例如,

    [2,3,4] 的中位数是 3

    [2,3] 的中位数是 (2 + 3) / 2 = 2.5

    设计一个支持以下两种操作的数据结构:

    void addNum(int num) - 从数据流中添加一个整数到数据结构中。
    double findMedian() - 返回目前所有元素的中位数。
    示例 1:

    输入:
    [“MedianFinder”,“addNum”,“addNum”,“findMedian”,“addNum”,“findMedian”]
    [[],[1],[2],[],[3],[]]
    输出:[null,null,null,1.50000,null,2.00000]

    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof

    思路:

    由于第一次接触这种题,第一想起来的就是初始化一个数组,当需要计算中位数时,我们就把数组排序,之后计算中位数,但是这样太慢,数据量大了之后,就超时。

    在这里插入图片描述

    既然也都说超时了,我也就不知廉耻的把代码也贴出来吧。

    type MedianFinder struct {
        slow []int //切片
        last int //指向下一个可以放数据的索引
    }
    
    
    /** initialize your data structure here. */
    func Constructor() MedianFinder {
        return MedianFinder{slow : make([]int , 5e4 + 1) , last : 0}
    }
    
    
    func (this *MedianFinder) AddNum(num int)  {
        this.slow[this.last] = num
        this.last ++
    }
    
    
    func (this *MedianFinder) FindMedian() float64 {
        sort.Ints(this.slow[:this.last])
        if this.last % 2 != 0 {
            return float64(this.slow[this.last / 2])
        } else {
            return float64(this.slow[(this.last - 1) / 2] + this.slow[this.last / 2]) / float64(2)
        }
    }
    
    
    /**
     * Your MedianFinder object will be instantiated and called as such:
     * obj := Constructor();
     * obj.AddNum(num);
     * param_2 := obj.FindMedian();
     */
    

    这种超时的原因一部分在于,一旦要是得到中位数,我们就要排序。

    大顶堆、小顶堆

    大顶堆:堆顶的数要大于等于它子树的所有数。
    小顶堆:堆顶的数要小于等于它子树的所有数。

    这道题我们可以将比作一个薯片桶,我们将大顶堆放到左侧,小顶堆放到右侧,也就是说我们新添加的数要根据添加的数大小判断放到大顶堆还是小顶堆,还要根据大顶堆和小顶堆的长度判断放到大顶堆还是小顶堆。

    1. 我们先要判断大顶堆和小顶堆的长度,我们让大顶堆和小顶堆的长度尽可能的相等,只有这样,我们计算中位数时,就可以直接找到中间的数。
    2. 我们不仅要判断大顶堆和小顶堆的长度,我们还要判断加入的数和大顶堆堆顶的数、小堆顶的堆顶的数的相对大小,如果我们要加入一个数,如果加入之前大顶堆长度小于小顶堆的长度,我们既然要让两个堆长度尽量相等,我们就要将这个数放入大顶堆,但是这时问题出现了,如果放入这个数,比大顶堆的堆顶的数还要大,那我们就不能放入大顶堆,只能放入小顶堆,但是如果这个数放入小顶堆,那么小顶堆就比大顶堆的长度长两个长度。所以我们将该数放入小顶堆之后,也要将小顶堆的数放入到大顶堆中。
    type maxheap []int
    
    type minheap []int
    
    func (m *maxheap) Push (num interface{}) {
        *m = append(*m , num.(int))
    }
    
    func (m *minheap) Push (num interface{}) {
        *m = append(*m , num.(int))
    }
    
    func (m *maxheap) Pop () interface{} {
        length := len(*m)
        num := (*m)[length - 1]
        *m = (*m)[:length - 1]
        return num
    }
    
    func (m *maxheap) Swap(i , j int) {
        (*m)[i] , (*m)[j] = (*m)[j] , (*m)[i]
    }
    
    func (m *minheap) Swap(i , j int) {
        (*m)[i] , (*m)[j] = (*m)[j] , (*m)[i]
    }
    
    func (m maxheap) Len() int {
        return len(m)
    }
    
    func (m minheap) Len() int {
        return len(m)
    }
    
    func (m maxheap) Less (i , j int) bool {
        return m[i] > m[j]
    }
    
    func (m minheap) Less (i , j int) bool {
        return m[i] < m[j]
    }
    
    func (m *minheap) Pop () interface{} {
        length := len(*m)
        num := (*m)[length - 1]
        *m = (*m)[:length - 1]
        return num
    }
    type MedianFinder struct {
        maxh *maxheap
        minh *minheap
    }
    
    
    /** initialize your data structure here. */
    func Constructor() MedianFinder {
        return MedianFinder{maxh : new(maxheap) , minh : new(minheap)}
    }
    
    
    func (this *MedianFinder) AddNum(num int)  {
        if this.maxh.Len() == this.minh.Len() {
            if this.minh.Len() == 0 || (*this.minh)[0] <= num {
                heap.Push(this.minh , num)
            } else {
                heap.Push(this.maxh , num)
                n := heap.Pop(this.maxh).(int)
                heap.Push(this.minh , n)
            }
        } else {
            if num > (*this.minh)[0] {
                heap.Push(this.minh , num)
                n := heap.Pop(this.minh)
                heap.Push(this.maxh , n.(int))
            } else {
                heap.Push(this.maxh , num)
            }
        }
    }
    
    
    func (this *MedianFinder) FindMedian() float64 {
        if this.minh.Len() == this.maxh.Len() {
            return float64((*this.minh)[0] + (*this.maxh)[0]) / 2.0
        } else {
            return float64((*this.minh)[0])
        }
    }
    
    
    /**
     * Your MedianFinder object will be instantiated and called as such:
     * obj := Constructor();
     * obj.AddNum(num);
     * param_2 := obj.FindMedian();
     */
    

    一共分为四种可能:

    一个数应该加入到大顶堆

    如果这个数大于大顶堆的堆顶那么这个数先放入小顶堆,然后再将小顶堆的堆顶放入大顶堆。
    一个数应该加入到小顶堆
    如果这个数小于小顶堆的堆顶,那么这个数先放入大顶堆,然后将大顶堆的堆顶放入小顶堆。

    展开全文
  • 构建大顶堆、堆排序实现(java)构建大顶堆、堆排序实现(java)堆排序介绍:①堆排序是利用堆的数据结构设计的一种排序算法,堆排序是一种选择排序,时间复杂度为O(nlogn),是不稳定排序;②堆是具有以下性质的完全...

    构建大顶堆、堆排序实现(java)

    构建大顶堆、堆排序实现(java)

    堆排序介绍:

    ①堆排序是利用堆的数据结构设计的一种排序算法,堆排序是一种选择排序,时间复杂度为O(nlogn),是不稳定排序;

    ②堆是具有以下性质的完全二叉树:每个节点的值都大于或者等于其左右孩子节点的值,称为大顶堆;(没有要求其左右孩子节点的值谁大谁小)

    ③每个节点的值都小于或者等于其左右孩子节点的值,称为小顶堆

    9e198ef77110c898790cd39ca1188600.png

    对堆中的节点按层进行编号,映射到数组中:

    41e01291ad1e62bd6726c4e179e7f095.png

    大顶堆的特点:

    arr[i] >= arr[2 * i + 1] && arr[i] >= arr[2 * i + 2]; //i对应的是第几个节点,i从0开始

    b2da2f1386f3f68cb06dc57c54e8cc23.png

    小顶堆特点:

    arr[i] <= arr[2 * i + 1] && arr[i] <= arr[2 * i + 2]; //i对应第几个节点,i从0开始编号

    一般升序的时候采用大顶堆,降序的时候采用小顶堆

    堆排序的基本思想:

    ①将待排序序列构造成一个大顶堆

    ②此时,整个序列的最大值就是堆顶的根节点

    ③将其与末尾元素进行交换,此时末尾就是最大值

    ④然后将剩余 n-1 个元素重新调整成一个堆,这样会得到n个元素的次小值,反复执行,就能得到一个有序序列

    在构建大顶堆的过程中,元素的个数逐渐减少,最后就能得到一个有序序列;

    例如: 将{4,6,8,5,9}使用堆排序,将数组升序排序

    155df6bfd6fd69a86ce042bd912d62b3.png

    664de456277eb78a1d46be4fd3ab2756.png

    b9696d3a0dc23149786ed2151fc052ed.png

    0a5af0a662ffa54f3e4bdd0747b5e092.png

    846b13491bac5b86974f1863efdec41a.png

    8e8fd30bb9ad79fd9d6a33be052a3ba4.png

    85220f626a173f132adab87e4c7f913f.png

    e2033e4a8bed25028babf22802b4bde0.png

    47e6561792e36c009a5be260cf57c811.png

    354ed7e63d96f6af58c9521a70bbdc70.png

    57d1cefefeb181ec978df9d44d2fa689.png

    调整大顶堆代码

    /**

    * 功能:完成将以 i 对应的非叶子节点的树调整成为大顶堆

    *

    * @param arr 待调整的数组

    * @param i 表示非叶子节点在数组中的索引

    * @param length 表示对多少个元素进行调整,每排序好一趟,length的长度就减1

    */

    public static void adjustHeap(int[] arr, int i, int length) {

    int temp = arr[i]; //先取出当前元素的值,保存在临时变量中

    //开始调整

    //1.k = i * 2 + 1 是i 节点的左子节点

    for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {

    if (k + 1 < length && arr[k] < arr[k + 1]) { //说明左子节点的值小于右子节点的值,

    k++; //k指向右子节点,目的是为了找到左右孩子节点中的最大值

    }

    if (arr[k] > temp) { //如果子节点大于父节点,说明需要调整

    arr[i] = arr[k]; //把较大的值作为当前的树的父节点

    i = k; // i 指向 k,继续循环比较

    } else {

    break;

    }

    }

    //当for循环结束后,已经将以 i 为父节点的树的最大值,放在了顶端(局部)

    arr[i] = temp; //将temp值放到调整后的位置

    }

    堆排序代码:

    public static void heapSort(int[] arr) {

    System.out.println("堆排序");

    //将无序序列构建成一个大顶堆

    for (int i = arr.length / 2 - 1; i >= 0; i--) {

    adjustHeap(arr, i, arr.length);

    }

    //将堆顶元素和末尾元素进行交换,将最大的数放到数组的末尾

    //再重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行 调整+交换 步骤,直到整个序列有序

    for (int j = arr.length - 1; j >= 0; j--) {

    //交换

    int temp = arr[j];

    arr[j] = arr[0];

    arr[0] = temp;

    adjustHeap(arr, 0, j);

    }

    // System.out.println("调整后的数组 = " + Arrays.toString(arr));

    }

    构建大顶堆、堆排序实现(java)相关教程

    每日算法----删除排序数组中的重复项----202010/5(4/4)(补)

    每日算法----删除排序数组中的重复项----202010/5(4/4)(补) 目录 1. 题目描述 2. 示例 3. 思路 4. 遇上的问题 5. 具体实现代码 6. 学习收获,官方一如既往的妙啊 7 题目来源 1. 题目描述 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元

    冒泡排序时间 / 空间复杂度

    冒泡排序时间 / 空间复杂度 ** 借鉴转载自十大经典排序算法 ** 十大算法复杂度: 冒泡排序 思想: 两两比较,A[i+1]A[i] 交换位置,循环i次 代码实现: class Solution { public static void main(String[] args) { int[] array = new int[]{2,2,3,1}; for(in

    【排序】插入排序

    【排序】插入排序 1、插入排序法介绍 插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。 2、插入排序法思想 插入排序(Insertion Sorting)的基本思想是: 把n个待排序的元素看成为一个有序表和一个无序表

    php如何实现排序算法

    php实现排序算法的方法:1、冒泡排序,两两相比,每循环一轮就不用再比较最后一个元素;2、选择排序,选定一个作为基本值,剩下的和这个比较,再调换位置。 php实现排序算法的方法: 1、冒泡排序: 两两相比,每循环一轮就不用再比较最后一个元素了,因为最

    LeetCode第 426 题:将二叉搜索树转化为排序的双向链表(C++)

    LeetCode第 426 题:将二叉搜索树转化为排序的双向链表(C++) 426. 将二叉搜索树转化为排序的双向链表 一样的剑指 Offer 36. 二叉搜索树与双向链表_zj-CSDN博客,中序遍历一边遍历一边调整指针 class Solution {public: //left, right对应前驱、后继 Node *hea

    C++ partial_sort(部分排序)

    C++ partial_sort(部分排序) 参考:http://c.biancheng.net/view/564.html 假设有一个容器,它保存了 100 万个数值,但我们只对其中最小的 100 个感兴趣。可以对容器的全部内容排序,然后选择前 100 个元素,但这可能有点消耗时间。这时候需要使用部分排序

    牛客IOI周赛19-普及组 A:小y的考试 (自定义排序)

    牛客IOI周赛19-普及组 A:小y的考试 (自定义排序) 吐槽:这道题没什么难的,题意也很好懂,但是我竟然忘了 自定义排序函数时使用时还需要加上cmp,结果就很尴尬了,另外 sort 的时候也要注意数组的范围 sort(0,3) 只能将数组中 [0,3) 中的数进行排序 。 题面: 水题

    按字典值排序--找出大小最大的十个文件

    按字典值排序--找出大小最大的十个文件 问题分析: 需要确认某路径下所有文件的大小 需要排序,找出最大的十个 以字典的形式保存数据 准备知识: operator模块: fun = operator.itemgetter(1), fun 是一个由operator.itemgetter(1) 返回的函数,当函数fun作

    展开全文
  • 堆排序(大顶堆、小顶堆)

    千次阅读 2020-08-14 19:59:19
    按照堆的特点可以把堆分为大顶堆和小顶堆 大顶堆:每个结点的值都大于或等于其左右孩子结点的值 小顶堆:每个结点的值都小于或等于其左右孩子结点的值 (堆的这种特性非常的有用,堆常常被当做优先队列使用,因为...

    **

    1、什么是堆?

    **
    堆是一种非线性结构,(本篇随笔主要分析堆的数组实现)可以把堆看作一个数组,也可以被看作一个完全二叉树,通俗来讲堆其实就是利用完全二叉树的结构来维护的一维数组

    按照堆的特点可以把堆分为大顶堆和小顶堆

    大顶堆:每个结点的值都大于或等于其左右孩子结点的值

    小顶堆:每个结点的值都小于或等于其左右孩子结点的值

    (堆的这种特性非常的有用,堆常常被当做优先队列使用,因为可以快速的访问到“最重要”的元素)

    **

    2、堆的特点(数组实现)

    **
    在这里插入图片描述
    我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子
    在这里插入图片描述
    我们用简单的公式来描述一下堆的定义就是:(读者可以对照上图的数组来理解下面两个公式)

    大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

    小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
    **

    3、堆和普通树的区别

    **
    内存占用:

    普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配额外的内存。堆仅仅使用数组,且不使用指针

    (可以使用普通树来模拟堆,但空间浪费比较大,不太建议这么做)

    平衡:
    二叉搜索树必须是“平衡”的情况下,其大部分操作的复杂度才能达到O(nlog2n)。你可以按任意顺序位置插入/删除数据,或者使用 AVL 树或者红黑树,但是在堆中实际上不需要整棵树都是有序的。我们只需要满足对属性即可,所以在堆中平衡不是问题。因为堆中数据的组织方式可以保证O(nlog2n) 的性能

    搜索:
    在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在最前面,从而快速的进行相关插入、删除操作

    **

    4、堆排序的过程

    **
    先了解下堆排序的基本思想:

    将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值,

    如此反复执行,便能得到一个有序序列了,建立最大堆时是从最后一个非叶子节点开始从下往上调整的(这句话可能不好太理解),下面会举一个例子来理解堆排序的基本思想

    给一个无序序列如下

    int a[6] = {7, 3, 8, 5, 1, 2};
    现在可以根据数组将完全二叉树还原出来
    在这里插入图片描述
    好了,现在我们要做的事情就是要把7,3,8,5,1,2变成一个有序的序列,如果想要升序就是1,2,3,5,7,8 如果想要降序就是8,7,5,3,2,1 ,这两种就是我们要的最终结果,然后我们就可以根据我们想要的结果来选择

    适合类型的堆来进行排序

    升序----使用大顶堆
    降序----使用小顶堆

    5、为什么升序要用大顶堆呢
    上面提到过大顶堆的特点:每个结点的值都大于或等于其左右孩子结点的值,我们把大顶堆构建完毕后根节点的值一定是最大的,然后把根节点的和最后一个元素(也可以说最后一个节点)交换位置,那么末尾元素此时就是最大元素了(理解这点很重要)

    知道了堆排序的原理下面就可以来操作了,在进行操作前先理清一下步骤

    (假设我们想要升序的排列)

    第一步:先n个元素的无序序列,构建成大顶堆

    第二步:将根节点与最后一个元素交换位置,(将最大元素"沉"到数组末端)

    第三步:交换过后可能不再满足大顶堆的条件,所以需要将剩下的n-1个元素重新构建成大顶堆

    第四步:重复第二步、第三步直到整个数组排序完成

    **

    6、图解交换过程(得到升序序列,使用大顶堆来调整)

    **
    这里以int a[6] = {7, 3, 8, 5, 1, 2}为例子

    先要找到最后一个非叶子节点,数组的长度为6,那么最后一个非叶子节点就是:长度/2-1,也就是6/2-1=2,然后下一步就是比较该节点值和它的子树值,如果该节点小于其左\右子树的值就交换(意思就是将最大的值放到该节点)

    8只有一个左子树,左子树的值为2,8>2不需要调整
    在这里插入图片描述
    下一步,继续找到下一个非叶子节点(其实就是当前坐标-1就行了),该节点的值为3小于其左子树的值,交换值,交换后该节点值为5,大于其右子树的值,不需要交换
    在这里插入图片描述
    下一步,继续找到下一个非叶子节点,该节点的值为7,大于其左子树的值,不需要交换,再看右子树,该节点的值小于右子树的值,需要交换值在这里插入图片描述
    下一步,检查调整后的子树,是否满足大顶堆性质,如果不满足则继续调整(这里因为只将右子树的值与根节点互换,只需要检查右子树是否满足,而7>2刚好满足大顶堆的性质,就不需要调整了,

    如果运气不好整个树的根节点的值是1,那么就还需要调整右子树)

    到这里大顶堆的构建就算完成了,然后下一步交换根节点(8)与最后一个元素(2)交换位置**(将最大元素"沉"到数组末端)**,此时最大的元素就归位了,然后对剩下的5个元素重复上面的操作
    在这里插入图片描述(这里用粉红色来表示已经归位的元素)
    剩下只有5个元素,最后一个非叶子节点是5/2-1=1,该节点的值(5)大于左子树的值(3)也大于右子树的值(1),满足大顶堆性质不需要交换在这里插入图片描述
    找到下一个非叶子节点,该节点的值(2)小于左子树的值(5),交换值,交换后左子树不再满足大顶堆的性质再调整左子树,左子树满足要求后再返回去看根节点,根节点的值(5)小于右子树的值(7),再次交换值
    在这里插入图片描述
    得到新的大顶堆,如下图,再把根节点的值(7)与当前数组最后一个元素值(1)交换,再重构大顶堆->交换值->重构大顶堆->交换值····,直到整个数组都变成有序序列在这里插入图片描述
    最后得到的升序序列如下图在这里插入图片描述
    **

    7、堆排序的代码实现

    **
    上面说了一大堆来详细说明堆排序的操作步骤,下面开始就开始来码代码了

    笔者将堆排序的过程分成了两个子函数

    void Swap(int *heap, int len);        /* 交换根节点和数组末尾元素的值 */
    void BuildMaxHeap(int *heap, int len);/* 构建大顶堆 */
    

    先来实现构建大堆的部分:

    /* Function: 构建大顶堆 */
    void BuildMaxHeap(int *heap, int len)
    {
        int i;
        int temp;
    
        for (i = len/2-1; i >= 0; i--)
        {
            if ((2*i+1) < len && heap[i] < heap[2*i+1])    /* 根节点小于左子树 */
            {
                temp = heap[i];
                heap[i] = heap[2*i+1];
                heap[2*i+1] = temp;
                /* 检查交换后的左子树是否满足大顶堆性质 如果不满足 则重新调整子树结构 */
                if ((2*(2*i+1)+1 < len && heap[2*i+1] < heap[2*(2*i+1)+1]) || (2*(2*i+1)+2 < len && heap[2*i+1] < heap[2*(2*i+1)+2]))
                {
                    BuildMaxHeap(heap, len);
                }
            }
            if ((2*i+2) < len && heap[i] < heap[2*i+2])    /* 根节点小于右子树 */
            {
                temp = heap[i];
                heap[i] = heap[2*i+2];
                heap[2*i+2] = temp;
                /* 检查交换后的右子树是否满足大顶堆性质 如果不满足 则重新调整子树结构 */
                if ((2*(2*i+2)+1 < len && heap[2*i+2] < heap[2*(2*i+2)+1]) || (2*(2*i+2)+2 < len && heap[2*i+2] < heap[2*(2*i+2)+2]))
                {
                    BuildMaxHeap(heap, len);
                }
            }
        }
    }
    

    上述代码中不易于理解的可能就是下面这条if判断语句

     /* 检查交换后的左子树是否满足大顶堆性质 如果不满足 则重新调整子树结构 */
     if ((2*(2*i+1)+1 < len && heap[2*i+1] < heap[2*(2*i+1)+1]) || (2*(2*i+1)+2 < len && heap[2*i+1] < heap[2*(2*i+1)+2]))
     {
           BuildMaxHeap(heap, len);
     }
    

    把if里面的条件分来开看2*(2i+1)+1 < len的作用是判断该左子树有没有左子树(可能有点绕),heap[2i+1] < heap[2*(2*i+1)+1]就是判断左子树的左子树的值是否大于左子树,如果是,那么就意味着交换值

    过后左子树大顶堆的性质被破环了,需要重构该左子树

    下面来实现交换部分

    /* Function: 交换交换根节点和数组末尾元素的值*/
    void Swap(int *heap, int len)
    {
        int temp;
    
        temp = heap[0];
        heap[0] = heap[len-1];
        heap[len-1] = temp;
    }
    

    然后来考虑下主函数部分,因为是int a[6] = {7, 3, 8, 5, 1, 2}长度为6,需要构建大顶堆,交换值6次才能得到有序序列,由此可以确定主函数的for循环为,for (i = len; i > 0; i–)

    int main()
    {
        int a[6] = {7, 3, 8, 5, 1, 2};
        int len = 6;    /* 数组长度 */
        int i;
    
        for (i = len; i > 0; i--)
        {
            BuildMaxHeap(a, i);
            Swap(a, i);
        }
        for (i = 0; i < len; i++)
        {
            printf("%d ", a[i]);
        }
    
        return 0;
    }
    

    下面附上堆排序完整代码:

    #include <stdio.h>
    
    void Swap(int *heap, int len);        /* 交换根节点和数组末尾元素的值 */
    void BuildMaxHeap(int *heap, int len);/* 构建大顶堆 */
    
    int main()
    {
        int a[6] = {7, 3, 8, 5, 1, 2};
        int len = 6;    /* 数组长度 */
        int i;
    
        for (i = len; i > 0; i--)
        {
            BuildMaxHeap(a, i);
            Swap(a, i);
        }
        for (i = 0; i < len; i++)
        {
            printf("%d ", a[i]);
        }
    
        return 0;
    }
    /* Function: 构建大顶堆 */
    void BuildMaxHeap(int *heap, int len)
    {
        int i;
        int temp;
    
        for (i = len/2-1; i >= 0; i--)
        {
            if ((2*i+1) < len && heap[i] < heap[2*i+1])    /* 根节点大于左子树 */
            {
                temp = heap[i];
                heap[i] = heap[2*i+1];
                heap[2*i+1] = temp;
                /* 检查交换后的左子树是否满足大顶堆性质 如果不满足 则重新调整子树结构 */
                if ((2*(2*i+1)+1 < len && heap[2*i+1] < heap[2*(2*i+1)+1]) || (2*(2*i+1)+2 < len && heap[2*i+1] < heap[2*(2*i+1)+2]))
                {
                    BuildMaxHeap(heap, len);
                }
            }
            if ((2*i+2) < len && heap[i] < heap[2*i+2])    /* 根节点大于右子树 */
            {
                temp = heap[i];
                heap[i] = heap[2*i+2];
                heap[2*i+2] = temp;
                /* 检查交换后的右子树是否满足大顶堆性质 如果不满足 则重新调整子树结构 */
                if ((2*(2*i+2)+1 < len && heap[2*i+2] < heap[2*(2*i+2)+1]) || (2*(2*i+2)+2 < len && heap[2*i+2] < heap[2*(2*i+2)+2]))
                {
                    BuildMaxHeap(heap, len);
                }
            }
        }
    }
    
    /* Function: 交换交换根节点和数组末尾元素的值*/
    void Swap(int *heap, int len)
    {
        int temp;
    
        temp = heap[0];
        heap[0] = heap[len-1];
        heap[len-1] = temp;
    }
    

    转自https://www.cnblogs.com/lanhaicode/p/10546257.html

    展开全文
  • 大顶堆(C语言)

    2011-12-26 19:17:02
    用C语言开发的调整大顶堆程序,欢迎下载使用。
  • 这两天在复习大顶堆和小顶堆,比起两年前的懵懵懂懂,这次理解起来就容易了一些。又翻看了一下自己之前的笔记数据结构与算法之PHP排序算法(堆排序),发现自己这次查阅资料,和之前的思路不太一样,遂写下这篇笔记,...
  • 大顶堆小顶堆,也叫优先队列,是一种基于数组+平衡二叉树的数据结构。 主要用于排序,增减操作的速度较快(O(logn)) 适合带有优先级的排序场景,比如处理订单的时候,VIP用户的优先级更高,当前队列有人排队可以...
  • 堆排序(浅谈大顶堆与小顶堆)

    万次阅读 多人点赞 2019-09-14 15:20:11
    堆是一种非线性结构,(本篇随笔主要分析堆的数组实现)可以把堆看作一个数组,也可以被看作一个完全二叉树,通俗来讲堆其实就是利用完全二叉树的结构来维护的一维数组,按照堆的特点可以把堆分为大顶堆和小顶堆。...
  • 数据结构之大顶堆

    2021-05-26 03:12:48
    数据结构之大顶堆(C语言实现)大顶堆:先用顺序表存储节点(0不存储节点),然后把根节点下面的都当做子树(也就是有n-1颗子树),然后把每个子树中的最大值都放在根节点上。从顺序表中第n/2个节点也就是倒数第二行的节点...
  • 堆排序和大顶堆小顶堆

    千次阅读 2020-09-06 11:26:44
    按照堆的特点可以把堆分为大顶堆和小顶堆 大顶堆:每个结点的值都大于或等于其左右孩子结点的值 小顶堆:每个结点的值都小于或等于其左右孩子结点的值 使用堆的原因? 如果仅仅是需要得到一个有序的序列,使用排序就...
  • C++大顶堆和小顶堆

    千次阅读 2020-11-24 09:04:59
    C++大顶堆和小顶堆原理大顶堆小顶堆大顶堆和小顶堆对比图大顶堆和小顶堆的实现代码 原理   堆数据结构是一种数组对象,它可以被视为一颗完全二叉树结构(或者也有可能是满二叉树) 大顶堆   根结点(亦称为堆顶...
  • C学习:基于大顶堆的优先队列数据结构实现分析功能分析函数拆分代码实现方法1方法2参考资料 相关博客:C刷题:LeetCode 239. 滑动窗口最大值 (困难) 分析 高级数据结构:大顶堆 两种实现:数组、二叉树 大顶堆:...
  • 大顶堆代码、小顶堆代码 实际应用及实例代码 小顶堆删除图解代码、插入代码 小顶堆插入图解 时间复杂度分析 1、百度-》概念:堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种...
  • 用二叉树实现大顶堆

    2020-09-14 16:51:21
    一、大顶堆与小顶堆的数据结构 即:大顶堆的每个分支上元素越来越小 小顶堆的每个分支上元素越来越大 二、大顶堆可以用数组实现,也可以用链表实现,本篇用数组来实现大顶堆 一个规律, 从上图中可以看出...
  • 大顶堆和小顶堆的理论是相同的,实现上就只有符号的变化。 堆的含义 堆是类似与完全二叉树的结构(注意可能不是满二叉树), 堆的结构是要使得每个根节点的元素大于(小与)子节点元素,左右节点是没有要求的。 ...
  • 本课程针对上述问题,有针对性的进行了升级 3)授课方式采用图解+算法游戏的方式,让课程生动有趣好理解 4)系统多面的讲解了数据结构和算法, 除常用数据结构和算法外,还包括程序员常用10算法:二分查找算法(非...
  • 文章目录前言一、存储结构1.一维数组存储2.堆的初始化二、入堆操作1.入堆算法(上浮)2.代码实现二、出堆操作1....这两个操作中都要保证在操作完成时仍要保持大顶堆的基本特点,即根节点的值一定大于其左右子
  • 大顶堆和小顶堆相关介绍可参看:北京大学空地学院数据结构与算法 第六章 6.8.2.2 小节代码实现如下class Heap:"""二叉堆的实现 小顶堆"""def __init__(self):self.heapList = [0] # 默认一个 0 做占位,使得根节点的...
  • 大顶堆的建立

    2019-12-01 11:03:05
    父节点总是不大于或者不小于自己的子节点,父节点总是不小于子节点的堆是一个大顶堆,反之则为一个小顶堆 这个堆的最深深度为 (log2n) + 1,所以第 i 个节点所在的层数也为 (log2n) + 1 堆里的第i层最多有 2^i - 1...
  • 一行实现大顶堆 大顶堆和小顶堆的性质 堆是一颗完全二叉树,树中每个结点的值都比其左右孩子的值大或小。 父亲结点 大于等于左右孩子就是大顶堆(从大到小) 小于等于左右孩子就是小顶堆(从小到大) (堆的这种...
  • 大顶堆

    2017-02-26 20:36:25
    大顶堆   根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆(大顶堆)。 大根堆要求根节点的关键字既大于或等于左子女的关键字值,又大于或等于右子女的关键字值,且要求是...
  • 数据结构之堆(大顶堆)实现

    千次阅读 2021-02-17 20:08:30
    按照堆的特点可以把堆分为大顶堆和小顶堆 大顶堆:每个结点的值都大于或等于其左右孩子结点的值 小顶堆:每个结点的值都小于或等于其左右孩子结点的值 完全二叉树的概念在我之前博客已经整理过了。这里不再论述 但是...
  • 堆排序 之前的随笔写了栈(顺序栈、链式栈)、队列(循环队列、链式队列)、链表、二叉树...按照堆的特点可以把堆分为大顶堆和小顶堆 大顶堆:每个结点的值都大于或等于其左右孩子结点的值 小顶堆:每个结点的值...
  • C语言实现大顶堆

    2021-08-04 14:28:03
    代码 #include <stdio.h> #include <...typedef struct Heap { //大顶堆 完全二叉树 int *data, size, len; //data线性 } Heap; Heap *initHeap(int n) { Heap *h = (Heap *)malloc(si
  • 大顶堆和小顶堆是什么? 概述:大顶堆和小顶堆的概念都是从堆的概念引申而来,对于n个元素的关键字序列{K1, K2,…,Kn}, 当且仅当满足下列关系时称其为堆,其中2i和2i+1要求不大于n。 若将此序列对应的一维数组...
  • 大顶堆升序、小顶堆降序的堆排序(以Java描述为例版本) 一、定义 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 33,265
精华内容 13,306
关键字:

大顶堆