【扩展作业分享】JAVA, 数据结构与算法, 操作系统,大数据可视化,游戏制作

一. 前言

分享一些自己的作业内容,和自己的课外学习的一点有趣的东西, 欢迎参考借鉴批评.


二. JAVA

(一). 作业

1. 2022/5/6的测试1答案

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

封装, 继承, 多态

1.	接口只能定义方法, 没有方法体, 抽象类可以定义方法并实现, 有方法体
2.	接口里的方法都是public abstract类型, 变量都是public static final类型, 且必须初始化, JDk1.8后可以定义default和static方法, 接口里的方法修饰符为public和protected, 属性则没有限制
3.	接口可以多重继承, 抽象方法只能多层继承
4.	接口通过implements实现, 抽象类通过extends继承
5.	接口没有构造方法, 抽象类有构造方法(不能直接实例化)
6.	接口有interface定义, 抽象类由abstract定义

类:对一类事物的行为和属性的抽象, 它描述的是所有实体具有的相同特征, 是一种数据类型
对象:是类描述的特征在某一个具体事物上的体现,是一一对应的关系
举例:
       类: 人
       对象: 张三

1.	重写的方法标签相同(方法名,参数列表和返回值类型相同), 重载方法名相同即可
2.	重载发生在本类, 重写发生在父子类
3.	重载的修饰符可以不同, 重写的修饰符权限要大于等于父类
/**
 * VIP服务接口
 */
package com.myinterface.www;

@FunctionalInterface
public interface IEmployee {
	void vipServerAble(); // vip服务
}
/**
 * 人类
 * 抽象类
 * 拥有人的基本属性和行为
 */
package com.myabstract.www;

public abstract class AbstractPeople {
	// 属性
	private String name;
	private String country;
	private int age;
	private String sex;

	// setter, getter
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getCountry() {
		return country;
	}

	public void setCountry(String country) {
		this.country = country;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}

	public String getInfo() {
		if (age != 0)
			return "名字: " + this.name + ", 年龄: " + this.age + ", 性别: " + this.sex + ", 国籍: " + this.country;
		else
			return "名字: " + this.name;
	}

}
/**
 * 继承人类
 */
package com.myabstract.www;

public abstract class AbstractEmployee extends AbstractPeople {
	private String empNo; // 员工编号
	
	public String getEmpNo() {
		return empNo;
	}
	public void setEmpNo(String empNo) {
		this.empNo = empNo;
	}
	
	// 构造方法
	public AbstractEmployee() {}
	public AbstractEmployee(String name, String empNo) {
		this.setName(name);
		this.setEmpNo(empNo);
	}
	
	// 工作
	public abstract void workAble();
	// 打卡
	public abstract void playCarAble();
}
/**
 * 厨师类
 */
package com.myclass.www;

import com.myabstract.www.AbstractEmployee;
import com.myinterface.www.IEmployee;

public class Cooker extends AbstractEmployee implements IEmployee{
	public Cooker() {}
	public Cooker(String name, String empNo) {
		super(name, empNo);
	}
	
	// 打卡
	@Override
	public void playCarAble() {
		System.out.println("厨师: 我上班打卡了!");
	}
	// 工作
	@Override
	public void workAble() {
		System.out.println("厨师: 我上班工作中!");
	}
	// vip服务
	@Override
	public void vipServerAble() {
		System.out.println("vip服务: 加菜!");
	}
}

/**
 * 服务员类
 */
package com.myclass.www;

import com.myabstract.www.AbstractEmployee;
import com.myinterface.www.IEmployee;

public class Waiter extends AbstractEmployee implements IEmployee{
	public Waiter() {}

	public Waiter(String name, String empNo) {
		super(name, empNo);
	}

	// 打卡
	@Override
	public void playCarAble() {
		System.out.println("服务员: 我上班打卡了!");
	}
	// 工作
	@Override
	public void workAble() {
		System.out.println("服务员: 我上班工作中!");
	}
	// vip服务
	@Override
	public void vipServerAble() {
		System.out.println("vip服务: 嘘寒问暖!");
	}
}

/**
 * 经理类
 */
package com.myclass.www;

import com.myabstract.www.AbstractEmployee;

public class Manager extends AbstractEmployee {
	public Manager() {
	}

	public Manager(String name, String empNo) {
		super(name, empNo);
	}

	// 打卡
	@Override
	public void playCarAble() {
		System.out.println("经理: 我上班打卡了!");
	}
	// 工作
	@Override
	public void workAble() {
		System.out.println("经理: 我上班工作中!");
	}
}
/**
 * 测试类
 * 测试公司员工下的各类
 */
package com.mytest.www;

import com.myabstract.www.AbstractEmployee;
import com.myclass.www.Cooker;
import com.myclass.www.Manager;
import com.myclass.www.Waiter;

public class TestFive {

	public static void main(String[] args) {
		// 厨师
		Cooker cooker = new Cooker("张三", "03");
		cooker.workAble(); // 工作
		cooker.playCarAble(); // 打卡
		cooker.vipServerAble(); // vip服务: 加菜
		
		System.out.println("=====================================");
		// 服务员
		Waiter water = new Waiter("李四", "02");
		water.workAble(); // 工作
		water.playCarAble(); // 打卡
		water.vipServerAble(); // vip服务: 嘘寒问暖
		
		System.out.println("=====================================");
		// 经理
		AbstractEmployee manager = new Manager("王五", "01");
		manager.workAble(); // 工作
		manager.playCarAble(); // 打卡
	}

}

2. 第九周作业–GUI编程

层级
在这里插入图片描述

代码

/**
 * 进行GUI的布局
 */
package com.myclass.www;
import javax.swing.*;
import java.awt.*;

public class GuiLayout {
	private JFrame frame; // 窗口
	private JPanel buttonPanel; // 按钮容器
	private JPanel inputPanel; // 输入容器
	private JButton closeButton; // 关闭按钮
	private JButton fireButton; // 发送按钮
	private JTextArea inputText; // 输入文本
	private JTextArea receiveText; // 接受文本
	
	public GuiLayout() {
		init(); // 调用初始化组件
		defaultSet(); // 调用默认设置
		contorlButton(); // 调用按钮控制
	}
	
	// 初始化组件
	private void init() {
		frame = new JFrame("                                                                                    威杨(WeYoung)工作室🖊");
		buttonPanel = new JPanel();
		inputPanel = new JPanel();
		closeButton = new JButton("关闭(C)");
		fireButton = new JButton("发送(S)    | ∨");
		inputText = new JTextArea();
		receiveText = new JTextArea();
	}
	
	// 设置默认JFrame
	private void defaultSet() {
		// 窗体设置
		frame.setLayout(new BorderLayout(0, 10)); // 边框布局
		frame.setBounds(250, 250, 250 * 3, 250 * 2); // 设置界限
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 退出操作
		frame.add(receiveText, BorderLayout.NORTH); // 添加接受文本框
		frame.add(inputPanel, BorderLayout.CENTER); // 添加输入容器
		
		// 输入容器设置
		inputPanel.setLayout(new BorderLayout()); // 边框布局
		inputPanel.add(buttonPanel, BorderLayout.SOUTH);
		inputPanel.add(inputText, BorderLayout.CENTER);
		
		// 按钮容器设置
		buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); // 流式布局
		buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); // 默认靠右的流式布局
		buttonPanel.add(closeButton); // 添加关闭按钮
		buttonPanel.add(fireButton); // 添加发送按钮
		buttonPanel.setBackground(Color.white);
		
		// 关闭按钮设置
		closeButton.setBackground(Color.white);
		closeButton.setFocusPainted(false); // 取消聚焦
		
		// 发送按钮设置
		fireButton.setBackground(Color.blue);
		fireButton.setForeground(Color.white); // 前景色
		fireButton.setFocusPainted(false); // 取消聚焦
		
		//输入文本框设置
		inputText.setFont(new Font("标楷体", Font.BOLD, 15));
		inputText.setLineWrap(true); // 自动换行
		inputText.setBackground(Color.white);
		
		// 接受文本框设置
		receiveText.setFont(new Font("标楷体", Font.BOLD, 15));
		receiveText.setLineWrap(true); // 自动换行
		receiveText.setBackground(Color.white);
		receiveText.setRows(13); // 行数, 只设置最后这个, 其余会自适应
		
		frame.setVisible(true); // 渲染
	}
	
	// 按钮控制
	private void contorlButton() {
		// 关闭按钮点击
		closeButton.addActionListener(e -> {
			System.exit(0);
		});
		
		// 发送按钮点击
		fireButton.addActionListener(e -> {
			if (!"".equals(inputText.getText())) {
				StringBuffer strNewLine = new StringBuffer("\n");
				if (!"".equals(receiveText.getText()))
					receiveText.setText(receiveText.getText() + strNewLine + inputText.getText() + strNewLine);
				else
					receiveText.setText(inputText.getText() + strNewLine);
				inputText.setText(null);
			}
		});
	}
}
/**
 * 本地聊天测试类 
 */
package com.mytest.www;
import com.myclass.www.*;

public class LocalChatTest {

	public static void main(String[] args) {
		GuiLayout guiLayout = new GuiLayout();
	}

}

效果
还有些不足之处

3. 常用类课后练习AC答案

题目
在这里插入图片描述

AC代码

import java.util.*;

public class Main {
  	private static boolean isLowdderCase(char ch) {
    	return ch >= 'a' && ch <= 'z' ? true : false;
    }
	public static void main(String[] args) {
    	Scanner in = new Scanner(System.in);
      	String str = in.nextLine();
      	char[] charArr = str.toCharArray();
      	if (isLowdderCase(charArr[0]))
      		charArr[0] -= 32;
      System.out.println(String.valueOf(charArr));
    }
}

题目
在这里插入图片描述

AC代码

import java.util.*;

public class Main {
		public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		String str = in.next();
		Map<String, String> hashMap = new HashMap();
		hashMap.put("txt", "文本文件");
		hashMap.put("TXT", "文本文件");
		hashMap.put("cod", "word文件");
		hashMap.put("COD", "word文件");
		hashMap.put("xcod", "word文件");
		hashMap.put("XCOD", "word文件");
		hashMap.put("exe", "可执行文件");
		hashMap.put("EXE", "可执行文件");
		char[] charArr = str.toCharArray();
		int n = charArr.length;
		String resStr = "";
		for (int i = n - 1; i >= 0; --i) {
			resStr += charArr[i];
			if (charArr[i - 1] == '.')
				break;
		}
		System.out.println(hashMap.get(resStr) != null ? hashMap.get(resStr) : "其他文件");
	}
}

题目
在这里插入图片描述

AC代码

import java.util.*;

public class Main {
		public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		Integer num = in.nextInt();
		System.out.println(Integer.toBinaryString(num));
		System.out.println("0" + Integer.toOctalString(num));
		System.out.println("0x" + Integer.toHexString(num));
	}
}

题目
在这里插入图片描述

AC代码

import java.util.*;

public class Main {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		String str = in.nextLine();
		int[] arr = new int[] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
		char[] date = str.toCharArray();
		int year = (date[0] - '0') * 1000 + (date[1] - '0') * 100 + (date[2] - '0') * 10 + (date[3] - '0');
		int month = (date[5] - '0') * 10 + (date[6] - '0');
		int day = (date[8] - '0') * 10 + (date[9] - '0');
		if (year != 2022 || month < 3 || month > 10) {
			System.out.println("不在本学期范围");
			return;
		}
		int countDay = day;
		for (int i = 2; i < month - 1; ++ i)
			countDay += arr[i];
		System.out.println(countDay % 7 == 0 ? countDay / 7 : countDay / 7 + 1);
	}
}

4. 实验 3 集合操作答案

层级
在这里插入图片描述

示例代码(AbstractPeople抽象类)

/**
 * 人类
 * 抽象类
 * 拥有人的基本属性和行为
 */
package com.myabstract.www;

public abstract class AbstractPeople {
	// 属性
	private String name;
	private String country;
	private int age;
	private String sex;

	public AbstractPeople() {
	}

	public AbstractPeople(String name, int age, String sex) {
		this.name = name;
		this.age = age;
		this.sex = sex;
	}

	// setter, getter
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getCountry() {
		return country;
	}

	public void setCountry(String country) {
		this.country = country;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getSex() {
		return sex;
	}

	public void setSex(String sex) {
		this.sex = sex;
	}

	public String getInfo() {
		if (age != 0)
			return "名字: " + this.name + ", 年龄: " + this.age + ", 性别: " + this.sex + ", 国籍: " + this.country;
		else
			return "名字: " + this.name;
	}

}

示例代码(Student实现类)

/**
 * 学生类
 */
package com.myclass.www;

import com.myabstract.www.AbstractPeople;

public class Student extends AbstractPeople {
	private String stuNo;// 学号
	private int height;// 身高

	public Student() {
	}

	public Student(String stuNo, String name, int age, String sex, int height) {
		super(name, age, sex);
		this.stuNo = stuNo;
		this.height = height;
	}

	public String getStuNo() {
		return stuNo;
	}

