第十五周项目3-B-树的基本操作

本文介绍了一个3阶B-树的实现案例,包括创建、插入及删除等基本操作,并展示了具体的C语言代码实现。
/*          
 *Copyright(c++)2014 烟台大学计算机学院               
 *All rights reserved.               
 *文件名称:main.cpp       
 *作者:李宁               
 *完成日期:2015.12.7       
 *版本号:v1.0               
 *问题描述:
 实现B-树的基本操作。基于序列{4, 9, 0, 1, 8, 6, 3, 5, 2, 7}完成测试。 
(1)创建对应的3阶B-树b,用括号法输出b树。 
(2)从b中分别删除关键字为8和1的节点,用括号法输出删除节点后的b树。 


 
*/ 
#include <stdio.h>
#include <malloc.h>
#define MAXM 10                     //定义B-树的最大的阶数
typedef int KeyType;                //KeyType为关键字类型
typedef struct node                 //B-树结点类型定义
{
    int keynum;                     //结点当前拥有的关键字的个数
    KeyType key[MAXM];              //key[1..keynum]存放关键字,key[0]不用
    struct node *parent;            //双亲结点指针
    struct node *ptr[MAXM];         //孩子结点指针数组ptr[0..keynum]
} BTNode;
typedef struct                      //B-树的查找结果类型
{
    BTNode *pt;                     //指向找到的结点
    int i;                          //1..m,在结点中的关键字序号
    int tag;                        //1:查找成功,O:查找失败
}  Result;
int m;                              //m阶B-树,为全局变量
int Max;                            //m阶B-树中每个结点的至多关键字个数,Max=m-1
int Min;                            //m阶B-树中非叶子结点的至少关键字个数,Min=(m-1)/2
int Search(BTNode *p,KeyType k)
{
    //在p->key[1..keynum]中查找i,使得p->key[i]<=k<p->key[i+1]
    int i=0;
    for(i=0; i<p->keynum && p->key[i+1]<=k; i++);
    return i;
}
Result SearchBTree(BTNode *t,KeyType k)
{
    /*在m阶t树t上查找关键字k,返回结果(pt,i,tag)。若查找成功,则特征值
     tag=1,指针pt所指结点中第i个关键字等于k;否则特征值tag=0,等于k的
     关键字应插入在指针Pt所指结点中第i和第i+1个关键字之间*/
    BTNode *p=t,*q=NULL; //初始化,p指向待查结点,q指向p的双亲
    int found=0,i=0;
    Result r;
    while (p!=NULL && found==0)
    {
        i=Search(p,k);              //在p->key[1..keynum]中查找i,使得p->key[i]<=k<p->key[i+1]
        if (i>0 && p->key[i]==k)    //找到待查关键字
            found=1;
        else
        {
            q=p;
            p=p->ptr[i];
        }
    }
    r.i=i;
    if (found==1)                   //查找成功
    {
        r.pt=p;
        r.tag=1;
    }
    else                            //查找不成功,返回K的插入位置信息
    {
        r.pt=q;
        r.tag=0;
    }
    return r;                       //返回k的位置(或插入位置)
}
void Insert(BTNode *&q,int i,KeyType x,BTNode *ap)
{
    //将x和ap分别插入到q->key[i+1]和q->ptr[i+1]中
    int j;
    for(j=q->keynum; j>i; j--)  //空出一个位置
    {
        q->key[j+1]=q->key[j];
        q->ptr[j+1]=q->ptr[j];
    }
    q->key[i+1]=x;
    q->ptr[i+1]=ap;
    if (ap!=NULL) ap->parent=q;
    q->keynum++;
}
void Split(BTNode *&q,BTNode *&ap)
{
    //将结点q分裂成两个结点,前一半保留,后一半移入新生结点ap
    int i,s=(m+1)/2;
    ap=(BTNode *)malloc(sizeof(BTNode));    //生成新结点*ap
    ap->ptr[0]=q->ptr[s];                   //后一半移入ap
    for (i=s+1; i<=m; i++)
    {
        ap->key[i-s]=q->key[i];
        ap->ptr[i-s]=q->ptr[i];
        if (ap->ptr[i-s]!=NULL)
            ap->ptr[i-s]->parent=ap;
    }
    ap->keynum=q->keynum-s;
    ap->parent=q->parent;
    for (i=0; i<=q->keynum-s; i++) //修改指向双亲结点的指针
        if (ap->ptr[i]!=NULL) ap->ptr[i]->parent = ap;
    q->keynum=s-1;                      //q的前一半保留,修改keynum
}
void NewRoot(BTNode *&t,BTNode *p,KeyType x,BTNode *ap)
{
    //生成含信息(T,x,ap)的新的根结点*t,原t和ap为子树指针
    t=(BTNode *)malloc(sizeof(BTNode));
    t->keynum=1;
    t->ptr[0]=p;
    t->ptr[1]=ap;
    t->key[1]=x;
    if (p!=NULL) p->parent=t;
    if (ap!=NULL) ap->parent=t;
    t->parent=NULL;
}
void InsertBTree(BTNode *&t, KeyType k, BTNode *q, int i)
{
    /*在m阶t树t上结点*q的key[i]与key[i+1]之间插入关键字k。若引起
     结点过大,则沿双亲链进行必要的结点分裂调整,使t仍是m阶t树。*/
    BTNode *ap;
    int finished,needNewRoot,s;
    KeyType x;
    if (q==NULL)                        //t是空树(参数q初值为NULL)
        NewRoot(t,NULL,k,NULL);         //生成仅含关键字k的根结点*t
    else
    {
        x=k;
        ap=NULL;
        finished=needNewRoot=0;
        while (needNewRoot==0 && finished==0)
        {
            Insert(q,i,x,ap);               //将x和ap分别插入到q->key[i+1]和q->ptr[i+1]
            if (q->keynum<=Max) finished=1; //插入完成
            else
            {
                //分裂结点*q,将q->key[s+1..m],q->ptr[s..m]和q->recptr[s+1..m]移入新结点*ap
                s=(m+1)/2;
                Split(q,ap);
                x=q->key[s];
                if (q->parent)              //在双亲结点*q中查找x的插入位置
                {
                    q=q->parent;
                    i=Search(q, x);
                }
                else needNewRoot=1;
            }
        }
        if (needNewRoot==1)                 //根结点已分裂为结点*q和*ap
            NewRoot(t,q,x,ap);              //生成新根结点*t,q和ap为子树指针
    }
}
void DispBTree(BTNode *t)   //以括号表示法输出B-树
{
    int i;
    if (t!=NULL)
    {
        printf("[");            //输出当前结点关键字
        for (i=1; i<t->keynum; i++)
            printf("%d ",t->key[i]);
        printf("%d",t->key[i]);
        printf("]");
        if (t->keynum>0)
        {
            if (t->ptr[0]!=0) printf("(");  //至少有一个子树时输出"("号
            for (i=0; i<t->keynum; i++)     //对每个子树进行递归调用
            {
                DispBTree(t->ptr[i]);
                if (t->ptr[i+1]!=NULL) printf(",");
            }
            DispBTree(t->ptr[t->keynum]);
            if (t->ptr[0]!=0) printf(")");  //至少有一个子树时输出")"号
        }
    }
}
void Remove(BTNode *p,int i)
//从*p结点删除key[i]和它的孩子指针ptr[i]
{
    int j;
    for (j=i+1; j<=p->keynum; j++)      //前移删除key[i]和ptr[i]
    {
        p->key[j-1]=p->key[j];
        p->ptr[j-1]=p->ptr[j];
    }
    p->keynum--;
}
void Successor(BTNode *p,int i)
//查找被删关键字p->key[i](在非叶子结点中)的替代叶子结点
{
    BTNode *q;
    for (q=p->ptr[i]; q->ptr[0]!=NULL; q=q->ptr[0]);
    p->key[i]=q->key[1];    //复制关键字值
}
void MoveRight(BTNode *p,int i)
//把一个关键字移动到右兄弟中
{
    int c;
    BTNode *t=p->ptr[i];
    for (c=t->keynum; c>0; c--) //将右兄弟中所有关键字移动一位
    {
        t->key[c+1]=t->key[c];
        t->ptr[c+1]=t->ptr[c];
    }
    t->ptr[1]=t->ptr[0];        //从双亲结点移动关键字到右兄弟中
    t->keynum++;
    t->key[1]=p->key[i];
    t=p->ptr[i-1];              //将左兄弟中最后一个关键字移动到双亲结点中
    p->key[i]=t->key[t->keynum];
    p->ptr[i]->ptr[0]=t->ptr[t->keynum];
    t->keynum--;
}
void MoveLeft(BTNode *p,int i)
//把一个关键字移动到左兄弟中
{
    int c;
    BTNode *t;
    t=p->ptr[i-1];              //把双亲结点中的关键字移动到左兄弟中
    t->keynum++;
    t->key[t->keynum]=p->key[i];
    t->ptr[t->keynum]=p->ptr[i]->ptr[0];

    t=p->ptr[i];                //把右兄弟中的关键字移动到双亲兄弟中
    p->key[i]=t->key[1];
    p->ptr[0]=t->ptr[1];
    t->keynum--;
    for (c=1; c<=t->keynum; c++)    //将右兄弟中所有关键字移动一位
    {
        t->key[c]=t->key[c+1];
        t->ptr[c]=t->ptr[c+1];
    }
}
void Combine(BTNode *p,int i)
//将三个结点合并到一个结点中
{
    int c;
    BTNode *q=p->ptr[i];            //指向右结点,它将被置空和删除
    BTNode *l=p->ptr[i-1];
    l->keynum++;                    //l指向左结点
    l->key[l->keynum]=p->key[i];
    l->ptr[l->keynum]=q->ptr[0];
    for (c=1; c<=q->keynum; c++)        //插入右结点中的所有关键字
    {
        l->keynum++;
        l->key[l->keynum]=q->key[c];
        l->ptr[l->keynum]=q->ptr[c];
    }
    for (c=i; c<p->keynum; c++)     //删除父结点所有的关键字
    {
        p->key[c]=p->key[c+1];
        p->ptr[c]=p->ptr[c+1];
    }
    p->keynum--;
    free(q);                        //释放空右结点的空间
}
void Restore(BTNode *p,int i)
//关键字删除后,调整B-树,找到一个关键字将其插入到p->ptr[i]中
{
    if (i==0)                           //为最左边关键字的情况
        if (p->ptr[1]->keynum>Min)
            MoveLeft(p,1);
        else
            Combine(p,1);
    else if (i==p->keynum)              //为最右边关键字的情况
        if (p->ptr[i-1]->keynum>Min)
            MoveRight(p,i);
        else
            Combine(p,i);
    else if (p->ptr[i-1]->keynum>Min)   //为其他情况
        MoveRight(p,i);
    else if (p->ptr[i+1]->keynum>Min)
        MoveLeft(p,i+1);
    else
        Combine(p,i);
}
int SearchNode(KeyType k,BTNode *p,int &i)
//在结点p中找关键字为k的位置i,成功时返回1,否则返回0
{
    if (k<p->key[1])    //k小于*p结点的最小关键字时返回0
    {
        i=0;
        return 0;
    }
    else                //在*p结点中查找
    {
        i=p->keynum;
        while (k<p->key[i] && i>1)
            i--;
        return(k==p->key[i]);
    }
}
int RecDelete(KeyType k,BTNode *p)
//查找并删除关键字k
{
    int i;
    int found;
    if (p==NULL)
        return 0;
    else
    {
        if ((found=SearchNode(k,p,i))==1)       //查找关键字k
        {
            if (p->ptr[i-1]!=NULL)              //若为非叶子结点
            {
                Successor(p,i);                 //由其后继代替它
                RecDelete(p->key[i],p->ptr[i]); //p->key[i]在叶子结点中
            }
            else
                Remove(p,i);                    //从*p结点中位置i处删除关键字
        }
        else
            found=RecDelete(k,p->ptr[i]);       //沿孩子结点递归查找并删除关键字k
        if (p->ptr[i]!=NULL)
            if (p->ptr[i]->keynum<Min)          //删除后关键字个数小于MIN
                Restore(p,i);
        return found;
    }
}
void DeleteBTree(KeyType k,BTNode *&root)
//从B-树root中删除关键字k,若在一个结点中删除指定的关键字,不再有其他关键字,则删除该结点
{
    BTNode *p;              //用于释放一个空的root
    if (RecDelete(k,root)==0)
        printf("   关键字%d不在B-树中\n",k);
    else if (root->keynum==0)
    {
        p=root;
        root=root->ptr[0];
        free(p);
    }
}
int main()
{
    BTNode *t=NULL;
    Result s;
    int j,n=10;
    KeyType a[]= {4,9,0,1,8,6,3,5,2,7},k;
    m=3;                                //3阶B-树
    Max=m-1;
    Min=(m-1)/2;
    printf("创建一棵%d阶B-树:\n",m);
    for (j=0; j<n; j++)                 //创建一棵3阶B-树t
    {
        s=SearchBTree(t,a[j]);
        if (s.tag==0)
            InsertBTree(t,a[j],s.pt,s.i);
        printf("   第%d步,插入%d: ",j+1,a[j]);
        DispBTree(t);
        printf("\n");
    }
    printf("  结果B-树: ");
    DispBTree(t);
    printf("\n");
    printf("删除操作:\n");
    k=8;
    DeleteBTree(k,t);
    printf("  删除%d: ",k);
    printf("B-树: ");
    DispBTree(t);
    printf("\n");
    k=1;
    DeleteBTree(k,t);
    printf("  删除%d: ",k);
    printf("B-树: ");
    DispBTree(t);
    printf("\n");
    return 0;
}


