首页 > 编程语言 >数据结构和算法系列课程(02) --- 线性表和贪吃蛇

数据结构和算法系列课程(02) --- 线性表和贪吃蛇

时间:2023-06-20 13:02:50浏览次数:72  
标签:02 index 线性表 int col --- row public size


线性结构是一种具有以下特点的结构:


  • 存在唯一一个被称为“第一个”的数据元素
  • 存在唯一一个被称为“最后一个”的数据元素
  • 除第一个元素之外,集合中的每个元素均有且仅有一个前驱
  • 除最后一个元素之外,集合中的每个元素均有且仅有一个后继




那么,线性表、栈、队列、数组、字符串都可以视为线性结构。



线性表是N个数据元素的有限序列,关于这部分的内容可以参考我的数据结构的课件


按照面向接口编程的思想,我们先通过一个接口为线性表拟定相应的方法,然后给出基于数组和基于链式结构的两种实现方式。


/**
 * 线性表接口
 * @author 骆昊
 *
 * @param <T> 泛型参数 - 线性表存储的元素类型
 */
public interface MyList<T> {

	/**
	 * 获取指定位置的元素
	 * @param index 索引
	 * @return 索引对应的元素
	 */
	public T get(int index);
	
	/**
	 * 为指定位置的元素设置值
	 * @param t 新元素
	 * @param index 索引
	 */
	public void set(T t, int index);
	
	/**
	 * 添加元素
	 * @param t 待添加的元素
	 */
	public void add(T t) ;
	
	/**
	 * 在线性表的指定位置添加元素
	 * @param t 待添加的元素
	 * @param index 指定的位置
	 */
	public void add(T t, int index);
	
	/**
	 * 删除元素
	 * @param index 删除元素的位置
	 * @return 被删除的元素
	 */
	public T remove(int index);
	
	/**
	 * 删除指定的元素
	 * @param t 待删除的元素
	 * @return 删除成功返回true否则返回false
	 */
	public boolean remove(T t);
	
	/**
	 * 找出第一个与指定元素匹配的元素的位置
	 * @param t 待匹配元素
	 * @return 找到了返回元素位置, 未找到返回-1
	 */
	public int indexOf(T t);
	
	/**
	 * 获取线性表中元素个数
	 * @return 元素个数
	 */
	public int size();
	
	/**
	 * 清空线性表
	 */
	public void clear();
	
	/**
	 * 判断线性表是否为空
	 * @return 没有元素返回true有元素返回false
	 */
	public boolean isEmpty();

}


将顺序表和链表的公共实现放在一个抽象的父类中


public abstract class MyAbstractList<T> implements MyList<T> {
	protected int size;
	
	@Override
	public void add(T t) {
		add(t, size);
	}
	
	@Override
	public boolean isEmpty() {
		return size == 0;
	}

	@Override
	public int size() {
		return size;
	}
	
	@Override
	public synchronized boolean remove(T t) {
		int index = indexOf(t);
		if(index >= 0) {
			remove(index);
			return true;
		}
		return false;
	}

}


线性表的数组实现版本 - 顺序表

import java.util.Arrays;

/**
 * 自定义线性表(通过数组实现)
 * @author Hao
 *
 * @param <T> 泛型参数(线性表存储元素的类型)
 */
public class MyArrayList<T> extends MyAbstractList<T> {
	private T[] content = null;
	private int capacity;	// 容量
	private int size;		// 有多少个元素
	private double factor;	// 空间追加因子(0~1)
	
	public MyArrayList() {
		this(50, 0.5);
	}
	
	public MyArrayList(double factor) {
		this(50, factor);
	}
	
	public MyArrayList(int capacity) {
		this(capacity, 0.5);
	}
	
	@SuppressWarnings("unchecked")
	public MyArrayList(int capacity, double factor) {
		content = (T[]) new Object[capacity];
		this.capacity = capacity;
		this.factor = factor;
		this.size = 0;
	}
	
