zoukankan      html  css  js  c++  java
  • [ Java学习 ] 实验 银行业务模拟

    题目:




    说明:

    /*
    
    虽然写了3种方法,但是在博客里,只对我自己最满意的法三做了分析
    
    不过其他两个方法,都贴出了代码和注释,说明了我的思路,以及我的bug出在哪里,每种方法是做了什么改进,以及一些总结和分析
    
    */

    /*法一:中转队列法(很不优)
     * 1.法一是我最初的版本,没做任何的优化,除了把找了很久的bug改对以外,就完全是“硬怼”的方法,非常不推荐这个版本,下次一定不会这么写了
     * (以前听到过一句话:第一眼能够想到的方法,往往时空上的效率都不理想...深以为然,这个方法就是这种类型)
     * 2.不推荐是因为,用数组实现队列,其实可能会假溢出,虽然这题看来没什么影响,但养成这样的习惯,毕竟还是不好的
     * 3.不推荐更因为,其实没必要再写一个栈Q3的,一旦数据规模变大,可以说是在时间、空间上都不优,甚至可以说,时间复杂度和空间复杂度都会极高,简称“双差”也不为过,这样的中转站的写法,一定要极力去避免
     * 4.再说一下为什么会写bug,因为今天数据结构才刚学队列,学的不是很熟,就有些按照自己的感觉来写,写时也没有仔细翻书和查资料求证,导致写队列这个类时,出现了bug,这个bug找了我一个下午,询问了多个师兄和老师,最后发现...如果我数据结构学好一些,这是完全可以避免的...
     * (我发现我对STL中的,list、queue、stack等等,我都只是会用,但是如果真正要我自己写代码,我其实是磕磕碰碰,写不出它们的类代码的,这非常不好,所以我打算把循环队列的代码也写一次,见法三)
     * 就算我数据结构学的不是那么好,本来嘛,如果不是太过自信和依赖自己的记忆,如果翻翻书,也是完全不会错的,然而...就是这样一个bug,因为我太过自信,浪费了一个下午,所以我在深刻反省自己...T^T
     */

    class Customer
    {
    	private int num, type, money;
    	private int solve; //记录被处理的次序
    	
    	public Customer(int n, int t, int m)
    	{
    		num = n; type = t; money = m;
    	}
    	
    	public void setSolve(int n)
    	{
    		solve = n;
    	}
    	
    	public void showInfo() //show information
    	{
    		System.out.println("我是第"+solve+"个被处理的,我的个人信息为:标号为"+num+";业务类型为:"+type+";金额为:"+money);
    	}
    	
    	public int getMoney()
    	{
    		return money;
    	}
    	
    	public int getType()
    	{
    		return type;
    	}
    }
    
    class Queue
    {
    	private Customer[] Customers;
    	private int front, rear; //头尾指针
    	private int size, limit; //size是队列的真实大小,limit是数组的限定大小
    	//队头指针始终指向队列头元素
    	//队尾指针始终指向队列尾元素的下一个位置
    	
    	public Queue(int n)
    	{
    		this.Customers = new Customer[n];
    		front = rear = size = 0;
    		limit = n;
    	}
    	
    	public boolean isEmpty() //判空
    	{
    		return front >= rear;
    		//return size == 0;
    	}
    	
    	public boolean isFull() //判满
    	{
    		return rear >= limit;
    		/*
    		 * 这种判满方式可能会导致"假溢出",所以此时不一定是真的栈满,这是用普通数组实现列表的巨大劣势,也是循环队列的引入背景,务必注意!!!
    		 * 如果要解决这个弊端,大致有三种方法:
    		 * 1.用循环队列实现;(1是最经常采用的方法)
    		 * 2.用链表实现队列(我觉得2不错,除了写法可能比较麻烦一些,但是链表尤其适合插入和删除)
    		 * 3.队列的大小限制开大一些以避免(3其实不太好,因为不知道究竟需要开多大才足够,度很难把握,而且空间上也可能有较大的浪费)
    		 */
    	}
    	
    	public void insert(Customer e) //尾插
    	{
    		if (isFull())
    		{
    			throw new RuntimeException("队满,无法尾插");
    		}
    		Customers[rear++] = e;//进队时,新元素按rear指针位置插入,然后队尾指针增一
    		size++;
    	}
    	
    	public Customer getFront() //取队首
    	{
    		if (isEmpty())
    		{
    			throw new RuntimeException("队已空,无队首");
    		}
    		return Customers[front]; //头指针始终指向队列头元素
    	}
    	
    	public Customer getRear() //取队尾
    	{
    		if (isEmpty())
    		{
    			throw new RuntimeException("队已空,无队尾");
    		}
    		return Customers[rear - 1]; //尾指针始终指向队列尾元素的下一个位置
    	}
    	
    	public Customer remove() //删除队首
    	{
    		if (isEmpty())
    		{
    			throw new RuntimeException("队空,删除失败");
    		}
    		return Customers[front++]; //返回被删除的元素
    	}
    }
    
    class BankManage
    {
    	static int Amount, Code, Order;
    	final int SIZE = 5;
    	Queue Q1 = new Queue(SIZE);
    	Queue Q2 = new Queue(SIZE);//2个队列	
    	
    	public void initBank() //初始化静态变量,新用户入队列
    	{
    		Amount = 1000; Code = Order = 1;
    		int info [][] = {{1, 700}, {1, 500}, {1, 200}, {2, 300},{2, 400}};
    		
    		for (int i = 0; i < info.length; i++)
    		{
    			Customer c = new Customer(Code++, info[i][0], info[i][1]);
    			Q1.insert(c);
    		}
    	}
    	public boolean withdraw(Customer c) //取款
    	{
    		if (c.getMoney() > Amount)
    			return false;
    		
    		Amount -= c.getMoney();
    		c.setSolve(Order++);
    		c.showInfo();
    		return true;
    	}
    	
    	public void deposit(Customer c) //存款
    	{
    		Amount += c.getMoney();
    		c.setSolve(Order++);
    		c.showInfo();
    	}
    	
    	public void solveQ2() //检查第二个队列所有客户,能满足的则满足,不满足的重新排到对尾
    	{
    		Queue Q3 = new Queue(SIZE); //Q3是一个临时队列,先将仍然不能取款的请求放入Q3,在Q2中所有取款判断完后(判断能否执行,能就取款,不能就插入队尾,但为了能够跳出循环,新建Q3作为中转队列),再将Q3中的所有取款操作,重新入队Q2
    		while (!Q2.isEmpty())
    		{
    			Customer tp = Q2.getFront();
    			boolean jud = withdraw(tp);
    			if (jud) Q2.remove();
    			else
    			{
    				Q3.insert(tp);
    				Q2.remove();
    			}
    		}
    		
    		while (!Q3.isEmpty())
    		{
    			Q2.insert(Q3.getFront());
    			Q3.remove();
    		}
    	}
    	
    	public void testBank()
    	{
    		while (!Q1.isEmpty())
    		{
    			Customer tp= Q1.getFront(); // tp(temp)
    			if (tp.getType() == 1) //取款类操作
    			{
    				boolean jud = withdraw(tp);
    				if (jud) Q1.remove();
    				else
    				{
    					Q2.insert(Q1.getFront());
    					Q1.remove();
    				}
    			}
    			else //存款类操作
    			{
    				deposit(tp);
    				Q1.remove();
    				solveQ2();
    			}
    			
    		}
    	}
    	
    }
    
    public class test 
    {
    	public static void main(String []args)
    	{
    		BankManage bank = new BankManage();
    		bank.initBank();
    		bank.testBank();		
    	}
    }


    /* 法二 用Q2原来的大小作为循环次数
     * 法二所做的改进,是将Q2的大小作为循环次数,省去了Q3的使用
     * 法一的Q3,是作为一个临时队列,先将仍然不能取款的请求放入Q3,在Q2中所有取款判断完后(判断能否执行,能就取款,不能就插入队尾,但为了能够跳出循环,新建Q3作为中转队列),再将Q3中的所有取款操作,重新入队Q2
     * 之前之所以定义Q3,是因为,如果用队列是否为空,来作为结束Q2循环的条件,可能会死循环
     * 但是...我当时思路太闭塞了,没有想到,其实完全可以用Q2最初的大小作为循环次数的,这样就完全没必要定义Q3了
     */

    class Customer
    {
    	private int num, type, money;
    	private int solve; //记录被处理的次序
    	
    	public Customer(int n, int t, int m)
    	{
    		num = n; type = t; money = m;
    	}
    	
    	public void setSolve(int n)
    	{
    		solve = n;
    	}
    	
    	public void showInfo() //show information
    	{
    		System.out.println("我是第"+solve+"个被处理的,我的个人信息为:标号为"+num+";业务类型为:"+type+";金额为:"+money);
    	}
    	
    	public int getMoney()
    	{
    		return money;
    	}
    	
    	public int getType()
    	{
    		return type;
    	}
    }
    
    class Queue
    {
    	private Customer[] Customers;
    	private int front, rear; //头尾指针
    	private int size, limit; //size是队列的真实大小,limit是数组的限定大小
    	//队头指针始终指向队列头元素
    	//队尾指针始终指向队列尾元素的下一个位置
    	
    	public Queue(int n)
    	{
    		this.Customers = new Customer[n];
    		front = rear = size = 0;
    		limit = n;
    	}
    	
    	public boolean isEmpty() //判空
    	{
    		return front >= rear;
    	//	return size == 0;
    	}
    	
    	public boolean isFull() //判满
    	{
    		return rear >= limit;
    		/*
    		 * 这种判满方式可能会导致"假溢出",所以此时不一定是真的栈满,这是用普通数组实现列表的巨大劣势,也是循环队列的引入背景,务必注意!!!
    		 * 如果要解决这个弊端,大致有三种方法:
    		 * 1.用循环队列实现;(1是最经常采用的方法)
    		 * 2.用链表实现队列(我觉得2不错,除了写法可能比较麻烦一些,但是链表尤其适合插入和删除)
    		 * 3.队列的大小限制开大一些以避免(3其实不太好,因为不知道究竟需要开多大才足够,度很难把握,而且空间上也可能有较大的浪费)
    		 */
    	}
    	
    	public void insert(Customer e) //尾插
    	{
    		if (isFull())
    		{
    			throw new RuntimeException("队满,无法尾插");
    		}
    		Customers[rear++] = e;//进队时,新元素按rear指针位置插入,然后队尾指针增一
    		size++;
    	}
    	
    	public Customer getFront() //取队首
    	{
    		return Customers[front]; //头指针始终指向队列头元素
    	}
    	
    	public Customer getRear() //取队尾
    	{
    		return Customers[rear - 1]; //尾指针始终指向队列尾元素的下一个位置
    	}
    	
    	public Customer remove() //删除队首
    	{
    		if (isEmpty())
    		{
    			throw new RuntimeException("队空,删除失败");
    		}
    		size--;
    		return Customers[front++]; //返回被删除的元素
    	}
    	public int getSize()
    	{
    		return size;
    	}
    }
    
    class BankManage
    {
    	static int Amount, Code, Order;
    	final int SIZE = 5;
    	Queue Q1 = new Queue(SIZE);
    	Queue Q2 = new Queue(SIZE);//2个队列	
    
    	
    	public void initBank() //初始化静态变量,新用户入队列
    	{
    		Amount = 1000; Code = Order = 1;
    		int info [][] = {{1, 700}, {1, 500}, {1, 200}, {2, 300},{2, 400}};
    		
    		for (int i = 0; i < info.length; i++)
    		{
    			Customer c = new Customer(Code++, info[i][0], info[i][1]);
    			Q1.insert(c);
    		}
    	}
    	public boolean withdraw(Customer c) //取款
    	{
    		if (c.getMoney() > Amount)
    			return false;
    		
    		Amount -= c.getMoney();
    		c.setSolve(Order++);
    		c.showInfo();
    		return true;
    	}
    	
    	public void deposit(Customer c) //存款
    	{
    		Amount += c.getMoney();
    		c.setSolve(Order++);
    		c.showInfo();
    	}
    	
    	public void solveQ2() //检查第二个队列所有客户,能满足的则满足,不满足的重新排到对尾
    	{
    		int times = Q2.getSize(); //循环次数
    		for (int i = 0; i < times; i++)
    		{
    			if (Q2.isEmpty()) return;
    			Customer tp = Q2.getFront();
    	//		if (tp == null) return;
    			boolean jud = withdraw(tp);
    			if (!jud) Q2.insert(tp);
    			Q2.remove();
    		}
    		
    	}
    	
    	public void testBank()
    	{
    		while (!Q1.isEmpty())
    		{
    			Customer tp= Q1.getFront(); // tp(temp)
    	//		if (tp == null) return;
    			if (tp.getType() == 1) //取款类操作
    			{
    				boolean jud = withdraw(tp);
    				if (jud) Q1.remove();
    				else
    				{
    					Q2.insert(Q1.getFront());
    					Q1.remove();
    				}
    			}
    			else //存款类操作
    			{
    				deposit(tp);
    				Q1.remove();
    				if (!Q2.isEmpty()) solveQ2();
    			}
    		}
    	}
    }
    
    public class test 
    {
    	public static void main(String []args)
    	{
    		BankManage bank = new BankManage();
    		bank.initBank();
    		bank.testBank();		
    	}
    }


    /* 法三:循环队列的实现
     * 循环队列的判空判满操作:
     * 注意!!!对循环队列而言,无法通过 front == rear 来判断队列是空还是满
     * 一般解决这个问题有3种方法:
     * 1. 设置一个布尔变量专门标记队列空满
     * 2. 转换队满的标准,并不将所有位置都有元素看作是队满...而是,只要它只有一个位置没放元素,我们就把它看作是队满了;
     * 换句话说,调整队满的判断标准
     * 这种方法的实现,一般是少用一个元素的空间。约定入队前,测试尾指针在循环意义下 +1后,是否等于头指针,若相等则认为队满 (rear所指的单元始终为空,循环意义+1一般用取余实现)
     * (法2最常用)
     * 3. 使用一个计数器记录队列种元素的总数,即队列长度
     */

    class Customer
    {
    	private int num, type, money;
    	private int solve; //记录被处理的次序
    	
    	public Customer(int n, int t, int m)
    	{
    		num = n; type = t; money = m;
    	}
    	
    	public void setSolve(int n)
    	{
    		solve = n;
    	}
    	
    	public void showInfo() //show information
    	{
    		System.out.println("我是第"+solve+"个被处理的,我的个人信息为:标号为"+num+";业务类型为:"+type+";金额为:"+money);
    	}
    	
    	public int getMoney()
    	{
    		return money;
    	}
    	
    	public int getType()
    	{
    		return type;
    	}
    }
    
    class Queue
    {
    	private Customer[] Customers;
    	private int front, rear; //头尾指针
    	private int size, limit; //size是队列的真实大小,limit是数组的限定大小
    	//队头指针始终指向队列头元素
    	//队尾指针始终指向队列尾元素的下一个位置
    	
    	public Queue(int n)
    	{
    		this.Customers = new Customer[n];
    		front = rear = size = 0;
    		limit = n;
    	}
    	
    	public boolean isEmpty() //判空
    	{
    		return front == rear;
    	//	return size == 0;
    	}
    	
    	public boolean isFull() //判满
    	{
    	//	return size == limit;
    		return (rear + 1) % limit == front; //我们把仅剩一个位置没放元素看作满,而不是所有元素都装了才叫满;此外,判满利用了“循环意义上+1”这一思想
    	}
    	
    	public void insert(Customer e) //尾插
    	{
    		if (isFull())
    		{
    		//	System.out.println("front == "+front+ " and rear == "+rear);
    			throw new RuntimeException("队满,无法尾插");
    		}
    		Customers[rear] = e;//进队时,新元素按rear指针位置插入,然后队尾指针增一
    		size++;
    		rear = (rear + 1) % limit;
    	}
    	
    	public Customer getFront() //取队首
    	{
    		return Customers[front]; //头指针始终指向队列头元素
    	}
    	
    	public Customer getRear() //取队尾
    	{
    		return Customers[rear - 1]; //尾指针始终指向队列尾元素的下一个位置
    	}
    	
    	public Customer remove() //删除队首
    	{
    		if (isEmpty())
    		{
    			throw new RuntimeException("队空,删除失败");
    		}
    		size--;
    		return Customers[front++]; //返回被删除的元素
    	}
    	public int getSize()
    	{
    		return size;
    	}
    }
    
    class BankManage
    {
    	static int Amount, Code, Order;
    	final int SIZE = 6; //一定要多设置一个,否则会报错: “Exception in thread "main" java.lang.RuntimeException: 队满,无法尾插”
    	//因为循环队列的队满并不是所有元素放满,而是恰好有一个位置没放元素,我们就已经当作是满了,这点尤其注意!!!
    	Queue Q1 = new Queue(SIZE);
    	Queue Q2 = new Queue(SIZE);//2个队列	
    
    	
    	public void initBank() //初始化静态变量,新用户入队列
    	{
    		Amount = 1000; Code = Order = 1;
    		int info [][] = {{1, 700}, {1, 500}, {1, 200}, {2, 300},{2, 400}};
    		
    		for (int i = 0; i < info.length; i++)
    		{
    			Customer c = new Customer(Code++, info[i][0], info[i][1]);
    			Q1.insert(c);
    		}
    	}
    	public boolean withdraw(Customer c) //取款
    	{
    		if (c.getMoney() > Amount)
    			return false;
    		
    		Amount -= c.getMoney();
    		c.setSolve(Order++);
    		c.showInfo();
    		return true;
    	}
    	
    	public void deposit(Customer c) //存款
    	{
    		Amount += c.getMoney();
    		c.setSolve(Order++);
    		c.showInfo();
    	}
    	
    	public void solveQ2() //检查第二个队列所有客户,能满足的则满足,不满足的重新排到对尾
    	{
    		int times = Q2.getSize(); //循环次数
    		for (int i = 0; i < times; i++)
    		{
    			if (Q2.isEmpty()) return;
    			Customer tp = Q2.getFront();
    			boolean jud = withdraw(tp);
    			if (!jud) Q2.insert(tp);
    			Q2.remove();
    		}
    		
    	}
    	
    	public void testBank()
    	{
    		while (!Q1.isEmpty())
    		{
    			Customer tp= Q1.getFront(); // tp(temp)
    			if (tp.getType() == 1) //取款类操作
    			{
    				boolean jud = withdraw(tp);
    				if (jud) Q1.remove();
    				else
    				{
    					Q2.insert(Q1.getFront());
    					Q1.remove();
    				}
    			}
    			else //存款类操作
    			{
    				deposit(tp);
    				Q1.remove();
    				if (!Q2.isEmpty()) solveQ2();
    			}
    		}
    	}
    }
    
    public class test 
    {
    	public static void main(String []args)
    	{
    		BankManage bank = new BankManage();
    		bank.initBank();
    		bank.testBank();		
    	}
    }

    法三的一些详细说明:

    Customer

    数据:

    1.      numtypemoney是题目给的数据,分别是顾客标号、业务类型、业务金额;

    2.      solve是我自己加上的类型,记录该顾客是第几个被处理的,这样就能方便地在顾客类中,输出该顾客的所有信息了

     

    方法:

    1.      构造方法,设定numtypemoney的初始值

    2.      setSolve()方法,设定顾客的solve值,也就是他是第几个被处理的客户

    3.      showInfo()方法,输出顾客的所有有关信息

    4.      getMoney()方法、getType()方法,都是起到获得类中的私有值的作用

     

    Queue

    数据:

    1.      Customer类的数组Customers

    2.      frontrear头尾指针

    3.      sizelimit,前者是队列实际大小,后者是Customers数组开辟的大小

     

    方法:

    1.      构造方法,为Customers开辟空间,并设定limit值,初始化frontrearsize0

    2.      isEmpty()isFull() 分别用于循环队列的判空和判满,遵循循环队列和判满判空原则

    3.      inset()方法用于尾插元素,使得元素入队

    4.      getFront()getRear()方法,分别用于取队首元素,和取队尾元素

    5.      remove()用于删除队首元素,使其出队

    6.      getSize()用于获取队列元素(这个在BankManage类的solveQ2()函数,比较有用)

     

    BankManage

    数据:

    1. 静态变量Amount, Code, Order分别表示银行的剩余金额、顾客总数、已处理顾客数

    2. 常量SIZE为队列大小的初值

    3. Q1Q2为题目提到的两个队列,排队队列和等待队列

     

    方法:

    1.      initBank()方法,初始化题目给定的5个客户的有关信息

    2.      withdraw(Customer c)deposit(Customer c)方法:分别完成取款和存款操作,存款无须判断,只要修改银行余额、设定处理序号,输出顾客信息即可;对于取款,要先判断是否有足够余额,没有则返回false,如果可以取,同理,修改银行余额、设定处理序号,输出顾客信息

    3.      solveQ2()方法,在每次有顾客存款后调用,用于判断第二个队列中是否有可处理的请求,如果有则处理,否则入队尾并删除。这里用到了 Q2getSize()函数,用于获取Q2的元素个数,也就是说,只需要判断这么多次即可

    4.      testBank()完成所有的存钱取钱操作

     

    test

    也就是我编写的测试类,建立一个BankManage类对象bank,完成bank的初始化,并调用它的测试函数,查看有关结果,即可完成有关数据的验证



    做该题时查阅过的相关资料:

    以下是在完成实验的过程中,曾经查阅过的资料,为了方便日后复习,已经都整理为超链接了,可直接点击

    队列(顺序存储)C++模板实现

    //因为最近才开始学队列,一开始不是很会写它的类代码(还因此出了bug,并找了一下午);后来痛定思痛,好好搜索学习了一下队列的有关知识,包括假溢出等等。

     

     throwsthrownewRuntimeExceptiontry-catch的区别

    //因为在别人的队列实现代码中,看到了throw new Runtime Exception这种用法,于是自己搜索了解了下这个用法

     

    StackOverflow: The static method…should be accessed in astatic way

    //自己的代码出现这个报错以后,搜到的结果

     

    java的二维数组中如何计算行数和列数?

     

     java.lang.NullPointerException空指针异常问题

     

    理解Java中的引用传递和值传递

     

    JAVA是按值传递还是按引用传递的,试试就知道

     

    深入理解Java引用类型





  • 相关阅读:
    使用Python通过docker api控制docker容器
    windows 编译 google v8
    Kali下Metasploit自动连接postgresql
    更新Kali中的metasploit
    spring + mybatis 注解式事务不回滚的原因分析 @Transactional
    ListView中使用type需要注意的东西 java.lang.ArrayIndexOutOfBoundsException: length=2; index=2 addScrapView
    Missing artifact com.sun:tools:jar:1.5.0的解决方案
    0919-The Standard of Code Review
    重定向URL乱码问题
    hive学习_01
  • 原文地址:https://www.cnblogs.com/mofushaohua/p/7789391.html
Copyright © 2011-2022 走看看