先赞后看,养成习惯!!! ^ _ ^ ❤️ ❤️ ❤️
码字不易,大家的支持就是我坚持下去的动力,点赞后不要忘记关注我哦
📘 本系列文章为本人在学习路上遇到的问题和解决方法,在这里撰写成文是为了巩固知识和帮助其他友友。
个人主页 🔍: 加瓦糕手
专栏链接 📁: 问题分析简介
如有错误,请您指正批评 ^
1. 顺序表
顺序表使用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删改查。
1.1 基于ArrayList实现的洗牌和发牌操作
java
package Card1;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
class Card{
public int rank;
public String suit;
@Override
public String toString() {
return String.format("[%s %d]", suit, rank);
}
}
public class CardDemo1 {
public static final String[] SUITS={"♠","♥","♣","♦"};
public static List<Card> buyDeck(){
List<Card> deck=new ArrayList<>();
for (int i=0;i<4;i++){
for(int j=1;j<=13;j++){
String suit=SUITS[i];
int rank=j;
Card card=new Card();
card.rank=rank;
card.suit=suit;
deck.add(card);
}
}
return deck;
}
//交换两张牌的位置
public static void swap(List<Card> deck,int i,int j){
Card t=deck.get(i);
deck.set(i,deck.get(j));
deck.set(j,t);
}
//从后往前依次遍历[0,i),交换随机r位置的牌和当前i位置的牌
private static void shuffle(List<Card> deck){
Random random=new Random();
for (int i=deck.size()-1;i>0;i--){
int r=random.nextInt(i);
swap(deck,i,r);
}
}
public static void main(String[] args) {
List<Card> deck=buyDeck();
System.out.println("刚买回来的牌:");
System.out.println( deck);
shuffle(deck);
System.out.println("洗过的牌:");
System.out.println(deck);
// 三个⼈,每个⼈轮流抓 5 张牌
List<List<Card>> hands=new ArrayList<>();
hands.add(new ArrayList<>());
hands.add(new ArrayList<>());
hands.add(new ArrayList<>());
for (int i=0;i<5;i++){
for (int j=0;j<3;j++){
hands.get(j).add(deck.remove(0));
}
}
System.out.println("剩余的牌");
System.out.println(deck);
System.out.println("A ⼿中的牌:");
System.out.println(hands.get(0));
System.out.println("B ⼿中的牌:");
System.out.println(hands.get(1));
System.out.println("C ⼿中的牌:");
System.out.println(hands.get(2));
}
}
1.2 通过数组模拟实现ArrayList
java
package MyArrayList;
public class MyArrayList {
private String[] data=null;
private int size=0;
public MyArrayList(){
data=new String[10];
}
public MyArrayList(int capacity){
if (capacity<=0){
capacity=10;
}
data=new String[capacity];
}
//扩容
public void resize(){
String[] newData=new String[data.length+(data.length>>1)];
for (int i=0;i<data.length;i++){
newData[i]=data[i];
}
data=newData;
}
//尾插操作
public void add(String elem){
if (size>=data.length){
resize();
}
data[size]=elem;
size++;
}
// 往中间位置插入. 往 index 元素之前插入, 确保新元素的下标就是 index.
// 时间复杂度 O(N)
public void add(int index,String elem){
// 判定 index 是否合法.
// 此处是否要写作 index <= 0 或者 index >= size??
// 边界值都需要重点讨论.
// 如果 index 为 0, 意思是插入完毕后, 元素就处于 0 号位置. 就相当于 "头插"
// 如果 index 为 size, 意思是插入完毕后, 元素处于 size 号位置. 就相当于 "尾插"
if(index<0 || index>size){
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
if(size>=data.length){
resize();
}
for(int i=size-1;i>=index;i--){
data[i+1]=data[i];
}
data[index]=elem;
size++;
}
// 按照下标删除
// 返回被删除的元素的值.
// 时间复杂度 O(N)
public String remove(int index){
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
String elem=data[index];
for (int i=index;i<size;i++){
data[i]=data[i+1];
}
size--;
return elem;
}
// 按照元素值来删除
// 如果删除成功, 返回 true. 否则, 返回 false.
// 如果 elem 本身不存在, 就认为是删除失败.
// 时间复杂度 O(N)
public Boolean remove(String elem){
int removePos = 0;
for (;removePos<size;removePos++){
if (data[removePos].equals(elem)){
break;
}
}
if (removePos == size) {
// 没找到.
return false;
}
remove(removePos);
return true;
}
public String get(int index){
if (index<0 || index>=size){
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
return data[index];
}
public void set(int index, String elem) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
data[index]=elem;
}
public void clear(){
size=0;
}
public boolean contains(String elem) {
return indexOf(elem)!=-1;
}
public int indexOf(String elem) {
for (int i=0;i<size;i++){
if (data[i].equals(elem)){
return i;
}
}
return -1;
}
public int lastIndexof(String elem){
for (int i=size-1;i>=0;i--){
if (data[i].equals(elem)){
return i;
}
}
return -1;
}
public MyArrayList subList(int fromIndex, int toIndex) {
if (fromIndex < 0 || toIndex > size || fromIndex > toIndex) {
throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + ", toIndex: " + toIndex + ", Size: " + size);
}
MyArrayList subList=new MyArrayList(toIndex-fromIndex);
for (int i=fromIndex;i<toIndex;i++){
String elem=this.get(i);
subList.add(elem);
}
return subList;
}
@Override
public String toString() {
// 把有效元素转为字符串, 并拼接到一起.
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
for (int i = 0; i < size; i++) {
stringBuilder.append(data[i]);
if (i < size - 1) {
// 如果 i 是 size - 1, 说明是最后一个元素, 不需要加 , 了.
stringBuilder.append(", ");
}
}
stringBuilder.append("]");
return stringBuilder.toString();
}
// 测试尾插
// 测试代码也很关键. 把每个功能点的测试代码单独拎出来, 作为一个测试方法.
// 这种测试的思路称为 "单元测试"
private static void test1() {
MyArrayList list = new MyArrayList();
list.add("hello");
list.add("world");
list.add("world");
list.add("world");
list.add("world");
list.add("world");
list.add("world");
list.add("world");
list.add("world");
list.add("world");
list.add("world");
// 还有办法, 不通过打印, 也能看到 list 中的内容. 借助调试器~~
System.out.println(list);
}
// 测试中间位置插入
private static void test2() {
MyArrayList list = new MyArrayList();
list.add(0, "aaa");
list.add(0, "bbb");
list.add(0, "ccc");
list.add(0, "ddd");
list.add(2, "xxx");
System.out.println(list);
}
// 测试删除操作
private static void test3() {
MyArrayList list = new MyArrayList();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
String elem = list.remove(1);
System.out.println(elem);
System.out.println(list);
}
private static void test4() {
MyArrayList list = new MyArrayList();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
boolean ret = list.remove("xx");
System.out.println(ret);
System.out.println(list);
}
private static void test5() {
MyArrayList list = new MyArrayList();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
System.out.println(list.contains("xx"));
}
private static void test6() {
MyArrayList list = new MyArrayList();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
System.out.println(list.indexOf("x"));
}
private static void test7() {
MyArrayList list = new MyArrayList();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
System.out.println(list.subList(1, 3));
}
public static void main(String[] args) {
// test2();
// test3();
// test4();
// test5();
// test6();
test7();
}
}
2. 链表
LinkedList底层是双向链表结构,由于链表没有将元素存储在连续的空间中,元素将存储在单独的节点中,然后通过引用将节点连起来,因此在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
2.1 模拟实现单向链表
java
package MyLinkedList;
import java.awt.*;
class Node{
public String value;
public Node next;
public Node(String value) {
this.value = value;
this.next = null;
}
}
public class MyLinkedList {
// 把链表的头结点表示出来, 此时整个链表就能够获取到了.
// 此处不包含傀儡节点, head == null 的时候表示空的链表.
private Node head=null;
//尾插
public void addLast(String value){
if (head==null){
Node newNode=new Node(value);
head=newNode;
return;
}
Node tail=head;
for (;tail!=null;tail=tail.next){
if (tail.next==null){
break;
}
}
Node newNode=new Node(value);
tail.next=newNode;
newNode.next=null;
}
//2.头插
public void addFirst(String value){
Node newNode=new Node(value);
newNode.next=head;
head=newNode;
}
//3.指定位置插入
public void add(int index,String value){
if (index<0 || index>size()){
throw new RuntimeException("下标超出范围");
}
if (index==0){
addFirst(value);
return;
}
Node newNode=new Node(value);
// 4. 找到 index 要插入的位置的前一个节点
// 由于当前的链表是 "单向链表" 每个节点, 只能找到 next.
// 插入新节点, 需要修改前一个节点的 next 值.
// 前一个节点的下标, 应该就是 index - 1
Node prev=head;
for (int i=0;i<index-1;i++){
prev=prev.next;
}
newNode.next=prev.next;
prev.next=newNode;
}
public int size(){
int size=0;
for (Node cur=head;cur!=null;cur=cur.next){
size++;
}
return size;
}
// 判定某个元素是否在链表中包含
public boolean contains(String value){
for (Node cur=head;cur!=null;cur=cur.next){
if (cur.value.equals(value)){
return true;
}
}
return false;
}
public int indexOf(String value){
int index=0;
for (Node cur=head;cur!=null;cur=cur.next){
if (cur.value.equals(value)) {
return index;
}
index++;
}
return -1;
}
// 删除链表中的元素.
// 按照下标删除
public void remove(int index){
if (index < 0 || index >= size()) {
throw new RuntimeException("下标超出范围");
}
if (index==0){
head=head.next;
return;
}
// 2. 找到被删除元素的前一个节点位置.
Node prev=head;
for (int i=0;i<index-1;i++){
prev=prev.next;
}
Node toRemove=prev.next;
prev.next=toRemove.next;
}
// 按照值来删除.
public void remove(String value){
if (head == null) {
// 链表为空, 直接返回.
return;
}
if (head.value.equals(value)){
head = head.next;
return;
}
// 2. 根据 value 值, 找到待删除元素的前一个位置.
Node prev = head;
for (;prev!=null;prev=prev.next){
if (prev.next!=null && prev.next.value.equals(value)){
break;
}
}
// 3. 通过上述循环, prev 就指向了待删除元素的前一个位置.
// 注意, 上述循环的结束可能有两种情况.
// 1) value 的值找到匹配的了
// 2) 链表遍历完了, 也没找到.
if (prev == null) {
// 没找到, 直接返回.
return;
}
Node toRemove=prev.next;
prev.next=toRemove.next;
}
// 清空链表的所有元素
public void clear() {
// 直接修改 head, head 原来指向的所有 Node 对象都没有引用指向了. 都会被释放掉.
head = null;
}
@Override
public String toString() {
// 通过这个方法, 遍历链表, 构成一个字符串.
// 遍历的时候, 需要从头结点开始, 进行一个一个元素的打印
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
for (Node cur = head; cur != null; cur = cur.next) {
stringBuilder.append(cur.value);
// 当前希望最后一个元素后面不加 , 需要判定当前元素是否是最后一个元素.
// 在链表中, 最后一个元素的 next 是 null.
if (cur.next != null) {
stringBuilder.append(", ");
}
}
stringBuilder.append("]");
return stringBuilder.toString();
}
private static void test1() {
MyLinkedList list = new MyLinkedList();
list.addFirst("a");
list.addFirst("b");
list.addFirst("c");
list.addFirst("d");
// 需要遍历链表并打印元素
// 还可以通过调试器.
System.out.println(list);
}
private static void test2() {
// 默认就是空的链表
MyLinkedList list = new MyLinkedList();
list.addLast("a");
list.addLast("b");
list.addLast("c");
list.addLast("d");
System.out.println(list);
}
private static void test3() {
MyLinkedList list = new MyLinkedList();
list.add(0, "a");
list.add(1, "b");
list.add(2, "c");
list.add(3, "d");
list.add(0, "x");
System.out.println(list);
}
private static void test4() {
MyLinkedList list = new MyLinkedList();
list.add(0, "a");
list.add(1, "b");
list.add(2, "c");
list.add(3, "d");
System.out.println(list.contains("d"));
System.out.println(list.contains("x"));
System.out.println(list.indexOf("d"));
System.out.println(list.indexOf("x"));
}
private static void test5() {
MyLinkedList list = new MyLinkedList();
list.add(0, "a");
list.add(1, "b");
list.add(2, "c");
list.add(3, "d");
list.remove(1);
System.out.println(list);
}
private static void test6() {
MyLinkedList list = new MyLinkedList();
list.add(0, "a");
list.add(1, "b");
list.add(2, "c");
list.add(3, "d");
list.remove("b");
System.out.println(list);
}
public static void main(String[] args) {
// test1();
// test2();
// test3();
// test4();
// test5();
test6();
}
}
2.2 模拟实现双向链表
java
package MyDLinkedList;
import javax.swing.*;
import java.net.http.HttpRequest;
// 此处实现双向链表.
public class MyDLinkedList {
static class Node{
public String val;
public Node prev = null;
public Node next = null;
public Node(String val) {
this.val = val;
}
}
// 表示整个链表. 此处不引入傀儡节点. 使用 null 表示空的链表.
private Node head = null;
// 为了方便后续实现尾插操作.
private Node tail = null;
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
for (Node cur = head; cur != null; cur = cur.next) {
stringBuilder.append(cur.val);
if (cur.next != null) {
stringBuilder.append(", ");
}
}
stringBuilder.append("]");
return stringBuilder.toString();
}
// 实现链表的一些核心操作.
// 头插
public void addFirst(String value){
Node newNode=new Node(value);
if (head==null){
head=newNode;
tail=newNode;
}else {
newNode.next=head;
head.prev=newNode;
head=newNode;
}
}
//尾插
public void addLast(String value){
Node newNode=new Node(value);
if (head==null){
head=newNode;
tail=newNode;
}else{
tail.next=newNode;
newNode.prev=tail;
tail=newNode;
}
}
//获取链表的长度
public int size(){
int size=0;
for (Node cur=head;cur!=null;cur=cur.next){
size++;
}
return size;
}
// 中间任意位置插入. 根据 index 下标, 插入到合适的位置上.
public void add(int index,String value){
if (index<0 || index>size()){
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());
}
if (index==0){
addFirst(value);
return;
}
if (index==size()){
addLast(value);
return;
}
//处理中间情况
Node prev=head;
for (int i=0;i<index-1;i++){
prev=prev.next;
}
// 经过上述循环, prev 已经指向待插入节点的前一个位置了.
// 把后一个节点的位置也记录出来.
Node next = prev.next;
// 新节点的插入, 就是要放到 prev 和 next 之间.
Node newNode = new Node(value);
// a) 建立 prev 和 newNode 的关联关系.
prev.next = newNode;
newNode.prev = prev;
// b) 建立 newNode 和 next 之间的关联关系.
newNode.next = next;
next.prev = newNode;
}
public boolean contains(String value){
for (Node cur = head;cur!=null;cur=cur.next){
if (cur.val.equals(value)){
return true;
}
}
return false;
}
// 查找元素, 返回该元素的下标. 如果没找到, 返回 -1.
public int indexOf(String value){
int index=0;
for (Node cur=head;cur!=null;cur=cur.next){
if (cur.val.equals(value)){
return index;
}
index++;
}
return -1;
}
// 删除头部元素
public void removeFirst(){
if (head==null){
return;
}
if (head.next==null){
head=null;
tail=null;
return;
}
head=head.next;
head.prev=null;
}
// 删除尾部元素
public void removeLast(){
if (head == null) {
// 空链表不需要删除.
return;
}
if (head.next == null) {
// 只有一个节点, 特殊处理.
head = null;
tail = null;
return;
}
tail=tail.prev;
tail.next=null;
}
// 指定位置删除
public void remove(int index){
if (index < 0 || index >= size()) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());
}
if (index == 0) {
// 头删
removeFirst();
return;
}
if (index == size() - 1) {
// 尾删
removeLast();
return;
}
// 一般情况. 找到待删除节点的前一个位置. index - 1 的位置.
Node prev=head;
for (int i=0;i<index-1;i++){
prev=prev.next;
}
Node toRemove=prev.next;
// 指向待删除元素的下一个位置.
Node next = toRemove.next;
prev.next = next;
next.prev=prev;
}
// 指定值删除
public void remove(String value){
if (head == null) {
return ;
}
// 判定头删/尾删特殊情况
if (head.val.equals(value)) {
removeFirst();
return;
}
if (tail.val.equals(value)) {
removeLast();
return;
}
Node toRemove=head;
for (; toRemove!=null;toRemove=toRemove.next){
if (toRemove.val.equals(value)){
break;
}
}
// 通过上述循环, 此时 toRemove 就指向了待删除元素.
if (toRemove==null){
return;
}
Node prev=toRemove.prev;
Node next=toRemove.next;
prev.next=next;
next.prev=prev;
}
public void clear(){
// 清空链表
head = null;
tail = null;
}
private static void test1() {
MyDLinkedList list = new MyDLinkedList();
list.addFirst("a");
list.addFirst("b");
list.addFirst("c");
list.addFirst("d");
System.out.println(list);
}
private static void test2() {
MyDLinkedList list = new MyDLinkedList();
list.addLast("a");
list.addLast("b");
list.addLast("c");
list.addLast("d");
System.out.println(list);
}
private static void test3() {
MyDLinkedList list = new MyDLinkedList();
list.add(0, "a");
list.add(1, "b");
list.add(2, "c");
list.add(3, "d");
list.add(3, "x");
// 预期结果: [a, b, c, x, d]
System.out.println(list);
}
private static void test4() {
MyDLinkedList list = new MyDLinkedList();
list.add(0, "a");
list.add(1, "b");
list.add(2, "c");
list.add(3, "d");
System.out.println(list.contains("a"));
System.out.println(list.contains("x"));
System.out.println(list.indexOf("c"));
System.out.println(list.indexOf("x"));
}
private static void test5() {
MyDLinkedList list = new MyDLinkedList();
list.add(0, "a");
list.add(1, "b");
list.add(2, "c");
list.add(3, "d");
list.removeFirst();
System.out.println(list);
list.removeFirst();
System.out.println(list);
list.removeFirst();
System.out.println(list);
list.removeFirst();
System.out.println(list);
}
private static void test6() {
MyDLinkedList list = new MyDLinkedList();
list.add(0, "a");
list.add(1, "b");
list.add(2, "c");
list.add(3, "d");
list.removeLast();
System.out.println(list);
list.removeLast();
System.out.println(list);
list.removeLast();
System.out.println(list);
list.removeLast();
System.out.println(list);
}
public static void test7() {
MyDLinkedList list = new MyDLinkedList();
list.add(0, "a");
list.add(1, "b");
list.add(2, "c");
list.add(3, "d");
list.remove(3);
System.out.println(list);
list.remove(1);
System.out.println(list);
list.remove(0);
System.out.println(list);
}
private static void test8() {
MyDLinkedList list = new MyDLinkedList();
list.add(0, "a");
list.add(1, "b");
list.add(2, "c");
list.add(3, "d");
list.remove("b");
System.out.println(list);
}
public static void main(String[] args) {
// test1();
// test2();
// test3();
// test4();
// test5();
// test6();
// test7();
// test8();
}
}
3. ArrayList与LinkedList的区别
|-------|-----------------|-----------------------|
| 不同点 | ArrayList | LinkedList |
| 存储空间上 | 物理上一定连续 | 逻辑上连续,物理上不一定连续 |
| 随机访问 | 支持O(1) | 不支持O(N) |
| 头插 | 需要搬移元素,效率低是O(N) | 只需要修改引用的指向,时间复杂度为O(1) |
| 插入 | 空间不够时需要扩容 | 没有容量的概念 |
| 应用场景 | 元素高效存储+随机访问 | 任意位置插入和删除频繁 |