	public void setStuNo(String stuNo) {
		this.stuNo = stuNo;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	@Override
	public String toString() {
		return "姓名: " + this.getName() + " 学号: " + this.getStuNo() + " 年龄: " + this.getAge() + " 性别: " + this.getSex()
				+ " 身高: " + this.getHeight();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + height;
		result = prime * result + ((stuNo == null) ? 0 : stuNo.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (height != other.height)
			return false;
		if (stuNo == null) {
			if (other.stuNo != null)
				return false;
		} else if (!stuNo.equals(other.stuNo))
			return false;
		return true;
	}
}

示例代码(TestSeven测试类)

/**
 * List是无序可重复的, Set是无序不可重复的, Map是有序可重复的
 */
package com.mytest.www;

import java.util.*;
import java.util.Map.*;

import com.myclass.www.Student;

public class TestSeven {

	public static void main(String[] args) {
		// 数组列表
		System.out.println("List 集合访问学生信息。");
		List<Student> studentsList = new ArrayList<>();
		// 1. 添加学生对象
		studentsList.add(new Student("200010304", "smith", 30, "man", 167));
		studentsList.add(new Student("200010308", "smith5", 31, "woman", 168));
		studentsList.add(new Student("200010307", "smith6", 32, "woman", 169));
		studentsList.add(new Student("200010306", "smith7", 33, "woman", 170));
		studentsList.add(new Student("200010305", "jack", 34, "man", 171));

		// 2. 根据学号升序排序学生信息并输出
		Collections.sort(studentsList, (other1, other2) -> {
			return other1.getStuNo().compareTo(other2.getStuNo());
		});
		for (Student stu : studentsList)
			System.out.println(stu);

		System.out.println("==============================================");
		// 3. 查询身高大于为"155"的学生信息并输出。
		studentsList.stream().filter((x) -> x.getHeight() > 155).forEach(stu -> System.out.println(stu));

		System.out.println("==============================================");
		// 4. 将姓名为"jack"的学生信息删除。
		studentsList.removeIf(stu -> stu.getName().equals("jack"));
		for (Student stu : studentsList)
			System.out.println(stu);

		System.out.println("==============================================");
		// 5. 向集合中在再插入一条 smith 的数据,观察集合里面数据的变化。
		studentsList.add(new Student("200010304", "smith", 30, "man", 167));
		studentsList.stream().forEach(stu -> System.out.println(stu));

		// set 集合
		System.out.println("\nSet 集合访问学生信息。");
		Set<Student> stuentsSet = new HashSet<>();
		// 1. 添加学生对象
		stuentsSet.add(new Student("200010304", "smith", 30, "man", 167));
		stuentsSet.add(new Student("200010306", "smith5", 31, "woman", 168));
		stuentsSet.add(new Student("200010305", "smith6", 32, "woman", 169));
		stuentsSet.add(new Student("200010307", "smith7", 33, "woman", 170));
		stuentsSet.add(new Student("200010308", "jack", 34, "man", 171));

		// 2. 根据学号升序排序学生信息并输出
		for (Student stu : stuentsSet)
			System.out.println(stu);

		System.out.println("==============================================");
		// 3. 查询身高大于为"155"的学生信息并输出。
		stuentsSet.stream().filter((x) -> x.getHeight() > 155).forEach(stu -> System.out.println(stu));

		System.out.println("==============================================");
		// 4. 将姓名为"jack"的学生信息删除。
		stuentsSet.removeIf(stu -> stu.getName().equals("jack"));
		for (Student stu : stuentsSet)
			System.out.println(stu);

		System.out.println("==============================================");
		// 5. 向集合中在再插入一条 smith 的数据,观察集合里面数据的变化。
		stuentsSet.add(new Student("200010304", "smith", 30, "man", 167));
		stuentsSet.stream().forEach(stu -> System.out.println(stu));

		// HashMap集合
		System.out.println("\nMap 集合访问学生信息,使用 Map 集合建立学号与学生对象的映射关\r\n" + "系。");
		Map<Integer, Student> stuentsHashMap = new HashMap<>();
		// 1. 添加学生对象
		stuentsHashMap.put(1, new Student("200010304", "smith", 30, "man", 167));
		stuentsHashMap.put(2, new Student("200010306", "smith5", 31, "woman", 168));
		stuentsHashMap.put(3, new Student("200010305", "smith6", 32, "woman", 169));
		stuentsHashMap.put(4, new Student("200010308", "smith7", 33, "woman", 170));
		stuentsHashMap.put(5, new Student("200010307", "jack", 34, "man", 171));

		// 2. 根据学号升序排序学生信息并输出
		for (Entry<Integer, Student> e : stuentsHashMap.entrySet())
			System.out.println(e.getValue());

		System.out.println("==============================================");
		// 3. 查询身高大于为"155"的学生信息并输出。
		stuentsHashMap.entrySet().stream().filter((x) -> x.getValue().getHeight() > 155)
				.forEach(e -> System.out.println(e.getValue()));

		System.out.println("==============================================");
		// 4. 将姓名为"jack"的学生信息删除。
		for (Entry<Integer, Student> e : stuentsHashMap.entrySet())
			if (e.getValue().getName().equals("jack"))
				stuentsHashMap.remove(e.getKey());
		for (Entry<Integer, Student> stu : stuentsHashMap.entrySet())
			System.out.println(stu.getValue());

		System.out.println("==============================================");
		// 5. 向集合中在再插入一条 smith 的数据,观察集合里面数据的变化。
		stuentsHashMap.put(stuentsHashMap.size() + 1, new Student("200010304", "smith", 30, "man", 167));
		stuentsHashMap.entrySet().stream().forEach(e -> System.out.println(e.getValue()));
	}
}

运行结果

List 集合访问学生信息。
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: jack 学号: 200010305 年龄: 34 性别: man 身高: 171
姓名: smith7 学号: 200010306 年龄: 33 性别: woman 身高: 170
姓名: smith6 学号: 200010307 年龄: 32 性别: woman 身高: 169
姓名: smith5 学号: 200010308 年龄: 31 性别: woman 身高: 168
==============================================
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: jack 学号: 200010305 年龄: 34 性别: man 身高: 171
姓名: smith7 学号: 200010306 年龄: 33 性别: woman 身高: 170
姓名: smith6 学号: 200010307 年龄: 32 性别: woman 身高: 169
姓名: smith5 学号: 200010308 年龄: 31 性别: woman 身高: 168
==============================================
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: smith7 学号: 200010306 年龄: 33 性别: woman 身高: 170
姓名: smith6 学号: 200010307 年龄: 32 性别: woman 身高: 169
姓名: smith5 学号: 200010308 年龄: 31 性别: woman 身高: 168
==============================================
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: smith7 学号: 200010306 年龄: 33 性别: woman 身高: 170
姓名: smith6 学号: 200010307 年龄: 32 性别: woman 身高: 169
姓名: smith5 学号: 200010308 年龄: 31 性别: woman 身高: 168
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167

Set 集合访问学生信息。
姓名: smith6 学号: 200010305 年龄: 32 性别: woman 身高: 169
姓名: smith5 学号: 200010306 年龄: 31 性别: woman 身高: 168
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: smith7 学号: 200010307 年龄: 33 性别: woman 身高: 170
姓名: jack 学号: 200010308 年龄: 34 性别: man 身高: 171
==============================================
姓名: smith6 学号: 200010305 年龄: 32 性别: woman 身高: 169
姓名: smith5 学号: 200010306 年龄: 31 性别: woman 身高: 168
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: smith7 学号: 200010307 年龄: 33 性别: woman 身高: 170
姓名: jack 学号: 200010308 年龄: 34 性别: man 身高: 171
==============================================
姓名: smith6 学号: 200010305 年龄: 32 性别: woman 身高: 169
姓名: smith5 学号: 200010306 年龄: 31 性别: woman 身高: 168
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: smith7 学号: 200010307 年龄: 33 性别: woman 身高: 170
==============================================
姓名: smith6 学号: 200010305 年龄: 32 性别: woman 身高: 169
姓名: smith5 学号: 200010306 年龄: 31 性别: woman 身高: 168
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: smith7 学号: 200010307 年龄: 33 性别: woman 身高: 170

Map 集合访问学生信息,使用 Map 集合建立学号与学生对象的映射关
系。
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: smith5 学号: 200010306 年龄: 31 性别: woman 身高: 168
姓名: smith6 学号: 200010305 年龄: 32 性别: woman 身高: 169
姓名: smith7 学号: 200010308 年龄: 33 性别: woman 身高: 170
姓名: jack 学号: 200010307 年龄: 34 性别: man 身高: 171
==============================================
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: smith5 学号: 200010306 年龄: 31 性别: woman 身高: 168
姓名: smith6 学号: 200010305 年龄: 32 性别: woman 身高: 169
姓名: smith7 学号: 200010308 年龄: 33 性别: woman 身高: 170
姓名: jack 学号: 200010307 年龄: 34 性别: man 身高: 171
==============================================
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: smith5 学号: 200010306 年龄: 31 性别: woman 身高: 168
姓名: smith6 学号: 200010305 年龄: 32 性别: woman 身高: 169
姓名: smith7 学号: 200010308 年龄: 33 性别: woman 身高: 170
==============================================
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167
姓名: smith5 学号: 200010306 年龄: 31 性别: woman 身高: 168
姓名: smith6 学号: 200010305 年龄: 32 性别: woman 身高: 169
姓名: smith7 学号: 200010308 年龄: 33 性别: woman 身高: 170
姓名: smith 学号: 200010304 年龄: 30 性别: man 身高: 167

4. 实验6 模拟QQ聊天群(局域网)

实现功能

1. 服务端
在这里插入图片描述
2. 客户端
在这里插入图片描述功能简述
1). 服务器

  1. 人数上限:
    限制连接服务器的客户端数量, 根据自己需求设置数值
  2. 端口:
    服务器的端口号, 可以自己设置, 但注意不要超出端口号范围(0-65535)
  3. 启动: 点击以后会启动服务器
  4. 停止: 点击以后会关闭服务器
  5. 导出: 导出用户的聊天信息, 并输出到控制台(后面可以改为存入数据库)
  6. 在线用户: 实时显示当前在线的用户信息(ID + Name)
  7. 消息显示区: 显示各个客户端的聊天信息
  8. 发送: 点击后发送给所有客户端左边输入框内的信息

2). 客户端

  1. 发送: 点击发送输入框内容给服务器, 再由服务器同步给所有客户端并显示
  2. 关闭: 退出聊天界面, 此时会刷新服务器在线用户列表
  3. 发送空消息会弹出提示框

不足

  1. 点击JFram自己提供的退出(就那个叉), 不可以正常刷新服务器在线用户列表
  2. 比较简陋, 相较于实际项目实用性低, 纯纯练手
  3. 对于一些输入不规范的问题没有校验
  4. 仍然存在一些Exception问题没有解决
  5. 代码设计感觉不是很规范
  6. 有部分.java文件编码字符集不一致(GBK 和 UTF-8)

层级
在这里插入图片描述

示例代码

用户界面

/**
 * 进行GUI的布局
 */
package www.myclass.com;

import javax.swing.*;
import java.awt.*;

public class UserGui {
	private JFrame frame; // 窗口
	private JPanel buttonPanel; // 按钮容器
	private JPanel inputPanel; // 输入容器
	private JButton closeButton; // 关闭按钮
	private JButton fireButton; // 发送按钮
	private JTextArea inputText; // 输入文本
	private JTextArea receiveText; // 接受文本

	public JPanel getInputPanel() {
		return inputPanel;
	}

	public void setInputPanel(JPanel inputPanel) {
		this.inputPanel = inputPanel;
	}

	public JButton getCloseButton() {
		return closeButton;
	}

	public void setCloseButton(JButton closeButton) {
		this.closeButton = closeButton;
	}

	public JButton getFireButton() {
		return fireButton;
	}

	public void setFireButton(JButton fireButton) {
		this.fireButton = fireButton;
	}

	public JTextArea getInputText() {
		return inputText;
	}

	public void setInputText(JTextArea inputText) {
		this.inputText = inputText;
	}

	public JTextArea getReceiveText() {
		return receiveText;
	}

	public void setReceiveText(JTextArea receiveText) {
		this.receiveText = receiveText;
	}

	public JFrame getFrame() {
		return frame;
	}

	public void setFrame(JFrame frame) {
		this.frame = frame;
	}

	public UserGui() {
		init(); // 调用初始化组件
		defaultSet(); // 调用默认设置
		contorlButton(); // 调用按钮控制
	}

	// 初始化组件
	private void init() {
		frame = new JFrame(
				"                                                                                    威杨(WeYoung)工作室🖊");
		buttonPanel = new JPanel();
		inputPanel = new JPanel();
		closeButton = new JButton("关闭(C)");
		fireButton = new JButton("发送(S)    | ∨");
		inputText = new JTextArea();
		receiveText = new JTextArea();
	}

