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引用类型





  • 相关阅读:
    openwrt 相关文章
    负载均衡相关文章
    Today's Progress
    Rodrigues formula is beautiful, but uneven to sine and cosine. (zz Berkeley's Page)
    Camera Calibration in detail
    Fundamental Matrix in Epipolar
    Camera Calibration's fx and fy do Cares in SLAM
    FilterEngine::apply
    FilterEngine 类解析——OpenCV图像滤波核心引擎(zz)
    gaussBlur
  • 原文地址:https://www.cnblogs.com/mofushaohua/p/7789391.html
Copyright © 2011-2022 走看看