目录
第1关:顺序表的实现之增删功能
任务描述
本关任务:实现一个顺序表,并实现增加元素,删除元素功能。
相关知识
顺序表
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素、使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系,采用顺序存储结构的线性表通常称为顺序表。
顺序表是将表中的结点依次存放在计算机内存中,一组地址连续的存储单元中。
下图是一个顺序表示意图:
增加元素
现要插入元素到顺序表中,若没有指定添加元素的位置,则元素直接添加到数组末尾,表元素个数加
1
。如指定要插入到位置
index
,则下标为index
及之后的元素都需相应的移动。现假设表中已有元素
15 23 46 16 57 10 64
,要插入一个新元素48
到位置3
(即调用list.Add(3,48)
方法),因这里下标为3
的元素是16
,则16
及之后的元素都需移动。表中元素的变化过程如下图:
删除元素
同样,假设现在表中已有元素
15 23 46 16 57 10 64
,现要删除下标为3
的元素,即调用list.remove(3)
方法,则表中元素的变化过程如下图:Java 数组
数组,是由相同类型的元素的集合所组成的数据结构,分配一块连续的内存来存储。你可以声明一个数组变量,如
arr[10]
来代替直接声明10
个独立变量arr0
,arr1
,....,arr9
。声明数组变量
首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:
Type[] arrVar; // 方式一 Type arrVar[]; // 方式二
这两种声明数组的方式都是正确的。 例如,声明一个
double
型数组,我们可以这样写:double[] myArr 或者 double myArr[]
创建数组
Java
语言使用new
操作符来创建数组,如下:myArr = new Type[arrSize];
arrSize
是数组的大小,所以这里我们创建了一个大小为arrSize
的Type
类型的数组,并把它赋值给myArr
。数组变量的声明、创建数组可以用一条语句完成,如下:
Type[] arrVar = new Type[arrSize];
Java 中的数组下标
在
Java
中,数组的下标是从0
开始的。如一个有7
个元素的数组,其下标范围是0,1,2,3,4,5,6
。我们实现的表中,下标仍然是从
0
开始,即list.get(0)
返回的是第一个元素,list.get(n-1)
返回的是第n
个元素。
代码:
package step1;
/**
* Created by zengpeng on 2017/12/25.
*/
public class MyArrayList {
private int[] elements;//元素
private int size;//List中当前的元素个数
public MyArrayList() {
this(1);//List默认大小为1
}
/**
* 按指定大小capacity构造List
*
* @param capacity List初始化时的大小
*/
public MyArrayList(int capacity) {
elements = new int[capacity];
size = 0;
}
/**
* 返回List中元素的个数
*
* @return
*/
public int size() {
return size;
}
/**
* 添加一个元素到末尾
*
* @param item
*/
public void Add(int item) {
int len = elements.length;
if (size == len - 1) {
resize(2 * len);
}
/********** Begin *********/
//添加一个元素到末尾
elements[size++] = item;//数组个数++,保存新元素
/********** End *********/
}
/**
* 添加一个元素到指定位置index
*
* @param index
* @param item
*/
public void Add(int index, int item) {
validateRangeForAdd(index);
int len = elements.length;
if (size == len - 1) {
resize(2 * len);
}
/********** Begin *********/
//添加一个元素到指定位置index
for (int i = size; i > index; i--) //寻找要插入的位置
{
elements[i] = elements[i - 1];//将要插入位置后的所有元素向后移动一位
}
elements[index] = item;//当前位置添加插入元素
size++;//元素个数+1
/********** End *********/
}
/**
* 删除指定位置index的元素,并返回被删除的元素
*
* @param index
* @return 被删除的元素
*/
public int remove(int index) {
validateRange(index);
/********** Begin *********/
//删除指定位置index的元素,并返回被删除的元素
int oldVal = elements[index];//保存要删除元素
for (int i = index; i < size - 1; i++) //寻找要删除的位置
{
elements[i] = elements[i + 1];//删除元素所有删除位置以后的元素向前移动一位
}
--size;//元素个数-1
return oldVal;//返回要删除元素
/********** End *********/
}
/**
* 校验索引范围
*
* @param index
*/
private void validateRange(int index) {
if (index >= size || index < 0) {
throw new ArrayIndexOutOfBoundsException("索引越界了哦!Index: " + index + ", Size: " + size);
}
}
/**
* 校验索引范围
*
* @param index
*/
private void validateRangeForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("索引越界了哦!Index: " + index + ", Size: " + size);
}
/**
* 动态扩展数组大小
*
* @param capacity
*/
private void resize(int capacity) {
assert capacity > size;
int[] tmp = new int[capacity];
for (int i = 0; i < size; i++) {
tmp[i] = elements[i];
}
elements = tmp;
}
}
以下是测试样例:
list.Add(0, 3);
list.Add(5);
list.Add(7);
list.Add(-1);
list.Add(2, 4);
list.Add(-6);
list.remove(0);
while (list.size() > 0) {
System.out.println(list.remove(0));
}
预期输出
5
4
7
-1
-6
第2关: 顺序表的实现之查询功能
任务描述
在上一关,我们实现了表的基本功能:添加元素,删除元素。本关的任务是在上一关的基础上,实现获取指定位置元素的功能。
如
list.get(2)
返回表中下标为2
的元素,即表中第3
个元素。相关知识
请参考上一关。
代码:
package step2;
/**
* Created by zengpeng on 2018/1/6.
*/
public class MyArrayList {
private int[] elements;//元素
private int size;//List中当前的元素个数
public MyArrayList() {
this(1);//List默认大小为1
}
/**
* 按指定大小capacity构造List
*
* @param capacity List初始化时的大小
*/
public MyArrayList(int capacity) {
elements = new int[capacity];
size = 0;
}
/**
* 返回List中元素的个数
*
* @return
*/
public int size() {
return size;
}
/**
* 添加一个元素到末尾
*
* @param item
*/
public void Add(int item) {
int len = elements.length;
if (size == len - 1) {
resize(2 * len);
}
elements[size++] = item;
}
/**
* 添加一个元素到指定位置index
*
* @param index
* @param item
*/
public void Add(int index, int item) {
validateRangeForAdd(index);
int len = elements.length;
if (size == len - 1) {
resize(2 * len);
}
for (int i = size; i > index; i--) {
elements[i] = elements[i - 1];
}
elements[index] = item;
size++;
}
/**
* 删除指定位置index的元素,并返回被删除的元素
*
* @param index
* @return 被删除的元素
*/
public int remove(int index) {
validateRange(index);
int oldVal = elements[index];
for (int i = index; i < size - 1; i++) {
elements[i] = elements[i + 1];
}
--size;
return oldVal;
}
/**
* 返回表中下标为index的元素
*
* @param index 下标
* @return
*/
public int get(int index) {
validateRange(index);
/********** Begin *********/
//返回表中下标为index的元素
return elements[index];//直接返回该数组储存的元素
/********** End *********/
}
/**
* 校验索引范围
*
* @param index
*/
private void validateRange(int index) {
if (index >= size || index < 0) {
throw new ArrayIndexOutOfBoundsException("索引越界了哦!Index: " + index + ", Size: " + size);
}
}
/**
* 校验索引范围
*
* @param index
*/
private void validateRangeForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("索引越界了哦!Index: " + index + ", Size: " + size);
}
/**
* 动态扩展数组大小
*
* @param capacity
*/
private void resize(int capacity) {
assert capacity > size;
int[] tmp = new int[capacity];
for (int i = 0; i < size; i++) {
tmp[i] = elements[i];
}
elements = tmp;
}
}
以下是测试样例:
list.Add(0, 3);
list.Add(5);
list.Add(7);
list.Add(-1);
list.Add(2, 4);
list.Add(-6);
list.remove(0);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
第3关:单链表的实现之增删功能
任务描述
在之前的实训中,我们用数组实现了表,在本关我们将使用另外一种数据组织方式实现表。
我们注意到,在之前的关卡中为了防止数组溢出,我们在必要的时候对数组进行了扩展,但这样做的缺点是很明显的,每次扩展数组时都需要移动数据。而且在为新插入的元素腾出空间或者弥合删除后留下的间隙时,也要移动数据。而之所以要移动数据,是因为表是连续存储的,所以如果我们能保证表可以不连续存储,那么这种开销是可以避免的。
本关任务:实现一个带头结点的单链表,并实现基本功能:插入元素,删除元素。
相关知识
链表
链表的概念
链式方式实现的表,简称链表。链表是使用指针(即引用)将存储元素的那些单元依次串联在一起。这种方法避免了数组中用连续的单元存储元素的缺点,因而在插入或者删除时不再需要移动元素来腾出空间或填补空缺。
链表中元素之间的逻辑关系(前还是后)是通过设置指针(引用)表示的。因此每个单元至少有两个域,一个用于数据元素的存储,一个用于指向其他单元的指针。这种具有一个数据域和多个指针域的存储单元称为结点。(
node
)。结点图示:
单链表
单链表的每个结点只有一个指向表中下一个结点的指针。我们称最后一个结点为尾结点,尾结点的特征是指针域(
next
)引用为空(null
)。单链表的一个图示如下:
下图是一个带头结点的单链表:
头结点中的数据域不存放数据,指针域指向链表的第一个结点,便于对链表操作。
向单链表中添加元素
插入数据示意图:
从单链表中删除元素
删除元素示意图:
Java
是一门面向对象的语言,所以对于链表结点,我们需要定义一个类来表示,这里我们采用静态内部类的形式定义。Java静态内部类
所谓内部类,就是在一个类内部定义的类。静态内部类是最简单的内部类形式,类定义时要加上
static
关键字,且不能和外部类有相同的名字。静态内部类会被编译成一个完全独立的.class
文件,名称为OuterClass$InnerClass.class
的形式。生成静态内部类对象的方式为:OuterClass.InnerClass inner = new OuterClass.InnerClass();
静态内部类使用代码示例:
代码:
package step3;
/**
* Created by zengpeng on 2017/12/25.
*/
public class MyLinkedList {
private Node first;//头结点,不存数据
private Node last;//指向链表的最后一个节点
private int size;
public MyLinkedList() {
size = 0;
first = new Node(0, null);
last = null;
}
/**
* 添加到链表尾部
*
* @param item
*/
public void add(int item) {
/********** Begin *********/
//添加到链表尾部
Node x = last;//保存尾结点
Node node = new Node(item, null);//创建新结点,并赋值元素
last = node;//尾结点变为新结点
if (first.next == null) //如果第一个结点(相当于头结点)指向空就表示没有元素
{//首次添加
first.next = node;//第一个结点指向新结点
}
else
{
x.next = node;//尾结点指向新结点
}
++size;//元素个数+1
/********** End *********/
}
/**
* 添加数据item到指定位置index
* index从0开始
* @param index
* @param item
*/
public void add(int index, int item) {
checkPosIndex(index);
/********** Begin *********/
//添加数据item到指定位置index
Node x = first;//保存第一个结点
while (index-- > 0) //个数减少,当为0时代表找到要添加的位置的前一个结点
{
x = x.next;//保存当前结点
}
Node node = new Node(item, null);//创建新结点,并赋值元素
if (null == first.next) //头结点指向为空代表没有数
{//首次添加
last = node;//尾结点指向新结点
}
node.next = x.next;//新结点指向要添加位置的前一个结点的指向
x.next = node;//将要添加位置的前一个结点的指向为新结点
++size;//保存元素个数
/********** End *********/
}
/**
* 删除指定位置index处的元素并返回, index从0开始
* @param index
* @return
*/
public int remove(int index) {
checkPosIndex(index);
/********** Begin *********/
//删除指定位置index处的元素并返回, index从0开始
Node g = first;//保存第一个结点
while (index-- > 0) //个数减少,当为0时代表找到要删除的位置的前一个结点
{
g = g.next;//保存当前结点
}
Node d = g.next;//保存需要删除的元素
if (d == last)
{//删除最后一个元素
last = g;//则让最后一个结点变为要删除的位置的前一个结点
}
g.next = d.next;//让要删除的位置的前一个结点的指向要删除元素的下一个结点(即跳过要删除的元素直接指向下一个,即为删除操作)
--size;//元素个数-1
return d.item;//返回要删除元素
/********** End *********/
}
public int size() {
return size;
}
private void checkPosIndex(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}
//结点内部类
private static class Node {
int item;
Node next;
Node(int item, Node next) {
this.item = item;
this.next = next;
}
}
}
以下是测试样例:
list.add(0,1);
list.add(5);
list.add(7);
list.add(-1);
list.add(0, 3);
list.add(2, 4);
list.add(-6);
list.remove(6);
list.add(33);
while (list.size()>0){
System.out.println(list.remove(0));
}
预期输出:
3
1
4
5
7
-1
33
第4关:单链表的实现之查询功能
任务描述
在上一关,我们实现了单链表的基本功能:添加元素,删除元素。链式实现的表无法实现随机存取,因此要获取指定位置的元素,只能从头结点开始遍历。 本关任务:在上一关的基础上继续完善单链表的功能,实现获取指定位置元素的功能。
相关知识
请参考上一关
代码:
package step4;
/**
* Created by zengpeng on 2017/12/25.
*/
public class MyLinkedList {
private Node first;//头结点,不存数据
private Node last;//指向链表的最后一个节点
private int size;
public MyLinkedList() {
size = 0;
first = new Node(0, null);
last = null;
}
/**
* 添加到链表尾部
*
* @param item
*/
public void add(int item) {
final Node l = last;
final Node node = new Node(item, null);
last = node;
if (first.next == null) {//首次添加
first.next = node;
} else {
l.next = node;
}
++size;
}
/**
* 添加数据item到指定位置index
* index从0开始
* @param index
* @param item
*/
public void add(int index, int item) {
checkPosIndex(index);
int n = index;
Node l = first;
while ((n--) > 0) {
l = l.next;
}
final Node node = new Node(item, null);
if (null == first.next) {//首次添加
last = node;
}
node.next = l.next;
l.next = node;
++size;
}
/**
* 删除指定位置index处的元素并返回, index从0开始
* @param index
* @return
*/
public int remove(int index) {
checkPosIndex(index);
Node f = first;
while ((index--) > 0) {
f = f.next;
}
Node del = f.next;
if (del == last) {//删除最后一个元素
last = f;
}
f.next = del.next;
del.next = null;
int oldVal = del.item;
del = null;
--size;
return oldVal;
}
/**
* 获取链表中第index个元素
* @param index
* @return
*/
public int get(int index) {
checkPosIndex(index);
/********** Begin *********/
//获取链表中第index个元素
Node l=first;//保存第一个结点
for(int i=0;i<=index;i++)//寻找要获取的元素的位置
{
l=l.next;//保存当前结点
}
return l.item;//返回要获取元素
/********** End *********/
}
public int size() {
return size;
}
private void checkPosIndex(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}
//结点内部类
private static class Node {
int item;
Node next;
Node(int item, Node next) {
this.item = item;
this.next = next;
}
}
}
以下是测试样例:
list.add(0,1);
list.add(5);
list.add(7);
list.add(-1);
list.add(0, 3);
list.add(2, 4);
list.add(-6);
list.remove(6);
list.add(33);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
预期输出
3
1
4
5
7
-1
33
关于结点指向null,释放结点内存的操作这里可以不用,除非需要控制内存