	public T get(int index) {
		if(index >= 0 && index < size) {
			return content[index];
		}
		else {
			throw new RuntimeException("下标越界: " + index);
		}
	}
	
	public synchronized void add(T t, int index) {
		if(index >= 0 && index <= size) {
			if(size >= capacity) {
				ensureCapacity();
			}
			for(int i = size - 1; i >= index; i--) {
				content[i + 1] = content[i];
			}
			content[index] = t;
			size++;
		}
		else {
			throw new RuntimeException("下标越界: " + index);
		}
	}
	
	private void ensureCapacity() {
		capacity += (int)(capacity * factor);
		content = Arrays.copyOf(content, capacity);
	}
	
	public synchronized T remove(int index) {
		if(index >= size || index < 0) {
			throw new IndexOutOfBoundsException("下标越界: " + index);
		}
		T temp = content[index];
		for(int i = index; i < size; i++) {
			content[i] = content[i + 1];
		}
		size--;
		return temp;
	}
	
	public void clear() {
		size = 0;
	}

	@Override
	public synchronized void set(T t, int index) {
		if(index >= size || index < 0) {
			throw new IndexOutOfBoundsException("下标越界: " + index);
		}
		content[index] = t;
	}

	@Override
	public int indexOf(T t) {
		for(int i = 0; i < size; i++) {
			if(content[i].equals(t)) {
				return i;
			}
		}
		return -1;
	}

}



链式实现方式 - 链表

public class MyLinkedList<T> extends MyAbstractList<T> {
	private ListNode head, tail;

	private class ListNode {
		public T element;
		public ListNode next;

		public ListNode(T element) {
			this.element = element;
		}
	}

	public synchronized T get(int index) {
		if (index < 0 || index >= size) {
			throw new RuntimeException("下标越界: " + index);
		}
		ListNode curr = head;
		for (int i = 0; i < index; i++) {
			curr = curr.next;
		}
		return curr.element;
	}

	public synchronized void addFirst(T t) {
		ListNode newNode = new ListNode(t);
		newNode.next = head;
		head = newNode;
		size++;
		if (tail == null) {
			tail = head;
		}
	}

	public synchronized void addLast(T t) {
		if (tail == null) {
			head = tail = new ListNode(t);
		} else {
			tail.next = new ListNode(t);
			tail = tail.next;
		}
		size++;
	}

	public synchronized void add(T t, int index) {
		if (index < 0 || index > size) {
			throw new RuntimeException("下标越界: " + index);
		} else {
			if (index == 0) {
				addFirst(t);
			} else if (index == size) {
				addLast(t);
			} else {
				ListNode curr = head;
				for (int i = 1; i < index; i++) {
					curr = curr.next;
				}
				ListNode temp = curr.next;
				curr.next = new ListNode(t);
				curr.next.next = temp;
				size++;
			}
		}
	}

	public synchronized T remove(int index) {
		if (index < 0 || index >= size) {
			throw new RuntimeException("下标越界: " + index);
		} else if (index == 0) {
			return removeFirst();
		} else if (index == size - 1) {
			return removeLast();
		} else {
			ListNode prev = head;
			for (int i = 1; i < index; i++) {
				prev = prev.next;
			}
			ListNode curr = prev.next;
			prev.next = curr.next;
			size--;
			return curr.element;
		}
	}

	public synchronized T removeFirst() {
		T temp = null;
		if (head != null) {
			temp = head.element;
			head = head.next;
			size--;
			if(head == null) {
				tail = null;
			}
		}
		return temp;
	}

	public synchronized T removeLast() {
		T temp = null;
		if (tail != null) {
			ListNode prev = head;
			if(prev == tail) {
				temp = prev.element;
				head = tail = null;
			}
			else {
				while(prev.next != tail) {
					prev = prev.next;
				}
				temp = prev.next.element;
				prev.next = null;
				tail = prev;
			}
			size--;
		}

		return temp;
	}

	public void clear() {
		head = tail = null;
	}