	// 设置默认JFrame
	private void defaultSet() {
		// 窗体设置
		frame.setLayout(new BorderLayout(0, 10)); // 边框布局
		frame.setBounds(250, 250, 250 * 3, 250 * 2); // 设置界限
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 退出操作
		frame.add(receiveText, BorderLayout.NORTH); // 添加接受文本框
		frame.add(inputPanel, BorderLayout.CENTER); // 添加输入容器

		// 输入容器设置
		inputPanel.setLayout(new BorderLayout()); // 边框布局
		inputPanel.add(buttonPanel, BorderLayout.SOUTH);
		inputPanel.add(inputText, BorderLayout.CENTER);

		// 按钮容器设置
		buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); // 流式布局
		buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); // 默认靠右的流式布局
		buttonPanel.add(closeButton); // 添加关闭按钮
		buttonPanel.add(fireButton); // 添加发送按钮
		buttonPanel.setBackground(Color.white);

		// 关闭按钮设置
		closeButton.setBackground(Color.white);
		closeButton.setFocusPainted(false); // 取消聚焦

		// 发送按钮设置
		fireButton.setBackground(Color.blue);
		fireButton.setForeground(Color.white); // 前景色
		fireButton.setFocusPainted(false); // 取消聚焦

		// 输入文本框设置
		inputText.setFont(new Font("标楷体", Font.BOLD, 15));
		inputText.setLineWrap(true); // 自动换行
		inputText.setBackground(Color.white);

		// 接受文本框设置
		receiveText.setEditable(false); // 不可编辑
		receiveText.setFont(new Font("标楷体", Font.BOLD, 15));
		receiveText.setLineWrap(true); // 自动换行
		receiveText.setBackground(Color.white);
		receiveText.setRows(13); // 行数, 只设置最后这个, 其余会自适应

		frame.setVisible(true); // 渲染
	}

	// 按钮控制
	private void contorlButton() {
		// 关闭按钮点击
		closeButton.addActionListener((e) -> {
			System.exit(0);
		});

		// 发送按钮点击
		fireButton.addActionListener((e) -> {
			if ("".equals(inputText.getText())) {
				JOptionPane.showMessageDialog(frame, "内容不能为空 !");
			}
			this.getInputText().setText(null);
		});
	}
}

服务端界面

package www.myclass.com;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.Toolkit;

import javax.swing.*;
import javax.swing.border.TitledBorder;

public class ServerGui {

	private JFrame frame; // 创建swing界面
	private JTextArea contentArea; // 文本城
	private JTextField txt_message; // 消息文本框
	private JTextField txt_max;// 人数上限文本框
	private JTextField txt_port;// 端口文本框
	private JButton btn_start;// 开始按钮
	private JButton btn_stop;// 停止按钮
	private JButton btn_send;// 发送按钮
	private JButton btn_out;// 导出按钮
	private JPanel northPanel;// 北(上)方面板
	private JPanel southPanel;// 南(下)方面板
	private JScrollPane rightPanel; // 滚动面板-右
	private JScrollPane leftPanel;// 滚动面板-左
	private JSplitPane centerSplit; // 分割面板可将两个组件同时显示在两个显示区中
	private JList<String> userList; // 列表面板
	private DefaultListModel<String> listModel; // 列表集合

	public JTextArea getContentArea() {
		return contentArea;
	}

	public void setContentArea(JTextArea contentArea) {
		this.contentArea = contentArea;
	}

	public JTextField getTxt_message() {
		return txt_message;
	}

	public void setTxt_message(JTextField txt_message) {
		this.txt_message = txt_message;
	}

	public JTextField getTxt_max() {
		return txt_max;
	}

	public void setTxt_max(JTextField txt_max) {
		this.txt_max = txt_max;
	}

	public JTextField getTxt_port() {
		return txt_port;
	}

	public void setTxt_port(JTextField txt_port) {
		this.txt_port = txt_port;
	}

	public JButton getBtn_start() {
		return btn_start;
	}

	public void setBtn_start(JButton btn_start) {
		this.btn_start = btn_start;
	}

	public JButton getBtn_stop() {
		return btn_stop;
	}

	public void setBtn_stop(JButton btn_stop) {
		this.btn_stop = btn_stop;
	}

	public JButton getBtn_send() {
		return btn_send;
	}

	public void setBtn_send(JButton btn_send) {
		this.btn_send = btn_send;
	}

	public JButton getBtn_out() {
		return btn_out;
	}

	public void setBtn_out(JButton btn_out) {
		this.btn_out = btn_out;
	}

	public ServerGui() {
		// 构造方法
		frame = new JFrame("服务器"); // 窗体名称
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 退出操作
		contentArea = new JTextArea(); // 实例化一个主聊天窗口(文本域)
		contentArea.setEditable(false); // 设该窗口不能被编辑
		txt_message = new JTextField(); // 实例化一个输入框
		txt_max = new JTextField("30"); // 设置人数上限文本框的显示值为30
		txt_port = new JTextField("6666");
		btn_start = new JButton("启动"); // 实例化一个按钮名称为[启动]
		btn_stop = new JButton("停止"); // 实例化一个按钮名称为[停止]
		btn_send = new JButton("发送"); // 实例化一个按钮,名称为[发送]
		btn_out = new JButton("导出"); // 实例化一个按钮名称为[导出]
		btn_stop.setEnabled(false);// [停止]按钮默认状态为假(不可用)
		listModel = new DefaultListModel<String>(); // 设置列表数据模型
		userList = new JList<String>(listModel); // 实例化列表窗体(存放列表数据模型)
		// 实例化南方(下方)面板,布局方式为BorderLayout:
		southPanel = new JPanel(new BorderLayout());
		southPanel.setBorder(new TitledBorder("公告")); // 设置该面板的名称
		southPanel.add(txt_message, "Center"); // 将消息输入框添加到该面板中,居中
		southPanel.add(btn_send, "East"); // 将发送按钮添加到该面板中,位置居右
		leftPanel = new JScrollPane(userList); // 实例化左滚动面板,存放用户列表
		leftPanel.setBorder(new TitledBorder("在线用户")); // 设置该面板的标题
		rightPanel = new JScrollPane(contentArea); // 实例化右滚动面板,存放聊天记录
		rightPanel.setBorder(new TitledBorder("消息显示区")); // 设置该面板的标题
		centerSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel); // 设置分割面板的结构
		centerSplit.setDividerLocation(100); // 设置分割面板大小
		// 实例化北方面板(上方)
		northPanel = new JPanel();
		northPanel.setLayout(new GridLayout(1, 7)); // 设置面板布局为GridLayout
		northPanel.add(new JLabel("人数上限")); // 在面板中添加一个标签[人数上限]
		northPanel.add(txt_max); // 将最大值的文本框添加在该标签后面
		northPanel.add(new JLabel("端口")); // 在面板中添加一一个标签[端口]
		northPanel.add(txt_port); // 将端口的文本框添加在该标签后面
		northPanel.add(btn_start); // 添加[开始]按钮到面板
		northPanel.add(btn_stop); // 添加[停止]按钮到面板
		northPanel.add(btn_out); // 添加[导出]按钮到面板
		northPanel.setBorder(new TitledBorder("配置信息")); // 添加-一个面板标题
		frame.setLayout(new BorderLayout()); // 设置主界面为BorderLayout布局
		frame.add(northPanel, "North"); // 添加配置面板添加到主界面上方
		frame.add(centerSplit, "Center"); // 添加分割面板添加到主界面中间
		frame.add(southPanel, "South"); // 添加消息面板添加到主界面下方
		frame.setSize(600, 400); // 设置主界面大小
		// 获取当前的屏幕宽度和高度
		int screen_w = Toolkit.getDefaultToolkit().getScreenSize().width;
		int screen_h = Toolkit.getDefaultToolkit().getScreenSize().height;
		// 设置主界面相对于屏幕左上角的位置
		frame.setLocation((screen_w - frame.getWidth()) / 2, (screen_h - frame.getHeight()) / 2);
		frame.setVisible(true); // 设置主界面为可见
	}

	/**
	 * @return the frame
	 */
	public JFrame getFrame() {
		return frame;
	}

	/**
	 * @param frame the frame to set
	 */
	public void setFrame(JFrame frame) {
		this.frame = frame;
	}

	/**
	 * @return the listModel
	 */
	public DefaultListModel<String> getListModel() {
		return listModel;
	}

	/**
	 * @param listModel the listModel to set
	 */
	public void setListModel(DefaultListModel<String> listModel) {
		this.listModel = listModel;
	}
}

用户类

/**
 * 用户类(客户端)
 * 包装Socket
 */
package www.myclass.com;

import java.io.Serializable;
import java.net.Socket;

public class User implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String nickName;
	private String id;
	private Socket socket;

	/**
	 * @return the id
	 */
	public String getId() {
		return id;
	}

	/**
	 * @param id the id to set
	 */
	public void setId(String id) {
		this.id = id;
	}

	/**
	 * @return the socket
	 */
	public Socket getSocket() {
		return socket;
	}

	/**
	 * @param socket the socket to set
	 */
	public void setSocket(Socket socket) {
		this.socket = socket;
	}

	public String getNickName() {
		return nickName;
	}

	public void setNickName(String nickName) {
		this.nickName = nickName;
	}

	public User() {
		super();
	}

	public User(String nickName) {
		super();
		this.setNickName(nickName);
	}

	public User(Socket socket) {
		super();
		this.socket = socket;
	}

	public User(String nickName, Socket socket) {
		super();
		this.nickName = nickName;
		this.socket = socket;
	}

	public User(String nickName, String id, Socket socket) {
		super();
		this.nickName = nickName;
		this.id = id;
		this.socket = socket;
	}

	public User(String nickName, String id) {
		super();
		this.nickName = nickName;
		this.id = id;
	}
}

服务器类

package www.mytest.com;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JOptionPane;

import www.myclass.com.ServerGui;
import www.myclass.com.User;

public class ServerDemo {
	// 单例设计模式--饿汉式
	private static final ServerDemo server = new ServerDemo();
	private List<User> userList = new ArrayList<>(); // 存储所有连接上的客户端
	private ServerSocket ss; // 服务器套接字
	private String info; // 接受的客户端的消息
	private ServerGui serverGui; // 服务器界面
	private int maxPeople; // 最大在线人数
	private boolean isConnect; // 是否连接服务器

	private ServerDemo() {
		this.serverGui = new ServerGui(); // 初始化服务器界面
	}

	private static ServerDemo getInstance() {
		return server;
	}

	// 发送给所有客户端
	private void fireAllClient() throws IOException {
		for (User user : ServerDemo.getInstance().userList) {
			DataOutputStream dos = new DataOutputStream(user.getSocket().getOutputStream());
			dos.writeUTF(info);
		}
	}

	// 发送给所有客户端
	private void fireAllClient(String str) throws IOException {
		for (User user : ServerDemo.getInstance().userList) {
			DataOutputStream dos = new DataOutputStream(user.getSocket().getOutputStream());
			dos.writeUTF(str);
		}
	}

	// 发送单个客户端
	private void fireClient(User user, String str) throws IOException {
		DataOutputStream dos = new DataOutputStream(user.getSocket().getOutputStream());
		dos.writeUTF(str);
	}

	// 监听客户端消息
	private void serverEventCallBack(User user) throws IOException {
		new Thread(() -> {
			while (isConnect) {
				try {
					DataInputStream dis = new DataInputStream(user.getSocket().getInputStream());
					info = dis.readUTF();
					boolean isExit = false;
					// 检测传递过来是否为用户下线消息
					for (User u : userList)
						if (info.equals(u.getId())) { // 判定内容
							removeOnlineUser(u); // 删除在线用户
							isExit = true;
						}
					if (!isExit) {
						String name = user.getNickName();
						info = name + ": " + info;
						if (ServerDemo.getInstance().serverGui.getContentArea().getText().equals("")) {
							ServerDemo.getInstance().serverGui.getContentArea().setText(info);
						} else {
							ServerDemo.getInstance().serverGui.getContentArea().setText(
									ServerDemo.getInstance().serverGui.getContentArea().getText() + "\n" + info);
						}
						// 发送所有客户端消息
						ServerDemo.getInstance().fireAllClient();
					}
				} catch (IOException e1) {
					break;
				}
			}
		}).start();
	}

	// 添加在线用户
	private void addOnlineUser(User user) {
		ServerDemo.getInstance().serverGui.getListModel().addElement(user.getId() + "-" + user.getNickName());
	}

	// 删除在线用户
	private void removeOnlineUser(User user) {
		ServerDemo.getInstance().serverGui.getListModel().removeElement(user.getId() + "-" + user.getNickName());
	}

	// 检测用户是否重复连接
	private boolean isConnected(User user) {
		for (User u : userList) {
			if (user.getId().equals(u.getId()))
				return true;
		}
		return false;
	}

