20172301 《Java软件结构与数据结构》实验一报告
课程:《Java软件结构与数据结构》
班级: 1723
姓名: 郭恺
学号:20172301
实验教师:王志强老师
实验日期:2018年5月30日
必修/选修: 必修
一.实验内容
实验1:基础链表建立
- 通过键盘输入一些整数,建立链表;这些数是你学号中依次取出的两位数,再加上今天的时间。
- 然后打印所有链表元素,并输出元素的总数。
- 在你的程序中,请用一个特殊变量名来纪录元素的总数,变量名就是你的名字。 例如你叫 张三, 那么这个变量名就是nZhangSan
- 做完这一步,把你的程序git push
实验2:实现节点插入、删除、输出操作
- 继续你上一个程序, 扩展它的功能,每做完一个新功能,或者写了超过10行新代码,就git push。
- 从磁盘读取一个文件。这个文件有两个数字。
- 从文件中读入数字1,插入到链表第5位,并打印所有数字,和元素的总数。保留这个链表,继续下面的操作。
- 从文件中读入数字2, 插入到链表第 0 位,并打印所有数字,和元素的总数。 保留这个链表,并继续下面的操作。
- 从链表中删除刚才的数字1 并打印所有数字和元素的总数。
实验3:实现链表的选择排序
- 使用冒泡排序法根据数值大小对链表进行排序;
- 在排序的每一个轮次中,打印元素的总数,和目前链表的所有元素。
实验4:实现数组插入、删除、输出操作
- 通过键盘输入一些整数,建立数组。这些数是你学号中依次取出的两位数。 再加上今天的时间。
- 打印所有数组元素, 并输出元素的总数。
- 在你的程序中,请用一个特殊变量名来纪录元素的总数,变量名就是你的名字。 例如你叫 张三, 那么这个变量名就是 nZhangSan
- 做完这一步,把你的程序git push。
- 实现数组插入、删除、输出操作
- 从磁盘读取一个文件。这个文件有两个数字。
- 从文件中读入数字1,插入到数组第5位,并打印所有数字,和元素的总数。保留这个数组,继续下面的操作。
- 从文件中读入数字2, 插入到数组第 0 位,并打印所有数字,和元素的总数。 保留这个数组,并继续下面的操作。
- 从数组中删除刚才的数字1 并打印所有数字和元素的总数。
实验5:实现数组的选择排序
- 使用选择排序法根据数值大小对数组进行排序
- 在排序的每一个轮次中,打印元素的总数,和目前数组的所有元素。
2.实验过程及结果
实验一:
- 首先,要想建立一个链表,我们需要有指针。需要新建一个指针类。这里我并没有直接套用之前用过的LinearNode类,而是新的Point类。
public class Point {
protected int number;
protected Point next = null;
public Point(int num)
{
this.number = num;
}
}
- 然后,建立链表同样需要需要插入和输出方法。这里的插入我一开始并没有实现中间插入的,而是直接使用了尾插法。
public void add(Point element) {
if ( head == null)
head = element;
else {
Point temp = head;
while (temp.next != null) {
temp = temp.next;
}
temp.next = element;
}
}
- 输出就比较简单了,遍历一遍链表就好了。
public void PrintLinkedList()
{
Point node = head;
while (node != null)
{
System.out.print(node.number + " ");
node = node.next;
}
}
- 最后是代码测试类的截图以及结果截图。
这里我一开始用的是String.spit(),有一定的局限性,用户输入的时候必须输入一个空格才能成功,在后面的数组实践中已经更改,这里便不多赘述。
实验二:
- 根据题目要求,从磁盘中读取文件,那么肯定会用到IO流。这里要注意抛出IO异常。 根据我们学习,IO流可以使用
FileReader
,BufferedReader
,FileInputStream
,这里我选择的是BufferedReader
。BufferedReader的有关问题,详见问题一 - 在实验二中,我根据题目要求实现了在中间插入的方法和删除方法。值得一提的是,起初的删除方法我并没有写出循环中的else条件,导致了无限循环。详见问题二
public void Insert(int x, Point element)
{
Point temp = head;
if (x == 0)
{
element.next = head;
head = element;
}
else {
for (int y = 1; y < x - 1; y++) {
temp = temp.next;
}
element.next = temp.next;
temp.next = element;
}
}
public void DeleteNode(Point element)
{
Point ProNode = head, CurrentNode = head;
while (CurrentNode != null)
{
if(CurrentNode.number != element.number)
{
ProNode = CurrentNode;
CurrentNode = CurrentNode.next;
}
else{
break;
}
}
ProNode.next = CurrentNode.next;
}
- main方法的代码截图和代码结果截图。
实验三:
- 实验三的重点也就是如何用链表实现冒泡排序并且如何在每一轮次中输出链表和个数。
- 首先,我们要清楚冒泡排序的原理。因为涉及到三种排序,选择排序,插入排序,冒泡排序。那么我就在这列统一辨析一下。接下来的选择排序便不多赘述。
- 选择排序:应该是最好理解的,最为简单直观的排序方法,它每次都遍历数组, 从中选择出最大 (最小) 的元素,放在数组的第一位, 直到所有元素全部排序完成。 选择排序在不同的场景下都会执行相同次数的遍历,所以性能不是很高。它的时间复杂度是O (n^2) 。
- 插入排序:原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 如果数组本来就是有序的, 那么此时的复杂度为O (n) ;如果数组本来是倒序的,那么插入排序的时间复杂度就为O (n^2)。 插入排序较适应于元素少的数组进行排序。
- 有个形象的图来分析插入排序,斗地主大部分人都玩过吧:
- 冒泡排序:原理是将前后每两个数进行比较,较大的数往后排,一轮下来最大的数就排到最后去了。然后再进行第二轮比较,第二大的数也排到倒数第二了,以此类推。它的最佳时间复杂度是O(n^2)。
- 冒泡排序算法:
public void BubbleSort()
{
Point Node , CurrentNode;
int temp;int nGuoKai = 0;
for (Node = head;Node.next != null;Node = Node.next)
{
for (CurrentNode = head;CurrentNode.next != null; CurrentNode = CurrentNode.next)
{
if (CurrentNode.number>CurrentNode.next.number)
{
temp = CurrentNode.number;
CurrentNode.setNumber(CurrentNode.next.number);
CurrentNode.next.setNumber(temp);
nGuoKai = length();
}
}
PrintLinkedList();
System.out.println();
System.out.println("个数:" + nGuoKai);
}
}
- 冒泡排序实现代码中,因为冒泡排序需要进行两个数的交换,所以我们的指针类
Point类
需要有一个setNumber(int num)
的方法。所以,我修改了Point类
public void setNumber(int number)
{
this.number = number;
}
- 在这个时候我是有疑问的,在每一轮中,输出链表简单,那么如何输出每次链表的个数。 链表不像数组,有固定的长度,那么也意味着我们无法通过直接调用某个变量来计算链表的长度。所以,我给出了
length()
方法来计算链表的长度。
public int length()
{
int length = 0;
Point temp;
temp = head;
while (temp != null)
{
length++;
temp = temp.next;
}
return length;
}
其实也就是一个遍历链表的过程。然后让我的nGuoKai变量 = length()
,便可以了。
- 实验结果截图:
实验四:
- 创建数组的实现非常简单,这里用户交互的时候,我使用的是
StringTokenizer类
以弥补上一个链表实验的不足。
这里直接给出测试类截图: - 在数组的添加和删除操作中,需要考虑数组内元素的移动。
public static int[] Insert(int[] nums, int position, int num)
{
// 对数组进行扩容
int[] nums1 = new int[nums.length + 1];
// 对数组进行扩容,不用新建一个数组。
// nums = Arrays.copyOf(nums, nums.length + 1);
//将原数组数据赋值给新数组
for(int i = 0; i < nums.length; i++)
{
nums1[i] = nums[i];
}
//将大于position的数据向后移动一位
for(int j = nums1.length-2;j>position - 1;j--)
{
nums1[j+1] = nums1[j];
}
//赋值到position位置
nums1[position] = num;
return nums1;
}
- 这里的添加操作,我是先对数组的尾端进行操作。因为数组扩容了,所以此时的尾端应该是空的。
- 所以我让前一个元素来覆盖后一个的元素。这样并不会出现丢失元素的情况。一直到我要插入的那个索引值
position
那里,跳出循环,把要插入的值赋给position
。最后返回数组。 - 这里在测试类测试时发生了问题。详见问题三
public static int[] Delete(int[]nums, int position) { // 对数组进行扩容 int[] nums1 = new int[nums.length - 1]; int i = 0; // 对数组进行扩容,不用新建一个数组。 // nums = Arrays.copyOf(nums, nums.length - 1); //将原数组数据赋值给新数组 for(int j = 0;j < nums.length ;j++) { if (position != j) { nums1[i] = nums[j]; i++; } } //赋值到position位置 return nums1; }
- 所以我让前一个元素来覆盖后一个的元素。这样并不会出现丢失元素的情况。一直到我要插入的那个索引值
- 删除操作也同样如此,把原来的数组重新复制到新的数组当中。当条件等于索引值时,就跳过即可。
- 特别注意一点,像我注释中所说的那样,对数组进行扩容,不用新建一个数组。 我们可以直接使用
Arrays类
中的CopyOf()
方法。当然它的实现也是基于新建一个数组来进行赋值。殊途同归。 - 最后是测试类代码和代码结果截图。
实验五:
- 选择排序在上面也已经具体介绍过,而且上学期实现过数组的选择排序。所以这里不多介绍。
- 数组的每一轮次的输出和个数就要比链表的简单了,因为数组的长度是固定的,而排序也并未改变数组的个数。
- 这里直接给出代码和截图。
3. 实验过程中遇到的问题和解决过程
问题1:在思考IO流BufferedReader的时候,我记得需要有BufferedReader.close()的操作,来关闭输入的流,释放内存。
但是我记得还有一个flush操作,似乎是刷新缓冲区的,但是不记得具体的用法和用途了。- 问题1解决方案:
- 参考资料: FileWriter的flush问题
- 首先,可以得知,flush方法是用于BufferedWriter的操作,是为了刷新缓冲区,使其写入文件。
- 同样,FileOutputStream 继承 OutputStream 并不提供flush方法的重写。所以无论内容多少write都会将二进制流直接传递给底层操作系统,flush无效果,而Buffered系列的输入输出流函数。单从Buffered这个单词就可以看出他们是使用缓冲区的,应用程序每次IO都要和设备进行通信,效率很低,因此缓冲区为了提高效率,当写入设备时,先写入缓冲区,等到缓冲区有足够多的数据时,就整体写入设备。
flush会刷新缓冲区,而close方法内部是会先调用flush方法再执行关闭方法的,异曲同工。
问题2:链表删除操作,导致了无限循环。无法进行后续的任何操作。
如图:- 问题2解决方案:
- 由图可见,我左边的滚动条已经拉到最低了。那么首先,要清楚问题出现的位置。这里一看就是删除中的操作陷入了无限循环,条件没有跳出,导致无法停止操作。
- 这是我的删除操作。注释后,是更改以后的操作。
- 之前没有加else,我并没有想到会陷入无限循环。以为当CurrentNode.number == element.number,跳出if,进行删除操作。
但是,并没有跳出while循环,这时,我还犯了第二个错误,我以为无限循环是整个链表的从头到尾从头到尾的不断循环。其实不然,当CurrentNode.number != element.number时,CurrentNode就不再改变了,也就是说CurrentNode一直不为空,并且一直不等于element.number。 - 在小组成员的帮助下,成功解决了问题,在此感谢段志轩和李馨雨同学。这个问题,暴露出了两个问题。第一,对于代码的分析和理解能力还不足。没有正确理解循环和跳出if和循环的条件。第二,对于代码的细心程度不够。没有考虑到if的多种可能,没有考虑到后续操作导致的后果。希望以后以此为戒,多加改进。
问题3:数组进行了添加操作,但是数组却没有变化。添加失败?
- 问题3解决方案:
- 在我进行了调试以后,发现
Insert()
方法后返回值是没有错误的。也就是说,对于Insert()
方法是没有任何错误的。 - 那么错误,就很有可能发生在测试类的代码编写当中。问题中的截图分别是我的第一次错误代码,和第一次更改的代码。然而,虽然,第一次更改解决了插入并且输出新数组的操作,但是无法再对新数组进行操作。所以,最后只能,创建另一个新的数组来存储排序后的数组。
- 在我进行了调试以后,发现
其他(感悟、思考等)
- 线性结构的应用,在上学期已经接触过,并且实现过部分。这是数据结构的基础。很多时候,我们并不能熟练的掌握,比如说让你直接说出某种排序的原理和关键代码。很多还是需要我们不断积累和沉淀。
- 慎终如始,则无败事。