	@Override
	public void set(T t, int index) {
		if (index < 0 || index >= size) {
			throw new RuntimeException("下标越界: " + index);
		}
		
		ListNode curr = head;
		for (int i = 0; i < index; i++) {
			curr = curr.next;
		}
		curr.element = t;
	}

	@Override
	public int indexOf(T t) {
		if(head != null) {
			ListNode curr = head;
			for (int i = 0; i < size; i++) {
				if(curr.element.equals(t)) {
					return i;
				}
				curr = curr.next;
			}
		}
		return -1;
	}

}



好了,到这里可以用链表来实现一个贪吃蛇游戏,其中的关键就是蛇吃到蛋以后如何加长。如果用链表来存储蛇身上的每个节点,那么可以通过在头或尾添加节点的方式来实现蛇的增长。同理,如果要让蛇移动,也可以通过删除尾部节点和添加头部节点来实现,也就是说利用链表提供的操作可以很容易的实现一个贪吃蛇游戏,代码如下所示:

先定义蛇身上的每一个节点:

import java.awt.Color;
import java.awt.Graphics;

public class SnakeNode {
	private int size = 10;
	private int row, col;

	public SnakeNode(int row, int col) {
		this.row = row;
		this.col = col;
	}

	public int getRow() {
		return row;
	}

	public void setRow(int row) {
		this.row = row;
	}

	public int getCol() {
		return col;
	}

	public void setCol(int col) {
		this.col = col;
	}

	public void draw(Graphics g) {
		g.setColor(Color.GREEN);
		g.fillRect(col * size, row * size, size, size);
	}

}


接下来是最重要的一个类 ---- 蛇:

import java.awt.Graphics;
import java.awt.Rectangle;

import com.accp.util.MyLinkedList;
import com.accp.util.MyList;

public class Snake {
	private MyLinkedList<SnakeNode> list = new MyLinkedList<SnakeNode>();
	private Direction dir = Direction.LEFT;
	private boolean alive = true;

	public Snake() {
		list.addLast(new SnakeNode(30, 30));
		list.addLast(new SnakeNode(30, 31));
		list.addLast(new SnakeNode(30, 32));
		list.addLast(new SnakeNode(30, 33));
		list.addLast(new SnakeNode(30, 34));
	}
	
	public Direction getDir() {
		return dir;
	}
	
	public MyList<SnakeNode> getBody() {
		return list;
	}

	public boolean isAlive() {
		return alive;
	}

	public void setAlive(boolean alive) {
		this.alive = alive;
	}
	
	public SnakeNode getHead() {
		return list.get(0);
	}

	public void move() {
		increaseLength();
		list.removeLast();
	}

	public void changeDirection(char ch) {
		switch (ch) {
		case 'w':
		case 'W':
			if (dir != Direction.DOWN) {
				dir = Direction.UP;
			}
			break;
		case 's':
		case 'S':
			if (dir != Direction.UP) {
				dir = Direction.DOWN;
			}
			break;
		case 'a':
		case 'A':
			if (dir != Direction.RIGHT) {
				dir = Direction.LEFT;
			}
			break;
		case 'd':
		case 'D':
			if (dir != Direction.LEFT) {
				dir = Direction.RIGHT;
			}
			break;
		}
	}

	public void draw(Graphics g) {
		for (int i = 0; i < list.size(); i++) {
			list.get(i).draw(g);
		}
	}

	public Rectangle getRectangle() {
		SnakeNode head = list.get(0);
		return new Rectangle(head.getCol() * 10, head.getRow() * 10, 10, 10);
	}

	public void eatEgg() {
		increaseLength();
		increaseLength();
	}
	
	private void increaseLength() {
		SnakeNode snakeHead = list.get(0);
		int row = snakeHead.getRow();
		int col = snakeHead.getCol();
		switch (dir) {
		case UP:
			row = row - 1;
			break;
		case DOWN:
			row = row + 1;
			break;
		case LEFT:
			col = col - 1;
			break;
		case RIGHT:
			col = col + 1;
			break;
		}
		SnakeNode newHead = new SnakeNode(row, col);
		list.addFirst(newHead);
	}
}