	// 监听客户端连接
	private void serverEventCallBack() {
		new Thread(() -> {
			while (isConnect) {
				try {
					ServerDemo.getInstance().maxPeople = Integer
							.parseInt(ServerDemo.getInstance().serverGui.getTxt_max().getText());
					if (ServerDemo.getInstance().maxPeople < userList.size())
						JOptionPane.showMessageDialog(ServerDemo.getInstance().serverGui.getFrame(), "服务器已满 !");
					else {
						Socket s = null;
						try {
							s = ss.accept(); // 等待式请求关闭关闭时出问题
						} catch (SocketException e) {
							break; // 不管这个异常
						}
						ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
						User user = (User) ois.readObject();
						User newUser = new User(user.getNickName(), user.getId(), s); // 包装Socket
						if (!isConnected(user)) {
							System.out.println("connect succeful !");
							addOnlineUser(newUser); // 添加在线用户
							ServerDemo.getInstance().userList.add(newUser);
							try {
								// 监听客户端消息
								ServerDemo.getInstance().serverEventCallBack(newUser);
							} catch (IOException e) {
								e.printStackTrace();
							}
						} else
							fireClient(newUser, "ALREADY_CONNECTED"); // 已经连接过了
					}
				} catch (IOException | ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	public static void main(String[] args) {
		// 开启服务器按钮点击事件
		ServerDemo.getInstance().serverGui.getBtn_start().addActionListener((li) -> {
			try {
				ServerDemo.getInstance().isConnect = true;
				ServerDemo.getInstance().serverGui.getBtn_start().setEnabled(false);
				ServerDemo.getInstance().serverGui.getBtn_stop().setEnabled(true);
				String port = ServerDemo.getInstance().serverGui.getTxt_port().getText();
				ServerDemo.getInstance().ss = new ServerSocket(Integer.parseInt(port));
				JOptionPane.showMessageDialog(ServerDemo.getInstance().serverGui.getFrame(), "服务器已启动 !");
				// 监听客户端连接
				ServerDemo.getInstance().serverEventCallBack();
			} catch (IOException e) {
				System.out.println("connect fail !");
				e.printStackTrace();
			}
		});
		// 关闭服务器按钮点击使事件
		ServerDemo.getInstance().serverGui.getBtn_stop().addActionListener(li -> {
			ServerDemo.getInstance().isConnect = false;
			ServerDemo.getInstance().serverGui.getBtn_start().setEnabled(true);
			ServerDemo.getInstance().serverGui.getBtn_stop().setEnabled(false);
			try {
				ServerDemo.getInstance().ss.close();
				ServerDemo.getInstance().ss = null;
			} catch (IOException e) {
				e.printStackTrace();
			}
		});
		// 导出按钮点击事件
		ServerDemo.getInstance().serverGui.getBtn_out().addActionListener(li -> {
			List<String> list = new ArrayList<>();
			for (User user : ServerDemo.getInstance().userList) {
				list.add("ID: " + user.getId() + "nickName: " + user.getNickName());
			}
			for (String str : list) {
				System.out.println(str); // 可跟换为导入数据库
			}
		});
		// 发送按钮点击事件
		ServerDemo.getInstance().serverGui.getBtn_send().addActionListener(li -> {
			String announcement = ServerDemo.getInstance().serverGui.getTxt_message().getText();
			ServerDemo.getInstance().serverGui.getTxt_message().setText(null);
			try {
				ServerDemo.getInstance().fireAllClient("公告: " + announcement);
			} catch (IOException e) {
				e.printStackTrace();
			}
		});
	}
}

客户端类A

package www.mytest.com;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;

import javax.swing.JOptionPane;

import www.myclass.com.User;
import www.myclass.com.UserGui;

public class ClientDemoA {
	private static final ClientDemoA client = new ClientDemoA(); // 客户端
	private Socket s; // 客戶端套接字
	private UserGui userGui; // 用户界面
	private User user; // 用户类

	private ClientDemoA() {
		this.userGui = new UserGui(); // 初始化用户界面
	}

	private static ClientDemoA getInstance() {
		return client;
	}

	// 监听服务器消息
		private void clientEventCallBack() throws IOException {
			new Thread(() -> {
				while (true) {
					try {
						DataInputStream dis = new DataInputStream(s.getInputStream());
						String info = dis.readUTF();
						if ("ALREADY_CONNECTED".equals(info)){ // 检测是否重复登陆
							JOptionPane.showMessageDialog(ClientDemoA.getInstance().userGui.getFrame(), "已登录 !");
							try {
								Thread.sleep(1500);
							} catch (InterruptedException e1) {
								e1.printStackTrace();
							}
							System.exit(0);
						}
						else
							ClientDemoA.getInstance().userGui.getReceiveText()
									.setText(ClientDemoA.getInstance().userGui.getReceiveText().getText() + "\n" + info);
					} catch (IOException e) {
						try {
							s.close();
						} catch (IOException e1) {
							e1.printStackTrace();
						}
						break;
					}
				}
			}).start();
		}

	// 发送服务器
	private void fireServer(String info) throws IOException {
		if (!"".equals(info)) {
			DataOutputStream dos = new DataOutputStream(s.getOutputStream());
			dos.writeUTF(info);
			dos.flush();
		}
	}

	// 发送服务器
	private void fireServer(User user) throws IOException {
		if (user != null) {
			ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
			oos.writeObject(user);
			oos.flush();
		}
	}

	public static void main(String[] args) {
		try {
			// 监听按钮点击
			ClientDemoA.getInstance().s = new Socket("localhost", 6666);
			ClientDemoA.getInstance().user = new User("HQZ", "0000001");
			ClientDemoA.getInstance().fireServer(ClientDemoA.getInstance().user); // 发送用户信息
			ClientDemoA.getInstance().userGui.getFireButton().addActionListener((e) -> {
				try {
					// 发送服务器消息
					ClientDemoA.getInstance().fireServer(ClientDemoA.getInstance().userGui.getInputText().getText());
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			});
			// 监听服务器消息
			ClientDemoA.getInstance().clientEventCallBack();
		} catch (IOException e) {
			JOptionPane.showMessageDialog(ClientDemoA.getInstance().userGui.getFrame(), "服务器异常 !");
			try {
				Thread.sleep(1500);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			System.exit(0);
		}
		ClientDemoA.getInstance().userGui.getCloseButton().addActionListener(e -> {
			try {
				// 通知服务器客户端下线
				ClientDemoA.getInstance().fireServer(ClientDemoA.getInstance().user.getId());
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		});	
	}
}

客户端类A2

package www.mytest.com;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;

import javax.swing.JOptionPane;

import www.myclass.com.User;
import www.myclass.com.UserGui;

public class ClientDemoA2 {
	private static final ClientDemoA2 client = new ClientDemoA2(); // 客户端
	private Socket s; // 客戶端套接字
	private UserGui userGui; // 用户界面
	private User user; // 用户类

	private ClientDemoA2() {
		this.userGui = new UserGui(); // 初始化用户界面
	}

	private static ClientDemoA2 getInstance() {
		return client;
	}

	// 监听服务器消息
	private void clientEventCallBack() throws IOException {
		new Thread(() -> {
			while (true) {
				try {
					DataInputStream dis = new DataInputStream(s.getInputStream());
					String info = dis.readUTF();
					if ("ALREADY_CONNECTED".equals(info)){ // 检测是否重复登陆
						JOptionPane.showMessageDialog(ClientDemoA2.getInstance().userGui.getFrame(), "已登录 !");
						try {
							Thread.sleep(1500);
						} catch (InterruptedException e1) {
							e1.printStackTrace();
						}
						System.exit(0);
					}
					else
						ClientDemoA2.getInstance().userGui.getReceiveText()
								.setText(ClientDemoA2.getInstance().userGui.getReceiveText().getText() + "\n" + info);
				} catch (IOException e) {
					try {
						s.close();
					} catch (IOException e1) {
						e1.printStackTrace();
					}
					break;
				}
			}
		}).start();
	}

	// 发送服务器
	private void fireServer(String info) throws IOException {
		if (!"".equals(info)) {
			DataOutputStream dos = new DataOutputStream(s.getOutputStream());
			dos.writeUTF(info);
			dos.flush();
		}
	}

	// 发送服务器
	private void fireServer(User user) throws IOException {
		if (user != null) {
			ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
			oos.writeObject(user);
			oos.flush();
		}
	}

	public static void main(String[] args) {
		try {
			// 监听按钮点击
			ClientDemoA2.getInstance().s = new Socket("localhost", 6666);
			ClientDemoA2.getInstance().user = new User("SY", "0000002");
			ClientDemoA2.getInstance().fireServer(ClientDemoA2.getInstance().user); // 发送用户信息
			ClientDemoA2.getInstance().userGui.getFireButton().addActionListener((e) -> {
				try {
					// 发送服务器消息
					ClientDemoA2.getInstance().fireServer(ClientDemoA2.getInstance().userGui.getInputText().getText());
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			});
			// 监听服务器消息
			ClientDemoA2.getInstance().clientEventCallBack();
		} catch (IOException e) {
			JOptionPane.showMessageDialog(ClientDemoA2.getInstance().userGui.getFrame(), "服务器异常 !");
			try {
				Thread.sleep(1500);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			System.exit(0);
		}
		ClientDemoA2.getInstance().userGui.getCloseButton().addActionListener(e -> {
			try {
				// 通知服务器客户端下线
				ClientDemoA2.getInstance().fireServer(ClientDemoA2.getInstance().user.getId());
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		});
	}
}

以上代码在JDK1.8运行成功 !


(二). 有趣代码

1.

(三). 我的小黄鸭

1. 抽象类实现接口

存在意义
当我们需要在一个接口里面定义多个方法但又不想全部实现这些方法时, 我们可以定义一个抽象类去实现这个接口, 再用一个普通的类(子类)去去继承这个抽象类(父类)即可.
解释
我们知道接口里面的抽象方法通过普通类实现时全部都需要重写, 而使用抽象类(父类)去实现接口时, 抽象类可以不重写接口里面的方法, 因为接口里面的方法和抽象类都由abstract关键字声明, 同属抽象概念, 因此在语法上并不会报错, 此时就需要普通类(子类)通过继承的抽象类(父类)去重写接口里面的方法了. 但显然这样做是没有意义的. 另外, 对于抽象类(父类)里面已经重写的方法, 我们在子类还可以继续重写.

示例代码:

package com.mytest.www;

//测试抽象类(父类)
public abstract class AbstractTest implements ITest {
	// 抽象类只重写 碰火方法
	@Override
	public void fireAble() {} // 可以喷火
}
package com.mytest.www;

//测试接口
public interface ITest {
	void flyAble(); // 可以飞
	void fireAble(); // 可以喷火
}
package com.mytest.www;

public class Test extends AbstractTest {
	// 子类重写 飞方法
	@Override
	public void flyAble() {
		System.out.println("可以飞");
	} // 可以飞

	public static void main(String[] args) {
		Test test = new Test();
		test.flyAble();
		test.fireAble();
	}

}

结果
在这里插入图片描述
解释
从上面的代码中我们可以看到接口里面有两个方法, flyAble() 和 fireAble() , 但是我们在 Test 类里面只重写了 flyAble() , 并没有重写全部方法但是却没有报错, 这是为什么呢? 因为我们在 Test 类的父类 AbstractTest 类里面重写了 fireAble() 方法, 因此总的来说, 方法是全部覆写了的, 因此不会报错. 总之, 理解不了的时候,放大问题规模,包治百病.

2. 子类的序列化和反序列化

在子类需要序列化而父类不序列化的时候, 我们会遇到流损坏异常或者父类的属性数据丢失的情况, 这时候, 就需要在需要序列化的子类里面,写入如下内容:

	private void writeObject(ObjectOutputStream oos) throws IOException {
		oos.defaultWriteObject(); // 序列化自身
		oos.writeObject(super.getName()); // 序列化父类的属性, 此处向上转型
	}

	private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
		ois.defaultReadObject(); // 反序列化自身
		super.setName((String) (ois.readObject())); // 反序列化父类属性, 向下转型
	}

同时, 在被继承的父类里面还需要提供一个constructer(无参构造)

	public AbstractPeople() {} // 无参构造, 子类序列化时需要
	public AbstractPeople(String name, Double length, Double weidth, String national, String sex) {
		super();
		this.name = name;
		this.length = length;
		this.weidth = weidth;
		this.national = national;
		this.sex = sex;
	}

这些都是在以上情况下时会默认调用的方法, 值得注意的是不要写错writeObject和readObject不然会报OptionalDataException, 此外, 当我们new 流的时候, 如果参数文件是一个空文件, 那么我们需要先new 输出流, 不然会EOFException.


三. 数据结构与算法

(一). 作业

1. 数据结构与算法期中测验答案

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

2. 实验7 基于查找和排序算法的学生成绩分析

示例代码

#include <bits/stdc++.h>
using namespace std;

struct studentScore {
	string no;
	string name;
	double math;
	double english;
	double solution;
};

studentScore** studentScoreArr; // 指向结构体指针的指针
unsigned int cnt = 1; // 记录结构体指针数组长度

void init(studentScore* ss) {
	// 转存结构体指针数组
	studentScore** tmp = new studentScore*[cnt];
	unsigned int len = cnt - 1;
	for (int i = 0; i < len; ++ i)
		tmp[i] = studentScoreArr[i];
	studentScoreArr = tmp;
	studentScoreArr[cnt ++ - 1] = ss;
}

void printStudentScore() {
	cout << "<<学生成绩情况表>>" << endl;
	unsigned int len = cnt - 1;
	for (int i = 0; i < len; ++ i) {
		cout << "学号: " << studentScoreArr[i] -> no << endl;
		cout << "姓名: " << studentScoreArr[i] -> name << endl;
		cout <<  "数学:" << studentScoreArr[i] -> math << endl;
		cout << "英语: " << studentScoreArr[i] -> english << endl;
		cout << "数据结构与算法: " << studentScoreArr[i] -> solution << endl;
		cout << "===========================" << endl;
	}
}

void add(studentScore* s, unsigned int position) {
	position --;
	cnt ++;
	unsigned int len = cnt - 1; // 原来数组的长度
	studentScore** tmp = new studentScore*[cnt];
	for (int i = 0, j = 0; j < len; ++ i, ++ j) {
		if (i == position)
			i ++;
		tmp[i] = studentScoreArr[j];
	}
	studentScoreArr = tmp;
	studentScoreArr[position] = s;
}

void remove(unsigned int position) {
	position --;
	cnt --;
	unsigned int len = cnt - 1; // 原来数组的长度
	studentScore** tmp = new studentScore*[cnt];
	for (int i = 0, j = 0; i < len; ++ i, j ++) {
		if (j == position)
			j ++;
		tmp[i] = studentScoreArr[j];
	}
	studentScoreArr = tmp;
}

studentScore* search(string keyNo) {
	for (int i = 0; i < cnt; ++ i)
		if (studentScoreArr[i] -> no == keyNo)
			return studentScoreArr[i];
	return NULL;
}

void insertSort(bool order = true) {// 默认升序排序
	unsigned int len = cnt - 1;
	for (int i = 1; i < len; ++ i) {
		studentScore* temp = studentScoreArr[i];
		int j;
		if (order) {
			for (j = i - 1; j > -1; -- j) {
				if (temp -> math < studentScoreArr[j] -> math)
					studentScoreArr[j + 1] = studentScoreArr[j];
				else
					break;
			}
		} else {
			for (j = i - 1; j > -1; -- j) {
				if (temp -> math > studentScoreArr[j] -> math)
					studentScoreArr[j + 1] = studentScoreArr[j];
				else
					break;
			}
		}
		studentScoreArr[j + 1] = temp;
	}
}

void quickSort(int L, int R, bool order = true) { // 默认升序排序
    studentScore* temp = NULL;
    if(L < R){
        temp = studentScoreArr[L];
        while (L != R)
        {
            if (order) {
				while(R > L && studentScoreArr[R] -> english > temp -> english) -- R;
					studentScoreArr[L] = studentScoreArr[R];
				while(L < R && studentScoreArr[L] -> english < temp -> english) ++ L;
					studentScoreArr[R] = studentScoreArr[L];
			} else {
				while(R > L && studentScoreArr[R] -> english > temp -> english) -- R;
					studentScoreArr[R] = studentScoreArr[L];
				while(L < R && studentScoreArr[L] -> english < temp -> english) ++ L;
					studentScoreArr[L] = studentScoreArr[R];
			}
        }
        studentScoreArr[L] = temp;
        quickSort(R, L - 1, order);
        quickSort(L + 1, R, order);
    }
}

void getMinheap(unsigned int parent, unsigned int len) {
	unsigned int child = parent * 2 + 1; // 左孩子节点
	while (child < len) {
		// 选最小孩子节点
		if (child + 1 < len && studentScoreArr[child] -> solution > studentScoreArr[child + 1] -> solution)
			child ++;
		if (studentScoreArr[child] -> solution < studentScoreArr[parent] -> solution) {
			swap(studentScoreArr[child], studentScoreArr[parent]);
			// 检查后续是否为最小堆
			parent = child;
			child = parent * 2 + 1;
		} else return ;
	}
}
void getMaxHeap(unsigned int parent, unsigned int len) {
	unsigned int child = parent * 2 + 1; // 左孩子节点
	while (child < len) {
		// 选最大孩子节点
		if (child + 1 < len && studentScoreArr[child] -> solution < studentScoreArr[child + 1] -> solution)
			child ++;
		if (studentScoreArr[child] -> solution > studentScoreArr[parent] -> solution) {
			swap(studentScoreArr[child], studentScoreArr[parent]);
			// 检查后续是否为最大堆
			parent = child;
			child = parent * 2 + 1;
		} else return ;
	}
}
void heapSort(bool order = true) {
	unsigned int len = cnt - 1;
	// 堆初始化
	for (int i = len / 2 - 1; i > -1; -- i)
		if (order)
			getMaxHeap(i, len);
		else
			getMinheap(i, len);
	// 调整堆
	for (int i = len - 1; i > 0; -- i) {
		swap(studentScoreArr[0], studentScoreArr[i]); // 交换首尾
		if (order)
			getMaxHeap(0, i - 1);
		else
			getMinheap(0, i - 1);
	}
}

studentScore* binarySearch(double score, string str) {
	unsigned int len = cnt - 1;
	unsigned int L = 0;
	unsigned int R = len - 1;
	unsigned int mid = 0;
	int n = str == "math" ? 0 : (str == "english" ? 1 : 2);
	switch (n) {
		case 0 : {
			while (studentScoreArr[L] -> math <= studentScoreArr[R] -> math) {
				mid = (L + R) / 2;
				if (score - studentScoreArr[mid] -> math < -1e-6)
					R = mid - 1;
				if (score - studentScoreArr[mid] -> math > 1e-6)
					L = mid + 1;
				if (abs(score - studentScoreArr[mid] -> math) < 1e-6)
					return studentScoreArr[mid];
			}
		} break;
		case 1 : {
			while (studentScoreArr[L] -> english <= studentScoreArr[R] -> english) {
				mid = (L + R) / 2;
				if (score - studentScoreArr[mid] -> english < -1e-6)
					R = mid - 1;
				if (score - studentScoreArr[mid] -> english > 1e-6)
					L = mid + 1;
				if (abs(score - studentScoreArr[mid] -> english) < 1e-6)
					return studentScoreArr[mid];
			} 
		} break;
		default : {
			while (studentScoreArr[L] -> solution <= studentScoreArr[R] -> solution) {
				mid = (L + R) / 2;
				if (score - studentScoreArr[mid] -> solution < -1e-6)
					R = mid - 1;
				if (score - studentScoreArr[mid] -> solution > 1e-6)
					L = mid + 1;
				if (abs(score - studentScoreArr[mid] -> solution) < 1e-6)
					return studentScoreArr[mid];
			}
		}
	}
	return NULL;
}

void getMax(string str) {
	int n = str == "math" ? 0 : (str == "english" ? 1 : 2);
	unsigned int len = cnt - 1;
	double maxVal = INT_MIN;
	switch (n) {
		case 0 : {
			for (int i = 0; i < len; ++ i)
				maxVal = max(maxVal, studentScoreArr[i] -> math);
		} break;
		case 1 : {
			for (int i = 0; i < len; ++ i)
				maxVal = max(maxVal, studentScoreArr[i] -> english);
		} break;
		default : {
			for (int i = 0; i < len; ++ i)
				maxVal = max(maxVal, studentScoreArr[i] -> solution);
		}
	}
	cout << "最高" << (str == "math" ? "数学: " : (str == "english" ? "英语: " : "数据结构与算法: ")) << maxVal << endl;
}

void getMin(string str) {
	int n = str == "math" ? 0 : (str == "english" ? 1 : 2);
	unsigned int len = cnt - 1;
	double minVal = INT_MAX;
	switch (n) {
		case 0 : {
			for (int i = 0; i < len; ++ i)
				minVal = min(minVal, studentScoreArr[i] -> math);
		} break;
		case 1 : {
			for (int i = 0; i < len; ++ i)
				minVal = min(minVal, studentScoreArr[i] -> english);
		} break;
		default : {
			for (int i = 0; i < len; ++ i)
				minVal = min(minVal, studentScoreArr[i] -> solution);
		}
	}
	cout << "最低" << (str == "math" ? "数学: " : (str == "english" ? "英语: " : "数据结构与算法: ")) << minVal << endl;
}

void getAvg(string str) {
	int n = str == "math" ? 0 : (str == "english" ? 1 : 2);
	unsigned int len = cnt - 1;
	double sum = 0;
	switch (n) {
		case 0 : {
			for (int i = 0; i < len; ++ i)
				sum += studentScoreArr[i] -> math;
		} break;
		case 1 : {
			for (int i = 0; i < len; ++ i)
				sum += studentScoreArr[i] -> english;
		} break;
		default : {
			for (int i = 0; i < len; ++ i)
				sum += studentScoreArr[i] -> solution;
		}
	}
	cout << "平均" << (str == "math" ? "数学: " : (str == "english" ? "英语: " : "数据结构与算法: ")) << sum / len << endl;
}

int main() {
	/* 数据 */
	// 209000741 余淇 60 90 62
	// 209000742 王韬 63 64 50
	// 209000743 金子棚 66 67 68
	// 209000744 周远洋 69 70 71
	// 209000745 蔡飞跃 72 73 74
	// 209000746 梁从敏 75 76 77
	// 209000747 杨丰帆 78 79 80
	// 209000748 管政南 80 82 83
	// 209000739 何乾泽 100 100 100
	
	cout << "__请输入学号前8位同学的成绩: __" << endl;
	for (int i = 0; i < 8; ++ i) {
		studentScore* ss = new studentScore;
		cin >> ss -> no >> ss -> name >> ss -> math >> ss -> english >> ss -> solution;
		init(ss);
	}
	
	cout << "__开始插入自己的成绩...__\n__请输入自己的成绩: __" << endl;
	studentScore* self = new studentScore;
	cin >> self -> no >> self -> name >> self -> math >> self -> english >> self -> solution;
	add(self, 1);
	cout << "__插入成功__" << endl;
	printStudentScore();
	
	cout << "__开始删除成绩__" << endl;
	remove(5);
	cout << "__删除成功__" << endl;
	printStudentScore();
	
	studentScore* sn = search("209000739");
	cout << "__搜索成功__" << endl;
	cout << "学号: " << sn -> no << endl;
	cout << "姓名: " << sn -> name << endl;
	cout <<  "数学:" << sn -> math << endl;
	cout << "英语: " << sn -> english << endl;
	cout << "数据结构与算法: " << sn -> solution << endl;
	
	cout << "__按数学成绩升序__" << endl;
	insertSort();
	printStudentScore();
	
	studentScore* sm = binarySearch(80, "math");
	if (sm != NULL) {
		cout << "__搜索成功__" << endl;
		cout << "学号: " << sm -> no << endl;
		cout << "姓名: " << sm -> name << endl;
		cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~" << endl;
	} else
		cout << "__搜索失败__" << endl;

	cout << "__按英语成绩升序__" << endl;
	quickSort(0, cnt - 2);
	printStudentScore();
	
	studentScore* se = binarySearch(90, "english");
	if (se != NULL) {
		cout << "__搜索成功__" << endl;
		cout << "学号: " << se -> no << endl;
		cout << "姓名: " << se -> name << endl;
		cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~" << endl;
	} else
		cout << "__搜索失败__" << endl;
	
	cout << "__按数据结构与算法成绩升序__" << endl;
	heapSort();
	printStudentScore();
	
	studentScore* ss = binarySearch(50, "solution");
	if (se != NULL) {
		cout << "__搜索成功__" << endl;
		cout << "学号: " << se -> no << endl;
		cout << "姓名: " << se -> name << endl;
		cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~" << endl;
	} else
		cout << "__搜索失败__" << endl;
	
	getMax("math");
	getMin("math");
	getAvg("math");
	
	getMax("english");
	getMin("english");
	getAvg("english");
	
	getMax("solution");
	getMin("solution");
	getAvg("solution");
	return 0;
}

(二). 有趣代码

1.

(三). 我的小黄鸭

1.Floyd算法

  • 应用方向:
    多源最短路径

  • 思路:
    转载自Ouyang_Lianjun的博客
    其实核心思路就是用每个节点充当桥梁连接两点, 进而来刷新任意两个点的最短距离, 当所有点都被充当过桥梁后自然所有点之间的距离最短了.

  • 代码示例:

    /**
     * 多源最短路径
     * 单向带权图
     * Floyd算法
     * input: 输入 n 表示 n * n 的矩阵, 接下来 n 行, 每行输入 n 个数据, 表示点 ni 和点 nj 的距离, -1 表示无穷
     * out: 任意两点间的最短距离
     */
     
    // 测试数据
    // 7
    // 0 12 -1 -1 -1 -1 14
    // 12 0 10 -1 -1 7 -1
    // -1 10 0 3 5 6 -1
    // -1 -1 3 0 4 -1 -1
    // -1 -1 5 4 0 2 8
    // 16 7 6 -1 2 0 9
    // 14 -1 -1 -1 -1 9 0
    
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define N 450
    
    ll graph[N][N]; // 邻接矩阵
    
    // O(pow(n, 3)), 适用数据量在 1 <= n * n <= 2 * 1e5
    void floyd(int n) {
    	for (int k = 0; k < n; ++ k) // 中转点
    		for (int i = 0; i < n; ++ i) // 行
    			for (int j = 0; j < n; ++ j) // 列
    				graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j]); // 刷新最短距离, 注意不要越界
    }
    
    int main() {
    	int n;
    	cin >> n;
    	// 建图
    	for (int i = 0; i < n; ++ i)
    		for (int j = 0; j < n; ++ j) {
    			cin >> graph[i][j];
    			graph[i][j] = (graph[i][j] == -1 ? INT_MAX: graph[i][j]);
    		}
    	// 计算
    	floyd(n);
    	// 输出
    	cout << "最短距离:" << endl;
    	for (int i = 0; i < n; ++ i)
    		for (int j = 0; j < n; ++ j)
    			cout << i + 1 << " -> " << j + 1 << " = " << graph[i][j] << endl;
    	return 0;
    }
    
  • 测试数据:

    7
    0 12 -1 -1 -1 -1 14
    12 0 10 -1 -1 7 -1
    -1 10 0 3 5 6 -1
    -1 -1 3 0 4 -1 -1
    -1 -1 5 4 0 2 8
    16 7 6 -1 2 0 9
    14 -1 -1 -1 -1 9 0

  • 运行结果:
    在g++(std = c++11)编译通过
    运行结果图

  • 解析:

    测试数据图:

    示例用图
    使用邻接矩阵存储
    A => A 的距离为0, A => B 的距离为12, A => C 的距离为-1(表示无穷, 因目前还不知道具体距离), A => D 的距离-1, A=> E 的距离-1, A => F 的距离16, A => G 的距离14;
    B => A 的距离12, B => B 的距离0, B => C 的距离10, B => D 的距离-1, B => E 的距离-1, B => F 的距离7, B => G 的距离-1,;
    其余依次推即可得到测试数据
    时间复杂度
    O(n^3), n为节点数, 例如上图的点 A~G

2. LC — 最长公共前缀

题目
在这里插入图片描述
知识点
在做这道题之前, 我们需要先知道以下概念:

  1. 字符串的前缀:
    转载自moodfriend的博客
  2. substr()
    转载自哦啦哦啦!的博客
  3. strstr()
    转载自the_tops的博客
  4. c_str()
    转载自Lemonbr的博客

思路
由于我们需要返回的是最大的公共前缀, 因此我们不妨设第一个字符串为我们的最大公共前缀, 那么接下来我们只需要判定其可不可以在后续的字符串中找到即可, 若找不到我们就对其从首地址开始切片, 直到通过strstr() 函数返回的地址不等于NULL(主串找到了最大公共前缀子串)且该地址等于主串第一个元素的首地址(保证是前缀串), 值得注意的是, 当我们的子串为空串时, 我们通过strstr() 函数返回的也是主串的第一个元素的地址, 因此我们并不需要额外判定子串长度.

示例代码

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        int len = strs.size();
        string str = strs[0];
        for (int i = 1; i < len; ++ i) {
            while (strstr(strs[i].c_str(), str.c_str()) != &strs[i][0])
                str = str.substr(0, str.length() - 1);
        }
        return str;
    }
};

结果
最好的情况

3. LC — 删除链表的倒数第N个节点

题目
在这里插入图片描述
思路
因为题目给我们的列表的头节点是一个假的头节点(有数据), 因此我们需要自己定义一个头节点. 对于题目要求的一次扫描, 我们如果可以知道我们需要移动的位置, 那么我们便可以达到一次扫描,所以我们可以这么操作: 我们知道, 我们需要的是删除倒数位置的节点, 那么如果我们正数呢? 显然会有一个相同的结果 – 余下的移动位数相同, 例如长度为5的链表([1, 2, 3, 4, 5]), 我们要删除倒数第3个节点, 那么倒数3位后位于2位置, 我们在移动一位就遍历结束, 那么如果我们正向移动3位后呢? 显然位于4位置, 同样的我们在移动一位就遍历结束. 因此我们只要从头节点开始移动一位即可.

示例代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
          ListNode* Realhead = new ListNode;
          Realhead -> next = head;
          ListNode *R = Realhead, *L = Realhead;
          // 确定L指针需要移动的位置
          for (int i = 0; i < n; i ++) {
              R = R -> next;
          } 
          while (R -> next != NULL) { // 用R限制L的移动距离
              R = R -> next;
              L = L -> next;
          }
          ListNode* Next = L -> next;
          L -> next = Next -> next;
          Next -> next = NULL;
          return Realhead -> next;
    }
};

运行结果
在这里插入图片描述

解释
建立一个真正的头节点 Realhead 指向 head , 定义两个指向头节点的指针 L 和 R , R 用来确定 L 要移动到的位置, L 用来指向要删除的节点, 最后返回 Realhead 的下一节点即可.

时间复杂度
O(n)

4. LC — 反转链表

题目
在这里插入图片描述
思路
直接上图
在这里插入图片描述
从图中我们可以发现反转的过程就是每次把 cur 节点移动到 head 节点的前面, 同时在这个过程中我们可以发现一些特点:

  1. pre 节点始终是 A
  2. head 节点是上一次移动操作的 cur 节点
  3. cur 节点为NULL时就是递归倒叙完成的条件
    不难看出我们需要传递两个参数 — head 节点(上一操作的 cur 节点) 和 pre 节点, 因此我们需要自定义一个函数来递归完成以上操作, 另外值得注意的是, 我们需要判定题目给出的 head 节点是否为空.

示例代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        return reverse(head, head);
    }
    ListNode* reverse(ListNode* head, ListNode* pre) {
        if (pre == NULL || pre -> next == NULL) {
            return head;
        }
        ListNode* cur = pre -> next;
        pre -> next = cur -> next;
        cur -> next = head;
        return reverse(cur, pre);
    }
};

解释
第一次的 pre 节点就是我们的 head 节点, 然后判定给出的 head 节点是否为 NULL以及我们递归的终止条件 pre -> next(cur节点)是否为NULL, 后面只需要不断刷新我们的 head 节点和 pre 节点即可

运行结果
最优结果
时间复杂度
O(n)

5. LC — 合并两个有序链表

题目
在这里插入图片描述
思路
示例图
在这里插入图片描述
由于给出的两个链表是已经升序排序的, 因此我们只需要每次对两个链表的第一个节点进行大小比较, 选出最小的那个作为上一节点的后继节点即可. 我们可以这样操作:
初始化 head 节点为最开始的最小的那个节点, 后续最小节点作为它的后继节点, 直到有一条链为NULL.

示例代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if (!list1 || !list2)
            return list1 ? list1 : list2;
        ListNode* head = list1 -> val < list2 -> val ? list1 : list2;
        head -> next = mergeTwoLists(head -> next, head == list1 ? list2 : list1);
        return head;
    }
};

