2023/7/12·数据结构之简单构造 ArrayList 和 LinkedList
1 · ArrayList的构造
1.1·原理
ArrayList底层就是一个数组,所以这里要还原一个ArrayList的话,就要在类中定义一个数组,当然这里不考虑泛型之类的,只是简单的做一下练习。
public class MyArrayList {
private Object[] arr;
}
1.2·方法
1.2.1·添加
然后添加第一个方法:添加方法add()
/**
* 添加
*
* @param obj 添加的变量
*/
public void add(Object obj) {
if (arr == null) { //判断是否为第一次添加
arr = new Object[1];
arr[0] = obj;
} else {
Object[] newArr = new Object[arr.length + 1]; //定义用于替换的新数组
for (int i = 0; i < newArr.length; i++) { //基于新数组长度遍历赋值
if (i != arr.length) { //如果不是新数组的最后一个元素
newArr[i] = arr[i]; //直接对应赋值
} else {
newArr[i] = obj; //否则将最后一个元素赋值为添加的元素
}
}
arr = newArr; //替换原数组
}
}
1.2.2·遍历和集合大小
当然少不了测试用的show() 方法,以及之后会用到的size() 方法
public void show() {
for (Object obj : arr) {
System.out.print(obj + " ");
}
}
/**
* 集合大小
* @return 返回成员数
*/
public int size() {
return arr.length;
}
1.2.3·获取值
获取值的get() 方法
/**
* 获取值
* @param index 索引
* @return 返回的值
*/
public Object get(int index) {
if (index < 0 || index >= arr.length) {
System.out.println("没有该索引!");
return null;
}
return arr[index];
}
1.2.4·修改
修改值的set() 方法
/**
* 修改
*
* @param index 索引
* @param newObj 新变量
*/
public void set(int index, Object newObj) {
if (index < 0 || index >= arr.length) {
System.out.println("没有该索引!");
return;
}
arr[index] = newObj;
}
1.2.5·移除
最后是移除的remove() 方法
/**
* 移除(按索引)
*
* @param index 索引
*/
public void remove(int index) {
if (index < 0 || index >= arr.length) { //判断索引是否合法
System.out.println("没有该索引!");
return;
}
Object[] newArr = new Object[arr.length - 1]; //定义用于替换的新数组
for (int i = 0; i < newArr.length; i++) { //基于新数组长度遍历赋值
if (i < index) { //如果新数组的索引小于要移除的索引
newArr[i] = arr[i]; //则对应赋值
} else {
newArr[i] = arr[i + 1]; //否则错位赋值,索引为原数组对应索引+1
}
}
arr = newArr; //替换原数组
}
1.3·测试
这样一个ArrayList 就基本构造完成了,简单测试下:
MyArrayList myArrayList = new MyArrayList();
myArrayList.add(1);
myArrayList.add(2);
myArrayList.add(3);
myArrayList.add(4);
System.out.print("遍历:");
myArrayList.show();
myArrayList.set(0,22);
System.out.print("\n设置新值后遍历:");
myArrayList.show();
System.out.println("\n索引为1的值:"+myArrayList.get(1));
myArrayList.remove(1);
System.out.println("集合大小:" + myArrayList.size());
System.out.print("移除索引为1的元素后遍历:");
myArrayList.show();
//控制台输出:
//遍历:1 2 3 4
//设置新值后遍历:22 2 3 4
//索引为1的值:2
//集合大小:3
//移除索引为1的元素后遍历:22 3 4
2 · LinkedList(单向)的构造
2.1·原理
链表的每个数据存储单元就是一个节点Node, 这里没用内部类,而是用一个公共类演示,链表的类中只有一个head属性,由头部节点开始,每个节点存储有下一个节点的属性next,最后一个节点的next的值就为null。
public class Node {
public Object value;
public Node next;
}
public class MyLinkedList {
private Node head;
}
2.2·方法
2.2.1·添加
添加方法add() ,这里单向链表就做了个尾部添加
/**
* 添加
* @param obj 新增的值
*/
public void add(Object obj) {
Node node = new Node(); //创建新节点
node.value = obj; //节点赋值
if (head == null) { //判断是否为首次添加
head = node; //头节点定为新创建的节点
} else {
Node temp = head; //存储头节点为循环指针
while (temp.next != null) { //判断指针的next属性是否为空
temp = temp.next; //不为空则将指针赋值为下一个节点
}
temp.next = node; //最后把找到的最后节点的next属性赋值新节点
}
}
2.2.2·获取节点和集合大小
获取节点的方法 get() ,这里需要另一个方法size() 来获取集合大小,用于判断索引是否越界
/**
* 获取
* @param index 获取值的索引
* @return 返回的节点
*/
public Node get(int index) {
if (head == null || index < 0 || index >= size()) { //判断head是否为空或者索引是否超出集合范围
Node error = new Node();
error.value = null;
return error; //返回空节点
}
int i = 0; //计数器
Node temp = head;
while (temp.next != null) {
if (i == index) //若计数器等于索引,则找到对应节点,跳出循环
break;
temp = temp.next;
i++;
}
return temp;
}
public int size() {
if (head == null) {
return 0;
}
int size = 1;
Node temp = head;
while (temp.next != null) {
temp = temp.next;
size++;
}
return size;
}
2.2.3·移除
移除的方法remove()
/**
* 移除
* @param index 移除的索引
*/
public void remove(int index) {
if (head == null || index < 0 || index >= size()) { //判断head是否为空或者索引是否超出集合范围
System.out.println("索引越界!");
return;
}
if (index == 0) { //判断移除的是否为头节点
head = head.next; //把头节点的下一个节点设置为头节点,原来的头节点就与链表失去关联了
return;
}
int i = 0;
Node temp = head;
while (temp.next != null) {
if (index - 1 == i) { //找到索引所在节点的上一个节点
temp.next = temp.next.next; //把该节点的next设置为下一个节点的下一个节点,即为索引所在节点的下一个节点
return;
}
temp = temp.next;
i++;
}
}
/**
* 测试用遍历方法
*/
public void show() {
if (head == null) {
System.out.println("链表为空");
return;
}
Node temp = head;
System.out.print(head.value + " ");
while (temp.next != null) {
temp = temp.next;
System.out.print(temp.value + " ");
}
}
}
2.3·测试
这里简单测试下
MyLinkedList myList = new MyLinkedList();
myList.add(1);
myList.add(2);
myList.add(3);
myList.add(4);
myList.remove(3);
myList.get(2).value = 4;
myList.show();
System.out.println("\n" + myList.get(3).value);
System.out.println("集合大小:" +myList.size());
//1 2 4
//null
//集合大小:3
3 · 双向链表
3.1·原理
原理和单向列表类似,不过节点多了个prev属性,即前一个节点,链表类里也有两个属性:头head和尾tail
public class DoubleNode {
public Object value;
public DoubleNode prev;
public DoubleNode next;
public DoubleNode() {
}
public DoubleNode(Object value) {
this.value = value;
}
}
public class MyDoubleLinkedList {
private DoubleNode head;
private DoubleNode tail;
}
3.2·方法
3.2.1·头插和尾插
添加方法分为头插addFirst()和尾插addLast()
/**
* 头插
* @param obj 值
*/
public void addFirst(Object obj) {
DoubleNode node = new DoubleNode(obj); //创建新节点
if (head == null) { //判断集合是否为空,为空则使新节点为头和尾
head = node;
tail = node;
return;
}
node.next = head; //集合不为空的情况,把新节点的next指向原头节点
head.prev = node; //再把头节点的prev指向新节点
head = node; //最后把头节点替换为新节点
}
/**
* 尾插
* @param obj 值
*/
public void addLast(Object obj) {
DoubleNode node = new DoubleNode(obj);
if (head == null) {
head = node;
tail = node;
return;
}
tail.next = node; //集合不为空的情况,把尾节点的next指向新节点
node.prev = tail; //再把新节点的prev指向原尾节点
tail = node; //最后把尾节点替换为新节点
}
3.2.2·获取节点和集合大小
获取方法get() 和获取集合大小的size()
/**
* 获取
* @param index 获取的索引
* @return 返回的节点
*/
public DoubleNode get(int index){
if (head == null || index < 0 || index >= size()) { //判断head是否为空或者索引是否超出集合范围
System.out.println("索引越界!");
return null; //返回空
}
int i = 0; //计数器
DoubleNode temp = head;
while (temp.next != null) {
if (i == index) //若计数器等于索引,则找到对应节点,跳出循环
break;
temp = temp.next;
i++;
}
return temp;
}
public int size() {
int size = 0;
DoubleNode temp = head;
while (temp != null) {
size++;
temp = temp.next;
}
return size;
}
3.2.3·移除
移除方法remove()
/**
* 移除
* @param index 移除的索引
*/
public void remove(int index) {
if (head == null || index < 0 || index >= size()) { //判断head是否为空或者索引是否超出集合范围
System.out.println("索引越界!");
return;
}
if (index == 0) { //判断移除的是否为头节点
head.next.prev = null; //把头节点的下一个节点的prev指向null
head = head.next; //把头节点的下一个节点设置为头节点,原来的头节点就与链表失去关联了
return;
}
int i = 0;
DoubleNode temp = head;
while (temp.next != null) {
if (index - 1 == i) { //找到索引所在节点的上一个节点
temp.next.next.prev = temp; //把该节点下一个节点的下一个节点的prev指向该节点
temp.next = temp.next.next; //把该节点的next指向下一个节点的下一个节点,即为索引所在节点的下一个节点
return;
}
temp = temp.next;
i++;
}
}
3.2.4·两种方向的遍历
遍历的方法displayFromHead() 和displayFromTail()
/**
* 从头部遍历
*/
public void displayFromHead() {
DoubleNode temp = head;
while (temp != null) {
System.out.print(temp.value +" ");
temp = temp.next;
}
System.out.println();
}
/**
* 从尾部遍历
*/
public void displayFromTail() {
DoubleNode temp = tail;
while (temp != null) {
System.out.print(temp.value +" ");
temp = temp.prev;
}
System.out.println();
}
3.3·测试
MyDoubleLinkedList myDoubleList = new MyDoubleLinkedList();
myDoubleList.addLast(1);
myDoubleList.addLast(2);
myDoubleList.addLast(3);
myDoubleList.addLast(4);
myDoubleList.addLast(5);
myDoubleList.addFirst(6);
myDoubleList.addLast(7);
System.out.print("集合元素(从头部遍历):");
myDoubleList.displayFromHead();
System.out.println("get方法:"+myDoubleList.get(6).value);
System.out.println("集合大小:"+myDoubleList.size());
System.out.print("集合元素:");
myDoubleList.displayFromHead();
myDoubleList.remove(0);
System.out.print("移除后集合元素(从头部遍历):");
myDoubleList.displayFromHead();
System.out.print("移除后集合元素(从尾部遍历):");
myDoubleList.displayFromTail();
System.out.println("集合大小:"+myDoubleList.size());
// 集合元素(从头部遍历):6 1 2 3 4 5 7
// get方法:7
// 集合大小:7
// 集合元素:6 1 2 3 4 5 7
// 移除后集合元素(从头部遍历):1 2 3 4 5 7
// 移除后集合元素(从尾部遍历):7 5 4 3 2 1
// 集合大小:6