面向对象的数据结构 – 以 Heap 为例

面向对象的数据结构 – 以 Heap 为例

之前学数据结构时都是使用 C/C++ 实现,习惯了面向过程的方式再使用面向对象还有一点点不知所措。

借着 操作系统短进程优先算法 的机会第一次尝试使用 Java 实现了一个简单的数据结构。

这里实现的 Heap 只包含了最基础的排序,没有添加任意删除等操作。

Motivation

在此之前,有个还没有公开发表的 flag,想在这学期结束前后写一个运行在 C51/STM32 上的简易的操作系统(尽管这学期才过了四分之一,但我已经可以预见这个 flag 要鸽了),因此最初的选择就是用 C 写。

可经过复习后发现想要实现操作系统级的调度算法有些许困难,如果只是实现算法思想,语言只是工具平台而已。用 C 当然可以,想着写习惯了面向过程还是应该试一下面向对象的方式。

Realization

Heap 的实现还是很简单的,这里整理了一下以前的笔记:

定义

堆是一颗完全二叉树,树中的每一个结点都大于(小于)等于其父亲节点的值,由此引出两种堆:

  • 大根堆:父亲结点的值大于或等于左右节点
  • 小根堆:父亲结点的值小于或等于左右结点

存储方式

由于堆是一个完全二叉树,可以使用数组进行存储,减少空间的浪费。(但也可以使用链式存储)
因为 Java 的链式存储没有写过,这里直接使用了 ArrayList 替代数组进行存储

维护

up 和 down 是两个基础的维护操作,指的是元素在树中的深度变化。两个操作的最坏时间复杂度均为$O(log h)$

在 up 和 down 操作里的 swapcompare 是两个自定义的方法,这里的 PCB 是一个自定义的对象,直接沿用的操作系统实验中的代码片段,具体定义可以参考这里:Operating Systems: Lab 1 SPF(Shortest-Process-First)

public void swap(int u, int v)
{
PCB Temp = heap.get(u);
heap.set(u, heap.get(v));
heap.set(v, Temp);
}
public int compare(int u, int v)
{
PCB pcb1 = this.heap.get(u);
PCB pcb2 = this.heap.get(v);
if (pcb1.getRemaining() == pcb2.getRemaining())
{
if(pcb1.getQueueID() > pcb2.getQueueID())
{
return 1;
}else{
return -1;
}
}
if (pcb1.getRemaining() > pcb2.getRemaining())
{
return 1;
}
if (pcb1.getRemaining() < pcb2.getRemaining())
{
return -1;
}
System.out.println("Error, please check the method!");
return 0;
}

up操作

up操作实际上就是对比自己与父节点的大小,从而进行交换,其中使用while或递归进行书写,可以保证把当前结点调整到合适的位置。

public void up(int point)
{
while (point != 0 && (this.compare(point, point / 2) < 0)) {
this.swap(point, point / 2);
point /= 2;
}
}

down操作

down操作实际上就是用自己和两个子节点进行比较,选择较大的子节点然后和父亲结点交换,其中使用while或者递归的方式书写,可以保证结点被放到合适的位置。

public void down(int point)
{
int size=heap.size()-1;
while(point * 2 <= size)
{
int temp = point;
if(point * 2 <= size && (this.compare(point, point * 2) > 0))
{
temp = 2 * point;
}
if(point * 2 + 1 <= size && (this.compare(temp,point * 2 + 1) > 0))
{
temp = 2 * point + 1;
}
if(point != temp)
{
this.swap(point,temp);
point = temp;
}else break;
}
}

基本操作

Heap 的操作其实也不算少,这里只根据实际实现了三个:

建堆与插入

想要使用数组维护 Heap,需要让数组的下标从 1 开始,对于 ArrayList 来说需要预先插入一个空节点,这里在 Constructor 中进行了处理。

Heap()
{
heap = new ArrayList<PCB>();
PCB emptyPCB = new PCB(-1);
heap.add(emptyPCB);
order = 0;
}