运行结果
最优结果
解释
定义一个 head 节点作为我们最后返回的节点, 然后不断递归调用, 此时传入最新的两头节点, 直到遇到一条链为NULL的情况, 开始回溯, 最后返回最开始的 head 节点.
时间复杂度
O(n)

6. LC — 回文链表

题目
在这里插入图片描述
思路
示例图(非递归)
在这里插入图片描述

双指针, L 和 R , L 每次移动一个节点, R 每次移动两个节点, 这样最终当 R 或者 R -> next 为NULL时, 我们的 L 指针就在整个链表的中心了, 然后我们再重 L 开始反转我们的链表, 把反转后的链表和原本的链表意义对比即可. 值得注意的是, 当我们的链表为奇数个节点时, 我们的 L 指针要后移一个节点, 最终的节点不参与对比(上图中的步骤5的链1和链2是同一条链在反转后分成的两个新链).

示例代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        ListNode *L = head, *R = head;
        while (R && R -> next && L) {
            L = L -> next;
            R = R -> next -> next;
        }
        if (R)
            L = L -> next;
        R = head;
        L = reverse(L, L);
        while (L) {
            if (L -> val != R -> val)
                return false;
            L = L -> next;
            R = R -> next;
        }
        return true;
    }
private:
       ListNode* reverse(ListNode* head, ListNode* pre) {
        if (pre == NULL || pre -> next == NULL)
            return head;
        ListNode* cur = pre -> next;
        pre -> next = cur -> next;
        cur -> next = head;
        return reverse(cur, pre);
    }
};