表示蛇的方向的枚举:

public enum Direction {
	LEFT, RIGHT, UP, DOWN;
}



游戏主窗体:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JOptionPane;

import com.accp.util.MyList;

@SuppressWarnings("serial")
public class GameFrame extends JFrame {
	private Snake s = new Snake();
	private Wall w = new Wall();
	private Egg e = null;
	private char oldKey = '#';
	
	private BufferedImage image = 
		new BufferedImage(600, 600, BufferedImage.TYPE_INT_RGB);
	
	private Egg makeAnEgg() {
		int row = (int) (Math.random() * 40 + 10);
		int col = (int) (Math.random() * 40 + 10);
		return new Egg(row, col);
	}
	
	public GameFrame() {
		e = makeAnEgg();
		
		this.setSize(600, 600);
		this.setResizable(false);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);
		
		this.addKeyListener(new KeyAdapter() {

			@Override
			public void keyPressed(KeyEvent e) {
				char ch = e.getKeyChar();
				if(ch != oldKey) {	// �������
					oldKey = ch;
					s.changeDirection(ch);
				}
			}	
		});
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true) {
					if(s.isAlive()) {
						s.move();
						if(e != null) {
							if(e.getRow() == s.getHead().getRow() && e.getCol() ==
								s.getHead().getCol()) {
								s.eatEgg();
								e = makeAnEgg();
							}
						}
						SnakeNode snakeHead = s.getHead();
						int row = snakeHead.getRow();
						int col = snakeHead.getCol();
						if(row <= 5 || row >= 55 || col <= 5 || col >= 55) {
							s.setAlive(false);
						}
						MyList<SnakeNode> snakeBody = s.getBody();
						for(int i = 1; i < snakeBody.size(); i++) {
							SnakeNode node = snakeBody.get(i);
							if(node.getRow() == row && node.getCol() == col) {
								s.setAlive(false);
								break;
							}
						}
						if(!s.isAlive()) {
							JOptionPane.showMessageDialog(null, "Game Over!!!");
						}
						try {
							Thread.sleep(200);
						}
						catch (InterruptedException e) {
						}
						repaint();
					}
				}
			}
		}).start();
	}
	
	@Override
	public void paint(Graphics g) {
		//super.paint(g);
		Graphics offG = image.getGraphics();
		offG.setColor(new Color(0xffff80));
		offG.fillRect(0, 0, 600, 600);
		w.draw(offG);
		s.draw(offG);
		if(e != null) {
			e.draw(offG);
		}
		g.drawImage(image, 0, 0, null);
	}
	
	public static void main(String[] args) {
		new GameFrame().setVisible(true);
	}
}



当然,这个游戏还少不了墙和蛋,代码如下:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;

/**
 * 砖块
 * @author 骆昊
 *
 */
public class Brick {
	private int row, col;
	private int size = 10;
	
	public Brick(int row, int col) {
		this.row = row;
		this.col = col;
	}
	
	public void draw(Graphics g) {
		g.setColor(new Color(64, 0, 0));
		g.fillRect(col * size, row * size, size, size);
	}
	
	public Rectangle getRectangle() {
		return new Rectangle(col * size, row * size, size, size);
	}
	
}



import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;

/**
 * 围墙
 * @author 骆昊
 *
 */
public class Wall {
	private List<Brick> list = new ArrayList<Brick>();
	
	public Wall() {
		for(int i = 5; i <= 55; i++) {
			list.add(new Brick(5, i));	// -
			list.add(new Brick(i, 5));	// |
			list.add(new Brick(55, i));	// -
			list.add(new Brick(i, 55));	// |
		}
	}
	
	public List<Brick> getAllBricks() {
		return list;
	}
	
	public void draw(Graphics g) {
		for(Brick b : list) {
			b.draw(g);
		}
	}
}



import java.awt.Color;
import java.awt.Graphics;