之后的建堆过程是通过不断的插入实现的。

public void push(PCB newNode)
{
newNode.setQueueID(order);
heap.add(newNode);
order++;
this.up(heap.size() - 1);
}

查询最值

由于堆的性质,查找最小(最大)值只需要$O(1)$的操作

public PCB top()
{
if(this.empty()) return null;
else return heap.get(1);
}

删除最值

删除时需要三个操作维护堆的合法性:

  1. 将最后一个元素赋值给堆顶元素
  2. 将最后一个元素删除
  3. 对堆顶元素进行 down() 操作
public void pop()
{
int size = heap.size();
heap.set(1,heap.get(size - 1));
heap.remove(size-1);
down(1);
}

Thinking

从抽象到更抽象,面向对象的思想确实是一个巨大的进步,受限于个人水平,在实现中还带有一些面向过程的风格,技术上还未涉及泛型、接口等内容,再次回忆 C++ 的 STL,也是从 具体 到 抽象 的过程,从学习的角度在一定阶段是要避免使用的,但是在实际的解决问题中 抽象/面向对象 的思的的确确为我们提供了便利。

Code

import java.util.*;
public class Heap
{
ArrayList<PCB> heap;
private static int order;
/*
* Initialize the heap.
* In order make the index of first process is one,
* we need use the constructor to push 'empyt process' to the heap.
*/
Heap()
{
heap = new ArrayList<PCB>();
PCB emptyPCB = new PCB(-1);
heap.add(emptyPCB);
order = 0;
}
/*
* Swap the element in ArrayList.
*/
public void swap(int u, int v)
{
PCB Temp = heap.get(u);
heap.set(u, heap.get(v));
heap.set(v, Temp);
}
/*
* Compare the element in ArrayList.
* The first keyword is remaining time,
* The second keyword is queue id.
*
* Queue id is the order of the element push the heap.
*
* As we know that remaining time may be the same, but queueID is unique.
* So according to the queueID to compare can always be true.
*/
public int compare(int u, int v)
{
PCB pcb1 = this.heap.get(u);
PCB pcb2 = this.heap.get(v);
if (pcb1.getRemaining() == pcb2.getRemaining())
{
if(pcb1.getQueueID() > pcb2.getQueueID())
{
return 1;
}else{
return -1;
}
}
if (pcb1.getRemaining() > pcb2.getRemaining())
{
return 1;
}
if (pcb1.getRemaining() < pcb2.getRemaining())
{
return -1;
}
System.out.println("Error, please check the method!");
return 0;
}
/*
* We need two operations to make the heap is legal.
* The purpose of up() is make the lesser element to upper layer.
* The purpose of down() is make the bigger element to lower layer.
*/
public void up(int point)
{
while (point != 0 && (this.compare(point, point / 2) < 0)) {
this.swap(point, point / 2);
point /= 2;
}
}
public void down(int point)
{
int size=heap.size()-1;
while(point * 2 <= size)
{
int temp = point;
if(point * 2 <= size && (this.compare(point, point * 2) > 0))
{
temp = 2 * point;
}
if(point * 2 + 1 <= size && (this.compare(temp,point * 2 + 1) > 0))
{
temp = 2 * point + 1;
}
if(point != temp)
{
this.swap(point,temp);
point = temp;
}else break;
}
}
/*
* There is some common method of data structure.
*/
public void push(PCB newNode)
{
newNode.setQueueID(order);
heap.add(newNode);
order++;
this.up(heap.size() - 1);
}
public PCB top()
{
if(this.empty()) return null;
else return heap.get(1);
}
public void pop()
{
int size = heap.size();
heap.set(1,heap.get(size - 1));
heap.remove(size-1);
down(1);
}
public boolean empty()
{
if(heap.size() > 1)
{
return false;
}else{
return true;
}
}
public int size()
{
return heap.size();
}
}

评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