解释
在第一个 while 进行中间位置定位, if 用于判定奇数个节点的情况, 然后再重置 R 的位置为 head 位置, 最后意义对比, 跳出条件是短的那条链的节点是否为NULL, 上图很明显显示的那条链较短. 关于反转链表的实现, 可以参考我的 LC — 反转链表, 当然反转链表方法不唯一.

运行结果
最优情况
时间复杂度
O(n)

递归
由于递归的效率低下, 我就不过多说明, 仅供感兴趣的参考

示例代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
    ListNode* newHead = NULL;
public:
    bool isPalindrome(ListNode* head) {
        newHead = head;
        return check(head);
    }
private:
    bool check(ListNode* head) {
        if (!head)
            return true;
        bool res = check(head -> next) && (head -> val == newHead -> val);
        newHead = newHead -> next;
        return res;
    }
};

运行结果
最最优情况
时间复杂度
O(n)

7. LC — 环形链表

题目
在这里插入图片描述
思路
为了满足O(1)的空间复杂度, 这道题使用快慢指针即可, 两个指针就像两个人跑步, 一个快一个慢, 如果跑道是环形的那么快的一定可以追到慢的, 如果不是环形跑道, 那么当快的指针为NULL时, 便可以判定不是环形链表了.

示例代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if (!head)
            return false;
        ListNode* fast = head;
        ListNode* slow = head;
        while (fast && fast -> next) {
            // cout << fast -> val << endl;
            fast = fast -> next -> next;
            slow = slow -> next;
            if (slow == fast)
                return true;
        }
        return false;
    }
};

解释
入股传入的头指针为NULL则肯定不是环形链表, 后面定义快慢指针, 通过判定 fast 指针是否为NULL或者 fast -> next 为NULL来结束链表的遍历, 如果在这个遍历过程中出现 fast 追上 slow 的情况则代表是一个环形的链表
运行结果
最优情况
时间复杂度
O(n)

8. LC — 二叉树的最大深度

题目
在这里插入图片描述
知识点
在做这道题前需要了解的知识:

二叉树的遍历:
转载自产品经理成长记

思路
跟前,中,后序遍历二叉树一样, 我们在此只是不用访问节点的值, 因此采用递归深搜即可.

示例代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (!root)
            return 0;
        return max(maxDepth(root -> left) + 1, maxDepth(root -> right) + 1);
    }
};

解释
每次递归左右子树都进行 +1 操作, 直到遍历到叶子节点的后继节点为NULL的情况开始回溯, 每次回溯都通过max()函数返回子树最深的深度, 最后返回的深度便是整棵树的最深深度

运行结果
最优结果
时间复杂度
O(n)

9. LC — 验证二叉搜索树

题目
在这里插入图片描述
在这里插入图片描述
预备知识
二叉树的中序遍历
可以参考我的 8. LC — 二叉树的最大深度

思路
题目需要我们左子树都小于其双亲节点, 右子树都大于其双亲节点, 因此我们很快便可以想到二叉树的中序遍历, 每次回溯时先访问左节点再访问根节点最后右节点, 然后我们可以定义一个全局变量 pre 用来存储访问过的上一节点用来跟当前的节点进行比较, 如果不符合有效二叉搜索树的定义就返回 false 这样在回溯的时候就会一直返回 false 最后必然返回的是 false.

示例代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
    TreeNode* pre;
public:
    bool isValidBST(TreeNode* root) {
        if (!root)
            return true;
        if (!isValidBST(root -> left))
            return false;
        if (pre && pre -> val >= root -> val)
            return false;
        pre = root;
        if (!isValidBST(root -> right))
            return false;
        return true;
    }
};

运行结果
最优结果
解释
示例图
在这里插入图片描述
跟一般中序遍历有点不同的是, 我们需要返回的是一个bool值, 且一旦有不符合题意
的情况出现就不再执行后续代码, 因此我们需要把递归调用放在判断条件里面

时间复杂度
O(n)

10. LC — 对称二叉树

题目
在这里插入图片描述
思路
对于这道题, 我们只需要从根节点开始, 分别向左向右遍历即可, 直到遇到不相等或者左右叶子节点的后继不是都为NULL的情况, 我们就返回 false, 为了我们可以一旦出现错误结果, 我们需要把逻辑表达式放在 return 语句中, 这样一旦错误就可以一直返回 false 直到回溯到根节点.

示例代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        return cmp(root -> left, root -> right);
    }
private:
    bool cmp(TreeNode* left, TreeNode* right) {
        if (!left && !right)
            return true;
        if (!left || !right || left -> val != right -> val)
            return false;
        return cmp(left -> left, right -> right) && cmp(left -> right, right -> left);
    }
};

解释
从根节点分别同时向左向右递归调用, 遇到左右子树同时为NULL的情况表示该左右子树的双亲节点是叶子节点, 若只有一个为NULL或者左右子树不相等, 则代表左右子树的双亲节点只有左子树或右子树或者左右子树不相等, 此时返回 false, 然后由于返回的是两个逻辑表达式的 && 运算, 因此一旦返回一个 false , 后续整颗树都将返回 false.

运行结果
最优结果
时间复杂度
O(n)