/**
 * 蛋
 * @author 骆昊
 *
 */
public class Egg {
	private int col, row;
	private int size = 10;


	public Egg(int col, int row) {
		this.row = row;
		this.col = col;
	}


	public int getCol() {
		return col;
	}


	public void setCol(int col) {
		this.col = col;
	}


	public int getRow() {
		return row;
	}


	public void setRow(int row) {
		this.row = row;
	}


	public void draw(Graphics g) {
		g.setColor(Color.RED);
		g.fillOval(col * size, row * size, size, size);
	}
}



好了,到这里一个贪吃蛇游戏就做好了,这个游戏的碰撞检测非常简单,因为蛇和蛋还有墙都在格子里(虽然没有绘制出来),通过横纵坐标的比较就可以完成了。

标签:02,index,线性表,int,col,---,row,public,size
From: https://blog.51cto.com/u_16166070/6521812

相关文章

  • 数据结构和算法系列课程(01)--- 排序二叉树和红黑树
    把排序二叉树放在这个系列课程的第一个部分似乎有些唐突,但是考虑到它在面试中出现的可能性,把它放在这样的一个位置也就不足为奇了。关于树和二叉树的基础知识,可以到下面的链接中下载我的课件进行了解。下面给出一个排序二叉树的Java实现:packagcom.loonstudio;/***排序二叉树......
  • 一个例子帮你搞懂C#语言高级特性系列(02) --- 委托、事件和Lambda表达式
    直接看例子吧:usingSystem;usingSystem.Windows.Forms;usingSystem.Threading;namespaceCom.LoonStudio.Example{publicclassCar{//定义一个汽车事件的委托publicdelegatevoidCarEventHandler(stringmsg);//定义加速事件......
  • [连载]C#程序设计(05)--- C#核心编程-3 --- 表达式和运算符
    ......
  • [连载]C#程序设计(09)--- 类和对象
    ......
  • Java面试题集(51-70)
    Java程序员面试题集(51-70)摘要:这一部分主要讲解了异常、多线程、容器和I/O的相关面试题。首先,异常机制提供了一种在不打乱原有业务逻辑的前提下,把程序在运行时可能出现的状况处理掉的优雅的解决方案,同时也是面向对象的解决方案。而Java的线程模型是建立在共享的、默认的可见的可变状......
  • 虚拟主机使用记录-20230620
    三丰云提供的免费虚拟主机和免费云服务器对于学生和初学者来说非常有吸引力,并且易于使用。同时,三丰云也提供付费计划,可以为更高级别的用户提供更多资源和功能支持。需要注意的是,免费服务通常会受到一些限制和局限性。因此,在选择免费主机或云服务器时,需要根据实际需求进行评估,以确保......
  • git --date时间显示设置格式命令
    git --date 显示与当前时间相关的日期relativelocaldefaultisorfcshortraw--date=relative显示用户本地时区中的时间戳。 --date=local(or--date=iso)以ISO8601格式显示时间戳。 --date=iso8601(or--date=rfc)以RFC2822格式显示时间戳,通常在电子邮件中找......
  • vue学习过程中 遇到的问题 CSS塌陷 ----- 高度塌陷和外边距塌陷
    1、高度塌陷原因:父元素没有设置高度,子元素设置浮动属性(float:left)之后,父元素的高度为0.***<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge">......
  • 「NOI2021」庆典
    首先可以注意到题面中的这个条件:对于三座城市\(x\),\(y\),\(z\),若\(x\Rightarrowz\)且\(y\Rightarrowz\),那么有\(x\Rightarrowy\)或\(y\Rightarrowx\)。这就代表着如果存在边\(x\rightarrowz\)和\(y\rightarrowz\),假设存在\(x\Rightarrowy\)那么删去边\(x\r......
  • Python进阶-上下文管理器
    上下文管理器定义包装任意代码确保执行的一致性语法with语句__enter__和__exit__方法classContextManager(object):def__init__(self):self.entered=Falsedef__enter__(self):self.entered=Truereturnself......