顺序表和链表都是 List(线性表) 接口下的实现类,线性表包括很多,除了顺序表、链表、还有字符串、栈、队列等,本篇主要介绍顺序表和链表
线性表
- 线性表(linear list)是n个具有相同特性的数据元素的有限序列
- 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组(顺序表)和链式结构(链表)的形式存储
方法 | 解释 |
---|---|
boolean add (E e) | 尾插 e |
void add (int index,E element) | 将 e 插入到 index 位置 |
boolead addAll (Collection<?extends E> c) | 尾插 c 中的元素 |
E remove (int index) | 删除 index 位置元素 |
boolean remove (Object o) | 删除遇到的第一个 o |
E get (int index) | 获取下标 index 位置元素 |
E set (int index,E element) | 将下标为 index 位置元素设为 element |
void clead () | 情空线性表 |
boolean contains (Object o) | 判断 o 是否在线性表中 |
int indexOf (Object 0) | 返回第一个 o 所在下标 |
int lastIndexOf (Object o) | 返回最后一个 o 所在下标 |
List< E > sublist (int fromIndex,int toIndex) | 截取部分 list,左闭右开 |
import java.util.ArrayList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List<String> courses = new ArrayList<>();
courses.add("C");
courses.add("计算机基础");
courses.add("JavaSE");
courses.add("JavaWeb");
courses.add("JavaEE");
System.out.println(courses);
System.out.println("===========");
System.out.println(courses.get(1));
//System.out.println(courses.get(10));
courses.set(0,"高数");
System.out.println(courses);
System.out.println("==================");
List<String> sub = courses.subList(1,3);//左闭右开
System.out.println(sub);
List<String> list = new ArrayList<>(10);
System.out.println(list.size());//0
//list.get(9);
}
}
//执行结果
[C, 计算机基础, JavaSE, JavaWeb, JavaEE]
===========
计算机基础
[高数, 计算机基础, JavaSE, JavaWeb, JavaEE]
==================
[计算机基础, JavaSE]
0
顺序表
- 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。
- 顺序表一般可以分为:
静态顺序表:使用定长数组存储。如 int[] arr = {1,2,3,4,5};
动态顺序表:使用动态开辟的数组存储。如 int[] a = new int[5];
//顺序表的元素类型为 int
public class MyArrayList {
//顺序表的属性信息
int[] array;
int size;//顺序表中元素个数
public MyArrayList(){
array = new int[2];
size = 0;
}
//扩容问题
private void ensureCapacity(){
if(size<array.length){
return;
}
int newCapacity = array.length * 2;
int[] newArray = new int[newCapacity];
for(int i = 0;i < size;i++){
newArray[i] = array[i];
}
array = newArray;
}
/**
* 增
*/
//尾插
public void pushBack(int element){
ensureCapacity();
array[size++] = element;
}
//头插
public void pushFront(int element) {
ensureCapacity();
for (int i = size; i > 0; i--) {
array[i] = array[i - 1];
}
array[0] = element;
size++;
}
//中间插 注意判断下标的合法性
public void insert(int index,int element){
if(index<0||index>size){
System.out.println("下标错误");
return;
}
ensureCapacity();
for(int i = size;i > index;i--){
array[i] = array[i-1];
}
array[index] = element;
size++;
}
/**
*删
* 删除之前都要判断顺序表是否为空
*/
//尾删
public void popBack(){
if(size<=0){
System.out.println("顺序表为空");
return;
}
--size;
}
//头删
public void popFront(){
if(size<=0){
System.out.println("顺序表为空");
return;
}
for(int i = 1;i<size;i++){
array[i-1] = array[i];
}
--size;
}
//中间删
public void delete(int index){
if(size<=0){
System.out.println("顺序表为空");
return;
}
if(index<=0||index>=size){
System.out.println("下标错误");
return;
}
for(int i = index;i<size-1;i++){
array[i] = array[i+1];
}
--size;
}
//打印
public void print(){
System.out.println("当前容量" + array.length);
for(int i = 0;i < size;i++){
System.out.print(array[i] + " ");
}
System.out.println();
}
//返回 element 在顺序表中的下标,
// 如果出现多次,返回第一个出现 element 的下标
public int indexOf(int element){
if(size<=0){
System.out.println("顺序表为空");
}
for(int i = 0;i < size;i++){
if(array[i]==element){
return i;
}
}
return -1;
}
//得到下标为 index 位置的元素
public int get(int index){
if(size<=0){
System.out.println("顺序表为空");
return -1;
}
if(index<0||index>=size){
System.out.println("下标错误");
return -1;
}
return array[index];
}
//将下标为 index 位置的元素设为 element
public void set(int index,int element){
if(size<=0){
System.out.println("顺序表为空");
return;
}
if(index<0||index>=size){
System.out.println("下标错误");
return;
}
array[index] = element;
}
//删除某一元素,如果多次出现,删除第一次出现的
public void remove(int element){
if(size<=0){
System.out.println("顺序表为空");
return;
}
int index = indexOf(element);
if(index!=-1){
delete(index);
}
}
public int size(){
return size;
}
public boolean isEmpty(){
return size==0;
}
public static void main(String[] args) {
MyArrayList list = new MyArrayList();
list.print();
list.pushBack(1);
list.pushBack(2);
list.pushBack(3);
list.print();// 1 2 3
list.pushFront(10);
list.pushFront(20);
list.pushFront(30);
list.print();//30 20 10 1 2 3
list.insert(3, 100);
list.print();//30 20 10 100 1 2 3
list.insert(20, 200);//下标错误
list.delete(2);
list.delete(2);
list.print();// 30 20 1 2 3
list.popFront();
list.popFront();
list.popFront();
list.print();// 2 3
list.popBack();
list.popBack();
list.print();//
list.popBack();//顺序表为空
}
}
//执行结果
当前容量2
当前容量4
1 2 3
当前容量8
30 20 10 1 2 3
当前容量8
30 20 10 100 1 2 3
下标错误
当前容量8
30 20 1 2 3
当前容量8
2 3
当前容量8
顺序表为空
顺序表是线性表,所以线性表的方法顺序表也适用
链表
在顺序表的扩容问题上,我们一次性扩大原来的 2 倍,但是我们可以看到,可能存储的数据少于顺序表的容量,岂不是会造成空间浪费,为了解决这一问题,链表就出现了
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的
// 前驱 prev previous
// 后继 next
class Node {
int val; // data | element
Node next; // 如果 next == null 表示是最后一个结点
Node(int val) {
this.val = val;
this.next = null;
}
public String toString() {
return String.format("Node(%d)", val);
}
}
public class MyLinkedList {
public static void main(String[] args) {
Node head = null;
// head 的意思是链表的第一个结点
// 通过第一个结点,就可以找到完整的链表的所有结点
// 所以,链表的第一个结点往往代表整个链表
// 空的链表,就是一个结点都没有的链表
// 也就没有第一个结点
// head == null 表示第一个结点不存在
// 也就是整个链表为空
// 头插
/*
int val = 0;
// 1. 结点
Node node = new Node(val);
// 2. 让原来的 head 成为 node 的下一个结点
node.next = head;
// 3. 更新第一个结点的引用
head = node;
pushFront(head, 0);
*/
head = pushFront(head, 0);
head = pushFront(head, 1);
head = pushFront(head, 2);
// 打印
print(head); // 2 1 0
// 尾插
head = popFront(head);
print(head); // 1 0
head = pushBack(head, 10);
head = pushBack(head, 20);
head = pushBack(head, 30);
print(head); // 1 0 10 20 30
head = popBack(head);
head = popBack(head);
head = popBack(head);
head = popBack(head);
head = popBack(head);
head = popBack(head); // 报错
print(head); // 空
head = pushBack(head, 100);
print(head); // 100
}
// 打印
private static void print(Node head) {
System.out.println("打印链表:");
for (Node cur = head; cur != null; cur = cur.next) {
System.out.print(cur + " --> ");
}
System.out.println("null");
}
// 头插
// head: 原来的第一个结点
// val:要插入的值
// 返回:新的第一个结点
private static Node pushFront(Node head, int val) {
// 1. 结点
Node node = new Node(val);
// 2. 让原来的 head 成为 node 的下一个结点
node.next = head;
// 3. 更新第一个结点的引用
return node;
}
private static Node pushBack(Node head, int val) {
Node node = new Node(val);
if (head == null) {
return node;
} else {
Node last = head;
while (last.next != null) {
last = last.next;
}
last.next = node;
return head;
}
}
private static Node popFront(Node head) {
if (head == null) {
System.err.println("空链表无法删除");
return null;
}
// 原来第一个结点,会因为没有引用指向而被回收
return head.next;
}
private static Node popBack(Node head) {
if (head == null) {
System.err.println("空链表无法删除");
return null;
}
if (head.next == null) {
return null;
} else {
Node lastSecond = head;
while (lastSecond.next.next != null) {
lastSecond = lastSecond.next;
}
lastSecond.next = null;
return head;
}
}
}
//执行结果
打印链表:
空链表无法删除
Node(2) --> Node(1) --> Node(0) --> null
打印链表:
Node(1) --> Node(0) --> null
打印链表:
Node(1) --> Node(0) --> Node(10) --> Node(20) --> Node(30) --> null
打印链表:
null
打印链表:
Node(100) --> null
顺序表和链表的区别
顺序表
原理:物理地址和逻辑地址都是连续的线性表
优点:
- 随机存取,直接通过下标进行访问,时间复杂度为O(1)
缺点: - 插入和删除操作比较慢,需要移动元素,时间复杂度为O(n)
- 不可以增加长度,有空间限制,当存取的数据元素个数大于顺序表元素个数时,空间不够,当存取的数据元素个数小于顺序表元素个数时,会造成空间浪费
链表
原理:逻辑地址连续、物理地址不连续的链式结构的线性表
优点:
-
插入删除操作速度快,时间复杂度O(1)
-
空间大小动态分配,不会造成空间浪费或者不够用
缺点: -
查找元素的时间复杂度为O(n)