11. LC — 二叉树的层序遍历

题目
在这里插入图片描述
思路
题目需要我们从上往下, 从左往右存储节点, 我们可以每层遍历即可, 为此我们用一个队列 que 来维护节点, 然后对于每层出队入队即可.

示例代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        if (!root)
            return res;
        queue<TreeNode*> que; // 维护队列
        que.push(root);
        while (!que.empty()) {
            int n = que.size();
            vector<int> temp;
            while (n --) { // 维护每层
                TreeNode* cur = que.front();
                que.pop();
                temp.push_back(cur -> val);
                if (cur -> left)
                    que.push(cur -> left);
                if (cur -> right)
                    que.push(cur -> right);
            }
            res.push_back(temp);
        }
        return res;
    }
};

解释
定义一个空的二维容器 res, 作为我们返回的结果, 在这里我们需要判定传入根节点是否为NULL. 定义一个维护队列 que, 根节点入队, 然后不断判定队列是否为NULL, 不为空, 定义一个 temp 容器, 用来存储每层的节点值, 此时获取当前层的节点数, 判定其是否为0, 若不为0, 出队, 把它出队节点值存储在 temp中, 然后把它的左右子树入队, 再看同层次的其它兄弟节点, 进行同样的操作, 这样最后队列为NULL时, 整颗树就遍历完成了.
运行结果
最优情况
时间复杂度
O(n)

12. LC — 将有序数组转换为二叉搜索树

题目
在这里插入图片描述
思路
观察示例, 我们可以发现, 其规则是: 中间的值作为节点, 然后分为的两半再取其中间的值作为节点, 这样不断进行下去直到NULL. 由于我们需要确定它的中间位置的话需知道传入容器的大小, 因此我们需要自定义一个方法, 该方法接受两个参数(迭代器起始位置, 容器大小), 然后递归调用该方法返回的节点作为左右子树, 直到容器大小为0, 值得注意的是, 我们传入的容器大小为偶数时, 我们需要对容器的大小减一.当然, 可以不用自定义方法, 如果这样的话就需要不断的创建新的容器, 很消耗内存, 因此不采纳.

示例代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        return build(nums.begin(), nums.size());
    }
private:
    TreeNode* build(vector<int> :: iterator nums, int numsSize) {
        if (!numsSize)
            return NULL;
        TreeNode* root = new TreeNode(nums[numsSize / 2]);
        root -> left = build(nums, numsSize / 2);
        root -> right = build(nums + numsSize / 2 + 1, numsSize / 2 - !(numsSize % 2));
        return root;
    }
};

解释
自定义方法 build, 第一个参数是迭代器(因为vector和数组[]在内存中都是连续存储的, 因此我们可以像数组一样, 使用首地址去截取我们第一个开始访问的元素), 第二个参数是容器的大小. 接下来就是把传入的迭代器指向的后续所有元素的中间元素作为节点, 然后再递归确定它的左子树和右子树, 注意两点: 1. 容器大小为0时返回NULL, 容器的大小为偶数时, 需要对容器的大小减一(因为用当前容器的大小 / 2后的结果在偶数情况下比实际大一个), 最后回溯返回每次递归的节点即可, 最后返回的一定是头节点了.

运行结果
最优情况
时间复杂度
O(n)

13. LC — 合并两个有序数组

题目
在这里插入图片描述
思路
为了满足时间复杂度为O(n + m), 我们可以这样操作: 对两个数组的最后一个元素(该元素是每个数组里面的最大元素)进行比较, 大的就排在最后一个位置, 然后对相应数组的位置减一, 最终只要我们的 nums2数组的 n 为0就代表合并完成(需要的是把nums2 数组的每个元素合并到 nums1, 因此结束条件就是 nums2的n值为0)

示例代码

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int len = m + n -1;
        m--, n--;
        while (n > -1)
            nums1[len --] = (m > -1 && nums1[m] > nums2[n]) ? nums1[m --] : nums2[n --];
    }
};

解释
总长度是 nums1 的m + nums2 的n - 1的结果, 对于 nums1 和 nums2 的最后位置的索引则是 n–, m–后的结果, 然后我们判定 n 是否大于-1即可(因为前面减了一), 需要注意的一点是对于 nums1为NULL的情况, 我们需要判断m是否也大于-1.

运行结果
最优结果
时间复杂度
O(n + m)

14. LC — 第一个错误的版本

题目
在这里插入图片描述
预备知识
二分查找
转载于解学武

思路
根据题意, 我们需要减少对 isBadVersioh()的调用, 此外由于后续版本继承前面版本, 是个有序的序列, 因此, 我们可以使用二分查找.

示例代码

// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
       long long L = 1, R = n;
       while (L < R) {
           if (!isBadVersion((R + L) / 2))
                L = (R + L) / 2 + 1;
            else
                R = (R + L) / 2;
       }
        return L;
    }
};

解释
核心就是定义的开始点 L, 和结束点 R来确定中间位置 (R + L) / 2.

运行结果
在这里插入图片描述
时间复杂度
O(lgn)

15. LC — 爬楼梯

题目
在这里插入图片描述
预备知识
斐波拉契数列
转载自Beatr1ce.

思路
由于前两个数相加等于第三个数, 因此进行递归调用, 每次都用 b 覆盖原来的 a , a + b 覆盖原来的 b 即可, 另外最开始我使用的是决策树, 但是超时了.此外这道题还可以使用斐波拉契数列的通项公式: 在这里插入图片描述

示例代码(递归)

class Solution {
public:
    int climbStairs(int n) {
        return Fabolaqi(1, 1, n);
    }
private:
    int Fabolaqi(int a, int b, int n) {
        if (n <= 1)
            return b;
        return Fabolaqi(b, a + b, n - 1);
    }
};

解释
每次用 b 覆盖 a, a + b 覆盖 b, 在传入一个参数 n用于记录剩余递归次数, n < = 1作为结束条件, 最后直接返回记录当前种数的 b
运行结果
最优结果
时间复杂度
O(n)

示例代码(通项公式)

class Solution {
public:
    int climbStairs(int n) {
        double a = sqrt(5);
        return (pow((1 + a) / 2, n + 1) - pow((1 - a) / 2, n + 1)) / a;
    }
};

解释
对于该题, 比真正的斐波拉契数列少了一项, 因此在使用通项公式的时候需要对 n 加一

运行结果
最优结果
时间复杂度
O(1)

16. LC — 买股票的最大时机

题目
在这里插入图片描述
预备知识
转载于纯钧
动态规划

思路
这道题归类于动态规划, 因此使用动态规划解答.
根据题意, 我们拥有两个状态: 手里有股票, 手里没有股票.

一. 对于有股票的情况, 我们今天有股票, 那么昨天可能有股票或者没有股票:

  1. 如果有股票, 那么我们今天的利润和昨天的利润相等,
  2. 如果没有股票, 那么就是我们今天买入了股票, 原本应该是在昨天的利润上减去今天股票的价格就是今天的利润, 但是由于根据题意我们只进行一次股票买与卖, 因此相当于我们昨天肯定是没有股票的状态, 即利润为0, 因此我们只需要减去今天的股票价格就是今天的利润了

二. 对于没有股票的情况, 我们今天没有股票, 那么昨天可能没有股票或者有股票:

  1. 如果有股票, 那么我们肯定是在今天卖掉的股票, 因此今天的利润为昨天的利润加上今天卖的股票钱
  2. 如果没有股票, 那么今天的利润就等于昨天的利润

然后, 对于每天的股票买卖情况,我们只需逐一返回以上的一,二两种情况对应的今天和昨天的利润的最大值即可, 最后我们手里没有股票的情况肯定是最大利润的时候

示例代码

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        int have = -prices[0];
        int no = 0;
        for (int i = 1; i < n; ++ i) {
            have = max(have, -prices[i]);
            no = max(no, have + prices[i]);
        }
        return no;
    }
};

运行结果
在这里插入图片描述
解释
定义变量 have 和 no 表示有股票和没有股票时候的最大利润, 初始化第一天有股票时利润为-prices[0], 没有股票时利润为0, 然后 对于以后的每一天都是在前一天的基础上返回有股票时和没有股票时的最大利润, 直至最后一天时, 没有股票的情况就是最大利润的时候
时间复杂度
O(n)


四. 操作系统

(一). 作业

1. 第4章 进程调度与死锁在线作业

总得分: 80.0
一.单选题(共10题,50.0分)
1.在为多道程序所提供的可共享的系统资源不足时,可能出现死锁,但是,不适当的(     )也可能产生死锁。
A、进程优先权
B、资源的线性分配
C、进程推进顺序
D、分配队列优先权
正确答案: C 我的答案:C得分: 5.0分

2.进程调度是从(     )选择一个进程投入运行。
A、就绪队列
B、等待队列
C、作业后备队列
D、提交队列
正确答案: A 我的答案:A得分: 5.0分

3.银行家算法是一种(     )算法。
A、死锁解除

B、
死锁避免
C、死锁预防
D、死锁检测
正确答案: B 我的答案:B得分: 5.0分

4.(   )优先权是在创建进程时确定的,确定之后在整个进程运行期间不再改变。
A、先来先服务
B、静态
C、动态
D、短作业
正确答案: B 我的答案:B得分: 5.0分

5.操作系统的作业管理是一种(    )。
A、宏观的高级管理
B、宏观的低级管理
C、系统刚开始加电
D、初始化引导完毕
正确答案: A 我的答案:A得分: 5.0分

6.作业调度的关键在于(   )。
A、选择恰当的进程管理程序
B、选择恰当的作业调度算法
C、用户作业准备充分
D、用一个较好的操作环境
正确答案: B 我的答案:A得分: 0.0分

7.为了对紧急进程或者重要进程进行调度,调度算法应采用(    )。
A、先来先服务法
B、短作业优先法
C、时间片轮转法
D、优先级法
正确答案: D 我的答案:D得分: 5.0分

8.在操作系统中,(    )负责对进程进行调度。
A、处理机管理
B、作业管理
C、高级调度管理
D、存储和设备管理
正确答案: A 我的答案:A得分: 5.0分

9.两个进程争夺同一个资源(    )。
A、一定死锁
B、不一定死锁
C、不死锁
D、以上说法都不对
正确答案: B 我的答案:B得分: 5.0分

10.在非剥夺调度方式下,运行进程执行V原语后,其状态(    )。
A、不变
B、要变
C、可能要变
D、可能不变
正确答案: A 我的答案:C得分: 0.0分

二.判断题(共10题,50.0分)
1.对信号量S的P-V操作的含义是:P(S)是释放资源,V(S)是申请资源。
我的答案:× 得分: 5.0分正确答案:×

2.一般而言,由于操作系统的并行与共享以及随机性等特点,通过预防与避免的手段达到排除死锁的目的是一件非常困难的事情。
我的答案:√ 得分: 5.0分正确答案:√

3.对于并发进程,同步与互斥是一个必要条件。
我的答案:× 得分: 0.0分正确答案:√

4.进程调度属于高级调度。
我的答案:× 得分: 5.0分正确答案:×

5.吞吐量是指系统在单位时间内所完成的作业或者进程的数量。
我的答案:√ 得分: 5.0分正确答案:√

6.CPU利用率是指CPU空闲时间与总的运行时间的比值。
我的答案:× 得分: 5.0分正确答案:×

7.每个作业的加权周转时间定义该作业的周转时间与该作业的提交时间的比值。
我的答案:× 得分: 5.0分正确答案:×

8.打印机是可剥夺的一种资源。
我的答案:√ 得分: 0.0分正确答案:×

9.若某一时刻系统中不存在一个安全序列,则称此时的系统状态为不安全状态。
我的答案:√ 得分: 5.0分正确答案:√

10.系统的每个进程对各种资源的仍然需求数量等于每个进程对各种资源最大需求量与已经给该进程分配的资源数量的差值。
我的答案:√ 得分: 5.0分正确答案:√

错误分析:
原语一旦开始执行,就要连续执行完,不允许中断
并发一定需要同步和互斥相互配合
打印机不是可剥夺的一种资源

2. 第五章 存储管理

一.单选题(共10题,60.0分)
1. 下列存储方案中,(   )不存在碎片问题。
A、可变分区管理
B、段式管理
C、可重定位分区管理
D、段页式管理
正确答案: D 我的答案:A得分: 0.0分

2. 要固定分区存储管理中,每个分区的大小是(   )。
A、相同
B、随进程的大小变化
C、可以不同,需预先设定
D、可以不同,根据进程的大小设定
正确答案: C 我的答案:C得分: 6.0分

3. 在可变分区存储管理中,合并分区的目的是(   )。
A、合并空闲区
B、合并分区
C、增加内存容量
D、便于地址变换
正确答案: A 我的答案:C得分: 0.0分

4. 在页式存储管理中,分页是由(   )完成的。
A、程序员
B、硬件
C、编译程序 
D、都不对
正确答案: B 我的答案:B得分: 6.0分

5. 把程序地址空间中的逻辑地址转换为内存的物理地址称为(   )。
A、加载
B、重定位 
C、物理化
D、链接
正确答案: B 我的答案:B得分: 6.0分

