目录
1. 线性表
n个具有相同特性的数据元素的有限序列。
-
线性表在逻辑上是连续的,但是在物理结构上不一定连续。
-
常见的线性表:顺序表、链表、栈、队列、字符串...
2. 顺序表
物理结构连续的线性表,一般采用数组存储。
-
2.1 分类
- 静态顺序表:定长数组存储
- 动态顺序表:动态数组存储
-
2.2 顺序表的增与删
- 2.2.1 思考
- 变量
- array:保存数据的空间
- size:保存有效数据的个数
- item:插入的数据
- index:插入位置
- 通俗的来讲,array就是房子的地址,size就是房子所能容纳的人数
- 头插
- 时间复杂度O(n)
- 步骤
- 将size个数据依次后移
- 为避免数据被覆盖,先移动末尾的数据
- 称数据当前所在下标称为数据的下标,称数据所要移动到的下标为空间的下标
- 空间的下标(size)= 数据的下标(size-1)+1
- 数据的下标[size-1,0],空间的下标[size,1]
- 移动数据:array[空间]=array[数据]
- 令array[0]=item
- 尾插
- size同时表示下一个可用空间(所插入位置)的下标
- 直接将item存储于下标为size的空间
- 时间复杂度 O(1)
- 插入
- 时间复杂度 O(n)
- 步骤(类似于头插)
- 将size-1个数后移
- 数据的下标[size-1,index],空间的下标[size,index+1]
- 头删
- 直接将下标为1及以后的数据(size-1个数据)前移
- 避免数据被覆盖,先移动前面的数据
- 数据的下标[1,size-1],空间的下标[0,size-2]
- 时间复杂度 O(n)
- 尾删
- size-1表示要删除的数据的下标
- 将末尾的数据置为0
- 时间复杂度 O(1)
- 删除(类似于头删)
- 将size-index-1个数前移
- 数据的下标[index+1,size-1],空间的下标[index,size-2]
- 时间复杂度 O(n)
- 变量
- 2.2.2 代码
/**
* 顺序表的增删
* Author:qqy
*/
public class MyArrayList implements IArrayList {
//ArrayList -> 底层使用数组实现
//保存数据的空间
private int[] array;
//保存有效数据的个数
private int size;
MyArrayList(int capacity) {
this.array = new int[capacity];
this.size = 0;
}
@Override
public void pushFront(int item) {
for (int i = this.size; i >= 1; i--) {
this.array[i] = array[i - 1];
}
this.array[0] = item;
this.size++;
}
@Override
public void pushBack(int item) {
this.array[this.size] = item;
this.size++;
}
@Override
public void add(int item, int index) {
if (index < 0 || index >= this.size) {
throw new Error();
}
for (int i = this.size; i > index; i--) {
this.array[i] = this.array[i - 1];
}
this.array[index] = item;
this.size++;
}
@Override
public void popFront() {
if (this.size == 0) {
throw new Error();
}
for (int i = 0; i < this.size - 1; i++) {
this.array[i] = this.array[i + 1];
}
this.array[--this.size] = 0;
}
@Override
public void popBack() {
if (this.size != 0) {
this.array[--this.size] = 0;
} else {
throw new Error();
}
}
@Override
public void remove(int index) {
if (index < 0 || index >= this.size) {
throw new Error();
}
if (this.size == 0) {
throw new Error();
}
for (int i = index + 1; i < size; i++) {
array[i - 1] = array[i];
}
size--;
}
}
- 2.2.3 时间复杂度
- 尾插、尾删 均摊O(1),偶然扩容时变为O(n)
- 头插、头删 O(n) 数据搬移耗时
- 中间插入、删除 O(n) 数据搬移耗时
-
2.3 顺序表的扩容
数组容量不够,需要扩容
- 2.3.1 思考
- 变量
- this.array 房子地址
- this.size 房子容纳人数
- 扩容条件(搬家)
- this,size==this.array.length
- 步骤
- 开辟新房子
- 搬家
- 登记新房子,退掉旧房子
- 变量
- 2.3.2 代码
//O(n)
private void ensureCapacity(){
if(this.size<this.array.length){
return;
}
//1.
int capacity=this.array.length*2;
int [] newArray=new int[capacity];
//2.
for(int i=0;i<this.size;i++){
newArray[i]=this.array[i];
}
//3.
this.array=newArray;
}
3. 链表
物理结构非连续的线性表,逻辑顺序通过链表中的引用连接次序实现。
-
3.1 简介
-
可以将链表看成如下结构:
-
引用{value,next}
-
- eg:@0X33{1,@0x66}、@0X66{1,@0x55}、@0X55{1,@0x77}、@0X77{1,null}
- Node n1=//@0x33 Node n2=//@0x77
- n1.next=n2; @0X33{1,@0x77}
- n2=n1.next; n2=//@0x66
- n1.next.next=n2; @0X66{1,@0x77}
- n1=n2; n1=//@0x77
-
第一个结点没有前驱结点,代表整个链表。
-
根据第一个结点,去寻找一个链表。如果一个结点都没有,则head=null
-
注意:真正有用的是value,next用于维持结构。
-
-
3.2 链表的增与删
-
3.2.1 思考
-
变量
- item:插入的数据
- index:插入位置
- next:下一个结点的线索(引用)
- head:保存链表中第一个结点的引用
-
value:保存有效数据
-
链表中做插入/删除,都需要相应结点的前驱结点
-
链表的第一个结点有特殊性,没有前驱结点
-
头插
-
将头结点赋给item的next
-
将item赋给头结点
-
时间复杂度O(1)
-
-
尾插
-
通过最后一个结点的next为null来找到最后一个结点
-
将最后一个结点的next变为新的结点
- 时间复杂度O(n)
-
-
头删
-
将第一个节点的next赋给头结点 -> 头结点的next赋给头结点
-
时间复杂度O(1)
-
-
尾删
-
通过倒数第二个结点的next的next为null,找到倒数第二个节点
-
将倒数第二的结点的next设为null
-
时间复杂度O(n)
-
-
插入
-
找到指定位置的前一个结点
-
插入
-
时间复杂度O(n)
-
-
-
3.2.2 代码
/**
* 链表的增删
* Author:qqy
*/
public class MyLinkedList {
/**
* 定义一个内部类,表示结点
*/
public class Node {
//保存有效数据
private int value;
//下一个结点的线索(引用)
private Node next;
public Node(int v) {
this.value = v;
this.next = null;
}
}
//保存链表中第一个结点的引用,若无结点 -> head=null
private Node head;
MyLinkedList(){
this.head=null;
}
public void pushFront(int item){
Node node=new Node(item);
node.next=this.head;
this.head=node;
}
public void pushBack(int item){
Node node=new Node(item);
//链表没有结点
if(this.head==null){
this.head=node;
}else {
Node cur = this.head;
//得到最后一个结点
while (cur.next != null) {
cur = cur.next;
}
//将node赋给最后一个结点的next
cur.next = node;
}
}
public void popFront(){
if(this.head==null){
throw new Error();
}
this.head=this.head.next;
}
public void popBack(){
if(this.head==null){
throw new Error();
}
//只有一个结点
if(this.head.next==null){
this.head=null;
}else {
Node cur = this.head;
while (cur.next.next != null) {
cur = cur.next;
}
cur.next = null;
}
}
//中间插入
public boolean addIndex(int index, int v) {
Node node=new Node(v);
if(index==0){
node.next=this.head.next;
this.head=node;
}else {
Node cur=this.head;
for (int i = 0; cur.next!=null&& i < index - 1; i++) {
cur = cur.next;
}
if(cur.next==null){
return false;
}
node.next=cur.next;
cur.next=node;
}
return true;
}
//通过循环遍历链表的每一个结点
public void display(){
Node cur=this.head;
while(cur!=null){
System.out.print(cur.value);
if(cur.next!=null){
System.out.print("-->");
}
cur=cur.next;
}
}
}
4. 顺序表 battle 链表
- 顺序表
- 可以通过索引随机访问任一结点,时间复杂度为O(1)
- 单个数据存储,占用空间少,存储效率高
- 不易产生内存碎片
- 链表
- 更好的插入、删除,头插尾插的时间复杂度都为O(1)
- 整体来看,存储效率更高