输出结果:

 

定义B-存储结构(要求m3;为方便操作,结点中增加双亲结点指针域,最底层的Fail结点用NULL指针表示并且所有结点均存储于内存)。定义B-插入关键字函数、删除关键字函数、查找关键字函数以及按层次遍历输出B-所有结点的函数。主函数定义菜单(1.插入关键字 2.删除关键字 3. 查找关键字 4.层次遍历输出B-所有结点 5.结束程序)。 1. 插入关键字功能的输入为一个关键字,输出为新插入关键字所在结点的信息。 要求结点信息输出格式如下所示: (R102, n, K1, K2, …, Kn) R102表示结点位置,R表示根结点指针;第一个数字1表示根结点的A[1]指针,第二个数字0表求R->A[1]所指结点的A[0]指针,第三个数字2表示R->A[1]->A[0]所指结点的A[2]指针,即该结点指针为: R->A[1]->A[0]->A[2](该结点在第4层上)。n为该结点的关键字数目,K1, K2, …, Kn为该结点中n个非递减有序的关键字。 2. 删除关键字功能的输入为一个关键字,输出为删除成功与失败的信息。 3. 查找关键字功能的输入为一个关键字,输出为查找成功与失败的信息,查找成功时,应输出关键字所在结点信息(结点信息输出方法同1.)。 4. 按层次遍历输出B-所有结点功能的输入为一个字符文件名,输出为该字符文件,字符文件中,一个结点的信息输出一行(结点信息输出方法同1.),结点输出次序为按层次号由小到大并且同层结点从左向右。
// BinaryTreePanel_Part1.java import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.LinkedList; import java.util.Queue; import java.util.Random; import java.util.ArrayList; import java.util.List; class BinaryTreePanel extends JPanel { ProcessNode root; private ResourceManager resourceManager; private Random random; int processId; private Font chineseFont; private Timer timeSliceTimer; public BinaryTreePanel() { this.root = null; this.resourceManager = new ResourceManager(3); this.random = new Random(); this.processId = 0; chineseFont = createChineseFont(); setBackground(Color.WHITE); setLayout(new BorderLayout()); timeSliceTimer = new Timer(1000, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { performTimeSliceCountdown(); } }); timeSliceTimer.start(); } private Font createChineseFont() { String[] fontNames = { "Microsoft YaHei", "SimHei", "SimSun", "KaiTi", "Arial Unicode MS", "Dialog", "SansSerif" }; for (String fontName : fontNames) { Font font = new Font(fontName, Font.PLAIN, 12); if (font.canDisplay(&#39; &#39;)) { return font; } } return new Font("Dialog", Font.PLAIN, 12); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); drawResourceAndQueueInfo(g2d); if (root != null) { drawTree(g2d, root); } drawQueueVisualization(g2d); } private void drawResourceAndQueueInfo(Graphics2D g2d) { g2d.setColor(Color.BLACK); g2d.setFont(chineseFont.deriveFont(Font.BOLD, 16)); g2d.drawString("I/O Դ1: " + resourceManager.getAvailableResources(), 20, 30); g2d.drawString("I/O Դ2: " + resourceManager.getAvailableResource2(), 20, 55); g2d.drawString(" CPU: " + resourceManager.getAvailableCPU(), 20, 80); g2d.setColor(Color.MAGENTA); g2d.drawString("ʱ Ƭ: " + resourceManager.getTimeSlice(), 20, 105); g2d.setFont(chineseFont.deriveFont(Font.BOLD, 14)); g2d.setColor(Color.BLUE); g2d.drawString(" : " + resourceManager.getReadyCount(), 200, 30); g2d.setColor(Color.GREEN); g2d.drawString(" ж : " + resourceManager.getRunningCount(), 350, 30); g2d.setColor(Color.RED); g2d.drawString(" 1 : " + resourceManager.getBlocked1Count(), 500, 30); g2d.setColor(Color.ORANGE); g2d.drawString(" 2 : " + resourceManager.getBlocked2Count(), 650, 30); g2d.setColor(Color.BLACK); g2d.drawString(" ܽ : " + (root != null ? countNodes(root) : 0), 800, 30); g2d.setColor(Color.RED); g2d.drawString("ʱ Ƭ Զ ʱ: ON", 20, 130); g2d.setFont(chineseFont.deriveFont(Font.BOLD, 12)); g2d.setColor(Color.MAGENTA); g2d.drawString(" ɫ: Դ", 20, 155); } private void drawTree(Graphics2D g2d, ProcessNode node) { if (node == null) return; if (node.left != null) { g2d.setColor(Color.GRAY); g2d.drawLine(node.x, node.y, node.left.x, node.left.y); drawTree(g2d, node.left); } if (node.right != null) { g2d.setColor(Color.GRAY); g2d.drawLine(node.x, node.y, node.right.x, node.right.y); drawTree(g2d, node.right); } Color nodeColor; if (node.status.equals("RUNNING")) { if (node.hasResource2 && node.resources > 0) { nodeColor = Color.MAGENTA; } else { nodeColor = Color.GREEN; } } else if (node.status.equals("BLOCKED_RESOURCE2")) { nodeColor = Color.ORANGE; } else if (node.status.equals("BLOCKED_RESOURCE1")) { nodeColor = Color.RED; } else { if (node.hasResource2 && node.resources > 0) { nodeColor = Color.MAGENTA; } else { nodeColor = Color.BLUE; } } g2d.setColor(nodeColor); g2d.fillOval(node.x - 25, node.y - 25, 50, 50); g2d.setColor(Color.WHITE); g2d.setFont(chineseFont.deriveFont(Font.BOLD, 12)); String nodeInfo = node.name; FontMetrics fm = g2d.getFontMetrics(); int textWidth = fm.stringWidth(nodeInfo); g2d.drawString(nodeInfo, node.x - textWidth / 2, node.y - 8); g2d.setFont(chineseFont.deriveFont(Font.PLAIN, 10)); String statusInfo = node.status; if (node.status.equals("BLOCKED_RESOURCE1")) { statusInfo = "BLOCKED(R1)"; } else if (node.status.equals("BLOCKED_RESOURCE2")) { statusInfo = "BLOCKED(R2)"; } statusInfo += "(" + node.resources + ")"; if (node.hasResource2) { statusInfo += "+R2"; } textWidth = fm.stringWidth(statusInfo); g2d.drawString(statusInfo, node.x - textWidth / 2, node.y + 5); String idInfo = "ID:" + node.id; if (node.status.equals("RUNNING") && node.remainingTime > 0) { idInfo += " T:" + node.remainingTime; } textWidth = fm.stringWidth(idInfo); g2d.drawString(idInfo, node.x - textWidth / 2, node.y + 18); } private void drawQueueVisualization(Graphics2D g2d) { int panelHeight = getHeight(); int queueStartY = panelHeight - 220; int boxHeight = 30; int boxWidth = 80; int spacing = 5; g2d.setColor(Color.BLUE); g2d.setFont(chineseFont.deriveFont(Font.BOLD, 14)); g2d.drawString(" (" + resourceManager.getReadyCount() + "):", 50, queueStartY - 10); int xPos = 50; int queueIndex = 1; for (ProcessNode process : resourceManager.getReadyQueue()) { if (process.hasResource2 && process.resources > 0) { g2d.setColor(Color.MAGENTA); } else { g2d.setColor(Color.BLUE); } g2d.fillRect(xPos, queueStartY, boxWidth, boxHeight); g2d.setColor(Color.WHITE); g2d.drawString(process.name, xPos + 5, queueStartY + 15); String resourceInfo = "R1:" + process.resources; if (process.hasResource2) { resourceInfo += "+R2"; } g2d.drawString(resourceInfo, xPos + 5, queueStartY + 28); g2d.setColor(Color.YELLOW); g2d.drawString("" + queueIndex, xPos + boxWidth - 15, queueStartY + 15); xPos += boxWidth + spacing; queueIndex++; } g2d.setColor(Color.GREEN); g2d.drawString(" ж (" + resourceManager.getRunningCount() + "):", 50, queueStartY + 50); xPos = 50; for (ProcessNode process : resourceManager.getRunningQueue()) { if (process.hasResource2 && process.resources > 0) { g2d.setColor(Color.MAGENTA); } else { g2d.setColor(Color.GREEN); } g2d.fillRect(xPos, queueStartY + 60, boxWidth, boxHeight); g2d.setColor(Color.WHITE); g2d.drawString(process.name, xPos + 5, queueStartY + 75); g2d.drawString("T:" + process.remainingTime, xPos + 5, queueStartY + 88); xPos += boxWidth + spacing; } g2d.setColor(Color.RED); g2d.drawString(" һ (" + resourceManager.getBlocked1Count() + "):", 50, queueStartY + 110); xPos = 50; for (ProcessNode process : resourceManager.getBlockedQueue1()) { g2d.setColor(Color.RED); g2d.fillRect(xPos, queueStartY + 120, boxWidth, boxHeight); g2d.setColor(Color.WHITE); g2d.drawString(process.name, xPos + 15, queueStartY + 140); xPos += boxWidth + spacing; } g2d.setColor(Color.ORANGE); g2d.drawString(" ڶ (" + resourceManager.getBlocked2Count() + "):", 50, queueStartY + 170); xPos = 50; for (ProcessNode process : resourceManager.getBlockedQueue2()) { g2d.setColor(Color.ORANGE); g2d.fillRect(xPos, queueStartY + 180, boxWidth, boxHeight); g2d.setColor(Color.WHITE); g2d.drawString(process.name, xPos + 15, queueStartY + 200); xPos += boxWidth + spacing; } } private int countNodes(ProcessNode node) { if (node == null) return 0; return 1 + countNodes(node.left) + countNodes(node.right); } private void calculatePositions() { if (root == null) return; int levelHeight = 80; int startX = getWidth() / 2; int startY = 120; calculatePositionRecursive(root, startX, startY, Math.max(200, getWidth() / 4), levelHeight, 0); } private void calculatePositionRecursive(ProcessNode node, int x, int y, int offsetX, int levelHeight, int level) { if (node == null) return; node.x = x; node.y = y; if (node.left != null) { calculatePositionRecursive(node.left, x - offsetX, y + levelHeight, offsetX / 2, levelHeight, level + 1); } if (node.right != null) { calculatePositionRecursive(node.right, x + offsetX, y + levelHeight, offsetX / 2, levelHeight, level + 1); } } @Override public Dimension getPreferredSize() { return new Dimension(1000, 1000); } }将改代码按照一、实验目的 理解操作系统的进程管理机制:通过可视化方式展示进程状态转换、资源分配与调度过程。 掌握PV操作原理:模拟信号量机制,实现进程的同步与互斥。 学习进程调度算法:实现时间片轮转(RR)调度算法,理解多队列管理。 提升面向对象编程能力:使用Java Swing开发图形界面,实践模块化设计思想。 培养系统分析与问题解决能力:设计并实现一个完整的进程管理系统。 二、实验环境 项目 说明 操作系统 Windows 10/11、macOS或Linux 开发语言 Java JDK版本 JDK 8及以上 GUI框架 Java Swing 开发工具 IntelliJ IDEA / Eclipse 内存要求 推荐2GB以上 三、系统设计 3.1 系统架构 系统采用三层架构设计: 3.2 核心类设计 3.2.1 ProcessNode(进程节点类) 表示二叉中的进程节点,包含进程的所有状态信息。 class ProcessNode { // 基本属性 int id; // 进程唯一标识 String name; // 进程名(P0, P1, ...) ProcessNode left, right; // 二叉左右子节点 ProcessNode parent; // 父节点(便于删除操作) // 显示属性 int x, y; // 绘制坐标 Color color; // 节点颜色 // 状态属性 String status; // 进程状态 int resources; // 持有的第一种资源数量 boolean hasResource2; // 是否持有第二种资源 int remainingTime; // 剩余时间片 String blockedReason; // 阻塞原因 boolean needsTwoResources; // 需要两类资源标志 } 状态说明: READY:就绪状态(蓝色),持有第一种资源,等待CPU分配。 RUNNING:运行状态(绿色/紫色),正在CPU上执行。 BLOCKED_RESOURCE1:第一阻塞状态(红色),因第一种I/O资源不足而阻塞。 BLOCKED_RESOURCE2:第二阻塞状态(橙色),因第二种I/O资源不足而阻塞。 3.2.2 ResourceManager(资源管理器类) 负责所有资源管理和进程调度,实现信号量机制。 class ResourceManager { // 资源定义 private int availableResources; // 第一种I/O资源(初始3个) private int availableResource2; // 第二种I/O资源(初始1个) private int availableCPU; // 可用CPU数量(初始1个) // 队列管理 private Queue<ProcessNode> blockedQueue1; // 第一阻塞队列 private Queue<ProcessNode> blockedQueue2; // 第二阻塞队列 private LinkedList<ProcessNode> readyQueue; // 就绪队列(FCFS) private List<ProcessNode> runningQueue; // 运行队列 // 调度参数 private int timeSlice; // 时间片大小(默认3) } 3.2.3 BinaryTreePanel(二叉面板类) 主显示面板,负责进程的绘制和用户交互。 3.2.4 ProcessBinaryTreeVisualization(主界面类) 应用程序主窗口,集成所有组件。 3.3 数据结构设计 3.3.1 二叉结构 根节点:P0进程,不参与资源分配。 插入规则:随机选择左子或右子插入新节点。 删除规则:删除节点时保留其子节点,重新连接到父节点。 3.3.2 四队列管理 就绪队列(READY):持有第一种资源,等待CPU。 运行队列(RUNNING):正在执行的进程(最多1个)。 第一阻塞队列(BLOCKED_RESOURCE1):因第一种资源不足阻塞。 第二阻塞队列(BLOCKED_RESOURCE2):因第二种资源不足阻塞。 3.4 算法设计 3.4.1 进程添加算法 输入:新进程节点process 输出:是否成功添加 if process.id == 0: return true // P0不参与资源分配 if availableResources > 0: availableResources-- readyQueue.add(process) process.status = "READY" process.resources = 1 checkReadyToRunning() // 检查是否可以运行 else: blockedQueue1.add(process) process.status = "BLOCKED_RESOURCE1" return true 3.4.2 PV操作算法 procedure performPVOperation(): // 优先处理第二阻塞队列(需要两种资源) while availableResources > 0 and availableResource2 > 0 and blockedQueue2非空: process = blockedQueue2.poll() readyQueue.add(process) availableResources-- availableResource2-- process.status = "READY" process.hasResource2 = true process.color = Color.MAGENTA // 然后处理第一阻塞队列(只需要第一种资源) while availableResources > 0 and blockedQueue1非空: process = blockedQueue1.poll() readyQueue.add(process) availableResources-- process.status = "READY" process.resources = 1 checkReadyToRunning() // 检查是否有进程可以运行 3.4.3 时间片调度算法 procedure performTimeSliceCountdown(): if runningQueue非空: runningProcess = runningQueue.get(0) runningProcess.remainingTime-- if runningProcess.remainingTime <= 0: // 时间片用完 runningQueue.remove(runningProcess) readyQueue.add(runningProcess) // 移回就绪队列尾部 runningProcess.status = "READY" availableCPU++ // 检查是否有新的进程可以运行 checkReadyToRunning() 3.4.4 第二种资源申请算法 function requestResource2(process): if availableResource2 > 0: // 申请成功 availableResource2-- process.hasResource2 = true process.color = Color.MAGENTA return true else: // 申请失败,进入第二阻塞队列 runningQueue.remove(process) blockedQueue2.add(process) process.status = "BLOCKED_RESOURCE2" process.color = Color.ORANGE // 释放持有的第一种资源 availableResources += process.resources process.resources = 0 // 释放CPU availableCPU++ // 检查第一阻塞队列 checkBlocked1ToReady() return false 四、系统实现 4.1 界面设计 4.1.1 主界面布局 4.1.2 颜色编码系统 颜色 含义 状态 资源持有情况 蓝色 就绪 READY 持有第一种资源 绿色 运行 RUNNING 持有第一种资源 紫色 双资源 RUNNING/READY 持有两种资源 红色 第一阻塞 BLOCKED_RESOURCE1 无资源 橙色 第二阻塞 BLOCKED_RESOURCE2 无资源 4.2 关键功能实现 4.2.1 进程绘制 坐标计算:递归计算每个节点的(x, y)坐标。 节点绘制:绘制圆形节点,内部显示进程信息。 连接线绘制:绘制父子节点间的连接线。 4.2.2 队列可视化 位置固定:在面板底部显示四个队列。 动态更新:队列内容随进程状态变化实时更新。 颜色区分:不同队列使用不同颜色边框。 4.2.3 自动时间片倒计时 // 每秒触发一次时间片倒计时 timeSliceTimer = new Timer(1000, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { performTimeSliceCountdown(); } }); timeSliceTimer.start(); 4.3 用户交互功能 4.3.1 控制按钮功能 按钮名称 功能说明 触发事件 添加单个进程 插入一个新进程节点 insertNode() 随机生成5个进程 批量添加5个进程 generateRandomNodes(5) 删除随机节点 随机删除一个节点 deleteRandomNode() 删除指定节点 用户选择节点删除 deleteSpecificNode() 执行PV操作 执行一次PV操作 executePVOperation() 申请第二种资源 运行进程申请资源2 requestResource2() 手动时间片轮换 强制时间片轮换 executeTimeSliceRotation() 设置时间片大小 修改时间片长度 setTimeSlice() 随机操作 随机执行一个操作 performRandomOperation() 开始/停止自动操作 自动模式切换 toggleAutoOperation() 清空所有节点 重置系统 clearAllNodes() 4.3.2 自动操作模式 启动:每2秒随机执行一个操作(PV、申请资源2、时间片轮换、随机操作)。 停止:恢复手动操作模式。 禁用按钮:自动模式下禁用大部分手动操作按钮。 五、实验结果与分析 5.1 功能测试 测试1:基本功能测试 测试项目 操作步骤 预期结果 实际结果 通过 添加进程 点击"添加单个进程" 进程新增节点,资源减少 符合预期 ✓ 资源分配 连续添加4个进程 前3个就绪,第4个阻塞 符合预期 ✓ PV操作 删除进程后执行PV 阻塞进程进入就绪队列 符合预期 ✓ 时间片倒计时 等待3秒 运行进程移回就绪队列 符合预期 ✓ 测试2:边界条件测试 测试场景 测试方法 结果 说明 空操作 清空后执行各种操作 显示提示信息 正确处理 资源耗尽 添加大量进程 后续进程进入阻塞队列 队列管理正常 时间片边界 设置时间片为1 快速轮换 调度正常 节点删除 删除根节点 结构保持完整 删除逻辑正确 测试3:并发操作测试 测试组合 操作序列 结果 稳定性 混合操作1 添加→PV→删除→申请资源2 状态正确转换 稳定 混合操作2 自动模式下连续操作 无崩溃,响应正常 稳定 压力测试 快速连续点击按钮 界面无闪烁,响应及时 良好 5.2 性能分析 响应时间测试 操作类型 10个节点 30个节点 50个节点 添加节点 10ms 15ms 20ms 删除节点 12ms 18ms 25ms PV操作 15ms 22ms 30ms 界面刷新 20ms 30ms 45ms 内存使用分析 节点数量 内存占用 CPU使用率 备注 10个节点 ~45MB 2-5% 基础占用 30个节点 ~55MB 3-7% 正常范围 50个节点 ~65MB 5-10% 可接受 100个节点 ~85MB 8-15% 建议不超过50节点 5.3 算法正确性验证 PV操作正确性 通过以下场景验证: 场景1:释放第一种资源后,第一阻塞队列进程正确进入就绪队列。 场景2:释放第二种资源后,第二阻塞队列进程正确进入就绪队列。 场景3:同时释放两种资源,第二阻塞队列优先于第一阻塞队列。 时间片调度正确性 验证点: 时间片自动倒计时功能正常。 时间片用完自动移回就绪队列尾部。 就绪队列头进程正确进入运行队列。 资源管理正确性 验证点: 资源分配符合初始设置(资源1=3,资源2=1,CPU=1)。 资源释放后正确回收。 资源申请失败时进程状态正确转换。 六、创新点与特色 6.1 技术创新 双阻塞队列设计 区分因不同资源类型阻塞的进程。 实现不同的唤醒条件(第二阻塞队列需要两种资源)。 动态颜色编码系统 五种颜色清晰区分五种进程状态。 紫色特别标识持有两种资源的特殊状态。 混合调度策略 时间片轮转(RR)用于CPU调度。 先来先服务(FCFS)用于就绪队列管理。 优先级调度用于PV操作(第二阻塞队列优先)。 实时可视化 进程与队列同步显示。 状态变化实时反馈。 6.2 教学价值 直观理解:将抽象的进程状态转换为可视化的颜色和图形。 交互学习:支持手动操作,亲身体验调度过程。 实验验证:可以验证各种调度算法的效果。 错误模拟:可以模拟死锁、资源竞争等场景。 6.3 用户体验 操作简便:按钮布局合理,功能明确。 信息完整:实时显示资源状态、队列长度等关键信息。 提示友好:操作结果通过对话框提示。 容错性强:对非法操作有友好的提示信息。 七、问题与改进 7.1 遇到的问题及解决方案 问题描述 难点分析 解决方案 效果 节点删除后资源释放逻辑复杂 不同状态节点需要不同的资源处理 设计统一的releaseNodeResources()方法 资源释放正确 界面刷新频繁导致闪烁 频繁重绘影响视觉体验 使用双缓冲技术,优化绘制逻辑 显示流畅 中文显示兼容性问题 不同系统字体支持不一致 创建字体检测机制,自动选择可用字体 中文正常显示 自动模式下的并发控制 多线程操作可能导致状态不一致 使用synchronized关键字同步关键方法 线程安全 7.2 系统局限性 扩展性有限:只支持两种资源类型,难以扩展到多种资源。 调度算法单一:仅实现了时间片轮转,缺少其他调度算法。 进程数限制:界面空间有限,大量节点时显示拥挤。 缺乏持久化:关闭程序后数据丢失。 7.3 改进建议 短期改进(1-2) 增加更多调度算法(优先级调度、多级反馈队列)。 添加进程间通信机制模拟。 实现数据导出功能(保存实验记录)。 中期改进(1-2个月) 支持更多资源类型(可配置)。 添加死锁检测和恢复机制。 实现分布式进程模拟。 长期改进(3-6个月) 开发Web版本,支持多用户协作。 集成机器学习算法,智能优化调度。 构建完整的操作系统教学平台。 八、实验总结 8.1 主要成果 成功实现了完整的进程管理系统: 支持进程的创建、删除、状态转换。 实现了PV操作、时间片调度、资源管理。 开发了直观的可视化界面: 进程和队列同步显示。 实时状态更新和颜色编码。 构建了交互式实验平台: 支持手动和自动两种操作模式。 提供丰富的操作选项。 验证了操作系统核心概念: 进程状态转换、资源分配、调度算法。 信号量机制、PV操作、时间片轮转。 8.2 技术收获 Java编程能力提升: 熟练使用Swing开发图形界面。 掌握多线程编程和同步机制。 实践面向对象设计原则。 系统设计能力培养: 从需求分析到系统实现的完整流程。 模块化设计和接口定义。 异常处理和用户交互设计。 操作系统知识深化: 深入理解进程管理和调度算法。 掌握资源分配和同步机制。 理解可视化在教学中的价值。 8.3 心得体会 通过本次实验,我深刻体会到: 理论与实践结合的重要性:书本上的操作系统概念通过可视化实现变得直观易懂。 系统设计的复杂性:即使是相对简单的系统,也需要考虑各种边界条件和异常情况。 用户交互的关键性:良好的用户体验需要精心设计界面和操作流程。 持续改进的价值:系统开发是一个不断迭代和完善的过程。 来写一份报告
最新发布
12-19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值