6. 在以下存储管理方案中,不适用于多道程序设计系统的是(   )。 
A、单一连续分区
B、固定分区
C、可变分区
D、页式存储管理
正确答案: A 我的答案:A得分: 6.0分

7. 有利于动态链接的内存管理方法是(   )。
A、可变分区管理
B、段式管理
C、固定分区管理
D、页式管理
正确答案: B 我的答案:B得分: 6.0分

8. 静态重定位是在(   )进行的。
A、程序编译时
B、程序链接时
C、程序装入时
D、程序运行时
正确答案: C 我的答案:A得分: 0.0分

9. 快表的作用是加快地址变换过程,它采用的硬件是(   )。
A、通用寄存器
B、外存
C、内存
D、CACHE
正确答案: D 我的答案:D得分: 6.0分

10. 采用动态分区存储管理系统中,其主存容量为55MB(初始状态全空),采用最佳适配算法,分配和释放的顺序为:分配15MB,分配30MB,释放15MB,分配8MB,分配6MB,此时主存中最大空闲分区大小是(   )。
A、7MB
B、9MB
C、10MB
D、15MB
正确答案: B 我的答案:B得分: 6.0分

二.判断题(共8题,40.0分)
1. 动态重定位需要重定位寄存器的支持。
我的答案:√ 得分: 5.0分正确答案:√

2. 页是信息的物理单位,段是信息的逻辑单位。
我的答案:√ 得分: 5.0分正确答案:√

3. 存储管理就是管理存储设备的,包括内存和外存。
我的答案:× 得分: 5.0分正确答案:×

4. 可变分区就是分区的大小和分区的数目在操作系统运行期间是变化的。
我的答案:√ 得分: 5.0分正确答案:√

5. 为了减少内部碎片,页的大小越小越好。
我的答案:× 得分: 0.0分正确答案:√

6. 最佳适应算法比首次适应算法具有更好的内存利用率。
我的答案:√ 得分: 0.0分正确答案:×

7. 页式系统的优点是消除了外部碎片,更有效地利用的内存。
我的答案:√ 得分: 5.0分正确答案:√

8. 段式系统中段的共享比页式系统中页的共享更方便。
我的答案:√ 得分: 5.0分正确答案:√

错误分析
段式管理存在外部碎片, 页式管理存在内部碎片,段页式管理可以结合前两者优点, 达到基本没有碎片的情况
可变式存储合并的是空闲分区, 内存容量一开始就确定好了
最佳适配算法不一定比首次适配算法更好利用内存, 要看数据样本
静态重定位在程序装入时就去定了地址

3. 第7章设备管理作业

一. 单选题(共12题,85.2分)

  1. (单选题)设备管理的( )功能来实现使用户所编制的程序与实际使用的物理设备无关。
    A. 设备独立性
    B. 设备分配
    C. 缓冲管理
    D. 虚拟设备
    我的答案: A正确答案: A

  2. (单选题)
    在对磁盘进行读/写操作时,下面给出的参数中,________是错误的。

A. 柱面号
B. 磁头号
C. 盘面号
D. 扇区号
我的答案: D正确答案: C

  1. (单选题)通道对CPU的请求形式是( )。
    A. 陷入
    B. 中断
    C. 通道命令
    D. 跳转指令
    我的答案: B正确答案: B

  2. (单选题)为改善系统的可适应性和可扩充性,要求用户程序对I/O设备的请求采用逻辑设备名,而在程序实际执行时使用物理设备名,这种方式称为__________ 。
    A. 设备独立性
    B. 方便性
    C. 可行性
    D. 高效性
    我的答案: A正确答案: A

  3. (单选题)以下( )是磁盘寻道调度算法。
    A. 时间片轮转
    B. 优先级调度算法
    C. 最近最久未使用
    D. 最短寻道时间优先算法
    我的答案: D正确答案: D

  4. (单选题)
    为了提高设备分配的灵活性,用户申请设备时应指定__________号。
    A. 设备类相对
    B. 设备类绝对
    C. 相对
    D. 绝对
    我的答案: A正确答案: A

  5. (单选题)设备的打开、关闭、读、写等操作是由( )完成的。
    A.用户程序
    B.编译程序
    C.设备驱动程序
    D. 设备分配程序
    我的答案: C正确答案: C

  6. (单选题)引入缓冲的目的是( )。
    A. 改善用户的编程环境
    B. 缓解外部设备与CPU速度不匹配的矛盾
    C. 提高CPU的处理速度
    D. 降低计算机的硬件成本
    我的答案: C正确答案: B

  7. (单选题)设备管理的( )功能来实现使用户所编制的程序与实际使用的物理设备无关
    A. 设备独立性
    B. 设备分配
    C. 缓冲管理
    D. 虚拟设备
    我的答案: A正确答案: A

  8. (单选题)I/O设备一般由机械和电子两部分组成,两部分分开处理,方便模块化通用设计,机械部分就是设备本身,电子部分称为________ 。
    A. 设备控制器
    B. 进程控制块
    C. 作业控制块
    D. 程序计数器
    我的答案: A正确答案: A

  9. (单选题) SPOOLing技术可以实现设备的( )。
    A. 独占分配
    B. 共享分配
    C. 虚拟分配
    D. 物理分配
    我的答案: C正确答案: C

  10. (单选题)大多数低速设备都属于( )。
    A. SPOOLing
    B. 虚拟设备
    C. 共享设备
    D. 独享设备
    我的答案: D正确答案: D

二. 多选题(共2题,14.8分)
13. (多选题)I/O管理的目标( )
A. 选择、分配及控制I/O设备,以便能进行数据传输工作
B. 为用户提供一个统一友好的接口,把用户与设备的硬件特性分开,用户与实际使用的具体物理设备无关,操作系统统一管理各种各样的物理设备。
C. I/O管理软件的层次结构
D. 高效性
我的答案: ABD正确答案: ABCD
3.5分
14. (多选题)I/O控制方式有( )
A. 程序直接控制方式
B. 中断控制方式
C. DMA控制方式
D. 通道控制方式
我的答案: BCD正确答案: ABCD
错误分析:
硬盘在逻辑上被划分为磁道、柱面以及扇区., 因此在对磁盘进行读/写操作时不会存在盘面相关参数
缓冲是为了匹配外设与CPU速度匹配问题, 而缓存才是为了提高CPU的处理速度(读取数据)


五. LUA脚本语言(ReWord游戏开发)

(一).有趣小代码

1.

(二). 面向对象

1.

(三). 自定义开发框架

1. 函数式框架

请访问我的网站
https://myrainbowcolor.github.io/Rworlder/#/
注意: 如果出现无法正常访问的情况, 可以设置你的服务器提供供商

地址:https://doh.cleanbrowsing.org/doh/family-filter{?dns}

在这里插入图片描述

2. 柏林噪声3D

理解
柏林噪声就是一个伪随机的随机数生成器, 即只要输入一个数(3D下是一个3D坐标)就可生成对应的一个随机数(哈希性),但是该数在整体分布上是有规律, 利用该规律让这个随机数去模拟地形高度即可用于模拟类似 我的世界 的地形生成
原理
该算法是一种基于 晶格的算法, 即在一个3D的立方体晶格中, 我们给与其一个伪随机的梯度影响因子, 那么对于一块区域的晶格而言,就会在整体上体现伪随机性,进而模拟地形等.
那么这个梯度影响因子如何计算呢?我们用每个立方体晶格的每个顶点随机生成一个梯度向量,然后对于我们输出的这个3D坐标,我们取它的小数部分代表它位于它所在的晶格的内部坐标,用于后续计算距离向量, 整数部分我们用于选取随机梯度向量, 使用公式 : p[p[p[ix] + iy] + iz] 即可选取3D随机向量, 最后我们把梯度向量和距离向量点乘即可得到梯度影响因子.现在就差距离向量了,对于这个问题,我们可以使用位运算的方式解决,直接看着后续代码即可

示例代码

#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
 
int* p;
 
int permutation[] = { 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};
 
 
static double fade(double t) { return t * t * t * (t * (t * 6 - 15) + 10); }
static double lerp(double t, double a, double b) { return a + t * (b - a); }
static double grad(int hash, double x, double y, double z) { // 计算梯度向量和距离向量的点乘
	switch(hash & 0xF) {
		case 0x0: return  x + y;
		case 0x1: return -x + y;
		case 0x2: return  x - y;
		case 0x3: return -x - y;
		case 0x4: return  x + z;
		case 0x5: return -x + z;
		case 0x6: return  x - z;
		case 0x7: return -x - z;
		case 0x8: return  y + z;
		case 0x9: return -y + z;
		case 0xA: return  y - z;
		case 0xB: return -y - z;
		case 0xC: return  y + x;
		case 0xD: return -y + z;
		case 0xE: return  y - x;
		case 0xF: return -y - z;
		default: return 0;
	}
}
 
double Noise3D(double x, double y, double z) {//输出范围为[-1,1]
	// 获取整数部分并限制在255范围内
	int iX = (int)floor(x) & 255,
		iY = (int)floor(y) & 255,
		iZ = (int)floor(z) & 255;
	// 获取小数部分
	x -= floor(x);
	y -= floor(y);
	z -= floor(z);
	// 计算平滑曲线
	double u = fade(x),
		  v = fade(y),
		  w = fade(z);
	// 随机梯度向量值
	int g000 = p[p[p[iX] + iY] + iZ],
		g001 = p[p[p[iX] + iY] + iZ + 1],
		g010 = p[p[p[iX] + iY + 1] + iZ],
		g100 = p[p[p[iX + 1] + iY] + iZ],
		g110 = p[p[p[iX + 1] + iY + 1] + iZ],
		g101 = p[p[p[iX + 1] + iY] + iZ + 1],
		g011 = p[p[p[iX] + iY + 1] + iZ + 1],
		g111 = p[p[p[iX + 1] + iY + 1] + iZ + 1];
	// 3次插值
	return lerp(w, lerp(v, 
						lerp(u, grad(g000, x, y, z), grad(g100, x - 1, y, z)),
						lerp(u, grad(g010, x, y - 1, z), grad(g110, x - 1, y - 1, z))),
				    lerp(v,
						lerp(u, grad(g001, x, y, z - 1), grad(g101, x - 1, y, z - 1)),
						lerp(u, grad(g011, x, y - 1, z - 1), grad(g111, x - 1, y - 1, z - 1))));
}
int main() {
	p = new int[512];
	for (int i=0; i < 256 ; i++)
		p[256+i] = p[i] = permutation[i];
	cout << Noise3D(1, 9.12241515, 1) << endl;
	delete [] p;
	return 0;
}

解释
permutation : 存储随机梯度的影响因子
grad() :巧妙的计算梯度向量和距离向量的影响因子
fade() :平滑曲线,让缝补更加连续
lerp() : 使两点之间更加过渡平滑,针对3D噪声用了3次lerp()
运行结果
在这里插入图片描述
注意
结果值是位于0~1的,因此要用该值来模拟地形高度时,需要自己参照比例变换

六.大数据可视化

1.实验1-人物关系网络图

示例代码

import openpyxl
import networkx as nx
import matplotlib.pyplot as plt

relationTable = openpyxl.load_workbook("C:/Users/HQZ/Desktop/harry potter.xlsx")["relation"]
characterTable = openpyxl.load_workbook("C:/Users/HQZ/Desktop/harry potter.xlsx")["character"]

# 查找character表中对应索引的名字
def getValue(index):
    for oneColumn in characterTable.columns:
        for cell in oneColumn:
            if cell.column == 1:
                if cell.value == index:
                    return characterTable.cell(cell.row, 2).value
            else:
                break
    return None

relationList = list()
for oneRow in relationTable.rows:
    nameList = list()
    for cell in oneRow:
        name = getValue(cell.value)
        if name != None:
            nameList.append(name)
    if len(nameList) != 0:
        relationList.append(tuple(nameList))

G = nx.Graph()
plt.figure(figsize = (40, 30))
G.add_edges_from(relationList)
nx.draw_networkx(G)
plt.savefig("C:/Users/HQZ/Desktop/全体人物关系网络图.png")
plt.show()

mainAvatarList = {getValue(21), getValue(39), getValue(59)}
# 判定参数是否包含在主角列表中
def isInclude(value):
    for name in mainAvatarList:
        if name == value:
            return True
    return False

newRelationList = list()
for tup in relationList:
    for element in tup:
        if isInclude(element):
            newRelationList.append(tup)
            break

G = nx.Graph()
plt.figure(figsize = (40, 30))
G.add_edges_from(relationList)
nx.draw_networkx(G)
plt.savefig("C:/Users/HQZ/Desktop/主角三人的关系网络图.png")
plt.show()

运行结果
所有人物网络图
主角网络图

七.其它

1. notepade++主题

从该链接选择喜欢的主题, 从github上下载xml文件到notepade++的themes即可.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. pip使用错误

描述:

 Fatal error in launcher: Unable to create process using '"E:\PyCharm\APP\python.exe"  "C:\Python\Scripts\pip.exe" ': ???????????

解决方法:
DOS窗口输入:

python -m pip install --upgrade --force-reinstall pip
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EGNE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值