1.優先キュー
- 概念:キューは先入れ先出し(FIFO)データ構造ですが、場合によっては、操作のデータが優先されることがあります。通常、優先度の高い要素を最初にキューから外す必要があります。シナリオ、キューを使用することは明らかに不適切です。たとえば、携帯電話でゲームをプレイするときに、着信がある場合、システムは着信を優先する必要があります。この場合、データ構造は2つの最も基本的な操作を提供する必要があります。1つは最も優先度の高いオブジェクトを返すことであり、もう1つは新しいオブジェクトを追加することです。このデータ構造が優先キューです。
import java.util.PriorityQueue;
public class TestDemo {
public static void main(String[] args) {
//优先级队列(最下面实现方法是堆),从小到大排列好的数据,
PriorityQueue priorityQueue = new PriorityQueue();
priorityQueue.offer(13);
priorityQueue.offer(3);
priorityQueue.offer(8);
priorityQueue.offer(49);
/*
本来按照队列的先进先出原则,队头元素是13,但是对于优先级队列来说,内部已经排好序,因此是3
*/
System.out.println(priorityQueue.peek());
System.out.println(priorityQueue.poll());//弹出队头3
System.out.println(priorityQueue.peek());//再次获取新队头就是8,这就是优先级队列
}
}
- 出力結果
2.ヒープ
-
概念:キーコードセットK = {k0、k1、k2、...、kn-1}がある場合、そのすべての要素を完全な二分木の順序で1次元配列に格納し、次の条件を満たす:Ki <= K2i +1およびKi <= K2i + 2(Ki> = K2i +1およびKi> = K2i + 2)i = 0、1、2 ...、これは小さなパイル(または大きなパイル)と呼ばれます。ルートノードが最大のヒープは最大ヒープまたはラージルートヒープと呼ばれ、ルートノードが最小のヒープは最小ヒープまたはスモールルートヒープと呼ばれます。
-
機能:
1。ヒープは常に完全なバイナリツリーです
。2。大きなルートヒープの場合、サブツリーのルートの値は、その左右のノード以上です。3 ...小さなルートパイルの場合、サブツリーのルートの値は、その左右のノードより大きくありません。
-
ヒープの格納方法:ヒープの概念から、ヒープは完全な二分木であることがわかっているため、階層の規則に従って順次効率的に格納できます。
-
重要な点に注意してください(たとえば、3つの重要なこと)
1。iが0の場合、iで表されるノードはルートノードです。それ以外の場合、iノードの親ノードは(i-1)/ 2です
。ルートノードの添え字はi、左側の子ノードは2 i + 1(存在する場合)、
3。右側の子ノードは2 i + 2(存在する場合)です。
1.杭の工法(大根杭と小根杭の工法と同じ)
-下方調整の方法
下向き調整とは、各サブツリーの観点から、親ノードから下向きに調整されることを意味するため、下向き調整と呼ばれます。
//将数组先放入堆中,然后调用向下调整方法(adjustDown),构造堆
public void initHeap(int[] arr){
for (int i = 0;i < arr.length;i++){
elem[i] = arr[i];
usedSize++;
}
//建堆时间复杂度O(n*log2n),一棵子树完成后,j就减减,这样就能够找到下一棵子树的双亲下标了
for (int j = (usedSize-1-1)/2;j >= 0;j--){
adjustDown(j,usedSize);
}
}
//向下调整的方法构造堆(大根堆)(时间复杂度log2n)
//参数传入的parent是最后一棵子树双亲下标,len就是usedSize,堆中节点的个数
public void adjustDown(int parent,int len){
int child = 2*parent+1;//左孩子下标
//1.首先判断是不是有左孩子
while(child < len){
//是否有右孩子,如果有的话,child保存的是左右孩子中最大值的下标;
if (child+1 < len && elem[child] < elem[child+1]){
child++;
}
//到这步,说明已经确定左右孩子中最大值的下标了,则和双亲节点开始进行比较
if(elem[child] > elem[parent]){
int tmp = elem[child];
elem[child] = elem[parent];
elem[parent] = tmp;
//下面这俩个式子用来进行判断子树,是否已经构造完毕
parent = child;
child = 2*parent+1;
}else{
//否则直接跳出循环,因为只要双亲节点大于孩子中最大值,那么子树一定已经构造成为一棵大根堆
break;
}
}
}
-上方調整の方法
上向きの調整は、子から親ノードまでの各サブツリーを調整することです。
//用向上调整的方法构造堆(向上调整就是从孩子节点向上,向双亲节点调整)
public void adjustUp(int child){
//知道孩子节点,根据孩子节点下标和双亲节点下标的关系,得到双亲节点下标
int parent = (child-1)/2;
/*
因为是向上走,因此下标肯定是从小到大,最后到根节点是0,
所以这里是大于0;child等于0;这个堆就已经走完了;
*/
while(child > 0){
if(elem[child] > elem[parent]){
//如果大于双亲节点,就交换;
int tmp = elem[child];
elem[child] = elem[parent];
elem[parent] = tmp;
//检查子树是否已经完成
child = parent;
parent = (child-1)/2;
}else{
break;
}
}
}
2.ヒープに新しい要素を追加します
- アイデア:次の図に示すように、最大のヒープの直後に追加し、37の右側のノード位置に直接追加してから、調整またはダウン調整を呼び出して、ヒープを直接再構築します。
//模拟队列形式,给堆添加新元素
public void push(int val){
//1.首先判断该堆的存储大小是否已经满了,如果满了要进行扩容
if (isFull()){
this.elem = Arrays.copyOf(this.elem,elem.length*2);//空间扩展为原来的二倍
}
elem[usedSize] = val;//将新的值,直接加到堆的最后面
usedSize++;//进行加加,说明堆中元素又多了一个;
adjustUp(this.usedSize-1);//然后从孩子开始,向上进行调整
}
//判断堆是否已经满了
public boolean isFull(){
return this.usedSize == this.elem.length;
}
3.ヒープから
- アイデア:ヒープの固定要素をヒープの最後の要素と交換し、それをポップアウトして、ヒープを再度調整します
//出堆(弹出堆顶元素)
public void pop(){
/*
思路:将堆定元素和堆最后一个元素进行交换,然后弹出,并且将堆重新进行调整
*/
//判断是否为空
if (isEmpty()){
return;
}
// 1.交换
int tmp = elem[0];
this.elem[0] = this.elem[this.usedSize-1];
this.elem[this.usedSize-1] = tmp;
this.usedSize--;//数据个数减少
//2.调用向下调整方法
adjustDown(0,usedSize);
System.out.println("=====");
}
//判断是否为空
public boolean isEmpty(){
return usedSize == 0;
}
}
完全なコード
import java.util.Arrays;
public class MyHeap {
int[] elem;
int usedSize;
public MyHeap(){
this.elem = new int[10];
}
//向下调整的方法构造堆(大根堆)(时间复杂度log2n)
//向下调整的意思就是,从每一棵子树的视角来看是从它的双亲节点往下调整的所以叫向下调整的
//参数传入的parent是最后一棵子树双亲下标,len就是usedSize,堆中节点的个数
public void adjustDown(int parent,int len){
int child = 2*parent+1;//左孩子下标
//1.首先判断是不是有左孩子
while(child < len){
//是否有右孩子,如果有的话,child保存的是左右孩子中最大值的下标;
if (child+1 < len && elem[child] < elem[child+1]){
child++;
}
//到这步,说明已经确定左右孩子中最大值的下标了,则和双亲节点开始进行比较
if(elem[child] > elem[parent]){
int tmp = elem[child];
elem[child] = elem[parent];
elem[parent] = tmp;
//下面这俩个式子用来进行判断子树,是否已经构造完毕
parent = child;
child = 2*parent+1;
}else{
//否则直接跳出循环,因为只要双亲节点大于孩子中最大值,那么子树一定已经构造成为一棵大根堆
break;
}
}
}
//将数组先放入堆中,然后调用向下调整方法(adjustDown),构造堆
public void initHeap(int[] arr){
for (int i = 0;i < arr.length;i++){
elem[i] = arr[i];
usedSize++;
}
//建堆时间复杂度O(n*log2n),一棵子树完成后,j就减减,这样就能够找到下一棵子树的双亲下标了
for (int j = (usedSize-1-1)/2;j >= 0;j--){
adjustDown(j,usedSize);
}
}
//用向上调整的方法构造堆(向上调整就是从孩子节点向上,向双亲节点调整)
public void adjustUp(int child){
int parent = (child-1)/2;
/*
因为是向上走,因此下标肯定是从小到大,最后到根节点是0,所以这里是大于0;child等于0;这个堆就已经走完了
*/
while(child > 0){
if(elem[child] > elem[parent]){
int tmp = elem[child];
elem[child] = elem[parent];
elem[parent] = tmp;
child = parent;
parent = (child-1)/2;
}else{
break;
}
}
}
//模拟队列形式,给堆添加新元素
public void push(int val){
//1.首先判断该堆的存储大小是否已经满了,如果满了要进行扩容
if (isFull()){
this.elem = Arrays.copyOf(this.elem,elem.length*2);//空间扩展为原来的二倍
}
elem[usedSize] = val;//将新的值,直接加到堆的最后面
usedSize++;//进行加加,说明堆中元素又多了一个;
adjustUp(this.usedSize-1);//然后从孩子开始,向上进行调整
}
//判断堆是否已经满了
public boolean isFull(){
return this.usedSize == this.elem.length;
}
//出堆(弹出堆顶元素)
public void pop(){
/*
思路:将堆定元素和堆最后一个元素进行交换,然后弹出,并且将堆重新进行调整
*/
//判断是否为空
if (isEmpty()){
return;
}
// 1.交换
int tmp = elem[0];
this.elem[0] = this.elem[this.usedSize-1];
this.elem[this.usedSize-1] = tmp;
this.usedSize--;//数据个数减少
//2.调用向下调整方法
adjustDown(0,usedSize);
System.out.println("=====");
}
//判断是否为空
public boolean isEmpty(){
return usedSize == 0;
}
}
//测试类,用来给堆里面传递元素
public class TestDemo {
public static void main(String[] args) {
int[] arr = {
27, 15, 19, 18, 28, 34, 65, 49, 25, 37};
MyHeap heap = new MyHeap();
heap.initHeap(arr);
}
}